github.com/grafana/pyroscope@v1.18.0/pkg/phlaredb/block_querier_test.go (about) 1 package phlaredb 2 3 import ( 4 "context" 5 "fmt" 6 "math" 7 "math/rand" 8 "strings" 9 "sync" 10 "testing" 11 "time" 12 13 "connectrpc.com/connect" 14 "github.com/oklog/ulid/v2" 15 "github.com/prometheus/common/model" 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 "go.uber.org/goleak" 19 "golang.org/x/sync/errgroup" 20 21 ingesterv1 "github.com/grafana/pyroscope/api/gen/proto/go/ingester/v1" 22 typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1" 23 "github.com/grafana/pyroscope/pkg/iter" 24 phlaremodel "github.com/grafana/pyroscope/pkg/model" 25 "github.com/grafana/pyroscope/pkg/objstore/providers/filesystem" 26 "github.com/grafana/pyroscope/pkg/phlaredb/block" 27 "github.com/grafana/pyroscope/pkg/phlaredb/tsdb/index" 28 "github.com/grafana/pyroscope/pkg/pprof/testhelper" 29 ) 30 31 const testDataPath = "./block/testdata/" 32 33 func TestQuerierBlockEviction(t *testing.T) { 34 type testCase struct { 35 blocks []string 36 expected []string 37 notEvicted bool 38 } 39 40 blockToEvict := "01H002D4Z9PKWSS17Q3XY1VEM9" 41 testCases := []testCase{ 42 { 43 notEvicted: true, 44 }, 45 { 46 blocks: []string{"01H002D4Z9ES0DHMMSD18H5J5M"}, 47 expected: []string{"01H002D4Z9ES0DHMMSD18H5J5M"}, 48 notEvicted: true, 49 }, 50 { 51 blocks: []string{blockToEvict}, 52 expected: []string{}, 53 }, 54 { 55 blocks: []string{blockToEvict, "01H002D4Z9ES0DHMMSD18H5J5M"}, 56 expected: []string{"01H002D4Z9ES0DHMMSD18H5J5M"}, 57 }, 58 { 59 blocks: []string{"01H002D4Z9ES0DHMMSD18H5J5M", blockToEvict}, 60 expected: []string{"01H002D4Z9ES0DHMMSD18H5J5M"}, 61 }, 62 { 63 blocks: []string{"01H002D4Z9ES0DHMMSD18H5J5M", blockToEvict, "01H003A2QTY5JF30Z441CDQE70"}, 64 expected: []string{"01H002D4Z9ES0DHMMSD18H5J5M", "01H003A2QTY5JF30Z441CDQE70"}, 65 }, 66 { 67 blocks: []string{"01H003A2QTY5JF30Z441CDQE70", blockToEvict, "01H002D4Z9ES0DHMMSD18H5J5M"}, 68 expected: []string{"01H003A2QTY5JF30Z441CDQE70", "01H002D4Z9ES0DHMMSD18H5J5M"}, 69 }, 70 } 71 72 for _, tc := range testCases { 73 q := BlockQuerier{queriers: make([]*singleBlockQuerier, len(tc.blocks))} 74 for i, b := range tc.blocks { 75 q.queriers[i] = &singleBlockQuerier{ 76 meta: &block.Meta{ULID: ulid.MustParse(b)}, 77 metrics: NewBlocksMetrics(nil), 78 } 79 } 80 81 evicted, err := q.evict(ulid.MustParse(blockToEvict)) 82 require.NoError(t, err) 83 require.Equal(t, !tc.notEvicted, evicted) 84 85 actual := make([]string, 0, len(tc.expected)) 86 for _, b := range q.queriers { 87 actual = append(actual, b.meta.ULID.String()) 88 } 89 90 require.ElementsMatch(t, tc.expected, actual) 91 } 92 } 93 94 type profileCounter struct { 95 iter.Iterator[Profile] 96 count int 97 } 98 99 func (p *profileCounter) Next() bool { 100 r := p.Iterator.Next() 101 if r { 102 p.count++ 103 } 104 105 return r 106 } 107 108 func TestBlockCompatability(t *testing.T) { 109 path := testDataPath 110 bucket, err := filesystem.NewBucket(path) 111 require.NoError(t, err) 112 113 ctx := context.Background() 114 metas, err := NewBlockQuerier(ctx, bucket).BlockMetas(ctx) 115 require.NoError(t, err) 116 117 for _, meta := range metas { 118 t.Run(fmt.Sprintf("block-v%d-%s", meta.Version, meta.ULID.String()), func(t *testing.T) { 119 q := NewSingleBlockQuerierFromMeta(ctx, bucket, meta) 120 require.NoError(t, q.Open(ctx)) 121 122 profilesTypes, err := q.index.LabelValues("__profile_type__") 123 require.NoError(t, err) 124 125 profileCount := 0 126 127 for _, profileType := range profilesTypes { 128 t.Log(profileType) 129 profileTypeParts := strings.Split(profileType, ":") 130 131 it, err := q.SelectMatchingProfiles(ctx, &ingesterv1.SelectProfilesRequest{ 132 LabelSelector: "{}", 133 Start: 0, 134 End: time.Now().UnixMilli(), 135 Type: &typesv1.ProfileType{ 136 Name: profileTypeParts[0], 137 SampleType: profileTypeParts[1], 138 SampleUnit: profileTypeParts[2], 139 PeriodType: profileTypeParts[3], 140 PeriodUnit: profileTypeParts[4], 141 }, 142 }) 143 require.NoError(t, err) 144 145 pcIt := &profileCounter{Iterator: it} 146 147 // TODO: It would be nice actually comparing the whole profile, but at present the result is not deterministic. 148 _, err = q.MergePprof(ctx, pcIt, 0, nil) 149 require.NoError(t, err) 150 151 profileCount += pcIt.count 152 } 153 154 require.Equal(t, int(meta.Stats.NumProfiles), profileCount) 155 }) 156 } 157 } 158 159 func TestBlockCompatability_SelectMergeSpans(t *testing.T) { 160 path := testDataPath 161 bucket, err := filesystem.NewBucket(path) 162 require.NoError(t, err) 163 164 ctx := context.Background() 165 metas, err := NewBlockQuerier(ctx, bucket).BlockMetas(ctx) 166 require.NoError(t, err) 167 168 for _, meta := range metas { 169 t.Run(fmt.Sprintf("block-v%d-%s", meta.Version, meta.ULID.String()), func(t *testing.T) { 170 q := NewSingleBlockQuerierFromMeta(ctx, bucket, meta) 171 require.NoError(t, q.Open(ctx)) 172 173 profilesTypes, err := q.index.LabelValues("__profile_type__") 174 require.NoError(t, err) 175 176 profileCount := 0 177 178 for _, profileType := range profilesTypes { 179 t.Log(profileType) 180 profileTypeParts := strings.Split(profileType, ":") 181 182 it, err := q.SelectMatchingProfiles(ctx, &ingesterv1.SelectProfilesRequest{ 183 LabelSelector: "{}", 184 Start: 0, 185 End: time.Now().UnixMilli(), 186 Type: &typesv1.ProfileType{ 187 Name: profileTypeParts[0], 188 SampleType: profileTypeParts[1], 189 SampleUnit: profileTypeParts[2], 190 PeriodType: profileTypeParts[3], 191 PeriodUnit: profileTypeParts[4], 192 }, 193 }) 194 require.NoError(t, err) 195 196 pcIt := &profileCounter{Iterator: it} 197 198 spanSelector, err := phlaremodel.NewSpanSelector([]string{}) 199 require.NoError(t, err) 200 resp, err := q.MergeBySpans(ctx, pcIt, spanSelector) 201 require.NoError(t, err) 202 203 require.Zero(t, resp.Total()) 204 profileCount += pcIt.count 205 } 206 207 require.Zero(t, profileCount) 208 }) 209 } 210 } 211 212 type fakeQuerier struct { 213 Querier 214 doErr bool 215 } 216 217 func (f *fakeQuerier) BlockID() string { 218 return "block-id" 219 } 220 221 func (f *fakeQuerier) SelectMatchingProfiles(ctx context.Context, params *ingesterv1.SelectProfilesRequest) (iter.Iterator[Profile], error) { 222 // add some jitter 223 time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) 224 if f.doErr { 225 return nil, fmt.Errorf("fake error") 226 } 227 profiles := []Profile{} 228 for i := 0; i < 100000; i++ { 229 profiles = append(profiles, BlockProfile{}) 230 } 231 return iter.NewSliceIterator(profiles), nil 232 } 233 234 func openSingleBlockQuerierIndex(t *testing.T, blockID string) *singleBlockQuerier { 235 t.Helper() 236 237 reader, err := index.NewFileReader(fmt.Sprintf("testdata/%s/index.tsdb", blockID)) 238 require.NoError(t, err) 239 240 q := &singleBlockQuerier{ 241 metrics: NewBlocksMetrics(nil), 242 meta: &block.Meta{ULID: ulid.MustParse(blockID)}, 243 opened: true, // Skip trying to open the block. 244 index: reader, 245 } 246 return q 247 } 248 249 func TestSelectMatchingProfilesCleanUp(t *testing.T) { 250 defer goleak.VerifyNone(t, goleak.IgnoreCurrent()) 251 252 _, err := SelectMatchingProfiles(context.Background(), &ingesterv1.SelectProfilesRequest{}, Queriers{ 253 &fakeQuerier{}, 254 &fakeQuerier{}, 255 &fakeQuerier{}, 256 &fakeQuerier{}, 257 &fakeQuerier{doErr: true}, 258 }) 259 require.Error(t, err) 260 } 261 262 func Test_singleBlockQuerier_Series(t *testing.T) { 263 ctx := context.Background() 264 q := openSingleBlockQuerierIndex(t, "01HA2V3CPSZ9E0HMQNNHH89WSS") 265 266 t.Run("get all names", func(t *testing.T) { 267 want := []string{ 268 "__delta__", 269 "__name__", 270 "__period_type__", 271 "__period_unit__", 272 "__profile_type__", 273 "__service_name__", 274 "__type__", 275 "__unit__", 276 "foo", 277 "function", 278 "pyroscope_spy", 279 "service_name", 280 "target", 281 "version", 282 } 283 got, err := q.index.LabelNames() 284 assert.NoError(t, err) 285 assert.Equal(t, want, got) 286 }) 287 288 t.Run("get label", func(t *testing.T) { 289 want := []*typesv1.Labels{ 290 {Labels: []*typesv1.LabelPair{ 291 {Name: "__name__", Value: "block"}, 292 }}, 293 {Labels: []*typesv1.LabelPair{ 294 {Name: "__name__", Value: "goroutine"}, 295 }}, 296 {Labels: []*typesv1.LabelPair{ 297 {Name: "__name__", Value: "memory"}, 298 }}, 299 {Labels: []*typesv1.LabelPair{ 300 {Name: "__name__", Value: "mutex"}, 301 }}, 302 {Labels: []*typesv1.LabelPair{ 303 {Name: "__name__", Value: "process_cpu"}, 304 }}, 305 } 306 got, err := q.Series(ctx, &ingesterv1.SeriesRequest{ 307 LabelNames: []string{ 308 "__name__", 309 }, 310 }) 311 312 assert.NoError(t, err) 313 assert.Equal(t, want, got) 314 }) 315 316 t.Run("get label with matcher", func(t *testing.T) { 317 want := []*typesv1.Labels{ 318 {Labels: []*typesv1.LabelPair{ 319 {Name: "__name__", Value: "block"}, 320 }}, 321 } 322 got, err := q.Series(ctx, &ingesterv1.SeriesRequest{ 323 Matchers: []string{`{__name__="block"}`}, 324 LabelNames: []string{"__name__"}, 325 }) 326 327 assert.NoError(t, err) 328 assert.Equal(t, want, got) 329 }) 330 331 t.Run("get multiple labels", func(t *testing.T) { 332 want := []*typesv1.Labels{ 333 {Labels: []*typesv1.LabelPair{ 334 {Name: "__name__", Value: "block"}, 335 {Name: "__type__", Value: "contentions"}, 336 }}, 337 {Labels: []*typesv1.LabelPair{ 338 {Name: "__name__", Value: "block"}, 339 {Name: "__type__", Value: "delay"}, 340 }}, 341 {Labels: []*typesv1.LabelPair{ 342 {Name: "__name__", Value: "goroutine"}, 343 {Name: "__type__", Value: "goroutines"}, 344 }}, 345 {Labels: []*typesv1.LabelPair{ 346 {Name: "__name__", Value: "memory"}, 347 {Name: "__type__", Value: "alloc_objects"}, 348 }}, 349 {Labels: []*typesv1.LabelPair{ 350 {Name: "__name__", Value: "memory"}, 351 {Name: "__type__", Value: "alloc_space"}, 352 }}, 353 {Labels: []*typesv1.LabelPair{ 354 {Name: "__name__", Value: "memory"}, 355 {Name: "__type__", Value: "inuse_objects"}, 356 }}, 357 {Labels: []*typesv1.LabelPair{ 358 {Name: "__name__", Value: "memory"}, 359 {Name: "__type__", Value: "inuse_space"}, 360 }}, 361 {Labels: []*typesv1.LabelPair{ 362 {Name: "__name__", Value: "mutex"}, 363 {Name: "__type__", Value: "contentions"}, 364 }}, 365 {Labels: []*typesv1.LabelPair{ 366 {Name: "__name__", Value: "mutex"}, 367 {Name: "__type__", Value: "delay"}, 368 }}, 369 {Labels: []*typesv1.LabelPair{ 370 {Name: "__name__", Value: "process_cpu"}, 371 {Name: "__type__", Value: "cpu"}, 372 }}, 373 } 374 got, err := q.Series(ctx, &ingesterv1.SeriesRequest{ 375 LabelNames: []string{"__name__", "__type__"}, 376 }) 377 378 assert.NoError(t, err) 379 assert.Equal(t, want, got) 380 }) 381 382 t.Run("get multiple labels with matcher", func(t *testing.T) { 383 want := []*typesv1.Labels{ 384 {Labels: []*typesv1.LabelPair{ 385 {Name: "__name__", Value: "memory"}, 386 {Name: "__type__", Value: "alloc_objects"}, 387 }}, 388 } 389 got, err := q.Series(ctx, &ingesterv1.SeriesRequest{ 390 Matchers: []string{`{__name__="memory",__type__="alloc_objects"}`}, 391 LabelNames: []string{"__name__", "__type__"}, 392 }) 393 394 assert.NoError(t, err) 395 assert.Equal(t, want, got) 396 }) 397 398 t.Run("empty labels and empty matcher", func(t *testing.T) { 399 want := []*typesv1.Labels{ 400 {Labels: []*typesv1.LabelPair{ 401 {Name: "__delta__", Value: "false"}, 402 {Name: "__name__", Value: "block"}, 403 {Name: "__profile_type__", Value: "block:contentions:count::"}, 404 {Name: "__service_name__", Value: "pyroscope"}, 405 {Name: "__type__", Value: "contentions"}, 406 {Name: "__unit__", Value: "count"}, 407 {Name: "pyroscope_spy", Value: "gospy"}, 408 {Name: "service_name", Value: "pyroscope"}, 409 {Name: "target", Value: "all"}, 410 {Name: "version", Value: "label-names-store-gateway-0e430f1e-WIP"}, 411 }}, 412 {Labels: []*typesv1.LabelPair{ 413 {Name: "__delta__", Value: "false"}, 414 {Name: "__name__", Value: "block"}, 415 {Name: "__profile_type__", Value: "block:delay:nanoseconds::"}, 416 {Name: "__service_name__", Value: "pyroscope"}, 417 {Name: "__type__", Value: "delay"}, 418 {Name: "__unit__", Value: "nanoseconds"}, 419 {Name: "pyroscope_spy", Value: "gospy"}, 420 {Name: "service_name", Value: "pyroscope"}, 421 {Name: "target", Value: "all"}, 422 {Name: "version", Value: "label-names-store-gateway-0e430f1e-WIP"}, 423 }}, 424 {Labels: []*typesv1.LabelPair{ 425 {Name: "__delta__", Value: "false"}, 426 {Name: "__name__", Value: "goroutine"}, 427 {Name: "__profile_type__", Value: "goroutine:goroutines:count::"}, 428 {Name: "__service_name__", Value: "pyroscope"}, 429 {Name: "__type__", Value: "goroutines"}, 430 {Name: "__unit__", Value: "count"}, 431 {Name: "pyroscope_spy", Value: "gospy"}, 432 {Name: "service_name", Value: "pyroscope"}, 433 {Name: "target", Value: "all"}, 434 {Name: "version", Value: "label-names-store-gateway-0e430f1e-WIP"}, 435 }}, 436 {Labels: []*typesv1.LabelPair{ 437 {Name: "__delta__", Value: "false"}, 438 {Name: "__name__", Value: "memory"}, 439 {Name: "__profile_type__", Value: "memory:alloc_objects:count::"}, 440 {Name: "__service_name__", Value: "pyroscope"}, 441 {Name: "__type__", Value: "alloc_objects"}, 442 {Name: "__unit__", Value: "count"}, 443 {Name: "pyroscope_spy", Value: "gospy"}, 444 {Name: "service_name", Value: "pyroscope"}, 445 {Name: "target", Value: "all"}, 446 {Name: "version", Value: "label-names-store-gateway-0e430f1e-WIP"}, 447 }}, 448 {Labels: []*typesv1.LabelPair{ 449 {Name: "__delta__", Value: "false"}, 450 {Name: "__name__", Value: "memory"}, 451 {Name: "__profile_type__", Value: "memory:alloc_objects:count::"}, 452 {Name: "__service_name__", Value: "simple.golang.app"}, 453 {Name: "__type__", Value: "alloc_objects"}, 454 {Name: "__unit__", Value: "count"}, 455 {Name: "pyroscope_spy", Value: "gospy"}, 456 {Name: "service_name", Value: "simple.golang.app"}, 457 }}, 458 {Labels: []*typesv1.LabelPair{ 459 {Name: "__delta__", Value: "false"}, 460 {Name: "__name__", Value: "memory"}, 461 {Name: "__profile_type__", Value: "memory:alloc_space:bytes::"}, 462 {Name: "__service_name__", Value: "pyroscope"}, 463 {Name: "__type__", Value: "alloc_space"}, 464 {Name: "__unit__", Value: "bytes"}, 465 {Name: "pyroscope_spy", Value: "gospy"}, 466 {Name: "service_name", Value: "pyroscope"}, 467 {Name: "target", Value: "all"}, 468 {Name: "version", Value: "label-names-store-gateway-0e430f1e-WIP"}, 469 }}, 470 {Labels: []*typesv1.LabelPair{ 471 {Name: "__delta__", Value: "false"}, 472 {Name: "__name__", Value: "memory"}, 473 {Name: "__profile_type__", Value: "memory:alloc_space:bytes::"}, 474 {Name: "__service_name__", Value: "simple.golang.app"}, 475 {Name: "__type__", Value: "alloc_space"}, 476 {Name: "__unit__", Value: "bytes"}, 477 {Name: "pyroscope_spy", Value: "gospy"}, 478 {Name: "service_name", Value: "simple.golang.app"}, 479 }}, 480 {Labels: []*typesv1.LabelPair{ 481 {Name: "__delta__", Value: "false"}, 482 {Name: "__name__", Value: "memory"}, 483 {Name: "__profile_type__", Value: "memory:inuse_objects:count::"}, 484 {Name: "__service_name__", Value: "pyroscope"}, 485 {Name: "__type__", Value: "inuse_objects"}, 486 {Name: "__unit__", Value: "count"}, 487 {Name: "pyroscope_spy", Value: "gospy"}, 488 {Name: "service_name", Value: "pyroscope"}, 489 {Name: "target", Value: "all"}, 490 {Name: "version", Value: "label-names-store-gateway-0e430f1e-WIP"}, 491 }}, 492 {Labels: []*typesv1.LabelPair{ 493 {Name: "__delta__", Value: "false"}, 494 {Name: "__name__", Value: "memory"}, 495 {Name: "__profile_type__", Value: "memory:inuse_objects:count::"}, 496 {Name: "__service_name__", Value: "simple.golang.app"}, 497 {Name: "__type__", Value: "inuse_objects"}, 498 {Name: "__unit__", Value: "count"}, 499 {Name: "pyroscope_spy", Value: "gospy"}, 500 {Name: "service_name", Value: "simple.golang.app"}, 501 }}, 502 {Labels: []*typesv1.LabelPair{ 503 {Name: "__delta__", Value: "false"}, 504 {Name: "__name__", Value: "memory"}, 505 {Name: "__profile_type__", Value: "memory:inuse_space:bytes::"}, 506 {Name: "__service_name__", Value: "pyroscope"}, 507 {Name: "__type__", Value: "inuse_space"}, 508 {Name: "__unit__", Value: "bytes"}, 509 {Name: "pyroscope_spy", Value: "gospy"}, 510 {Name: "service_name", Value: "pyroscope"}, 511 {Name: "target", Value: "all"}, 512 {Name: "version", Value: "label-names-store-gateway-0e430f1e-WIP"}, 513 }}, 514 {Labels: []*typesv1.LabelPair{ 515 {Name: "__delta__", Value: "false"}, 516 {Name: "__name__", Value: "memory"}, 517 {Name: "__profile_type__", Value: "memory:inuse_space:bytes::"}, 518 {Name: "__service_name__", Value: "simple.golang.app"}, 519 {Name: "__type__", Value: "inuse_space"}, 520 {Name: "__unit__", Value: "bytes"}, 521 {Name: "pyroscope_spy", Value: "gospy"}, 522 {Name: "service_name", Value: "simple.golang.app"}, 523 }}, 524 {Labels: []*typesv1.LabelPair{ 525 {Name: "__delta__", Value: "false"}, 526 {Name: "__name__", Value: "mutex"}, 527 {Name: "__profile_type__", Value: "mutex:contentions:count::"}, 528 {Name: "__service_name__", Value: "pyroscope"}, 529 {Name: "__type__", Value: "contentions"}, 530 {Name: "__unit__", Value: "count"}, 531 {Name: "pyroscope_spy", Value: "gospy"}, 532 {Name: "service_name", Value: "pyroscope"}, 533 {Name: "target", Value: "all"}, 534 {Name: "version", Value: "label-names-store-gateway-0e430f1e-WIP"}, 535 }}, 536 {Labels: []*typesv1.LabelPair{ 537 {Name: "__delta__", Value: "false"}, 538 {Name: "__name__", Value: "mutex"}, 539 {Name: "__profile_type__", Value: "mutex:delay:nanoseconds::"}, 540 {Name: "__service_name__", Value: "pyroscope"}, 541 {Name: "__type__", Value: "delay"}, 542 {Name: "__unit__", Value: "nanoseconds"}, 543 {Name: "pyroscope_spy", Value: "gospy"}, 544 {Name: "service_name", Value: "pyroscope"}, 545 {Name: "target", Value: "all"}, 546 {Name: "version", Value: "label-names-store-gateway-0e430f1e-WIP"}, 547 }}, 548 {Labels: []*typesv1.LabelPair{ 549 {Name: "__delta__", Value: "false"}, 550 {Name: "__name__", Value: "process_cpu"}, 551 {Name: "__period_type__", Value: "cpu"}, 552 {Name: "__period_unit__", Value: "nanoseconds"}, 553 {Name: "__profile_type__", Value: "process_cpu:cpu:nanoseconds:cpu:nanoseconds"}, 554 {Name: "__service_name__", Value: "pyroscope"}, 555 {Name: "__type__", Value: "cpu"}, 556 {Name: "__unit__", Value: "nanoseconds"}, 557 {Name: "pyroscope_spy", Value: "gospy"}, 558 {Name: "service_name", Value: "pyroscope"}, 559 {Name: "target", Value: "all"}, 560 {Name: "version", Value: "label-names-store-gateway-0e430f1e-WIP"}, 561 }}, 562 {Labels: []*typesv1.LabelPair{ 563 {Name: "__delta__", Value: "false"}, 564 {Name: "__name__", Value: "process_cpu"}, 565 {Name: "__period_type__", Value: "cpu"}, 566 {Name: "__period_unit__", Value: "nanoseconds"}, 567 {Name: "__profile_type__", Value: "process_cpu:cpu:nanoseconds:cpu:nanoseconds"}, 568 {Name: "__service_name__", Value: "simple.golang.app"}, 569 {Name: "__type__", Value: "cpu"}, 570 {Name: "__unit__", Value: "nanoseconds"}, 571 {Name: "foo", Value: "bar"}, 572 {Name: "function", Value: "fast"}, 573 {Name: "pyroscope_spy", Value: "gospy"}, 574 {Name: "service_name", Value: "simple.golang.app"}, 575 }}, 576 {Labels: []*typesv1.LabelPair{ 577 {Name: "__delta__", Value: "false"}, 578 {Name: "__name__", Value: "process_cpu"}, 579 {Name: "__period_type__", Value: "cpu"}, 580 {Name: "__period_unit__", Value: "nanoseconds"}, 581 {Name: "__profile_type__", Value: "process_cpu:cpu:nanoseconds:cpu:nanoseconds"}, 582 {Name: "__service_name__", Value: "simple.golang.app"}, 583 {Name: "__type__", Value: "cpu"}, 584 {Name: "__unit__", Value: "nanoseconds"}, 585 {Name: "foo", Value: "bar"}, 586 {Name: "function", Value: "slow"}, 587 {Name: "pyroscope_spy", Value: "gospy"}, 588 {Name: "service_name", Value: "simple.golang.app"}, 589 }}, 590 {Labels: []*typesv1.LabelPair{ 591 {Name: "__delta__", Value: "false"}, 592 {Name: "__name__", Value: "process_cpu"}, 593 {Name: "__period_type__", Value: "cpu"}, 594 {Name: "__period_unit__", Value: "nanoseconds"}, 595 {Name: "__profile_type__", Value: "process_cpu:cpu:nanoseconds:cpu:nanoseconds"}, 596 {Name: "__service_name__", Value: "simple.golang.app"}, 597 {Name: "__type__", Value: "cpu"}, 598 {Name: "__unit__", Value: "nanoseconds"}, 599 {Name: "foo", Value: "bar"}, 600 {Name: "pyroscope_spy", Value: "gospy"}, 601 {Name: "service_name", Value: "simple.golang.app"}, 602 }}, 603 {Labels: []*typesv1.LabelPair{ 604 {Name: "__delta__", Value: "false"}, 605 {Name: "__name__", Value: "process_cpu"}, 606 {Name: "__period_type__", Value: "cpu"}, 607 {Name: "__period_unit__", Value: "nanoseconds"}, 608 {Name: "__profile_type__", Value: "process_cpu:cpu:nanoseconds:cpu:nanoseconds"}, 609 {Name: "__service_name__", Value: "simple.golang.app"}, 610 {Name: "__type__", Value: "cpu"}, 611 {Name: "__unit__", Value: "nanoseconds"}, 612 {Name: "pyroscope_spy", Value: "gospy"}, 613 {Name: "service_name", Value: "simple.golang.app"}, 614 }}, 615 } 616 got, err := q.Series(ctx, &ingesterv1.SeriesRequest{ 617 Matchers: []string{}, 618 LabelNames: []string{}, 619 }) 620 621 assert.NoError(t, err) 622 assert.Equal(t, want, got) 623 }) 624 625 t.Run("ui plugin", func(t *testing.T) { 626 want := []*typesv1.Labels{ 627 {Labels: []*typesv1.LabelPair{ 628 {Name: "__name__", Value: "block"}, 629 {Name: "__profile_type__", Value: "block:contentions:count::"}, 630 {Name: "__type__", Value: "contentions"}, 631 {Name: "service_name", Value: "pyroscope"}, 632 }}, 633 {Labels: []*typesv1.LabelPair{ 634 {Name: "__name__", Value: "block"}, 635 {Name: "__profile_type__", Value: "block:delay:nanoseconds::"}, 636 {Name: "__type__", Value: "delay"}, 637 {Name: "service_name", Value: "pyroscope"}, 638 }}, 639 {Labels: []*typesv1.LabelPair{ 640 {Name: "__name__", Value: "goroutine"}, 641 {Name: "__profile_type__", Value: "goroutine:goroutines:count::"}, 642 {Name: "__type__", Value: "goroutines"}, 643 {Name: "service_name", Value: "pyroscope"}, 644 }}, 645 {Labels: []*typesv1.LabelPair{ 646 {Name: "__name__", Value: "memory"}, 647 {Name: "__profile_type__", Value: "memory:alloc_objects:count::"}, 648 {Name: "__type__", Value: "alloc_objects"}, 649 {Name: "service_name", Value: "pyroscope"}, 650 }}, 651 {Labels: []*typesv1.LabelPair{ 652 {Name: "__name__", Value: "memory"}, 653 {Name: "__profile_type__", Value: "memory:alloc_objects:count::"}, 654 {Name: "__type__", Value: "alloc_objects"}, 655 {Name: "service_name", Value: "simple.golang.app"}, 656 }}, 657 {Labels: []*typesv1.LabelPair{ 658 {Name: "__name__", Value: "memory"}, 659 {Name: "__profile_type__", Value: "memory:alloc_space:bytes::"}, 660 {Name: "__type__", Value: "alloc_space"}, 661 {Name: "service_name", Value: "pyroscope"}, 662 }}, 663 {Labels: []*typesv1.LabelPair{ 664 {Name: "__name__", Value: "memory"}, 665 {Name: "__profile_type__", Value: "memory:alloc_space:bytes::"}, 666 {Name: "__type__", Value: "alloc_space"}, 667 {Name: "service_name", Value: "simple.golang.app"}, 668 }}, 669 {Labels: []*typesv1.LabelPair{ 670 {Name: "__name__", Value: "memory"}, 671 {Name: "__profile_type__", Value: "memory:inuse_objects:count::"}, 672 {Name: "__type__", Value: "inuse_objects"}, 673 {Name: "service_name", Value: "pyroscope"}, 674 }}, 675 {Labels: []*typesv1.LabelPair{ 676 {Name: "__name__", Value: "memory"}, 677 {Name: "__profile_type__", Value: "memory:inuse_objects:count::"}, 678 {Name: "__type__", Value: "inuse_objects"}, 679 {Name: "service_name", Value: "simple.golang.app"}, 680 }}, 681 {Labels: []*typesv1.LabelPair{ 682 {Name: "__name__", Value: "memory"}, 683 {Name: "__profile_type__", Value: "memory:inuse_space:bytes::"}, 684 {Name: "__type__", Value: "inuse_space"}, 685 {Name: "service_name", Value: "pyroscope"}, 686 }}, 687 {Labels: []*typesv1.LabelPair{ 688 {Name: "__name__", Value: "memory"}, 689 {Name: "__profile_type__", Value: "memory:inuse_space:bytes::"}, 690 {Name: "__type__", Value: "inuse_space"}, 691 {Name: "service_name", Value: "simple.golang.app"}, 692 }}, 693 {Labels: []*typesv1.LabelPair{ 694 {Name: "__name__", Value: "mutex"}, 695 {Name: "__profile_type__", Value: "mutex:contentions:count::"}, 696 {Name: "__type__", Value: "contentions"}, 697 {Name: "service_name", Value: "pyroscope"}, 698 }}, 699 {Labels: []*typesv1.LabelPair{ 700 {Name: "__name__", Value: "mutex"}, 701 {Name: "__profile_type__", Value: "mutex:delay:nanoseconds::"}, 702 {Name: "__type__", Value: "delay"}, 703 {Name: "service_name", Value: "pyroscope"}, 704 }}, 705 {Labels: []*typesv1.LabelPair{ 706 {Name: "__name__", Value: "process_cpu"}, 707 {Name: "__profile_type__", Value: "process_cpu:cpu:nanoseconds:cpu:nanoseconds"}, 708 {Name: "__type__", Value: "cpu"}, 709 {Name: "service_name", Value: "pyroscope"}, 710 }}, 711 {Labels: []*typesv1.LabelPair{ 712 {Name: "__name__", Value: "process_cpu"}, 713 {Name: "__profile_type__", Value: "process_cpu:cpu:nanoseconds:cpu:nanoseconds"}, 714 {Name: "__type__", Value: "cpu"}, 715 {Name: "service_name", Value: "simple.golang.app"}, 716 }}, 717 } 718 got, err := q.Series(ctx, &ingesterv1.SeriesRequest{ 719 Matchers: []string{}, 720 LabelNames: []string{ 721 "pyroscope_app", 722 "service_name", 723 "__profile_type__", 724 "__type__", 725 "__name__", 726 }, 727 }) 728 729 assert.NoError(t, err) 730 assert.Equal(t, want, got) 731 }) 732 } 733 734 func Test_singleBlockQuerier_LabelNames(t *testing.T) { 735 ctx := context.Background() 736 reader, err := index.NewFileReader("testdata/01HA2V3CPSZ9E0HMQNNHH89WSS/index.tsdb") 737 assert.NoError(t, err) 738 739 q := &singleBlockQuerier{ 740 metrics: NewBlocksMetrics(nil), 741 meta: &block.Meta{ULID: ulid.MustParse("01HA2V3CPSZ9E0HMQNNHH89WSS")}, 742 opened: true, // Skip trying to open the block. 743 index: reader, 744 } 745 746 t.Run("no matchers", func(t *testing.T) { 747 want := []string{ 748 "__delta__", 749 "__name__", 750 "__period_type__", 751 "__period_unit__", 752 "__profile_type__", 753 "__service_name__", 754 "__type__", 755 "__unit__", 756 "foo", 757 "function", 758 "pyroscope_spy", 759 "service_name", 760 "target", 761 "version", 762 } 763 764 got, err := q.LabelNames(ctx, connect.NewRequest(&typesv1.LabelNamesRequest{ 765 Matchers: []string{}, 766 })) 767 assert.NoError(t, err) 768 assert.Equal(t, want, got.Msg.Names) 769 }) 770 771 t.Run("empty matcher", func(t *testing.T) { 772 want := []string{ 773 "__delta__", 774 "__name__", 775 "__period_type__", 776 "__period_unit__", 777 "__profile_type__", 778 "__service_name__", 779 "__type__", 780 "__unit__", 781 "foo", 782 "function", 783 "pyroscope_spy", 784 "service_name", 785 "target", 786 "version", 787 } 788 789 got, err := q.LabelNames(ctx, connect.NewRequest(&typesv1.LabelNamesRequest{ 790 Matchers: []string{`{}`}, 791 })) 792 assert.NoError(t, err) 793 assert.Equal(t, want, got.Msg.Names) 794 }) 795 796 t.Run("single matcher", func(t *testing.T) { 797 want := []string{ 798 "__delta__", 799 "__name__", 800 "__period_type__", 801 "__period_unit__", 802 "__profile_type__", 803 "__service_name__", 804 "__type__", 805 "__unit__", 806 "foo", 807 "function", 808 "pyroscope_spy", 809 "service_name", 810 "target", 811 "version", 812 } 813 814 got, err := q.LabelNames(ctx, connect.NewRequest(&typesv1.LabelNamesRequest{ 815 Matchers: []string{`{__name__="process_cpu"}`}, 816 })) 817 assert.NoError(t, err) 818 assert.Equal(t, want, got.Msg.Names) 819 }) 820 821 t.Run("multiple matchers", func(t *testing.T) { 822 want := []string{ 823 "__delta__", 824 "__name__", 825 "__profile_type__", 826 "__service_name__", 827 "__type__", 828 "__unit__", 829 "pyroscope_spy", 830 "service_name", 831 "target", 832 "version", 833 } 834 835 got, err := q.LabelNames(ctx, connect.NewRequest(&typesv1.LabelNamesRequest{ 836 Matchers: []string{`{__name__="memory",__type__="alloc_objects"}`}, 837 })) 838 assert.NoError(t, err) 839 assert.Equal(t, want, got.Msg.Names) 840 }) 841 842 t.Run("ui plugin", func(t *testing.T) { 843 want := []string{ 844 "__delta__", 845 "__name__", 846 "__period_type__", 847 "__period_unit__", 848 "__profile_type__", 849 "__service_name__", 850 "__type__", 851 "__unit__", 852 "foo", 853 "function", 854 "pyroscope_spy", 855 "service_name", 856 } 857 858 got, err := q.LabelNames(ctx, connect.NewRequest(&typesv1.LabelNamesRequest{ 859 Matchers: []string{`{__profile_type__="process_cpu:cpu:nanoseconds:cpu:nanoseconds"}`, `{service_name="simple.golang.app"}`}, 860 })) 861 assert.NoError(t, err) 862 assert.Equal(t, want, got.Msg.Names) 863 }) 864 } 865 866 func Test_singleBlockQuerier_LabelValues(t *testing.T) { 867 ctx := context.Background() 868 reader, err := index.NewFileReader("testdata/01HA2V3CPSZ9E0HMQNNHH89WSS/index.tsdb") 869 assert.NoError(t, err) 870 871 q := &singleBlockQuerier{ 872 metrics: NewBlocksMetrics(nil), 873 meta: &block.Meta{ULID: ulid.MustParse("01HA2V3CPSZ9E0HMQNNHH89WSS")}, 874 opened: true, // Skip trying to open the block. 875 index: reader, 876 } 877 878 t.Run("no matchers", func(t *testing.T) { 879 want := []string{ 880 "pyroscope", 881 "simple.golang.app", 882 } 883 884 got, err := q.LabelValues(ctx, connect.NewRequest(&typesv1.LabelValuesRequest{ 885 Matchers: []string{}, 886 Name: "service_name", 887 })) 888 assert.NoError(t, err) 889 assert.Equal(t, want, got.Msg.Names) 890 }) 891 892 t.Run("empty matcher", func(t *testing.T) { 893 want := []string{ 894 "pyroscope", 895 "simple.golang.app", 896 } 897 898 got, err := q.LabelValues(ctx, connect.NewRequest(&typesv1.LabelValuesRequest{ 899 Matchers: []string{`{}`}, 900 Name: "service_name", 901 })) 902 assert.NoError(t, err) 903 assert.Equal(t, want, got.Msg.Names) 904 }) 905 906 t.Run("single matcher", func(t *testing.T) { 907 want := []string{ 908 "fast", 909 "slow", 910 } 911 912 got, err := q.LabelValues(ctx, connect.NewRequest(&typesv1.LabelValuesRequest{ 913 Matchers: []string{`{service_name="simple.golang.app"}`}, 914 Name: "function", 915 })) 916 assert.NoError(t, err) 917 assert.Equal(t, want, got.Msg.Names) 918 919 // Pyroscope app shouldn't have any function label values. 920 got, err = q.LabelValues(ctx, connect.NewRequest(&typesv1.LabelValuesRequest{ 921 Matchers: []string{`{service_name="pyroscope"}`}, 922 Name: "function", 923 })) 924 assert.NoError(t, err) 925 assert.Empty(t, got.Msg.Names) 926 }) 927 928 t.Run("multiple matchers", func(t *testing.T) { 929 want := []string{ 930 "fast", 931 "slow", 932 } 933 934 got, err := q.LabelValues(ctx, connect.NewRequest(&typesv1.LabelValuesRequest{ 935 Matchers: []string{`{__profile_type__="process_cpu:cpu:nanoseconds:cpu:nanoseconds", service_name="simple.golang.app"}`}, 936 Name: "function", 937 })) 938 assert.NoError(t, err) 939 assert.Equal(t, want, got.Msg.Names) 940 941 // Memory profiles shouldn't have 'function' label values. 942 got, err = q.LabelValues(ctx, connect.NewRequest(&typesv1.LabelValuesRequest{ 943 Matchers: []string{`{__profile_type__="memory:alloc_objects:count:space:bytes", service_name="simple.golang.app"}`}, 944 Name: "function", 945 })) 946 assert.NoError(t, err) 947 assert.Empty(t, got.Msg.Names) 948 }) 949 950 t.Run("ui plugin", func(t *testing.T) { 951 want := []string{ 952 "fast", 953 "slow", 954 } 955 956 got, err := q.LabelValues(ctx, connect.NewRequest(&typesv1.LabelValuesRequest{ 957 Matchers: []string{`{__profile_type__="process_cpu:cpu:nanoseconds:cpu:nanoseconds", service_name="simple.golang.app"}`}, 958 Name: "function", 959 })) 960 assert.NoError(t, err) 961 assert.Equal(t, want, got.Msg.Names) 962 }) 963 } 964 965 func Test_singleBlockQuerier_ProfileTypes(t *testing.T) { 966 ctx := context.Background() 967 reader, err := index.NewFileReader("testdata/01HA2V3CPSZ9E0HMQNNHH89WSS/index.tsdb") 968 assert.NoError(t, err) 969 970 q := &singleBlockQuerier{ 971 metrics: NewBlocksMetrics(nil), 972 meta: &block.Meta{ULID: ulid.MustParse("01HA2V3CPSZ9E0HMQNNHH89WSS")}, 973 opened: true, // Skip trying to open the block. 974 index: reader, 975 } 976 977 want := []*typesv1.ProfileType{ 978 { 979 ID: "block:contentions:count::", 980 Name: "block", 981 SampleType: "contentions", 982 SampleUnit: "count", 983 PeriodType: "", 984 PeriodUnit: "", 985 }, 986 { 987 ID: "block:delay:nanoseconds::", 988 Name: "block", 989 SampleType: "delay", 990 SampleUnit: "nanoseconds", 991 PeriodType: "", 992 PeriodUnit: "", 993 }, 994 { 995 ID: "goroutine:goroutines:count::", 996 Name: "goroutine", 997 SampleType: "goroutines", 998 SampleUnit: "count", 999 PeriodType: "", 1000 PeriodUnit: "", 1001 }, 1002 { 1003 ID: "memory:alloc_objects:count::", 1004 Name: "memory", 1005 SampleType: "alloc_objects", 1006 SampleUnit: "count", 1007 PeriodType: "", 1008 PeriodUnit: "", 1009 }, 1010 { 1011 ID: "memory:alloc_space:bytes::", 1012 Name: "memory", 1013 SampleType: "alloc_space", 1014 SampleUnit: "bytes", 1015 PeriodType: "", 1016 PeriodUnit: "", 1017 }, 1018 { 1019 ID: "memory:inuse_objects:count::", 1020 Name: "memory", 1021 SampleType: "inuse_objects", 1022 SampleUnit: "count", 1023 PeriodType: "", 1024 PeriodUnit: "", 1025 }, 1026 { 1027 ID: "memory:inuse_space:bytes::", 1028 Name: "memory", 1029 SampleType: "inuse_space", 1030 SampleUnit: "bytes", 1031 PeriodType: "", 1032 PeriodUnit: "", 1033 }, 1034 { 1035 ID: "mutex:contentions:count::", 1036 Name: "mutex", 1037 SampleType: "contentions", 1038 SampleUnit: "count", 1039 PeriodType: "", 1040 PeriodUnit: "", 1041 }, 1042 { 1043 ID: "mutex:delay:nanoseconds::", 1044 Name: "mutex", 1045 SampleType: "delay", 1046 SampleUnit: "nanoseconds", 1047 PeriodType: "", 1048 PeriodUnit: "", 1049 }, 1050 { 1051 ID: "process_cpu:cpu:nanoseconds:cpu:nanoseconds", 1052 Name: "process_cpu", 1053 SampleType: "cpu", 1054 SampleUnit: "nanoseconds", 1055 PeriodType: "cpu", 1056 PeriodUnit: "nanoseconds", 1057 }, 1058 } 1059 1060 got, err := q.ProfileTypes(ctx, &connect.Request[ingesterv1.ProfileTypesRequest]{}) 1061 assert.NoError(t, err) 1062 assert.Equal(t, want, got.Msg.ProfileTypes) 1063 } 1064 1065 func Benchmark_singleBlockQuerier_Series(b *testing.B) { 1066 const id = "01HA2V3CPSZ9E0HMQNNHH89WSS" 1067 1068 ctx := context.Background() 1069 reader, err := index.NewFileReader(fmt.Sprintf("testdata/%s/index.tsdb", id)) 1070 assert.NoError(b, err) 1071 1072 q := &singleBlockQuerier{ 1073 metrics: NewBlocksMetrics(nil), 1074 meta: &block.Meta{ULID: ulid.MustParse(id)}, 1075 opened: true, // Skip trying to open the block. 1076 index: reader, 1077 } 1078 1079 b.Run("multiple labels", func(b *testing.B) { 1080 for n := 0; n < b.N; n++ { 1081 q.Series(ctx, &ingesterv1.SeriesRequest{ //nolint:errcheck 1082 Matchers: []string{`{__name__="block"}`}, 1083 LabelNames: []string{"__name__"}, 1084 }) 1085 } 1086 }) 1087 1088 b.Run("multiple labels with matcher", func(b *testing.B) { 1089 for n := 0; n < b.N; n++ { 1090 q.Series(ctx, &ingesterv1.SeriesRequest{ //nolint:errcheck 1091 Matchers: []string{`{__name__="memory",__type__="alloc_objects"}`}, 1092 LabelNames: []string{"__name__", "__type__"}, 1093 }) 1094 } 1095 }) 1096 1097 b.Run("UI request", func(b *testing.B) { 1098 for n := 0; n < b.N; n++ { 1099 q.Series(ctx, &ingesterv1.SeriesRequest{ //nolint:errcheck 1100 Matchers: []string{}, 1101 LabelNames: []string{"pyroscope_app", "service_name", "__profile_type__", "__type__", "__name__"}, 1102 }) 1103 } 1104 }) 1105 } 1106 1107 func Benchmark_singleBlockQuerier_LabelNames(b *testing.B) { 1108 ctx := context.Background() 1109 reader, err := index.NewFileReader("testdata/01HA2V3CPSZ9E0HMQNNHH89WSS/index.tsdb") 1110 assert.NoError(b, err) 1111 1112 q := &singleBlockQuerier{ 1113 metrics: NewBlocksMetrics(nil), 1114 meta: &block.Meta{ULID: ulid.MustParse("01HA2V3CPSZ9E0HMQNNHH89WSS")}, 1115 opened: true, // Skip trying to open the block. 1116 index: reader, 1117 } 1118 1119 b.Run("multiple matchers", func(b *testing.B) { 1120 for n := 0; n < b.N; n++ { 1121 q.LabelNames(ctx, connect.NewRequest(&typesv1.LabelNamesRequest{ //nolint:errcheck 1122 Matchers: []string{`{__profile_type__="process_cpu:cpu:nanoseconds:cpu:nanoseconds"}`, `{service_name="simple.golang.app"}`}, 1123 })) 1124 } 1125 }) 1126 } 1127 1128 func TestSelectMergeStacktraces(t *testing.T) { 1129 ctx := context.Background() 1130 1131 querier := newBlock(t, func() (res []*testhelper.ProfileBuilder) { 1132 for i := int64(1); i < 1001; i++ { 1133 res = append(res, testhelper.NewProfileBuilder(int64(time.Second)*i). 1134 CPUProfile(). 1135 WithLabels( 1136 "job", "a", 1137 ).ForStacktraceString("foo", "bar", "baz").AddSamples(1), 1138 testhelper.NewProfileBuilder(int64(time.Second*2)*i). 1139 CPUProfile(). 1140 WithLabels( 1141 "job", "b", 1142 ).ForStacktraceString("foo", "bar", "buzz").AddSamples(1), 1143 testhelper.NewProfileBuilder(int64(time.Second*3)*i). 1144 CPUProfile(). 1145 WithLabels( 1146 "job", "c", 1147 ).ForStacktraceString("foo", "bar").AddSamples(1)) 1148 } 1149 return res 1150 }) 1151 1152 err := querier.Open(ctx) 1153 require.NoError(t, err) 1154 1155 merge, err := querier.SelectMergeByStacktraces(ctx, &ingesterv1.SelectProfilesRequest{ 1156 LabelSelector: `{}`, 1157 Type: &typesv1.ProfileType{ 1158 ID: "process_cpu:cpu:nanoseconds:cpu:nanoseconds", 1159 Name: "process_cpu", 1160 SampleType: "cpu", 1161 SampleUnit: "nanoseconds", 1162 PeriodType: "cpu", 1163 PeriodUnit: "nanoseconds", 1164 }, 1165 Start: 0, 1166 End: int64(model.TimeFromUnixNano(math.MaxInt64)), 1167 }, 16<<10) 1168 require.NoError(t, err) 1169 expected := phlaremodel.Tree{} 1170 expected.InsertStack(1000, "baz", "bar", "foo") 1171 expected.InsertStack(1000, "buzz", "bar", "foo") 1172 expected.InsertStack(1000, "bar", "foo") 1173 require.Equal(t, expected.String(), merge.String()) 1174 require.NoError(t, querier.Close()) 1175 } 1176 1177 func TestSelectMergeLabels(t *testing.T) { 1178 ctx := context.Background() 1179 1180 querier := newBlock(t, func() (res []*testhelper.ProfileBuilder) { 1181 for i := int64(1); i < 6; i++ { 1182 res = append(res, testhelper.NewProfileBuilder(int64(time.Second)*i). 1183 CPUProfile(). 1184 WithLabels( 1185 "job", "a", 1186 ).ForStacktraceString("foo", "bar", "baz").AddSamples(1), 1187 testhelper.NewProfileBuilder(int64(time.Second)*i). 1188 CPUProfile(). 1189 WithLabels( 1190 "job", "b", 1191 ).ForStacktraceString("foo", "bar", "buzz").AddSamples(1), 1192 testhelper.NewProfileBuilder(int64(time.Second)*i). 1193 CPUProfile(). 1194 WithLabels( 1195 "job", "c", 1196 ).ForStacktraceString("foo", "bar").AddSamples(1)) 1197 } 1198 return res 1199 }) 1200 1201 err := querier.Open(ctx) 1202 require.NoError(t, err) 1203 1204 merge, err := querier.SelectMergeByLabels(ctx, &ingesterv1.SelectProfilesRequest{ 1205 LabelSelector: `{}`, 1206 Type: &typesv1.ProfileType{ 1207 ID: "process_cpu:cpu:nanoseconds:cpu:nanoseconds", 1208 Name: "process_cpu", 1209 SampleType: "cpu", 1210 SampleUnit: "nanoseconds", 1211 PeriodType: "cpu", 1212 PeriodUnit: "nanoseconds", 1213 }, 1214 Start: 0, 1215 End: int64(model.TimeFromUnixNano(math.MaxInt64)), 1216 }, nil, "job") 1217 require.NoError(t, err) 1218 expected := []*typesv1.Series{ 1219 { 1220 Labels: phlaremodel.LabelsFromStrings("job", "a"), 1221 Points: genPoints(5), 1222 }, 1223 { 1224 Labels: phlaremodel.LabelsFromStrings("job", "b"), 1225 Points: genPoints(5), 1226 }, 1227 { 1228 Labels: phlaremodel.LabelsFromStrings("job", "c"), 1229 Points: genPoints(5), 1230 }, 1231 } 1232 require.Equal(t, expected, merge) 1233 require.NoError(t, querier.Close()) 1234 } 1235 1236 func TestSelectMergeLabels_StackTraceSelector(t *testing.T) { 1237 ctx := context.Background() 1238 1239 querier := newBlock(t, func() (res []*testhelper.ProfileBuilder) { 1240 for i := int64(1); i < 7; i++ { 1241 // Keep in mind that leaf is at location[0]. 1242 res = append(res, testhelper.NewProfileBuilder(int64(time.Second)*i). 1243 CPUProfile(). 1244 WithLabels("job", "a"). 1245 ForStacktraceString("foo").AddSamples(1). 1246 ForStacktraceString("baz", "bar", "foo").AddSamples(1). 1247 ForStacktraceString("baz", "foo").AddSamples(1), 1248 ) 1249 } 1250 return res 1251 }) 1252 1253 err := querier.Open(ctx) 1254 require.NoError(t, err) 1255 1256 merge, err := querier.SelectMergeByLabels(ctx, &ingesterv1.SelectProfilesRequest{ 1257 LabelSelector: `{}`, 1258 Type: &typesv1.ProfileType{ 1259 ID: "process_cpu:cpu:nanoseconds:cpu:nanoseconds", 1260 Name: "process_cpu", 1261 SampleType: "cpu", 1262 SampleUnit: "nanoseconds", 1263 PeriodType: "cpu", 1264 PeriodUnit: "nanoseconds", 1265 }, 1266 Start: 0, 1267 End: int64(model.TimeFromUnixNano(math.MaxInt64)), 1268 }, &typesv1.StackTraceSelector{ 1269 CallSite: []*typesv1.Location{ 1270 {Name: "foo"}, 1271 {Name: "bar"}, 1272 }, 1273 }, "job") 1274 require.NoError(t, err) 1275 expected := []*typesv1.Series{ 1276 { 1277 Labels: phlaremodel.LabelsFromStrings("job", "a"), 1278 Points: genPoints(6), 1279 }, 1280 } 1281 require.Equal(t, expected, merge) 1282 require.NoError(t, querier.Close()) 1283 } 1284 1285 func genPoints(count int) []*typesv1.Point { 1286 points := make([]*typesv1.Point, 0, count) 1287 for i := 1; i < count+1; i++ { 1288 points = append(points, &typesv1.Point{ 1289 Timestamp: int64(model.TimeFromUnixNano(int64(time.Second * time.Duration(i)))), 1290 Value: 1, 1291 Annotations: []*typesv1.ProfileAnnotation{}, 1292 }) 1293 } 1294 return points 1295 } 1296 1297 func TestSelectMergeByStacktracesRace(t *testing.T) { 1298 testSelectMergeByStacktracesRace(t, 30) 1299 } 1300 1301 func BenchmarkSelectMergeByStacktracesRace(b *testing.B) { 1302 testSelectMergeByStacktracesRace(b, b.N) 1303 } 1304 1305 func testSelectMergeByStacktracesRace(t testing.TB, times int) { 1306 defer goleak.VerifyNone(t, goleak.IgnoreCurrent()) 1307 1308 ctx := context.Background() 1309 1310 querier := newBlock(t, func() []*testhelper.ProfileBuilder { 1311 return []*testhelper.ProfileBuilder{ 1312 testhelper.NewProfileBuilder(int64(time.Second*1)). 1313 CPUProfile(). 1314 WithLabels( 1315 "job", "a", 1316 ).ForStacktraceString("foo", "bar", "baz").AddSamples(1), 1317 testhelper.NewProfileBuilder(int64(time.Second*2)). 1318 CPUProfile(). 1319 WithLabels( 1320 "job", "b", 1321 ).ForStacktraceString("foo", "bar", "baz").AddSamples(1), 1322 testhelper.NewProfileBuilder(int64(time.Second*3)). 1323 CPUProfile(). 1324 WithLabels( 1325 "job", "c", 1326 ).ForStacktraceString("foo", "bar", "baz").AddSamples(1), 1327 } 1328 }) 1329 1330 err := querier.Open(ctx) 1331 require.NoError(t, err) 1332 g, ctx := errgroup.WithContext(ctx) 1333 tree := new(phlaremodel.Tree) 1334 var m sync.Mutex 1335 1336 if b, ok := t.(*testing.B); ok { 1337 b.ResetTimer() 1338 b.ReportAllocs() 1339 } 1340 1341 for i := 0; i < times; i++ { 1342 g.Go(func() error { 1343 it, err := querier.SelectMatchingProfiles(ctx, &ingesterv1.SelectProfilesRequest{ 1344 LabelSelector: `{}`, 1345 Type: &typesv1.ProfileType{ 1346 ID: "process_cpu:cpu:nanoseconds:cpu:nanoseconds", 1347 Name: "process_cpu", 1348 SampleType: "cpu", 1349 SampleUnit: "nanoseconds", 1350 PeriodType: "cpu", 1351 PeriodUnit: "nanoseconds", 1352 }, 1353 Start: 0, 1354 End: int64(model.TimeFromUnixNano(math.MaxInt64)), 1355 }) 1356 if err != nil { 1357 return err 1358 } 1359 defer it.Close() 1360 for it.Next() { 1361 } 1362 return nil 1363 }) 1364 g.Go(func() error { 1365 merge, err := querier.SelectMergeByStacktraces(ctx, &ingesterv1.SelectProfilesRequest{ 1366 LabelSelector: `{}`, 1367 Type: &typesv1.ProfileType{ 1368 ID: "process_cpu:cpu:nanoseconds:cpu:nanoseconds", 1369 Name: "process_cpu", 1370 SampleType: "cpu", 1371 SampleUnit: "nanoseconds", 1372 PeriodType: "cpu", 1373 PeriodUnit: "nanoseconds", 1374 }, 1375 Start: 0, 1376 End: int64(model.TimeFromUnixNano(math.MaxInt64)), 1377 }, 16<<10) 1378 if err != nil { 1379 return err 1380 } 1381 m.Lock() 1382 tree.Merge(merge) 1383 m.Unlock() 1384 return nil 1385 }) 1386 } 1387 1388 require.NoError(t, g.Wait()) 1389 require.NoError(t, querier.Close()) 1390 } 1391 1392 func TestBlockMeta_loadsMetasIndividually(t *testing.T) { 1393 path := testDataPath 1394 bucket, err := filesystem.NewBucket(path) 1395 require.NoError(t, err) 1396 1397 ctx := context.Background() 1398 blockQuerier := NewBlockQuerier(ctx, bucket) 1399 metas, err := blockQuerier.BlockMetas(ctx) 1400 require.NoError(t, err) 1401 require.NotEmpty(t, metas) 1402 1403 for _, meta := range metas { 1404 singleMeta, err := blockQuerier.BlockMeta(ctx, meta.ULID.String()) 1405 require.NoError(t, err) 1406 1407 require.Equal(t, meta, singleMeta) 1408 } 1409 }