github.com/grafana/pyroscope@v1.18.0/pkg/block/compaction_test.go (about) 1 package block_test 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "os" 8 "path/filepath" 9 "sort" 10 "strings" 11 "testing" 12 13 "github.com/prometheus/prometheus/model/labels" 14 "github.com/prometheus/prometheus/prompb" 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/mock" 17 "github.com/stretchr/testify/require" 18 "google.golang.org/protobuf/encoding/protojson" 19 20 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" 24 "github.com/grafana/pyroscope/pkg/objstore/testutil" 25 "github.com/grafana/pyroscope/pkg/test/mocks/mockmetrics" 26 ) 27 28 func Test_CompactBlocks(t *testing.T) { 29 ctx := context.Background() 30 bucket, _ := testutil.NewFilesystemBucket(t, ctx, "testdata") 31 32 var resp metastorev1.GetBlockMetadataResponse 33 raw, err := os.ReadFile("testdata/block-metas.json") 34 require.NoError(t, err) 35 err = protojson.Unmarshal(raw, &resp) 36 require.NoError(t, err) 37 38 dst, tempdir := testutil.NewFilesystemBucket(t, ctx, t.TempDir()) 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. 45 ) 46 47 require.NoError(t, err) 48 require.Len(t, compactedBlocks, 1) 49 require.NotZero(t, compactedBlocks[0].Size) 50 require.Len(t, compactedBlocks[0].Datasets, 4) 51 52 compactedJson, err := json.MarshalIndent(compactedBlocks, "", " ") 53 require.NoError(t, err) 54 expectedJson, err := os.ReadFile("testdata/compacted.golden") 55 require.NoError(t, err) 56 assert.Equal(t, string(expectedJson), string(compactedJson)) 57 58 t.Run("Compact compacted blocks", func(t *testing.T) { 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. 65 ) 66 67 require.NoError(t, err) 68 require.Len(t, compactedBlocks, 1) 69 require.NotZero(t, compactedBlocks[0].Size) 70 require.Len(t, compactedBlocks[0].Datasets, 4) 71 }) 72 } 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 }