github.com/thanos-io/thanos@v0.32.5/pkg/store/storepb/custom_test.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package storepb 5 6 import ( 7 "fmt" 8 "path/filepath" 9 "sort" 10 "testing" 11 "time" 12 13 "github.com/pkg/errors" 14 "github.com/prometheus/prometheus/model/labels" 15 "github.com/prometheus/prometheus/promql/parser" 16 "github.com/prometheus/prometheus/tsdb/chunkenc" 17 18 "github.com/efficientgo/core/testutil" 19 "github.com/thanos-io/thanos/pkg/store/labelpb" 20 ) 21 22 type sample struct { 23 t int64 24 v float64 25 } 26 27 type listSeriesSet struct { 28 series []Series 29 idx int 30 } 31 32 func newSeries(tb testing.TB, lset labels.Labels, smplChunks [][]sample) Series { 33 s := Series{ 34 Labels: labelpb.ZLabelsFromPromLabels(lset), 35 } 36 37 for _, smpls := range smplChunks { 38 c := chunkenc.NewXORChunk() 39 a, err := c.Appender() 40 testutil.Ok(tb, err) 41 42 for _, smpl := range smpls { 43 a.Append(smpl.t, smpl.v) 44 } 45 46 ch := AggrChunk{ 47 MinTime: smpls[0].t, 48 MaxTime: smpls[len(smpls)-1].t, 49 Raw: &Chunk{Type: Chunk_XOR, Data: c.Bytes()}, 50 } 51 52 s.Chunks = append(s.Chunks, ch) 53 } 54 return s 55 } 56 57 func newListSeriesSet(tb testing.TB, raw []rawSeries) *listSeriesSet { 58 var series []Series 59 for _, s := range raw { 60 series = append(series, newSeries(tb, s.lset, s.chunks)) 61 } 62 return &listSeriesSet{ 63 series: series, 64 idx: -1, 65 } 66 } 67 68 func (s *listSeriesSet) Next() bool { 69 s.idx++ 70 return s.idx < len(s.series) 71 } 72 73 func (s *listSeriesSet) At() (labels.Labels, []AggrChunk) { 74 if s.idx < 0 || s.idx >= len(s.series) { 75 return nil, nil 76 } 77 78 return s.series[s.idx].PromLabels(), s.series[s.idx].Chunks 79 } 80 81 func (s *listSeriesSet) Err() error { return nil } 82 83 type errSeriesSet struct{ err error } 84 85 func (errSeriesSet) Next() bool { return false } 86 87 func (errSeriesSet) At() (labels.Labels, []AggrChunk) { return nil, nil } 88 89 func (e errSeriesSet) Err() error { return e.err } 90 91 func TestMergeSeriesSets(t *testing.T) { 92 for _, tcase := range []struct { 93 desc string 94 in [][]rawSeries 95 expected []rawSeries 96 }{ 97 { 98 desc: "nils", 99 in: nil, 100 expected: nil, 101 }, 102 { 103 desc: "single seriesSet, distinct series", 104 in: [][]rawSeries{{{ 105 lset: labels.FromStrings("a", "a"), 106 chunks: [][]sample{{{1, 1}, {2, 2}}, {{3, 3}, {4, 4}}}, 107 }, { 108 lset: labels.FromStrings("a", "c"), 109 chunks: [][]sample{{{11, 1}, {12, 2}}, {{13, 3}, {14, 4}}}, 110 }}}, 111 112 expected: []rawSeries{ 113 { 114 lset: labels.FromStrings("a", "a"), 115 chunks: [][]sample{{{1, 1}, {2, 2}}, {{3, 3}, {4, 4}}}, 116 }, { 117 lset: labels.FromStrings("a", "c"), 118 chunks: [][]sample{{{11, 1}, {12, 2}}, {{13, 3}, {14, 4}}}, 119 }, 120 }, 121 }, 122 { 123 desc: "two seriesSets, distinct series", 124 in: [][]rawSeries{{{ 125 lset: labels.FromStrings("a", "a"), 126 chunks: [][]sample{{{1, 1}, {2, 2}}, {{3, 3}, {4, 4}}}, 127 }}, {{ 128 lset: labels.FromStrings("a", "c"), 129 chunks: [][]sample{{{11, 1}, {12, 2}}, {{13, 3}, {14, 4}}}, 130 }}}, 131 132 expected: []rawSeries{ 133 { 134 lset: labels.FromStrings("a", "a"), 135 chunks: [][]sample{{{1, 1}, {2, 2}}, {{3, 3}, {4, 4}}}, 136 }, { 137 lset: labels.FromStrings("a", "c"), 138 chunks: [][]sample{{{11, 1}, {12, 2}}, {{13, 3}, {14, 4}}}, 139 }, 140 }, 141 }, 142 { 143 desc: "two seriesSets, {a=c} series to merge, sorted", 144 in: [][]rawSeries{ 145 { 146 { 147 lset: labels.FromStrings("a", "a"), 148 chunks: [][]sample{{{1, 1}, {2, 2}}, {{3, 3}, {4, 4}}}, 149 }, 150 { 151 lset: labels.FromStrings("a", "c"), 152 chunks: [][]sample{{{11, 1}, {12, 2}}, {{13, 3}, {14, 4}}}, 153 }, 154 }, 155 { 156 { 157 lset: labels.FromStrings("a", "c"), 158 chunks: [][]sample{{{7, 1}, {8, 2}}, {{9, 3}, {10, 4}, {11, 4444}}}, // Last sample overlaps, merge ignores that. 159 }, 160 }, 161 }, 162 163 expected: []rawSeries{ 164 { 165 lset: labels.FromStrings("a", "a"), 166 chunks: [][]sample{{{1, 1}, {2, 2}}, {{3, 3}, {4, 4}}}, 167 }, { 168 lset: labels.FromStrings("a", "c"), 169 chunks: [][]sample{{{7, 1}, {8, 2}}, {{9, 3}, {10, 4}, {11, 4444}}, {{11, 1}, {12, 2}}, {{13, 3}, {14, 4}}}, 170 }, 171 }, 172 }, 173 { 174 desc: "single seriesSets, {a=c} series to merge, sorted", 175 in: [][]rawSeries{ 176 { 177 { 178 lset: labels.FromStrings("a", "a"), 179 chunks: [][]sample{{{1, 1}, {2, 2}}, {{3, 3}, {4, 4}}}, 180 }, 181 { 182 lset: labels.FromStrings("a", "c"), 183 chunks: [][]sample{{{7, 1}, {8, 2}}, {{9, 3}, {10, 4}, {11, 4444}}}, 184 }, 185 { 186 lset: labels.FromStrings("a", "c"), 187 chunks: [][]sample{{{11, 1}, {12, 2}}, {{13, 3}, {14, 4}}}, 188 }, 189 }, 190 }, 191 192 expected: []rawSeries{ 193 { 194 lset: labels.FromStrings("a", "a"), 195 chunks: [][]sample{{{1, 1}, {2, 2}}, {{3, 3}, {4, 4}}}, 196 }, { 197 lset: labels.FromStrings("a", "c"), 198 chunks: [][]sample{{{7, 1}, {8, 2}}, {{9, 3}, {10, 4}, {11, 4444}}, {{11, 1}, {12, 2}}, {{13, 3}, {14, 4}}}, 199 }, 200 }, 201 }, 202 { 203 desc: "four seriesSets, {a=c} series to merge AND deduplicate exactly the same chunks", 204 in: [][]rawSeries{ 205 { 206 { 207 lset: labels.FromStrings("a", "a"), 208 chunks: [][]sample{{{1, 1}, {2, 2}}, {{3, 3}, {4, 4}}}, 209 }, 210 { 211 lset: labels.FromStrings("a", "c"), 212 chunks: [][]sample{ 213 {{11, 11}, {12, 12}, {13, 13}, {14, 14}}, 214 {{15, 15}, {16, 16}, {17, 17}, {18, 18}}, 215 }, 216 }, 217 { 218 lset: labels.FromStrings("a", "c"), 219 chunks: [][]sample{ 220 {{20, 20}, {21, 21}, {22, 22}, {24, 24}}, 221 }, 222 }, 223 }, 224 { 225 { 226 lset: labels.FromStrings("a", "c"), 227 chunks: [][]sample{ 228 {{1, 1}, {2, 2}, {3, 3}, {4, 4}}, 229 {{11, 11}, {12, 12}, {13, 13}, {14, 14}}, // Same chunk as in set 1. 230 }, 231 }, 232 { 233 lset: labels.FromStrings("a", "d"), 234 chunks: [][]sample{{{11, 1}, {12, 2}}, {{13, 3}, {14, 4}}}, 235 }, 236 }, 237 { 238 { 239 lset: labels.FromStrings("a", "c"), 240 chunks: [][]sample{ 241 {{11, 11}, {12, 12}, {13, 13}, {14, 14}}, // Same chunk as in set 1. 242 {{20, 20}, {21, 21}, {22, 23}, {24, 24}}, // Almost same chunk as in set 1 (one value is different). 243 }, 244 }, 245 }, 246 { 247 { 248 lset: labels.FromStrings("a", "c"), 249 chunks: [][]sample{ 250 {{11, 11}, {12, 12}, {14, 14}}, // Almost same chunk as in set 1 (one sample is missing). 251 {{20, 20}, {21, 21}, {22, 22}, {24, 24}}, // Same chunk as in set 1. 252 }, 253 }, 254 }, 255 }, 256 257 expected: []rawSeries{ 258 { 259 lset: labels.Labels{labels.Label{Name: "a", Value: "a"}}, 260 chunks: [][]sample{{{t: 1, v: 1}, {t: 2, v: 2}}, {{t: 3, v: 3}, {t: 4, v: 4}}}, 261 }, { 262 lset: labels.Labels{labels.Label{Name: "a", Value: "c"}}, 263 chunks: [][]sample{ 264 {{t: 1, v: 1}, {t: 2, v: 2}, {t: 3, v: 3}, {t: 4, v: 4}}, 265 {{t: 11, v: 11}, {t: 12, v: 12}, {t: 13, v: 13}, {t: 14, v: 14}}, 266 {{t: 11, v: 11}, {t: 12, v: 12}, {t: 14, v: 14}}, 267 {{t: 15, v: 15}, {t: 16, v: 16}, {t: 17, v: 17}, {t: 18, v: 18}}, 268 {{t: 20, v: 20}, {t: 21, v: 21}, {t: 22, v: 22}, {t: 24, v: 24}}, 269 {{t: 20, v: 20}, {t: 21, v: 21}, {t: 22, v: 23}, {t: 24, v: 24}}, 270 }, 271 }, { 272 lset: labels.Labels{labels.Label{Name: "a", Value: "d"}}, 273 chunks: [][]sample{{{t: 11, v: 1}, {t: 12, v: 2}}, {{t: 13, v: 3}, {t: 14, v: 4}}}, 274 }, 275 }, 276 }, 277 { 278 desc: "four seriesSets, {a=c} series to merge, unsorted chunks, so dedup is expected to not be fully done", 279 in: [][]rawSeries{ 280 { 281 { 282 lset: labels.FromStrings("a", "c"), 283 chunks: [][]sample{ 284 {{20, 20}, {21, 21}, {22, 22}, {24, 24}}, 285 }, 286 }, 287 { 288 lset: labels.FromStrings("a", "c"), 289 chunks: [][]sample{ 290 {{11, 11}, {12, 12}, {13, 13}, {14, 14}}, 291 {{15, 15}, {16, 16}, {17, 17}, {18, 18}}, 292 }, 293 }, 294 }, 295 { 296 { 297 lset: labels.FromStrings("a", "c"), 298 chunks: [][]sample{ 299 {{11, 11}, {12, 12}, {13, 13}, {14, 14}}, // Same chunk as in set 1. 300 {{1, 1}, {2, 2}, {3, 3}, {4, 4}}, 301 }, 302 }, 303 }, 304 { 305 { 306 lset: labels.FromStrings("a", "c"), 307 chunks: [][]sample{ 308 {{20, 20}, {21, 21}, {22, 23}, {24, 24}}, // Almost same chunk as in set 1 (one value is different). 309 {{11, 11}, {12, 12}, {13, 13}, {14, 14}}, // Same chunk as in set 1. 310 }, 311 }, 312 }, 313 }, 314 315 expected: []rawSeries{ 316 { 317 lset: labels.Labels{labels.Label{Name: "a", Value: "c"}}, 318 chunks: [][]sample{ 319 {{t: 11, v: 11}, {t: 12, v: 12}, {t: 13, v: 13}, {t: 14, v: 14}}, 320 {{t: 1, v: 1}, {t: 2, v: 2}, {t: 3, v: 3}, {t: 4, v: 4}}, 321 {{t: 20, v: 20}, {t: 21, v: 21}, {t: 22, v: 22}, {t: 24, v: 24}}, 322 {{t: 11, v: 11}, {t: 12, v: 12}, {t: 13, v: 13}, {t: 14, v: 14}}, 323 {{t: 15, v: 15}, {t: 16, v: 16}, {t: 17, v: 17}, {t: 18, v: 18}}, 324 {{t: 20, v: 20}, {t: 21, v: 21}, {t: 22, v: 23}, {t: 24, v: 24}}, 325 {{t: 11, v: 11}, {t: 12, v: 12}, {t: 13, v: 13}, {t: 14, v: 14}}, 326 }, 327 }, 328 }, 329 }, 330 } { 331 t.Run(tcase.desc, func(t *testing.T) { 332 var input []SeriesSet 333 for _, iss := range tcase.in { 334 input = append(input, newListSeriesSet(t, iss)) 335 } 336 testutil.Equals(t, tcase.expected, expandSeriesSet(t, MergeSeriesSets(input...))) 337 }) 338 } 339 } 340 341 func TestMergeSeriesSetError(t *testing.T) { 342 var input []SeriesSet 343 for _, iss := range [][]rawSeries{{{ 344 lset: labels.FromStrings("a", "a"), 345 chunks: [][]sample{{{1, 1}, {2, 2}}, {{3, 3}, {4, 4}}}, 346 }}, {{ 347 lset: labels.FromStrings("a", "c"), 348 chunks: [][]sample{{{11, 1}, {12, 2}}, {{13, 3}, {14, 4}}}, 349 }}} { 350 input = append(input, newListSeriesSet(t, iss)) 351 } 352 expectedErr := errors.New("test error") 353 ss := MergeSeriesSets(append(input, errSeriesSet{err: expectedErr})...) 354 testutil.Equals(t, expectedErr, ss.Err()) 355 } 356 357 type rawSeries struct { 358 lset labels.Labels 359 chunks [][]sample 360 } 361 362 func expandSeriesSet(t *testing.T, gotSS SeriesSet) (ret []rawSeries) { 363 for gotSS.Next() { 364 lset, chks := gotSS.At() 365 366 r := rawSeries{lset: lset, chunks: make([][]sample, len(chks))} 367 for i, chk := range chks { 368 c, err := chunkenc.FromData(chunkenc.EncXOR, chk.Raw.Data) 369 testutil.Ok(t, err) 370 371 iter := c.Iterator(nil) 372 for iter.Next() != chunkenc.ValNone { 373 t, v := iter.At() 374 r.chunks[i] = append(r.chunks[i], sample{t: t, v: v}) 375 } 376 testutil.Ok(t, iter.Err()) 377 } 378 ret = append(ret, r) 379 } 380 testutil.Ok(t, gotSS.Err()) 381 return ret 382 } 383 384 // Test the cost of merging series sets for different number of merged sets and their size. 385 func BenchmarkMergedSeriesSet(b *testing.B) { 386 b.Run("overlapping chunks", func(b *testing.B) { 387 benchmarkMergedSeriesSet(testutil.NewTB(b), true) 388 }) 389 b.Run("non-overlapping chunks", func(b *testing.B) { 390 benchmarkMergedSeriesSet(testutil.NewTB(b), false) 391 }) 392 } 393 394 func TestMergedSeriesSet_Labels(t *testing.T) { 395 t.Run("overlapping chunks", func(t *testing.T) { 396 benchmarkMergedSeriesSet(testutil.NewTB(t), true) 397 }) 398 t.Run("non-overlapping chunks", func(t *testing.T) { 399 benchmarkMergedSeriesSet(testutil.NewTB(t), false) 400 }) 401 } 402 403 func benchmarkMergedSeriesSet(b testutil.TB, overlappingChunks bool) { 404 var sel func(sets []SeriesSet) SeriesSet 405 sel = func(sets []SeriesSet) SeriesSet { 406 if len(sets) == 0 { 407 return EmptySeriesSet() 408 } 409 if len(sets) == 1 { 410 return sets[0] 411 } 412 l := len(sets) / 2 413 return newMergedSeriesSet(sel(sets[:l]), sel(sets[l:])) 414 } 415 416 chunks := [][]sample{{{1, 1}, {2, 2}}, {{3, 3}, {4, 4}}} 417 for _, k := range []int{ 418 100, 419 1000, 420 10000, 421 20000, 422 } { 423 for _, j := range []int{1, 2, 4, 8, 16, 32} { 424 b.Run(fmt.Sprintf("series=%d,blocks=%d", k, j), func(b testutil.TB) { 425 lbls, err := labels.ReadLabels(filepath.Join("../../testutil/testdata", "20kseries.json"), k) 426 testutil.Ok(b, err) 427 428 sort.Sort(labels.Slice(lbls)) 429 430 blocks := make([][]rawSeries, j) 431 for _, l := range lbls { 432 for j := range blocks { 433 if overlappingChunks { 434 blocks[j] = append(blocks[j], rawSeries{lset: l, chunks: chunks}) 435 continue 436 } 437 blocks[j] = append(blocks[j], rawSeries{ 438 lset: l, 439 chunks: [][]sample{ 440 {{int64(4*j) + 1, 1}, {int64(4*j) + 2, 2}}, 441 {{int64(4*j) + 3, 3}, {int64(4*j) + 4, 4}}, 442 }, 443 }) 444 } 445 } 446 447 b.ResetTimer() 448 449 for i := 0; i < b.N(); i++ { 450 var sets []SeriesSet 451 for _, s := range blocks { 452 sets = append(sets, newListSeriesSet(b, s)) 453 } 454 ms := sel(sets) 455 456 i := 0 457 for ms.Next() { 458 i++ 459 } 460 testutil.Ok(b, ms.Err()) 461 testutil.Equals(b, len(lbls), i) 462 } 463 }) 464 } 465 } 466 } 467 468 func TestMatchersToString_Translate(t *testing.T) { 469 for _, c := range []struct { 470 ms []LabelMatcher 471 expected string 472 }{ 473 { 474 ms: []LabelMatcher{ 475 {Name: "__name__", Type: LabelMatcher_EQ, Value: "up"}, 476 }, 477 expected: `{__name__="up"}`, 478 }, 479 { 480 ms: []LabelMatcher{ 481 {Name: "__name__", Type: LabelMatcher_NEQ, Value: "up"}, 482 {Name: "job", Type: LabelMatcher_EQ, Value: "test"}, 483 }, 484 expected: `{__name__!="up", job="test"}`, 485 }, 486 { 487 ms: []LabelMatcher{ 488 {Name: "__name__", Type: LabelMatcher_EQ, Value: "up"}, 489 {Name: "job", Type: LabelMatcher_RE, Value: "test"}, 490 }, 491 expected: `{__name__="up", job=~"test"}`, 492 }, 493 { 494 ms: []LabelMatcher{ 495 {Name: "job", Type: LabelMatcher_NRE, Value: "test"}, 496 }, 497 expected: `{job!~"test"}`, 498 }, 499 { 500 ms: []LabelMatcher{ 501 {Name: "__name__", Type: LabelMatcher_EQ, Value: "up"}, 502 {Name: "__name__", Type: LabelMatcher_NEQ, Value: "up"}, 503 }, 504 // We cannot use up{__name__!="up"} in this case. 505 expected: `{__name__="up", __name__!="up"}`, 506 }, 507 } { 508 t.Run(c.expected, func(t *testing.T) { 509 testutil.Equals(t, c.expected, MatchersToString(c.ms...)) 510 511 promMs, err := MatchersToPromMatchers(c.ms...) 512 testutil.Ok(t, err) 513 514 testutil.Equals(t, c.expected, PromMatchersToString(promMs...)) 515 516 ms, err := PromMatchersToMatchers(promMs...) 517 testutil.Ok(t, err) 518 519 testutil.Equals(t, c.ms, ms) 520 testutil.Equals(t, c.expected, MatchersToString(ms...)) 521 522 // Is this parsable? 523 promMsParsed, err := parser.ParseMetricSelector(c.expected) 524 testutil.Ok(t, err) 525 testutil.Equals(t, promMs, promMsParsed) 526 }) 527 528 } 529 } 530 531 func TestSeriesRequestToPromQL(t *testing.T) { 532 ts := []struct { 533 name string 534 r *SeriesRequest 535 expected string 536 }{ 537 { 538 name: "Single matcher regular expression", 539 r: &SeriesRequest{ 540 Matchers: []LabelMatcher{ 541 { 542 Type: LabelMatcher_RE, 543 Name: "namespace", 544 Value: "kube-.+", 545 }, 546 }, 547 QueryHints: &QueryHints{ 548 Func: &Func{ 549 Name: "max", 550 }, 551 }, 552 }, 553 expected: `max ({namespace=~"kube-.+"})`, 554 }, 555 { 556 name: "Single matcher regular expression with grouping", 557 r: &SeriesRequest{ 558 Matchers: []LabelMatcher{ 559 { 560 Type: LabelMatcher_RE, 561 Name: "namespace", 562 Value: "kube-.+", 563 }, 564 }, 565 QueryHints: &QueryHints{ 566 Func: &Func{ 567 Name: "max", 568 }, 569 Grouping: &Grouping{ 570 By: false, 571 Labels: []string{"container", "pod"}, 572 }, 573 }, 574 }, 575 expected: `max without (container,pod) ({namespace=~"kube-.+"})`, 576 }, 577 { 578 name: "Multiple matchers with grouping", 579 r: &SeriesRequest{ 580 Matchers: []LabelMatcher{ 581 { 582 Type: LabelMatcher_EQ, 583 Name: "__name__", 584 Value: "kube_pod_info", 585 }, 586 { 587 Type: LabelMatcher_RE, 588 Name: "namespace", 589 Value: "kube-.+", 590 }, 591 }, 592 QueryHints: &QueryHints{ 593 Func: &Func{ 594 Name: "max", 595 }, 596 Grouping: &Grouping{ 597 By: false, 598 Labels: []string{"container", "pod"}, 599 }, 600 }, 601 }, 602 expected: `max without (container,pod) ({__name__="kube_pod_info", namespace=~"kube-.+"})`, 603 }, 604 { 605 name: "Query with vector range selector", 606 r: &SeriesRequest{ 607 Matchers: []LabelMatcher{ 608 { 609 Type: LabelMatcher_EQ, 610 Name: "__name__", 611 Value: "kube_pod_info", 612 }, 613 { 614 Type: LabelMatcher_RE, 615 Name: "namespace", 616 Value: "kube-.+", 617 }, 618 }, 619 QueryHints: &QueryHints{ 620 Func: &Func{ 621 Name: "max_over_time", 622 }, 623 Range: &Range{ 624 Millis: 10 * time.Minute.Milliseconds(), 625 }, 626 }, 627 }, 628 expected: `max_over_time ({__name__="kube_pod_info", namespace=~"kube-.+"}[600000ms])`, 629 }, 630 { 631 name: "Query with grouping and vector range selector", 632 r: &SeriesRequest{ 633 Matchers: []LabelMatcher{ 634 { 635 Type: LabelMatcher_EQ, 636 Name: "__name__", 637 Value: "kube_pod_info", 638 }, 639 { 640 Type: LabelMatcher_RE, 641 Name: "namespace", 642 Value: "kube-.+", 643 }, 644 }, 645 QueryHints: &QueryHints{ 646 Func: &Func{ 647 Name: "max", 648 }, 649 Grouping: &Grouping{ 650 By: false, 651 Labels: []string{"container", "pod"}, 652 }, 653 Range: &Range{ 654 Millis: 10 * time.Minute.Milliseconds(), 655 }, 656 }, 657 }, 658 expected: `max without (container,pod) ({__name__="kube_pod_info", namespace=~"kube-.+"}[600000ms])`, 659 }, 660 } 661 662 for _, tc := range ts { 663 t.Run(tc.name, func(t *testing.T) { 664 actual := tc.r.ToPromQL() 665 if tc.expected != actual { 666 t.Fatalf("invalid promql result, got %s, want %s", actual, tc.expected) 667 } 668 }) 669 } 670 }