github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/ingester/ingester_test.go (about) 1 package ingester 2 3 import ( 4 "fmt" 5 "log" 6 "net" 7 "net/http" 8 "sort" 9 "sync" 10 "testing" 11 "time" 12 13 "github.com/grafana/dskit/flagext" 14 "github.com/grafana/dskit/services" 15 "github.com/prometheus/common/model" 16 "github.com/prometheus/prometheus/model/labels" 17 "github.com/stretchr/testify/require" 18 "github.com/weaveworks/common/httpgrpc" 19 "github.com/weaveworks/common/middleware" 20 "github.com/weaveworks/common/user" 21 "golang.org/x/net/context" 22 "google.golang.org/grpc" 23 "google.golang.org/grpc/credentials/insecure" 24 "google.golang.org/grpc/metadata" 25 "google.golang.org/grpc/test/bufconn" 26 27 "github.com/grafana/dskit/tenant" 28 29 "github.com/grafana/loki/pkg/chunkenc" 30 "github.com/grafana/loki/pkg/ingester/client" 31 "github.com/grafana/loki/pkg/ingester/index" 32 "github.com/grafana/loki/pkg/iter" 33 "github.com/grafana/loki/pkg/logproto" 34 "github.com/grafana/loki/pkg/logql" 35 "github.com/grafana/loki/pkg/runtime" 36 "github.com/grafana/loki/pkg/storage/chunk" 37 "github.com/grafana/loki/pkg/storage/chunk/fetcher" 38 "github.com/grafana/loki/pkg/storage/config" 39 "github.com/grafana/loki/pkg/storage/stores/index/stats" 40 "github.com/grafana/loki/pkg/validation" 41 ) 42 43 func TestIngester(t *testing.T) { 44 ingesterConfig := defaultIngesterTestConfig(t) 45 limits, err := validation.NewOverrides(defaultLimitsTestConfig(), nil) 46 require.NoError(t, err) 47 48 store := &mockStore{ 49 chunks: map[string][]chunk.Chunk{}, 50 } 51 52 i, err := New(ingesterConfig, client.Config{}, store, limits, runtime.DefaultTenantConfigs(), nil) 53 require.NoError(t, err) 54 defer services.StopAndAwaitTerminated(context.Background(), i) //nolint:errcheck 55 56 req := logproto.PushRequest{ 57 Streams: []logproto.Stream{ 58 { 59 Labels: `{foo="bar",bar="baz1"}`, 60 }, 61 { 62 Labels: `{foo="bar",bar="baz2"}`, 63 }, 64 }, 65 } 66 for i := 0; i < 10; i++ { 67 req.Streams[0].Entries = append(req.Streams[0].Entries, logproto.Entry{ 68 Timestamp: time.Unix(0, 0), 69 Line: fmt.Sprintf("line %d", i), 70 }) 71 req.Streams[1].Entries = append(req.Streams[1].Entries, logproto.Entry{ 72 Timestamp: time.Unix(0, 0), 73 Line: fmt.Sprintf("line %d", i), 74 }) 75 } 76 77 ctx := user.InjectOrgID(context.Background(), "test") 78 _, err = i.Push(ctx, &req) 79 require.NoError(t, err) 80 81 result := mockQuerierServer{ 82 ctx: ctx, 83 } 84 err = i.Query(&logproto.QueryRequest{ 85 Selector: `{foo="bar"}`, 86 Limit: 100, 87 Start: time.Unix(0, 0), 88 End: time.Unix(1, 0), 89 }, &result) 90 require.NoError(t, err) 91 require.Len(t, result.resps, 1) 92 require.Len(t, result.resps[0].Streams, 2) 93 94 result = mockQuerierServer{ 95 ctx: ctx, 96 } 97 err = i.Query(&logproto.QueryRequest{ 98 Selector: `{foo="bar",bar="baz1"}`, 99 Limit: 100, 100 Start: time.Unix(0, 0), 101 End: time.Unix(1, 0), 102 }, &result) 103 require.NoError(t, err) 104 require.Len(t, result.resps, 1) 105 require.Len(t, result.resps[0].Streams, 1) 106 require.Equal(t, `{bar="baz1", foo="bar"}`, result.resps[0].Streams[0].Labels) 107 108 result = mockQuerierServer{ 109 ctx: ctx, 110 } 111 err = i.Query(&logproto.QueryRequest{ 112 Selector: `{foo="bar",bar="baz2"}`, 113 Limit: 100, 114 Start: time.Unix(0, 0), 115 End: time.Unix(1, 0), 116 }, &result) 117 require.NoError(t, err) 118 require.Len(t, result.resps, 1) 119 require.Len(t, result.resps[0].Streams, 1) 120 require.Equal(t, `{bar="baz2", foo="bar"}`, result.resps[0].Streams[0].Labels) 121 122 // Series 123 124 // empty matcher return all series 125 resp, err := i.Series(ctx, &logproto.SeriesRequest{ 126 Start: time.Unix(0, 0), 127 End: time.Unix(1, 0), 128 }) 129 require.Nil(t, err) 130 require.ElementsMatch(t, []logproto.SeriesIdentifier{ 131 { 132 Labels: map[string]string{ 133 "foo": "bar", 134 "bar": "baz1", 135 }, 136 }, 137 { 138 Labels: map[string]string{ 139 "foo": "bar", 140 "bar": "baz2", 141 }, 142 }, 143 }, resp.GetSeries()) 144 145 // wrong matchers fmt 146 _, err = i.Series(ctx, &logproto.SeriesRequest{ 147 Start: time.Unix(0, 0), 148 End: time.Unix(1, 0), 149 Groups: []string{`{a="b`}, 150 }) 151 require.Error(t, err) 152 153 // no selectors 154 _, err = i.Series(ctx, &logproto.SeriesRequest{ 155 Start: time.Unix(0, 0), 156 End: time.Unix(1, 0), 157 Groups: []string{`{foo="bar"}`, `{}`}, 158 }) 159 require.Error(t, err) 160 161 // foo=bar 162 resp, err = i.Series(ctx, &logproto.SeriesRequest{ 163 Start: time.Unix(0, 0), 164 End: time.Unix(1, 0), 165 Groups: []string{`{foo="bar"}`}, 166 }) 167 require.Nil(t, err) 168 require.ElementsMatch(t, []logproto.SeriesIdentifier{ 169 { 170 Labels: map[string]string{ 171 "foo": "bar", 172 "bar": "baz1", 173 }, 174 }, 175 { 176 Labels: map[string]string{ 177 "foo": "bar", 178 "bar": "baz2", 179 }, 180 }, 181 }, resp.GetSeries()) 182 183 // foo=bar, bar=~"baz[2-9]" 184 resp, err = i.Series(ctx, &logproto.SeriesRequest{ 185 Start: time.Unix(0, 0), 186 End: time.Unix(1, 0), 187 Groups: []string{`{foo="bar", bar=~"baz[2-9]"}`}, 188 }) 189 require.Nil(t, err) 190 require.ElementsMatch(t, []logproto.SeriesIdentifier{ 191 { 192 Labels: map[string]string{ 193 "foo": "bar", 194 "bar": "baz2", 195 }, 196 }, 197 }, resp.GetSeries()) 198 199 // foo=bar, bar=~"baz[2-9]" in different groups should OR the results 200 resp, err = i.Series(ctx, &logproto.SeriesRequest{ 201 Start: time.Unix(0, 0), 202 End: time.Unix(1, 0), 203 Groups: []string{`{foo="bar"}`, `{bar=~"baz[2-9]"}`}, 204 }) 205 require.Nil(t, err) 206 require.ElementsMatch(t, []logproto.SeriesIdentifier{ 207 { 208 Labels: map[string]string{ 209 "foo": "bar", 210 "bar": "baz1", 211 }, 212 }, 213 { 214 Labels: map[string]string{ 215 "foo": "bar", 216 "bar": "baz2", 217 }, 218 }, 219 }, resp.GetSeries()) 220 } 221 222 func TestIngesterStreamLimitExceeded(t *testing.T) { 223 ingesterConfig := defaultIngesterTestConfig(t) 224 defaultLimits := defaultLimitsTestConfig() 225 defaultLimits.MaxLocalStreamsPerUser = 1 226 overrides, err := validation.NewOverrides(defaultLimits, nil) 227 228 require.NoError(t, err) 229 230 store := &mockStore{ 231 chunks: map[string][]chunk.Chunk{}, 232 } 233 234 i, err := New(ingesterConfig, client.Config{}, store, overrides, runtime.DefaultTenantConfigs(), nil) 235 require.NoError(t, err) 236 defer services.StopAndAwaitTerminated(context.Background(), i) //nolint:errcheck 237 238 req := logproto.PushRequest{ 239 Streams: []logproto.Stream{ 240 { 241 Labels: `{foo="bar",bar="baz1"}`, 242 }, 243 }, 244 } 245 for i := 0; i < 10; i++ { 246 req.Streams[0].Entries = append(req.Streams[0].Entries, logproto.Entry{ 247 Timestamp: time.Unix(0, 0), 248 Line: fmt.Sprintf("line %d", i), 249 }) 250 } 251 252 ctx := user.InjectOrgID(context.Background(), "test") 253 _, err = i.Push(ctx, &req) 254 require.NoError(t, err) 255 256 req.Streams[0].Labels = `{foo="bar",bar="baz2"}` 257 258 _, err = i.Push(ctx, &req) 259 if resp, ok := httpgrpc.HTTPResponseFromError(err); !ok || resp.Code != http.StatusTooManyRequests { 260 t.Fatalf("expected error about exceeding metrics per user, got %v", err) 261 } 262 } 263 264 type mockStore struct { 265 mtx sync.Mutex 266 chunks map[string][]chunk.Chunk 267 } 268 269 func (s *mockStore) Put(ctx context.Context, chunks []chunk.Chunk) error { 270 s.mtx.Lock() 271 defer s.mtx.Unlock() 272 273 userid, err := tenant.TenantID(ctx) 274 if err != nil { 275 return err 276 } 277 278 s.chunks[userid] = append(s.chunks[userid], chunks...) 279 return nil 280 } 281 282 func (s *mockStore) SelectLogs(ctx context.Context, req logql.SelectLogParams) (iter.EntryIterator, error) { 283 return nil, nil 284 } 285 286 func (s *mockStore) SelectSamples(ctx context.Context, req logql.SelectSampleParams) (iter.SampleIterator, error) { 287 return nil, nil 288 } 289 290 func (s *mockStore) GetSeries(ctx context.Context, req logql.SelectLogParams) ([]logproto.SeriesIdentifier, error) { 291 return nil, nil 292 } 293 294 func (s *mockStore) GetSchemaConfigs() []config.PeriodConfig { 295 return defaultPeriodConfigs 296 } 297 298 func (s *mockStore) SetChunkFilterer(_ chunk.RequestChunkFilterer) { 299 } 300 301 // chunk.Store methods 302 func (s *mockStore) PutOne(ctx context.Context, from, through model.Time, chunk chunk.Chunk) error { 303 return nil 304 } 305 306 func (s *mockStore) GetChunkRefs(ctx context.Context, userID string, from, through model.Time, matchers ...*labels.Matcher) ([][]chunk.Chunk, []*fetcher.Fetcher, error) { 307 return nil, nil, nil 308 } 309 310 func (s *mockStore) LabelValuesForMetricName(ctx context.Context, userID string, from, through model.Time, metricName string, labelName string, matchers ...*labels.Matcher) ([]string, error) { 311 return []string{"val1", "val2"}, nil 312 } 313 314 func (s *mockStore) LabelNamesForMetricName(ctx context.Context, userID string, from, through model.Time, metricName string) ([]string, error) { 315 return nil, nil 316 } 317 318 func (s *mockStore) GetChunkFetcher(tm model.Time) *fetcher.Fetcher { 319 return nil 320 } 321 322 func (s *mockStore) Stats(ctx context.Context, userID string, from, through model.Time, matchers ...*labels.Matcher) (*stats.Stats, error) { 323 return &stats.Stats{}, nil 324 } 325 326 func (s *mockStore) Stop() {} 327 328 type mockQuerierServer struct { 329 ctx context.Context 330 resps []*logproto.QueryResponse 331 grpc.ServerStream 332 } 333 334 func (*mockQuerierServer) SetTrailer(metadata.MD) {} 335 336 func (m *mockQuerierServer) Send(resp *logproto.QueryResponse) error { 337 m.resps = append(m.resps, resp) 338 return nil 339 } 340 341 func (m *mockQuerierServer) Context() context.Context { 342 return m.ctx 343 } 344 345 func defaultLimitsTestConfig() validation.Limits { 346 limits := validation.Limits{} 347 flagext.DefaultValues(&limits) 348 return limits 349 } 350 351 func TestIngester_buildStoreRequest(t *testing.T) { 352 now := time.Now() 353 for _, tc := range []struct { 354 name string 355 queryStore bool 356 maxLookBackPeriod time.Duration 357 start, end time.Time 358 expectedStart, expectedEnd time.Time 359 shouldQuery bool 360 }{ 361 { 362 name: "do not query store", 363 queryStore: false, 364 start: now.Add(-time.Minute), 365 end: now, 366 shouldQuery: false, 367 }, 368 { 369 name: "query store with max look back covering whole request duration", 370 queryStore: true, 371 maxLookBackPeriod: time.Hour, 372 start: now.Add(-10 * time.Minute), 373 end: now, 374 expectedStart: now.Add(-10 * time.Minute), 375 expectedEnd: now, 376 shouldQuery: true, 377 }, 378 { 379 name: "query store with max look back covering partial request duration", 380 queryStore: true, 381 maxLookBackPeriod: time.Hour, 382 start: now.Add(-2 * time.Hour), 383 end: now, 384 expectedStart: now.Add(-time.Hour), 385 expectedEnd: now, 386 shouldQuery: true, 387 }, 388 { 389 name: "query store with max look back not covering request duration at all", 390 queryStore: true, 391 maxLookBackPeriod: time.Hour, 392 start: now.Add(-4 * time.Hour), 393 end: now.Add(-2 * time.Hour), 394 shouldQuery: false, 395 }, 396 } { 397 t.Run(tc.name, func(t *testing.T) { 398 ingesterConfig := defaultIngesterTestConfig(t) 399 ingesterConfig.QueryStore = tc.queryStore 400 ingesterConfig.QueryStoreMaxLookBackPeriod = tc.maxLookBackPeriod 401 402 start, end, ok := buildStoreRequest(ingesterConfig, tc.start, tc.end, now) 403 404 if !tc.shouldQuery { 405 require.False(t, ok) 406 return 407 } 408 require.Equal(t, tc.expectedEnd, end, "end") 409 require.Equal(t, tc.expectedStart, start, "start") 410 }) 411 } 412 } 413 414 func TestIngester_asyncStoreMaxLookBack(t *testing.T) { 415 now := model.Now() 416 417 for _, tc := range []struct { 418 name string 419 periodicConfigs []config.PeriodConfig 420 expectedMaxLookBack time.Duration 421 }{ 422 { 423 name: "not using async index store", 424 periodicConfigs: []config.PeriodConfig{ 425 { 426 From: config.DayTime{Time: now.Add(-24 * time.Hour)}, 427 IndexType: "bigtable", 428 }, 429 }, 430 }, 431 { 432 name: "just one periodic config with boltdb-shipper", 433 periodicConfigs: []config.PeriodConfig{ 434 { 435 From: config.DayTime{Time: now.Add(-24 * time.Hour)}, 436 IndexType: "boltdb-shipper", 437 }, 438 }, 439 expectedMaxLookBack: time.Since(now.Add(-24 * time.Hour).Time()), 440 }, 441 { 442 name: "just one periodic config with tsdb", 443 periodicConfigs: []config.PeriodConfig{ 444 { 445 From: config.DayTime{Time: now.Add(-24 * time.Hour)}, 446 IndexType: "tsdb", 447 }, 448 }, 449 expectedMaxLookBack: time.Since(now.Add(-24 * time.Hour).Time()), 450 }, 451 { 452 name: "active config boltdb-shipper, previous config non async index store", 453 periodicConfigs: []config.PeriodConfig{ 454 { 455 From: config.DayTime{Time: now.Add(-48 * time.Hour)}, 456 IndexType: "bigtable", 457 }, 458 { 459 From: config.DayTime{Time: now.Add(-24 * time.Hour)}, 460 IndexType: "boltdb-shipper", 461 }, 462 }, 463 expectedMaxLookBack: time.Since(now.Add(-24 * time.Hour).Time()), 464 }, 465 { 466 name: "current and previous config both using async index store", 467 periodicConfigs: []config.PeriodConfig{ 468 { 469 From: config.DayTime{Time: now.Add(-48 * time.Hour)}, 470 IndexType: "boltdb-shipper", 471 }, 472 { 473 From: config.DayTime{Time: now.Add(-24 * time.Hour)}, 474 IndexType: "tsdb", 475 }, 476 }, 477 expectedMaxLookBack: time.Since(now.Add(-48 * time.Hour).Time()), 478 }, 479 { 480 name: "active config non async index store, previous config tsdb", 481 periodicConfigs: []config.PeriodConfig{ 482 { 483 From: config.DayTime{Time: now.Add(-48 * time.Hour)}, 484 IndexType: "tsdb", 485 }, 486 { 487 From: config.DayTime{Time: now.Add(-24 * time.Hour)}, 488 IndexType: "bigtable", 489 }, 490 }, 491 }, 492 } { 493 t.Run(tc.name, func(t *testing.T) { 494 ingester := Ingester{periodicConfigs: tc.periodicConfigs} 495 mlb := ingester.asyncStoreMaxLookBack() 496 require.InDelta(t, tc.expectedMaxLookBack, mlb, float64(time.Second)) 497 }) 498 } 499 } 500 501 func TestValidate(t *testing.T) { 502 for i, tc := range []struct { 503 in Config 504 err bool 505 expected Config 506 }{ 507 { 508 in: Config{ 509 MaxChunkAge: time.Minute, 510 ChunkEncoding: chunkenc.EncGZIP.String(), 511 IndexShards: index.DefaultIndexShards, 512 }, 513 expected: Config{ 514 MaxChunkAge: time.Minute, 515 ChunkEncoding: chunkenc.EncGZIP.String(), 516 parsedEncoding: chunkenc.EncGZIP, 517 IndexShards: index.DefaultIndexShards, 518 }, 519 }, 520 { 521 in: Config{ 522 ChunkEncoding: chunkenc.EncSnappy.String(), 523 IndexShards: index.DefaultIndexShards, 524 }, 525 expected: Config{ 526 ChunkEncoding: chunkenc.EncSnappy.String(), 527 parsedEncoding: chunkenc.EncSnappy, 528 IndexShards: index.DefaultIndexShards, 529 }, 530 }, 531 { 532 in: Config{ 533 IndexShards: index.DefaultIndexShards, 534 ChunkEncoding: "bad-enc", 535 }, 536 err: true, 537 }, 538 { 539 in: Config{ 540 MaxChunkAge: time.Minute, 541 ChunkEncoding: chunkenc.EncGZIP.String(), 542 }, 543 err: true, 544 }, 545 } { 546 t.Run(fmt.Sprint(i), func(t *testing.T) { 547 err := tc.in.Validate() 548 if tc.err { 549 require.NotNil(t, err) 550 return 551 } 552 require.Nil(t, err) 553 require.Equal(t, tc.expected, tc.in) 554 }) 555 } 556 } 557 558 func Test_InMemoryLabels(t *testing.T) { 559 ingesterConfig := defaultIngesterTestConfig(t) 560 limits, err := validation.NewOverrides(defaultLimitsTestConfig(), nil) 561 require.NoError(t, err) 562 563 store := &mockStore{ 564 chunks: map[string][]chunk.Chunk{}, 565 } 566 567 i, err := New(ingesterConfig, client.Config{}, store, limits, runtime.DefaultTenantConfigs(), nil) 568 require.NoError(t, err) 569 defer services.StopAndAwaitTerminated(context.Background(), i) //nolint:errcheck 570 571 req := logproto.PushRequest{ 572 Streams: []logproto.Stream{ 573 { 574 Labels: `{foo="bar",bar="baz1"}`, 575 }, 576 { 577 Labels: `{foo="bar",bar="baz2"}`, 578 }, 579 }, 580 } 581 for i := 0; i < 10; i++ { 582 req.Streams[0].Entries = append(req.Streams[0].Entries, logproto.Entry{ 583 Timestamp: time.Unix(0, 0), 584 Line: fmt.Sprintf("line %d", i), 585 }) 586 req.Streams[1].Entries = append(req.Streams[1].Entries, logproto.Entry{ 587 Timestamp: time.Unix(0, 0), 588 Line: fmt.Sprintf("line %d", i), 589 }) 590 } 591 592 ctx := user.InjectOrgID(context.Background(), "test") 593 _, err = i.Push(ctx, &req) 594 require.NoError(t, err) 595 596 start := time.Unix(0, 0) 597 res, err := i.Label(ctx, &logproto.LabelRequest{ 598 Start: &start, 599 Name: "bar", 600 Values: true, 601 }) 602 603 require.NoError(t, err) 604 require.Equal(t, []string{"baz1", "baz2"}, res.Values) 605 606 res, err = i.Label(ctx, &logproto.LabelRequest{Start: &start}) 607 require.NoError(t, err) 608 require.Equal(t, []string{"bar", "foo"}, res.Values) 609 } 610 611 func Test_DedupeIngester(t *testing.T) { 612 var ( 613 requests = int64(400) 614 streamCount = int64(20) 615 streams []labels.Labels 616 streamHashes []uint64 617 ingesterCount = 100 618 619 ingesterConfig = defaultIngesterTestConfig(t) 620 ctx, _ = user.InjectIntoGRPCRequest(user.InjectOrgID(context.Background(), "foo")) 621 ) 622 // make sure we will cut blocks and chunks and use head chunks 623 ingesterConfig.TargetChunkSize = 800 624 ingesterConfig.BlockSize = 300 625 626 // created many different ingesters 627 ingesterSet, closer := createIngesterSets(t, ingesterConfig, ingesterCount) 628 defer closer() 629 630 for i := int64(0); i < streamCount; i++ { 631 s := labels.FromStrings("foo", "bar", "bar", fmt.Sprintf("baz%d", i)) 632 streams = append(streams, s) 633 streamHashes = append(streamHashes, s.Hash()) 634 } 635 sort.Slice(streamHashes, func(i, j int) bool { return streamHashes[i] < streamHashes[j] }) 636 637 for i := int64(0); i < requests; i++ { 638 for _, ing := range ingesterSet { 639 _, err := ing.Push(ctx, buildPushRequest(i, streams)) 640 require.NoError(t, err) 641 } 642 } 643 644 t.Run("backward log", func(t *testing.T) { 645 iterators := make([]iter.EntryIterator, 0, len(ingesterSet)) 646 for _, client := range ingesterSet { 647 stream, err := client.Query(ctx, &logproto.QueryRequest{ 648 Selector: `{foo="bar"} | label_format bar=""`, // making it difficult to dedupe by removing uncommon label. 649 Start: time.Unix(0, 0), 650 End: time.Unix(0, requests+1), 651 Limit: uint32(requests * streamCount), 652 Direction: logproto.BACKWARD, 653 }) 654 require.NoError(t, err) 655 iterators = append(iterators, iter.NewQueryClientIterator(stream, logproto.BACKWARD)) 656 } 657 it := iter.NewMergeEntryIterator(ctx, iterators, logproto.BACKWARD) 658 659 for i := requests - 1; i >= 0; i-- { 660 actualHashes := []uint64{} 661 for j := 0; j < int(streamCount); j++ { 662 require.True(t, it.Next()) 663 require.Equal(t, fmt.Sprintf("line %d", i), it.Entry().Line) 664 require.Equal(t, i, it.Entry().Timestamp.UnixNano()) 665 require.Equal(t, `{bar="", foo="bar"}`, it.Labels()) 666 actualHashes = append(actualHashes, it.StreamHash()) 667 } 668 sort.Slice(actualHashes, func(i, j int) bool { return actualHashes[i] < actualHashes[j] }) 669 require.Equal(t, streamHashes, actualHashes) 670 } 671 require.False(t, it.Next()) 672 require.NoError(t, it.Error()) 673 }) 674 t.Run("forward log", func(t *testing.T) { 675 iterators := make([]iter.EntryIterator, 0, len(ingesterSet)) 676 for _, client := range ingesterSet { 677 stream, err := client.Query(ctx, &logproto.QueryRequest{ 678 Selector: `{foo="bar"} | label_format bar=""`, // making it difficult to dedupe by removing uncommon label. 679 Start: time.Unix(0, 0), 680 End: time.Unix(0, requests+1), 681 Limit: uint32(requests * streamCount), 682 Direction: logproto.FORWARD, 683 }) 684 require.NoError(t, err) 685 iterators = append(iterators, iter.NewQueryClientIterator(stream, logproto.FORWARD)) 686 } 687 it := iter.NewMergeEntryIterator(ctx, iterators, logproto.FORWARD) 688 689 for i := int64(0); i < requests; i++ { 690 actualHashes := []uint64{} 691 for j := 0; j < int(streamCount); j++ { 692 require.True(t, it.Next()) 693 require.Equal(t, fmt.Sprintf("line %d", i), it.Entry().Line) 694 require.Equal(t, i, it.Entry().Timestamp.UnixNano()) 695 require.Equal(t, `{bar="", foo="bar"}`, it.Labels()) 696 actualHashes = append(actualHashes, it.StreamHash()) 697 } 698 sort.Slice(actualHashes, func(i, j int) bool { return actualHashes[i] < actualHashes[j] }) 699 require.Equal(t, streamHashes, actualHashes) 700 } 701 require.False(t, it.Next()) 702 require.NoError(t, it.Error()) 703 }) 704 t.Run("sum by metrics", func(t *testing.T) { 705 iterators := make([]iter.SampleIterator, 0, len(ingesterSet)) 706 for _, client := range ingesterSet { 707 stream, err := client.QuerySample(ctx, &logproto.SampleQueryRequest{ 708 Selector: `sum(rate({foo="bar"}[1m])) by (bar)`, 709 Start: time.Unix(0, 0), 710 End: time.Unix(0, requests+1), 711 }) 712 require.NoError(t, err) 713 iterators = append(iterators, iter.NewSampleQueryClientIterator(stream)) 714 } 715 it := iter.NewMergeSampleIterator(ctx, iterators) 716 var expectedLabels []string 717 for _, s := range streams { 718 expectedLabels = append(expectedLabels, labels.NewBuilder(s).Del("foo").Labels().String()) 719 } 720 sort.Strings(expectedLabels) 721 for i := int64(0); i < requests; i++ { 722 labels := []string{} 723 actualHashes := []uint64{} 724 for j := 0; j < int(streamCount); j++ { 725 require.True(t, it.Next()) 726 require.Equal(t, float64(1), it.Sample().Value) 727 require.Equal(t, i, it.Sample().Timestamp) 728 labels = append(labels, it.Labels()) 729 actualHashes = append(actualHashes, it.StreamHash()) 730 } 731 sort.Strings(labels) 732 sort.Slice(actualHashes, func(i, j int) bool { return actualHashes[i] < actualHashes[j] }) 733 require.Equal(t, expectedLabels, labels) 734 require.Equal(t, streamHashes, actualHashes) 735 } 736 require.False(t, it.Next()) 737 require.NoError(t, it.Error()) 738 }) 739 t.Run("sum metrics", func(t *testing.T) { 740 iterators := make([]iter.SampleIterator, 0, len(ingesterSet)) 741 for _, client := range ingesterSet { 742 stream, err := client.QuerySample(ctx, &logproto.SampleQueryRequest{ 743 Selector: `sum(rate({foo="bar"}[1m]))`, 744 Start: time.Unix(0, 0), 745 End: time.Unix(0, requests+1), 746 }) 747 require.NoError(t, err) 748 iterators = append(iterators, iter.NewSampleQueryClientIterator(stream)) 749 } 750 it := iter.NewMergeSampleIterator(ctx, iterators) 751 for i := int64(0); i < requests; i++ { 752 actualHashes := []uint64{} 753 for j := 0; j < int(streamCount); j++ { 754 require.True(t, it.Next()) 755 require.Equal(t, float64(1), it.Sample().Value) 756 require.Equal(t, i, it.Sample().Timestamp) 757 require.Equal(t, "{}", it.Labels()) 758 actualHashes = append(actualHashes, it.StreamHash()) 759 } 760 sort.Slice(actualHashes, func(i, j int) bool { return actualHashes[i] < actualHashes[j] }) 761 require.Equal(t, streamHashes, actualHashes) 762 } 763 require.False(t, it.Next()) 764 require.NoError(t, it.Error()) 765 }) 766 } 767 768 func Test_DedupeIngesterParser(t *testing.T) { 769 var ( 770 requests = 100 771 streamCount = 10 772 streams []labels.Labels 773 ingesterCount = 30 774 775 ingesterConfig = defaultIngesterTestConfig(t) 776 ctx, _ = user.InjectIntoGRPCRequest(user.InjectOrgID(context.Background(), "foo")) 777 ) 778 // make sure we will cut blocks and chunks and use head chunks 779 ingesterConfig.TargetChunkSize = 800 780 ingesterConfig.BlockSize = 300 781 782 // created many different ingesters 783 ingesterSet, closer := createIngesterSets(t, ingesterConfig, ingesterCount) 784 defer closer() 785 786 for i := 0; i < streamCount; i++ { 787 streams = append(streams, labels.FromStrings("foo", "bar", "bar", fmt.Sprintf("baz%d", i))) 788 } 789 790 for i := 0; i < requests; i++ { 791 for _, ing := range ingesterSet { 792 _, err := ing.Push(ctx, buildPushJSONRequest(int64(i), streams)) 793 require.NoError(t, err) 794 } 795 } 796 797 t.Run("backward log", func(t *testing.T) { 798 iterators := make([]iter.EntryIterator, 0, len(ingesterSet)) 799 for _, client := range ingesterSet { 800 stream, err := client.Query(ctx, &logproto.QueryRequest{ 801 Selector: `{foo="bar"} | json`, 802 Start: time.Unix(0, 0), 803 End: time.Unix(0, int64(requests+1)), 804 Limit: uint32(requests * streamCount * 2), 805 Direction: logproto.BACKWARD, 806 }) 807 require.NoError(t, err) 808 iterators = append(iterators, iter.NewQueryClientIterator(stream, logproto.BACKWARD)) 809 } 810 it := iter.NewMergeEntryIterator(ctx, iterators, logproto.BACKWARD) 811 812 for i := requests - 1; i >= 0; i-- { 813 for j := 0; j < streamCount; j++ { 814 for k := 0; k < 2; k++ { // 2 line per entry 815 require.True(t, it.Next()) 816 require.Equal(t, int64(i), it.Entry().Timestamp.UnixNano()) 817 } 818 } 819 } 820 require.False(t, it.Next()) 821 require.NoError(t, it.Error()) 822 }) 823 824 t.Run("forward log", func(t *testing.T) { 825 iterators := make([]iter.EntryIterator, 0, len(ingesterSet)) 826 for _, client := range ingesterSet { 827 stream, err := client.Query(ctx, &logproto.QueryRequest{ 828 Selector: `{foo="bar"} | json`, // making it difficult to dedupe by removing uncommon label. 829 Start: time.Unix(0, 0), 830 End: time.Unix(0, int64(requests+1)), 831 Limit: uint32(requests * streamCount * 2), 832 Direction: logproto.FORWARD, 833 }) 834 require.NoError(t, err) 835 iterators = append(iterators, iter.NewQueryClientIterator(stream, logproto.FORWARD)) 836 } 837 it := iter.NewMergeEntryIterator(ctx, iterators, logproto.FORWARD) 838 839 for i := 0; i < requests; i++ { 840 for j := 0; j < streamCount; j++ { 841 for k := 0; k < 2; k++ { // 2 line per entry 842 require.True(t, it.Next()) 843 require.Equal(t, int64(i), it.Entry().Timestamp.UnixNano()) 844 } 845 } 846 } 847 require.False(t, it.Next()) 848 require.NoError(t, it.Error()) 849 }) 850 t.Run("no sum metrics", func(t *testing.T) { 851 iterators := make([]iter.SampleIterator, 0, len(ingesterSet)) 852 for _, client := range ingesterSet { 853 stream, err := client.QuerySample(ctx, &logproto.SampleQueryRequest{ 854 Selector: `rate({foo="bar"} | json [1m])`, 855 Start: time.Unix(0, 0), 856 End: time.Unix(0, int64(requests+1)), 857 }) 858 require.NoError(t, err) 859 iterators = append(iterators, iter.NewSampleQueryClientIterator(stream)) 860 } 861 it := iter.NewMergeSampleIterator(ctx, iterators) 862 863 for i := 0; i < requests; i++ { 864 for j := 0; j < streamCount; j++ { 865 for k := 0; k < 2; k++ { // 2 line per entry 866 require.True(t, it.Next()) 867 require.Equal(t, float64(1), it.Sample().Value) 868 require.Equal(t, int64(i), it.Sample().Timestamp) 869 } 870 } 871 } 872 require.False(t, it.Next()) 873 require.NoError(t, it.Error()) 874 }) 875 t.Run("sum metrics", func(t *testing.T) { 876 iterators := make([]iter.SampleIterator, 0, len(ingesterSet)) 877 for _, client := range ingesterSet { 878 stream, err := client.QuerySample(ctx, &logproto.SampleQueryRequest{ 879 Selector: `sum by (c,d,e,foo) (rate({foo="bar"} | json [1m]))`, 880 Start: time.Unix(0, 0), 881 End: time.Unix(0, int64(requests+1)), 882 }) 883 require.NoError(t, err) 884 iterators = append(iterators, iter.NewSampleQueryClientIterator(stream)) 885 } 886 it := iter.NewMergeSampleIterator(ctx, iterators) 887 888 for i := 0; i < requests; i++ { 889 for j := 0; j < streamCount; j++ { 890 for k := 0; k < 2; k++ { // 2 line per entry 891 require.True(t, it.Next()) 892 require.Equal(t, float64(1), it.Sample().Value) 893 require.Equal(t, int64(i), it.Sample().Timestamp) 894 } 895 } 896 } 897 require.False(t, it.Next()) 898 require.NoError(t, it.Error()) 899 }) 900 } 901 902 type ingesterClient struct { 903 logproto.PusherClient 904 logproto.QuerierClient 905 } 906 907 func createIngesterSets(t *testing.T, config Config, count int) ([]ingesterClient, func()) { 908 result := make([]ingesterClient, count) 909 closers := make([]func(), count) 910 for i := 0; i < count; i++ { 911 ingester, closer := createIngesterServer(t, config) 912 result[i] = ingester 913 closers[i] = closer 914 } 915 return result, func() { 916 for _, closer := range closers { 917 closer() 918 } 919 } 920 } 921 922 func createIngesterServer(t *testing.T, ingesterConfig Config) (ingesterClient, func()) { 923 t.Helper() 924 limits, err := validation.NewOverrides(defaultLimitsTestConfig(), nil) 925 require.NoError(t, err) 926 927 ing, err := New(ingesterConfig, client.Config{}, &mockStore{}, limits, runtime.DefaultTenantConfigs(), nil) 928 require.NoError(t, err) 929 930 listener := bufconn.Listen(1024 * 1024) 931 932 server := grpc.NewServer(grpc.ChainStreamInterceptor(func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { 933 return middleware.StreamServerUserHeaderInterceptor(srv, ss, info, handler) 934 }), grpc.ChainUnaryInterceptor(func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { 935 return middleware.ServerUserHeaderInterceptor(ctx, req, info, handler) 936 })) 937 938 logproto.RegisterPusherServer(server, ing) 939 logproto.RegisterQuerierServer(server, ing) 940 go func() { 941 if err := server.Serve(listener); err != nil { 942 log.Fatal(err) 943 } 944 }() 945 conn, err := grpc.DialContext(context.Background(), "", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithContextDialer(func(ctx context.Context, s string) (net.Conn, error) { 946 return listener.Dial() 947 })) 948 require.NoError(t, err) 949 950 return ingesterClient{ 951 PusherClient: logproto.NewPusherClient(conn), 952 QuerierClient: logproto.NewQuerierClient(conn), 953 }, func() { 954 _ = services.StopAndAwaitTerminated(context.Background(), ing) 955 server.Stop() 956 _ = listener.Close() 957 } 958 } 959 960 func buildPushRequest(ts int64, streams []labels.Labels) *logproto.PushRequest { 961 req := &logproto.PushRequest{} 962 963 for _, stream := range streams { 964 req.Streams = append(req.Streams, logproto.Stream{ 965 Labels: stream.String(), 966 Entries: []logproto.Entry{ 967 { 968 Timestamp: time.Unix(0, ts), 969 Line: fmt.Sprintf("line %d", ts), 970 }, 971 }, 972 }) 973 } 974 975 return req 976 } 977 978 func buildPushJSONRequest(ts int64, streams []labels.Labels) *logproto.PushRequest { 979 req := &logproto.PushRequest{} 980 981 for _, stream := range streams { 982 req.Streams = append(req.Streams, logproto.Stream{ 983 Labels: stream.String(), 984 Entries: []logproto.Entry{ 985 { 986 Timestamp: time.Unix(0, ts), 987 Line: jsonLine(ts, 0), 988 }, 989 { 990 Timestamp: time.Unix(0, ts), 991 Line: jsonLine(ts, 1), 992 }, 993 }, 994 }) 995 } 996 997 return req 998 } 999 1000 func jsonLine(ts int64, i int) string { 1001 if i%2 == 0 { 1002 return fmt.Sprintf(`{"a":"b", "c":"d", "e":"f", "g":"h", "ts":"%d"}`, ts) 1003 } 1004 return fmt.Sprintf(`{"e":"f", "h":"i", "j":"k", "g":"h", "ts":"%d"}`, ts) 1005 }