Skip to content

Commit e6937e7

Browse files
authored
fix: not missing recording rules symbols by relaxing ObserveSymbols condition (#4545)
1 parent b025909 commit e6937e7

File tree

4 files changed

+343
-15
lines changed

4 files changed

+343
-15
lines changed

pkg/block/compaction_test.go

Lines changed: 294 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
1-
package block
1+
package block_test
22

33
import (
44
"context"
55
"encoding/json"
6+
"fmt"
67
"os"
78
"path/filepath"
9+
"sort"
10+
"strings"
811
"testing"
912

13+
"github.com/prometheus/prometheus/model/labels"
14+
"github.com/prometheus/prometheus/prompb"
1015
"github.com/stretchr/testify/assert"
16+
"github.com/stretchr/testify/mock"
1117
"github.com/stretchr/testify/require"
1218
"google.golang.org/protobuf/encoding/protojson"
1319

1420
metastorev1 "github.com/grafana/pyroscope/api/gen/proto/go/metastore/v1"
21+
"github.com/grafana/pyroscope/pkg/block"
22+
"github.com/grafana/pyroscope/pkg/metrics"
23+
phlaremodel "github.com/grafana/pyroscope/pkg/model"
1524
"github.com/grafana/pyroscope/pkg/objstore/testutil"
25+
"github.com/grafana/pyroscope/pkg/test/mocks/mockmetrics"
1626
)
1727

1828
func Test_CompactBlocks(t *testing.T) {
@@ -26,12 +36,12 @@ func Test_CompactBlocks(t *testing.T) {
2636
require.NoError(t, err)
2737

2838
dst, tempdir := testutil.NewFilesystemBucket(t, ctx, t.TempDir())
29-
compactedBlocks, err := Compact(ctx, resp.Blocks, bucket,
30-
WithCompactionDestination(dst),
31-
WithCompactionTempDir(tempdir),
32-
WithCompactionObjectOptions(
33-
WithObjectDownload(filepath.Join(tempdir, "source")),
34-
WithObjectMaxSizeLoadInMemory(0)), // Force download.
39+
compactedBlocks, err := block.Compact(ctx, resp.Blocks, bucket,
40+
block.WithCompactionDestination(dst),
41+
block.WithCompactionTempDir(tempdir),
42+
block.WithCompactionObjectOptions(
43+
block.WithObjectDownload(filepath.Join(tempdir, "source")),
44+
block.WithObjectMaxSizeLoadInMemory(0)), // Force download.
3545
)
3646

3747
require.NoError(t, err)
@@ -46,12 +56,12 @@ func Test_CompactBlocks(t *testing.T) {
4656
assert.Equal(t, string(expectedJson), string(compactedJson))
4757

4858
t.Run("Compact compacted blocks", func(t *testing.T) {
49-
compactedBlocks, err = Compact(ctx, compactedBlocks, dst,
50-
WithCompactionDestination(dst),
51-
WithCompactionTempDir(tempdir),
52-
WithCompactionObjectOptions(
53-
WithObjectDownload(filepath.Join(tempdir, "source")),
54-
WithObjectMaxSizeLoadInMemory(0)), // Force download.
59+
compactedBlocks, err = block.Compact(ctx, compactedBlocks, dst,
60+
block.WithCompactionDestination(dst),
61+
block.WithCompactionTempDir(tempdir),
62+
block.WithCompactionObjectOptions(
63+
block.WithObjectDownload(filepath.Join(tempdir, "source")),
64+
block.WithObjectMaxSizeLoadInMemory(0)), // Force download.
5565
)
5666

5767
require.NoError(t, err)
@@ -60,3 +70,274 @@ func Test_CompactBlocks(t *testing.T) {
6070
require.Len(t, compactedBlocks[0].Datasets, 4)
6171
})
6272
}
73+
74+
func Test_CompactBlocks_recordingRules(t *testing.T) {
75+
ctx := context.Background()
76+
bucket, _ := testutil.NewFilesystemBucket(t, ctx, "testdata")
77+
78+
var resp metastorev1.GetBlockMetadataResponse
79+
raw, err := os.ReadFile("testdata/block-metas.json")
80+
require.NoError(t, err)
81+
err = protojson.Unmarshal(raw, &resp)
82+
require.NoError(t, err)
83+
84+
exporter := &stringExporter{}
85+
ruler := new(mockmetrics.MockRuler)
86+
ruler.On("RecordingRules", mock.Anything).Return([]*phlaremodel.RecordingRule{
87+
{
88+
Matchers: []*labels.Matcher{
89+
labels.MustNewMatcher(labels.MatchEqual, "__profile_type__", "goroutine:goroutine:count:goroutine:count"),
90+
},
91+
GroupBy: []string{"service_name"},
92+
ExternalLabels: labels.New(labels.Label{Name: "__name__", Value: "profiles_recorded_goroutines_total_count"}),
93+
},
94+
{
95+
Matchers: []*labels.Matcher{
96+
labels.MustNewMatcher(labels.MatchEqual, "__profile_type__", "memory:alloc_objects:count:space:bytes"),
97+
},
98+
GroupBy: []string{"service_name"},
99+
ExternalLabels: labels.New(labels.Label{Name: "__name__", Value: "profiles_recorded_mem_alloc_total_count"}),
100+
},
101+
{
102+
Matchers: []*labels.Matcher{
103+
labels.MustNewMatcher(labels.MatchEqual, "__profile_type__", "memory:alloc_space:bytes:space:bytes"),
104+
},
105+
GroupBy: []string{"service_name"},
106+
ExternalLabels: labels.New(labels.Label{Name: "__name__", Value: "profiles_recorded_mem_alloc_total_bytes"}),
107+
},
108+
{
109+
Matchers: []*labels.Matcher{
110+
labels.MustNewMatcher(labels.MatchEqual, "__profile_type__", "memory:inuse_objects:count:space:bytes"),
111+
},
112+
GroupBy: []string{"service_name"},
113+
ExternalLabels: labels.New(labels.Label{Name: "__name__", Value: "profiles_recorded_mem_inuse_total_count"}),
114+
},
115+
{
116+
Matchers: []*labels.Matcher{
117+
labels.MustNewMatcher(labels.MatchEqual, "__profile_type__", "memory:inuse_space:bytes:space:bytes"),
118+
},
119+
GroupBy: []string{"service_name"},
120+
ExternalLabels: labels.New(labels.Label{Name: "__name__", Value: "profiles_recorded_mem_inuse_total_bytes"}),
121+
},
122+
{
123+
Matchers: []*labels.Matcher{
124+
labels.MustNewMatcher(labels.MatchEqual, "__profile_type__", "process_cpu:cpu:nanoseconds:cpu:nanoseconds"),
125+
},
126+
GroupBy: []string{"service_name"},
127+
ExternalLabels: labels.New(labels.Label{Name: "__name__", Value: "profiles_recorded_cpu_usage_total_nanoseconds"}),
128+
},
129+
{
130+
Matchers: []*labels.Matcher{
131+
labels.MustNewMatcher(labels.MatchEqual, "__profile_type__", "process_cpu:samples:count:cpu:nanoseconds"),
132+
},
133+
GroupBy: []string{"service_name"},
134+
ExternalLabels: labels.New(labels.Label{Name: "__name__", Value: "profiles_recorded_cpu_usage_total_samples"}),
135+
},
136+
// functions
137+
{
138+
Matchers: []*labels.Matcher{
139+
labels.MustNewMatcher(labels.MatchEqual, "__profile_type__", "goroutine:goroutine:count:goroutine:count"),
140+
},
141+
GroupBy: []string{"service_name"},
142+
ExternalLabels: labels.New(labels.Label{Name: "__name__", Value: "profiles_recorded_goroutines_function_total_servehttp_count"}),
143+
FunctionName: "net/http.HandlerFunc.ServeHTTP",
144+
},
145+
{
146+
Matchers: []*labels.Matcher{
147+
labels.MustNewMatcher(labels.MatchEqual, "__profile_type__", "memory:alloc_objects:count:space:bytes"),
148+
},
149+
GroupBy: []string{"service_name"},
150+
ExternalLabels: labels.New(labels.Label{Name: "__name__", Value: "profiles_recorded_mem_alloc_function_total_servehttp_count"}),
151+
FunctionName: "net/http.HandlerFunc.ServeHTTP",
152+
},
153+
{
154+
Matchers: []*labels.Matcher{
155+
labels.MustNewMatcher(labels.MatchEqual, "__profile_type__", "memory:alloc_space:bytes:space:bytes"),
156+
},
157+
GroupBy: []string{"service_name"},
158+
ExternalLabels: labels.New(labels.Label{Name: "__name__", Value: "profiles_recorded_mem_alloc_function_total_servehttp_bytes"}),
159+
FunctionName: "net/http.HandlerFunc.ServeHTTP",
160+
},
161+
{
162+
Matchers: []*labels.Matcher{
163+
labels.MustNewMatcher(labels.MatchEqual, "__profile_type__", "memory:inuse_objects:count:space:bytes"),
164+
},
165+
GroupBy: []string{"service_name"},
166+
ExternalLabels: labels.New(labels.Label{Name: "__name__", Value: "profiles_recorded_mem_inuse_function_total_servehttp_count"}),
167+
FunctionName: "net/http.HandlerFunc.ServeHTTP",
168+
},
169+
{
170+
Matchers: []*labels.Matcher{
171+
labels.MustNewMatcher(labels.MatchEqual, "__profile_type__", "memory:inuse_space:bytes:space:bytes"),
172+
},
173+
GroupBy: []string{"service_name"},
174+
ExternalLabels: labels.New(labels.Label{Name: "__name__", Value: "profiles_recorded_mem_inuse_function_total_servehttp_bytes"}),
175+
FunctionName: "net/http.HandlerFunc.ServeHTTP",
176+
},
177+
{
178+
Matchers: []*labels.Matcher{
179+
labels.MustNewMatcher(labels.MatchEqual, "__profile_type__", "process_cpu:cpu:nanoseconds:cpu:nanoseconds"),
180+
},
181+
GroupBy: []string{"service_name"},
182+
ExternalLabels: labels.New(labels.Label{Name: "__name__", Value: "profiles_recorded_cpu_usage_function_total_servehttp_nanoseconds"}),
183+
FunctionName: "net/http.HandlerFunc.ServeHTTP",
184+
},
185+
{
186+
Matchers: []*labels.Matcher{
187+
labels.MustNewMatcher(labels.MatchEqual, "__profile_type__", "process_cpu:samples:count:cpu:nanoseconds"),
188+
},
189+
GroupBy: []string{"service_name"},
190+
ExternalLabels: labels.New(labels.Label{Name: "__name__", Value: "profiles_recorded_cpu_usage_function_total_servehttp_samples"}),
191+
FunctionName: "net/http.HandlerFunc.ServeHTTP",
192+
},
193+
})
194+
sampleObserver := metrics.NewSampleObserver(0, exporter, ruler, labels.EmptyLabels())
195+
196+
compactedBlocks, err := block.Compact(ctx, resp.Blocks, bucket,
197+
block.WithSampleObserver(sampleObserver),
198+
)
199+
// Close observer to flush export
200+
sampleObserver.Close()
201+
202+
require.NoError(t, err)
203+
require.Len(t, compactedBlocks, 1)
204+
require.NotZero(t, compactedBlocks[0].Size)
205+
require.Len(t, compactedBlocks[0].Datasets, 4)
206+
207+
expectedMetrics, err := os.ReadFile("testdata/profiles_recorded.txt")
208+
require.NoError(t, err)
209+
expectedMetricsArray := strings.Split(string(expectedMetrics), "\n")
210+
sort.Strings(expectedMetricsArray)
211+
actualMetricsArray := strings.Split(exporter.String(), "\n")
212+
sort.Strings(actualMetricsArray)
213+
assert.Equal(t, expectedMetricsArray, actualMetricsArray)
214+
215+
compactedJson, err := json.MarshalIndent(compactedBlocks, "", " ")
216+
require.NoError(t, err)
217+
expectedJson, err := os.ReadFile("testdata/compacted.golden")
218+
require.NoError(t, err)
219+
assert.Equal(t, string(expectedJson), string(compactedJson))
220+
}
221+
222+
func Test_CompactBlocks_recordingRules_shadowedSymbols(t *testing.T) {
223+
ctx := context.Background()
224+
bucket, _ := testutil.NewFilesystemBucket(t, ctx, "testdata")
225+
226+
var resp metastorev1.GetBlockMetadataResponse
227+
raw, err := os.ReadFile("testdata/block-metas.json")
228+
require.NoError(t, err)
229+
err = protojson.Unmarshal(raw, &resp)
230+
require.NoError(t, err)
231+
232+
exporter := &stringExporter{}
233+
ruler := new(mockmetrics.MockRuler)
234+
ruler.On("RecordingRules", mock.Anything).Return([]*phlaremodel.RecordingRule{
235+
{
236+
Matchers: []*labels.Matcher{
237+
labels.MustNewMatcher(labels.MatchEqual, "service_name", "pyroscope-test/ingester"),
238+
labels.MustNewMatcher(labels.MatchEqual, "__profile_type__", "memory:alloc_space:bytes:space:bytes"),
239+
},
240+
ExternalLabels: labels.New(labels.Label{Name: "__name__", Value: "profiles_recorded_mem_alloc_total_pyroscope_ingester_Push_bytes"}),
241+
FunctionName: "github.com/grafana/pyroscope/pkg/ingester.(*Ingester).Push",
242+
},
243+
{
244+
Matchers: []*labels.Matcher{
245+
labels.MustNewMatcher(labels.MatchEqual, "service_name", "pyroscope-test/ingester"),
246+
labels.MustNewMatcher(labels.MatchEqual, "__profile_type__", "memory:inuse_space:bytes:space:bytes"),
247+
},
248+
ExternalLabels: labels.New(labels.Label{Name: "__name__", Value: "profiles_recorded_mem_inuse_total_pyroscope_ingester_Push_bytes"}),
249+
FunctionName: "github.com/grafana/pyroscope/pkg/ingester.(*Ingester).Push",
250+
},
251+
{
252+
Matchers: []*labels.Matcher{
253+
labels.MustNewMatcher(labels.MatchEqual, "service_name", "pyroscope-test/ingester"),
254+
labels.MustNewMatcher(labels.MatchEqual, "__profile_type__", "process_cpu:samples:count:cpu:nanoseconds"),
255+
},
256+
ExternalLabels: labels.New(labels.Label{Name: "__name__", Value: "profiles_recorded_cpu_usage_total_pyroscope_ingester_Push_samples"}),
257+
FunctionName: "github.com/grafana/pyroscope/pkg/ingester.(*Ingester).Push",
258+
},
259+
{
260+
Matchers: []*labels.Matcher{
261+
labels.MustNewMatcher(labels.MatchEqual, "service_name", "pyroscope-test/ingester"),
262+
labels.MustNewMatcher(labels.MatchEqual, "__profile_type__", "memory:alloc_space:bytes:space:bytes"),
263+
},
264+
ExternalLabels: labels.New(labels.Label{Name: "__name__", Value: "profiles_recorded_mem_alloc_total_pyroscope_ingester_Serve_bytes"}),
265+
FunctionName: "net/http.HandlerFunc.ServeHTTP",
266+
},
267+
{
268+
Matchers: []*labels.Matcher{
269+
labels.MustNewMatcher(labels.MatchEqual, "service_name", "pyroscope-test/ingester"),
270+
labels.MustNewMatcher(labels.MatchEqual, "__profile_type__", "memory:inuse_space:bytes:space:bytes"),
271+
},
272+
ExternalLabels: labels.New(labels.Label{Name: "__name__", Value: "profiles_recorded_mem_inuse_total_pyroscope_ingester_Serve_bytes"}),
273+
FunctionName: "net/http.HandlerFunc.ServeHTTP",
274+
},
275+
{
276+
Matchers: []*labels.Matcher{
277+
labels.MustNewMatcher(labels.MatchEqual, "service_name", "pyroscope-test/ingester"),
278+
labels.MustNewMatcher(labels.MatchEqual, "__profile_type__", "process_cpu:samples:count:cpu:nanoseconds"),
279+
},
280+
ExternalLabels: labels.New(labels.Label{Name: "__name__", Value: "profiles_recorded_cpu_usage_total_pyroscope_ingester_Serve_samples"}),
281+
FunctionName: "net/http.HandlerFunc.ServeHTTP",
282+
},
283+
{
284+
Matchers: []*labels.Matcher{
285+
labels.MustNewMatcher(labels.MatchEqual, "service_name", "pyroscope-test/query-frontend"),
286+
labels.MustNewMatcher(labels.MatchEqual, "__profile_type__", "memory:inuse_space:bytes:space:bytes"),
287+
},
288+
ExternalLabels: labels.New(labels.Label{Name: "__name__", Value: "profiles_recorded_mem_inuse_total_query_Serve_bytes"}),
289+
FunctionName: "net/http.HandlerFunc.ServeHTTP",
290+
},
291+
{
292+
Matchers: []*labels.Matcher{
293+
labels.MustNewMatcher(labels.MatchEqual, "service_name", "pyroscope-test/query-frontend"),
294+
labels.MustNewMatcher(labels.MatchEqual, "__profile_type__", "memory:alloc_space:bytes:space:bytes"),
295+
},
296+
ExternalLabels: labels.New(labels.Label{Name: "__name__", Value: "profiles_recorded_mem_alloc_total_query_Serve_bytes"}),
297+
FunctionName: "net/http.HandlerFunc.ServeHTTP",
298+
},
299+
})
300+
sampleObserver := metrics.NewSampleObserver(0, exporter, ruler, labels.EmptyLabels())
301+
302+
compactedBlocks, err := block.Compact(ctx, resp.Blocks, bucket,
303+
block.WithSampleObserver(sampleObserver),
304+
)
305+
// Close observer to flush export
306+
sampleObserver.Close()
307+
308+
require.NoError(t, err)
309+
require.Len(t, compactedBlocks, 1)
310+
require.NotZero(t, compactedBlocks[0].Size)
311+
require.Len(t, compactedBlocks[0].Datasets, 4)
312+
313+
expectedMetrics, err := os.ReadFile("testdata/profiles_recorded_shadowed.txt")
314+
require.NoError(t, err)
315+
expectedMetricsArray := strings.Split(string(expectedMetrics), "\n")
316+
sort.Strings(expectedMetricsArray)
317+
actualMetricsArray := strings.Split(exporter.String(), "\n")
318+
sort.Strings(actualMetricsArray)
319+
assert.Equal(t, expectedMetricsArray, actualMetricsArray)
320+
321+
compactedJson, err := json.MarshalIndent(compactedBlocks, "", " ")
322+
require.NoError(t, err)
323+
expectedJson, err := os.ReadFile("testdata/compacted.golden")
324+
require.NoError(t, err)
325+
assert.Equal(t, string(expectedJson), string(compactedJson))
326+
}
327+
328+
type stringExporter struct {
329+
output string
330+
}
331+
332+
func (e *stringExporter) Send(tenant string, series []prompb.TimeSeries) error {
333+
for _, s := range series {
334+
e.output += fmt.Sprintf("%s: %s\n", tenant, s.String())
335+
}
336+
return nil
337+
}
338+
339+
func (*stringExporter) Flush() {}
340+
341+
func (e *stringExporter) String() string {
342+
return e.output
343+
}

0 commit comments

Comments
 (0)