github.com/thanos-io/thanos@v0.32.5/pkg/store/tsdb_test.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package store 5 6 import ( 7 "context" 8 "fmt" 9 "io" 10 "math" 11 "math/rand" 12 "sort" 13 "testing" 14 15 "github.com/cespare/xxhash" 16 "github.com/go-kit/log" 17 "github.com/prometheus/prometheus/model/labels" 18 "github.com/prometheus/prometheus/tsdb" 19 20 "github.com/efficientgo/core/testutil" 21 22 "github.com/thanos-io/thanos/pkg/component" 23 "github.com/thanos-io/thanos/pkg/store/labelpb" 24 "github.com/thanos-io/thanos/pkg/store/storepb" 25 storetestutil "github.com/thanos-io/thanos/pkg/store/storepb/testutil" 26 "github.com/thanos-io/thanos/pkg/testutil/custom" 27 "github.com/thanos-io/thanos/pkg/testutil/e2eutil" 28 ) 29 30 const skipMessage = "Chunk behavior changed due to https://github.com/prometheus/prometheus/pull/8723. Skip for now." 31 32 func TestTSDBStore_Info(t *testing.T) { 33 defer custom.TolerantVerifyLeak(t) 34 35 ctx, cancel := context.WithCancel(context.Background()) 36 defer cancel() 37 38 db, err := e2eutil.NewTSDB() 39 defer func() { testutil.Ok(t, db.Close()) }() 40 testutil.Ok(t, err) 41 42 tsdbStore := NewTSDBStore(nil, db, component.Rule, labels.FromStrings("region", "eu-west")) 43 44 resp, err := tsdbStore.Info(ctx, &storepb.InfoRequest{}) 45 testutil.Ok(t, err) 46 47 testutil.Equals(t, []labelpb.ZLabel{{Name: "region", Value: "eu-west"}}, resp.Labels) 48 testutil.Equals(t, storepb.StoreType_RULE, resp.StoreType) 49 testutil.Equals(t, int64(math.MaxInt64), resp.MinTime) 50 testutil.Equals(t, int64(math.MaxInt64), resp.MaxTime) 51 52 app := db.Appender(context.Background()) 53 _, err = app.Append(0, labels.FromStrings("a", "a"), 12, 0.1) 54 testutil.Ok(t, err) 55 testutil.Ok(t, app.Commit()) 56 57 resp, err = tsdbStore.Info(ctx, &storepb.InfoRequest{}) 58 testutil.Ok(t, err) 59 60 testutil.Equals(t, []labelpb.ZLabel{{Name: "region", Value: "eu-west"}}, resp.Labels) 61 testutil.Equals(t, storepb.StoreType_RULE, resp.StoreType) 62 testutil.Equals(t, int64(12), resp.MinTime) 63 testutil.Equals(t, int64(math.MaxInt64), resp.MaxTime) 64 } 65 66 func TestTSDBStore_Series_ChunkChecksum(t *testing.T) { 67 defer custom.TolerantVerifyLeak(t) 68 69 ctx, cancel := context.WithCancel(context.Background()) 70 defer cancel() 71 72 db, err := e2eutil.NewTSDB() 73 defer func() { testutil.Ok(t, db.Close()) }() 74 testutil.Ok(t, err) 75 76 tsdbStore := NewTSDBStore(nil, db, component.Rule, labels.FromStrings("region", "eu-west")) 77 78 appender := db.Appender(context.Background()) 79 80 for i := 1; i <= 3; i++ { 81 _, err = appender.Append(0, labels.FromStrings("a", "1"), int64(i), float64(i)) 82 testutil.Ok(t, err) 83 } 84 err = appender.Commit() 85 testutil.Ok(t, err) 86 87 srv := newStoreSeriesServer(ctx) 88 89 req := &storepb.SeriesRequest{ 90 MinTime: 1, 91 MaxTime: 3, 92 Matchers: []storepb.LabelMatcher{ 93 {Type: storepb.LabelMatcher_EQ, Name: "a", Value: "1"}, 94 }, 95 } 96 97 err = tsdbStore.Series(req, srv) 98 testutil.Ok(t, err) 99 100 for _, chk := range srv.SeriesSet[0].Chunks { 101 want := xxhash.Sum64(chk.Raw.Data) 102 testutil.Equals(t, want, chk.Raw.Hash) 103 } 104 } 105 106 func TestTSDBStore_Series(t *testing.T) { 107 defer custom.TolerantVerifyLeak(t) 108 109 ctx, cancel := context.WithCancel(context.Background()) 110 defer cancel() 111 112 db, err := e2eutil.NewTSDB() 113 defer func() { testutil.Ok(t, db.Close()) }() 114 testutil.Ok(t, err) 115 116 tsdbStore := NewTSDBStore(nil, db, component.Rule, labels.FromStrings("region", "eu-west")) 117 118 appender := db.Appender(context.Background()) 119 120 for i := 1; i <= 3; i++ { 121 _, err = appender.Append(0, labels.FromStrings("a", "1"), int64(i), float64(i)) 122 testutil.Ok(t, err) 123 } 124 err = appender.Commit() 125 testutil.Ok(t, err) 126 127 for _, tc := range []struct { 128 title string 129 req *storepb.SeriesRequest 130 expectedSeries []rawSeries 131 expectedError string 132 }{ 133 { 134 title: "total match series", 135 req: &storepb.SeriesRequest{ 136 MinTime: 1, 137 MaxTime: 3, 138 Matchers: []storepb.LabelMatcher{ 139 {Type: storepb.LabelMatcher_EQ, Name: "a", Value: "1"}, 140 }, 141 }, 142 expectedSeries: []rawSeries{ 143 { 144 lset: labels.FromStrings("a", "1", "region", "eu-west"), 145 chunks: [][]sample{{{1, 1}, {2, 2}, {3, 3}}}, 146 }, 147 }, 148 }, 149 { 150 title: "partially match time range series", 151 req: &storepb.SeriesRequest{ 152 MinTime: 1, 153 MaxTime: 2, 154 Matchers: []storepb.LabelMatcher{ 155 {Type: storepb.LabelMatcher_EQ, Name: "a", Value: "1"}, 156 }, 157 }, 158 expectedSeries: []rawSeries{ 159 { 160 lset: labels.FromStrings("a", "1", "region", "eu-west"), 161 chunks: [][]sample{{{1, 1}, {2, 2}}}, 162 }, 163 }, 164 }, 165 { 166 title: "dont't match time range series", 167 req: &storepb.SeriesRequest{ 168 MinTime: 4, 169 MaxTime: 6, 170 Matchers: []storepb.LabelMatcher{ 171 {Type: storepb.LabelMatcher_EQ, Name: "a", Value: "1"}, 172 }, 173 }, 174 expectedSeries: []rawSeries{}, 175 }, 176 { 177 title: "only match external label", 178 req: &storepb.SeriesRequest{ 179 MinTime: 1, 180 MaxTime: 3, 181 Matchers: []storepb.LabelMatcher{ 182 {Type: storepb.LabelMatcher_EQ, Name: "region", Value: "eu-west"}, 183 }, 184 }, 185 expectedError: "rpc error: code = InvalidArgument desc = no matchers specified (excluding external labels)", 186 }, 187 { 188 title: "dont't match labels", 189 req: &storepb.SeriesRequest{ 190 MinTime: 1, 191 MaxTime: 3, 192 Matchers: []storepb.LabelMatcher{ 193 {Type: storepb.LabelMatcher_EQ, Name: "b", Value: "1"}, 194 }, 195 }, 196 expectedSeries: []rawSeries{}, 197 }, 198 { 199 title: "no chunk", 200 req: &storepb.SeriesRequest{ 201 MinTime: 1, 202 MaxTime: 3, 203 Matchers: []storepb.LabelMatcher{ 204 {Type: storepb.LabelMatcher_EQ, Name: "a", Value: "1"}, 205 }, 206 SkipChunks: true, 207 }, 208 expectedSeries: []rawSeries{ 209 { 210 lset: labels.FromStrings("a", "1", "region", "eu-west"), 211 }, 212 }, 213 }, 214 } { 215 if ok := t.Run(tc.title, func(t *testing.T) { 216 srv := newStoreSeriesServer(ctx) 217 err := tsdbStore.Series(tc.req, srv) 218 if len(tc.expectedError) > 0 { 219 testutil.NotOk(t, err) 220 testutil.Equals(t, tc.expectedError, err.Error()) 221 } else { 222 testutil.Ok(t, err) 223 seriesEquals(t, tc.expectedSeries, srv.SeriesSet) 224 } 225 }); !ok { 226 return 227 } 228 } 229 } 230 231 // Regression test for https://github.com/thanos-io/thanos/issues/1038. 232 func TestTSDBStore_Series_SplitSamplesIntoChunksWithMaxSizeOf120(t *testing.T) { 233 defer custom.TolerantVerifyLeak(t) 234 235 db, err := e2eutil.NewTSDB() 236 defer func() { testutil.Ok(t, db.Close()) }() 237 testutil.Ok(t, err) 238 239 testSeries_SplitSamplesIntoChunksWithMaxSizeOf120(t, db.Appender(context.Background()), func() storepb.StoreServer { 240 return NewTSDBStore(nil, db, component.Rule, labels.FromStrings("region", "eu-west")) 241 242 }) 243 } 244 245 type delegatorServer struct { 246 *storetestutil.SeriesServer 247 248 closers []io.Closer 249 } 250 251 func (s *delegatorServer) Delegate(c io.Closer) { 252 s.closers = append(s.closers, c) 253 } 254 255 // Regression test for: https://github.com/thanos-io/thanos/issues/3013 . 256 func TestTSDBStore_SeriesAccessWithDelegateClosing(t *testing.T) { 257 t.Skip(skipMessage) 258 259 tmpDir := t.TempDir() 260 261 var ( 262 random = rand.New(rand.NewSource(120)) 263 logger = log.NewNopLogger() 264 ) 265 266 // Generate one series in two parts. Put first part in block, second in just WAL. 267 head, _ := storetestutil.CreateHeadWithSeries(t, 0, storetestutil.HeadGenOptions{ 268 TSDBDir: tmpDir, 269 SamplesPerSeries: 300, 270 Series: 2, 271 Random: random, 272 SkipChunks: true, 273 }) 274 _ = createBlockFromHead(t, tmpDir, head) 275 testutil.Ok(t, head.Close()) 276 277 head, _ = storetestutil.CreateHeadWithSeries(t, 1, storetestutil.HeadGenOptions{ 278 TSDBDir: tmpDir, 279 SamplesPerSeries: 300, 280 Series: 2, 281 WithWAL: true, 282 Random: random, 283 SkipChunks: true, 284 }) 285 testutil.Ok(t, head.Close()) 286 287 db, err := tsdb.OpenDBReadOnly(tmpDir, logger) 288 testutil.Ok(t, err) 289 290 dbToClose := make(chan *tsdb.DBReadOnly, 1) 291 dbToClose <- db 292 t.Cleanup(func() { 293 // Close if not closed before. 294 select { 295 case db := <-dbToClose: 296 testutil.Ok(t, db.Close()) 297 default: 298 } 299 }) 300 301 extLabels := labels.FromStrings("ext", "1") 302 store := NewTSDBStore(logger, &mockedStartTimeDB{DBReadOnly: db, startTime: 0}, component.Receive, extLabels) 303 304 srv := storetestutil.NewSeriesServer(context.Background()) 305 csrv := &delegatorServer{SeriesServer: srv} 306 t.Run("call series and access results", func(t *testing.T) { 307 testutil.Ok(t, store.Series(&storepb.SeriesRequest{ 308 MinTime: 0, 309 MaxTime: math.MaxInt64, 310 Matchers: []storepb.LabelMatcher{ 311 {Type: storepb.LabelMatcher_EQ, Name: "foo", Value: "bar"}, 312 }, 313 PartialResponseStrategy: storepb.PartialResponseStrategy_ABORT, 314 }, csrv)) 315 testutil.Equals(t, 0, len(srv.Warnings)) 316 testutil.Equals(t, 0, len(srv.HintsSet)) 317 testutil.Equals(t, 4, len(srv.SeriesSet)) 318 319 // All chunks should be accessible for read, but not necessarily for write. 320 for _, s := range srv.SeriesSet { 321 testutil.Equals(t, 3, len(s.Chunks)) 322 for _, c := range s.Chunks { 323 testutil.Ok(t, testutil.FaultOrPanicToErr(func() { 324 _ = string(c.Raw.Data) // Access bytes by converting them to different type. 325 })) 326 } 327 testutil.NotOk(t, testutil.FaultOrPanicToErr(func() { 328 s.Chunks[0].Raw.Data[0] = 0 // Check if we can write to the byte range. 329 s.Chunks[1].Raw.Data[0] = 0 330 s.Chunks[2].Raw.Data[0] = 0 331 })) 332 } 333 }) 334 335 flushDone := make(chan error) 336 t.Run("flush WAL and access results", func(t *testing.T) { 337 go func() { 338 // This should block until all queries are closed. 339 flushDone <- db.FlushWAL(tmpDir) 340 close(flushDone) 341 }() 342 // All chunks should be still accessible for read, but not necessarily for write. 343 for _, s := range srv.SeriesSet { 344 for _, c := range s.Chunks { 345 testutil.Ok(t, testutil.FaultOrPanicToErr(func() { 346 _ = string(c.Raw.Data) // Access bytes by converting them to different type. 347 })) 348 } 349 testutil.NotOk(t, testutil.FaultOrPanicToErr(func() { 350 s.Chunks[0].Raw.Data[0] = 0 // Check if we can write to the byte range. 351 s.Chunks[1].Raw.Data[0] = 0 352 s.Chunks[2].Raw.Data[0] = 0 353 })) 354 } 355 }) 356 select { 357 case err, ok := <-flushDone: 358 if !ok { 359 t.Fatalf("expected flush to be blocked, but it seems it completed. Result: %v", err) 360 } 361 default: 362 } 363 364 closeDone := make(chan error) 365 t.Run("close db with block readers and access results", func(t *testing.T) { 366 go func() { 367 select { 368 case db := <-dbToClose: 369 // This should block until all queries are closed. 370 closeDone <- db.Close() 371 default: 372 } 373 close(closeDone) 374 }() 375 // All chunks should be still accessible for read, but not necessarily for write. 376 for _, s := range srv.SeriesSet { 377 for _, c := range s.Chunks { 378 testutil.Ok(t, testutil.FaultOrPanicToErr(func() { 379 _ = string(c.Raw.Data) // Access bytes by converting them to different type. 380 })) 381 } 382 testutil.NotOk(t, testutil.FaultOrPanicToErr(func() { 383 s.Chunks[0].Raw.Data[0] = 0 // Check if we can write to the byte range. 384 s.Chunks[1].Raw.Data[0] = 0 385 s.Chunks[2].Raw.Data[0] = 0 386 })) 387 } 388 }) 389 select { 390 case _, ok := <-closeDone: 391 if !ok { 392 t.Fatalf("expected db cloe to be blocked, but it seems it completed. Result: %v", err) 393 394 } 395 default: 396 } 397 398 t.Run("close querier and access results", func(t *testing.T) { 399 // Let's close pending querier! 400 testutil.Equals(t, 1, len(csrv.closers)) 401 testutil.Ok(t, csrv.closers[0].Close()) 402 403 // Expect flush and close to be unblocked and without errors. 404 testutil.Ok(t, <-flushDone) 405 testutil.Ok(t, <-closeDone) 406 407 // Expect segfault on read and write. 408 t.Run("non delegatable", func(t *testing.T) { 409 for _, s := range srv.SeriesSet { 410 testutil.NotOk(t, testutil.FaultOrPanicToErr(func() { 411 _ = string(s.Chunks[0].Raw.Data) // Access bytes by converting them to different type. 412 _ = string(s.Chunks[1].Raw.Data) 413 _ = string(s.Chunks[2].Raw.Data) 414 })) 415 testutil.NotOk(t, testutil.FaultOrPanicToErr(func() { 416 s.Chunks[0].Raw.Data[0] = 0 // Check if we can write to the byte range. 417 s.Chunks[1].Raw.Data[0] = 0 418 s.Chunks[2].Raw.Data[0] = 0 419 })) 420 } 421 }) 422 }) 423 } 424 425 func TestTSDBStore_SeriesAccessWithoutDelegateClosing(t *testing.T) { 426 t.Skip(skipMessage) 427 428 tmpDir := t.TempDir() 429 430 var ( 431 random = rand.New(rand.NewSource(120)) 432 logger = log.NewNopLogger() 433 ) 434 435 // Generate one series in two parts. Put first part in block, second in just WAL. 436 head, _ := storetestutil.CreateHeadWithSeries(t, 0, storetestutil.HeadGenOptions{ 437 TSDBDir: tmpDir, 438 SamplesPerSeries: 300, 439 Series: 2, 440 Random: random, 441 SkipChunks: true, 442 }) 443 _ = createBlockFromHead(t, tmpDir, head) 444 testutil.Ok(t, head.Close()) 445 446 head, _ = storetestutil.CreateHeadWithSeries(t, 1, storetestutil.HeadGenOptions{ 447 TSDBDir: tmpDir, 448 SamplesPerSeries: 300, 449 Series: 2, 450 WithWAL: true, 451 Random: random, 452 SkipChunks: true, 453 }) 454 testutil.Ok(t, head.Close()) 455 456 db, err := tsdb.OpenDBReadOnly(tmpDir, logger) 457 testutil.Ok(t, err) 458 t.Cleanup(func() { 459 if db != nil { 460 testutil.Ok(t, db.Close()) 461 } 462 }) 463 464 extLabels := labels.FromStrings("ext", "1") 465 store := NewTSDBStore(logger, &mockedStartTimeDB{DBReadOnly: db, startTime: 0}, component.Receive, extLabels) 466 467 srv := storetestutil.NewSeriesServer(context.Background()) 468 t.Run("call series and access results", func(t *testing.T) { 469 testutil.Ok(t, store.Series(&storepb.SeriesRequest{ 470 MinTime: 0, 471 MaxTime: math.MaxInt64, 472 Matchers: []storepb.LabelMatcher{ 473 {Type: storepb.LabelMatcher_EQ, Name: "foo", Value: "bar"}, 474 }, 475 PartialResponseStrategy: storepb.PartialResponseStrategy_ABORT, 476 }, srv)) 477 testutil.Equals(t, 0, len(srv.Warnings)) 478 testutil.Equals(t, 0, len(srv.HintsSet)) 479 testutil.Equals(t, 4, len(srv.SeriesSet)) 480 481 // All chunks should be accessible for read, but not necessarily for write. 482 for _, s := range srv.SeriesSet { 483 testutil.Equals(t, 3, len(s.Chunks)) 484 for _, c := range s.Chunks { 485 testutil.Ok(t, testutil.FaultOrPanicToErr(func() { 486 _ = string(c.Raw.Data) // Access bytes by converting them to different type. 487 })) 488 } 489 testutil.NotOk(t, testutil.FaultOrPanicToErr(func() { 490 s.Chunks[0].Raw.Data[0] = 0 // Check if we can write to the byte range. 491 s.Chunks[1].Raw.Data[0] = 0 492 s.Chunks[2].Raw.Data[0] = 0 493 })) 494 } 495 }) 496 497 t.Run("flush WAL and access results", func(t *testing.T) { 498 // This should NOT block as close was not delegated. 499 testutil.Ok(t, db.FlushWAL(tmpDir)) 500 501 // Expect segfault on read and write. 502 for _, s := range srv.SeriesSet { 503 testutil.NotOk(t, testutil.FaultOrPanicToErr(func() { 504 _ = string(s.Chunks[0].Raw.Data) // Access bytes by converting them to different type. 505 _ = string(s.Chunks[1].Raw.Data) 506 _ = string(s.Chunks[2].Raw.Data) 507 })) 508 testutil.NotOk(t, testutil.FaultOrPanicToErr(func() { 509 s.Chunks[0].Raw.Data[0] = 0 // Check if we can write to the byte range. 510 s.Chunks[1].Raw.Data[0] = 0 511 s.Chunks[2].Raw.Data[0] = 0 512 })) 513 } 514 }) 515 t.Run("close db with block readers and access results", func(t *testing.T) { 516 // This should NOT block as close was not delegated. 517 testutil.Ok(t, db.Close()) 518 db = nil 519 520 // Expect segfault on read and write. 521 for _, s := range srv.SeriesSet { 522 testutil.NotOk(t, testutil.FaultOrPanicToErr(func() { 523 _ = string(s.Chunks[0].Raw.Data) // Access bytes by converting them to different type. 524 _ = string(s.Chunks[1].Raw.Data) 525 _ = string(s.Chunks[2].Raw.Data) 526 })) 527 testutil.NotOk(t, testutil.FaultOrPanicToErr(func() { 528 s.Chunks[0].Raw.Data[0] = 0 // Check if we can write to the byte range. 529 s.Chunks[1].Raw.Data[0] = 0 530 s.Chunks[2].Raw.Data[0] = 0 531 })) 532 } 533 }) 534 } 535 536 func TestTSDBStoreSeries(t *testing.T) { 537 tb := testutil.NewTB(t) 538 // Make sure there are more samples, so we can check framing code. 539 storetestutil.RunSeriesInterestingCases(tb, 10e6, 200e3, func(t testutil.TB, samplesPerSeries, series int) { 540 benchTSDBStoreSeries(t, samplesPerSeries, series) 541 }) 542 } 543 544 func BenchmarkTSDBStoreSeries(b *testing.B) { 545 tb := testutil.NewTB(b) 546 storetestutil.RunSeriesInterestingCases(tb, 10e6, 10e5, func(t testutil.TB, samplesPerSeries, series int) { 547 benchTSDBStoreSeries(t, samplesPerSeries, series) 548 }) 549 } 550 551 func benchTSDBStoreSeries(t testutil.TB, totalSamples, totalSeries int) { 552 tmpDir := t.TempDir() 553 554 // This means 3 blocks and the head. 555 const numOfBlocks = 4 556 557 samplesPerSeriesPerBlock := totalSamples / numOfBlocks 558 if samplesPerSeriesPerBlock == 0 { 559 samplesPerSeriesPerBlock = 1 560 } 561 seriesPerBlock := totalSeries / numOfBlocks 562 if seriesPerBlock == 0 { 563 seriesPerBlock = 1 564 } 565 566 var ( 567 resps = make([][]*storepb.SeriesResponse, 4) 568 random = rand.New(rand.NewSource(120)) 569 logger = log.NewNopLogger() 570 ) 571 572 for j := 0; j < 3; j++ { 573 head, created := storetestutil.CreateHeadWithSeries(t, j, storetestutil.HeadGenOptions{ 574 TSDBDir: tmpDir, 575 SamplesPerSeries: samplesPerSeriesPerBlock, 576 Series: seriesPerBlock, 577 Random: random, 578 }) 579 for i := 0; i < len(created); i++ { 580 resps[j] = append(resps[j], storepb.NewSeriesResponse(created[i])) 581 } 582 583 _ = createBlockFromHead(t, tmpDir, head) 584 testutil.Ok(t, head.Close()) 585 } 586 587 head2, created := storetestutil.CreateHeadWithSeries(t, 3, storetestutil.HeadGenOptions{ 588 TSDBDir: tmpDir, 589 SamplesPerSeries: samplesPerSeriesPerBlock, 590 Series: seriesPerBlock, 591 WithWAL: true, 592 Random: random, 593 }) 594 testutil.Ok(t, head2.Close()) 595 596 for i := 0; i < len(created); i++ { 597 resps[3] = append(resps[3], storepb.NewSeriesResponse(created[i])) 598 } 599 600 db, err := tsdb.OpenDBReadOnly(tmpDir, logger) 601 testutil.Ok(t, err) 602 603 defer func() { testutil.Ok(t, db.Close()) }() 604 605 extLabels := labels.FromStrings("ext", "1") 606 store := NewTSDBStore(logger, &mockedStartTimeDB{DBReadOnly: db, startTime: 0}, component.Receive, extLabels) 607 608 var expected []*storepb.Series 609 for _, resp := range resps { 610 for _, r := range resp { 611 // Add external labels & frame it. 612 s := r.GetSeries() 613 bytesLeftForChunks := store.maxBytesPerFrame 614 lbls := make([]labelpb.ZLabel, 0, len(s.Labels)+len(extLabels)) 615 for _, l := range s.Labels { 616 lbls = append(lbls, labelpb.ZLabel{ 617 Name: l.Name, 618 Value: l.Value, 619 }) 620 bytesLeftForChunks -= lbls[len(lbls)-1].Size() 621 } 622 for _, l := range extLabels { 623 lbls = append(lbls, labelpb.ZLabel{ 624 Name: l.Name, 625 Value: l.Value, 626 }) 627 bytesLeftForChunks -= lbls[len(lbls)-1].Size() 628 } 629 sort.Slice(lbls, func(i, j int) bool { 630 return lbls[i].Name < lbls[j].Name 631 }) 632 633 frameBytesLeft := bytesLeftForChunks 634 frame := &storepb.Series{Labels: lbls} 635 for i, c := range s.Chunks { 636 frame.Chunks = append(frame.Chunks, c) 637 frameBytesLeft -= c.Size() 638 639 if i == len(s.Chunks)-1 { 640 break 641 } 642 643 if frameBytesLeft > 0 { 644 continue 645 } 646 expected = append(expected, frame) 647 frameBytesLeft = bytesLeftForChunks 648 frame = &storepb.Series{Labels: lbls} 649 } 650 expected = append(expected, frame) 651 } 652 } 653 654 storetestutil.TestServerSeries(t, store, 655 &storetestutil.SeriesCase{ 656 Name: fmt.Sprintf("%d blocks and one WAL with %d samples, %d series each", numOfBlocks-1, samplesPerSeriesPerBlock, seriesPerBlock), 657 Req: &storepb.SeriesRequest{ 658 MinTime: 0, 659 MaxTime: math.MaxInt64, 660 Matchers: []storepb.LabelMatcher{ 661 {Type: storepb.LabelMatcher_EQ, Name: "foo", Value: "bar"}, 662 }, 663 PartialResponseStrategy: storepb.PartialResponseStrategy_ABORT, 664 }, 665 ExpectedSeries: expected, 666 }, 667 ) 668 }