github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/ingester/instance_test.go (about) 1 package ingester 2 3 import ( 4 "context" 5 "fmt" 6 "math/rand" 7 "runtime" 8 "sort" 9 "sync" 10 "testing" 11 "time" 12 13 "github.com/grafana/loki/pkg/logql/syntax" 14 "github.com/grafana/loki/pkg/querier/astmapper" 15 "github.com/grafana/loki/pkg/storage/chunk" 16 "github.com/grafana/loki/pkg/storage/config" 17 18 "github.com/pkg/errors" 19 "github.com/prometheus/common/model" 20 "github.com/prometheus/prometheus/model/labels" 21 "github.com/stretchr/testify/require" 22 23 "github.com/grafana/loki/pkg/logproto" 24 "github.com/grafana/loki/pkg/logql" 25 loki_runtime "github.com/grafana/loki/pkg/runtime" 26 "github.com/grafana/loki/pkg/validation" 27 ) 28 29 func defaultConfig() *Config { 30 cfg := Config{ 31 BlockSize: 512, 32 ChunkEncoding: "gzip", 33 IndexShards: 32, 34 } 35 if err := cfg.Validate(); err != nil { 36 panic(errors.Wrap(err, "error building default test config")) 37 } 38 return &cfg 39 } 40 41 func MustParseDayTime(s string) config.DayTime { 42 t, err := time.Parse("2006-01-02", s) 43 if err != nil { 44 panic(err) 45 } 46 return config.DayTime{Time: model.TimeFromUnix(t.Unix())} 47 } 48 49 var defaultPeriodConfigs = []config.PeriodConfig{ 50 { 51 From: MustParseDayTime("1900-01-01"), 52 IndexType: config.StorageTypeBigTable, 53 }, 54 } 55 56 var NilMetrics = newIngesterMetrics(nil) 57 58 func TestLabelsCollisions(t *testing.T) { 59 limits, err := validation.NewOverrides(defaultLimitsTestConfig(), nil) 60 require.NoError(t, err) 61 limiter := NewLimiter(limits, NilMetrics, &ringCountMock{count: 1}, 1) 62 63 i, err := newInstance(defaultConfig(), defaultPeriodConfigs, "test", limiter, loki_runtime.DefaultTenantConfigs(), noopWAL{}, NilMetrics, &OnceSwitch{}, nil) 64 require.Nil(t, err) 65 66 // avoid entries from the future. 67 tt := time.Now().Add(-5 * time.Minute) 68 69 // Notice how labels aren't sorted. 70 err = i.Push(context.Background(), &logproto.PushRequest{Streams: []logproto.Stream{ 71 // both label sets have FastFingerprint=e002a3a451262627 72 {Labels: "{app=\"l\",uniq0=\"0\",uniq1=\"1\"}", Entries: entries(5, tt.Add(time.Minute))}, 73 {Labels: "{uniq0=\"1\",app=\"m\",uniq1=\"1\"}", Entries: entries(5, tt)}, 74 75 // e002a3a451262247 76 {Labels: "{app=\"l\",uniq0=\"1\",uniq1=\"0\"}", Entries: entries(5, tt.Add(time.Minute))}, 77 {Labels: "{uniq1=\"0\",app=\"m\",uniq0=\"0\"}", Entries: entries(5, tt)}, 78 79 // e002a2a4512624f4 80 {Labels: "{app=\"l\",uniq0=\"0\",uniq1=\"0\"}", Entries: entries(5, tt.Add(time.Minute))}, 81 {Labels: "{uniq0=\"1\",uniq1=\"0\",app=\"m\"}", Entries: entries(5, tt)}, 82 }}) 83 require.NoError(t, err) 84 } 85 86 func TestConcurrentPushes(t *testing.T) { 87 limits, err := validation.NewOverrides(defaultLimitsTestConfig(), nil) 88 require.NoError(t, err) 89 limiter := NewLimiter(limits, NilMetrics, &ringCountMock{count: 1}, 1) 90 91 inst, err := newInstance(defaultConfig(), defaultPeriodConfigs, "test", limiter, loki_runtime.DefaultTenantConfigs(), noopWAL{}, NilMetrics, &OnceSwitch{}, nil) 92 require.Nil(t, err) 93 94 const ( 95 concurrent = 10 96 iterations = 100 97 entriesPerIteration = 100 98 ) 99 100 uniqueLabels := map[string]bool{} 101 startChannel := make(chan struct{}) 102 103 wg := sync.WaitGroup{} 104 for i := 0; i < concurrent; i++ { 105 l := makeRandomLabels() 106 for uniqueLabels[l.String()] { 107 l = makeRandomLabels() 108 } 109 uniqueLabels[l.String()] = true 110 111 wg.Add(1) 112 go func(labels string) { 113 defer wg.Done() 114 115 <-startChannel 116 117 tt := time.Now().Add(-5 * time.Minute) 118 119 for i := 0; i < iterations; i++ { 120 err := inst.Push(context.Background(), &logproto.PushRequest{Streams: []logproto.Stream{ 121 {Labels: labels, Entries: entries(entriesPerIteration, tt)}, 122 }}) 123 124 require.NoError(t, err) 125 126 tt = tt.Add(entriesPerIteration * time.Nanosecond) 127 } 128 }(l.String()) 129 } 130 131 time.Sleep(100 * time.Millisecond) // ready 132 close(startChannel) // go! 133 134 wg.Wait() 135 // test passes if no goroutine reports error 136 } 137 138 func TestSyncPeriod(t *testing.T) { 139 limits, err := validation.NewOverrides(defaultLimitsTestConfig(), nil) 140 require.NoError(t, err) 141 limiter := NewLimiter(limits, NilMetrics, &ringCountMock{count: 1}, 1) 142 143 const ( 144 syncPeriod = 1 * time.Minute 145 randomStep = time.Second 146 entries = 1000 147 minUtil = 0.20 148 ) 149 150 inst, err := newInstance(defaultConfig(), defaultPeriodConfigs, "test", limiter, loki_runtime.DefaultTenantConfigs(), noopWAL{}, NilMetrics, &OnceSwitch{}, nil) 151 require.Nil(t, err) 152 153 lbls := makeRandomLabels() 154 155 tt := time.Now() 156 157 var result []logproto.Entry 158 for i := 0; i < entries; i++ { 159 result = append(result, logproto.Entry{Timestamp: tt, Line: fmt.Sprintf("hello %d", i)}) 160 tt = tt.Add(time.Duration(1 + rand.Int63n(randomStep.Nanoseconds()))) 161 } 162 pr := &logproto.PushRequest{Streams: []logproto.Stream{{Labels: lbls.String(), Entries: result}}} 163 err = inst.Push(context.Background(), pr) 164 require.NoError(t, err) 165 166 // let's verify results 167 s, err := inst.getOrCreateStream(pr.Streams[0], recordPool.GetRecord()) 168 require.NoError(t, err) 169 170 // make sure each chunk spans max 'sync period' time 171 for _, c := range s.chunks { 172 start, end := c.chunk.Bounds() 173 span := end.Sub(start) 174 175 const format = "15:04:05.000" 176 t.Log(start.Format(format), "--", end.Format(format), span, c.chunk.Utilization()) 177 178 require.True(t, span < syncPeriod || c.chunk.Utilization() >= minUtil) 179 } 180 } 181 182 func setupTestStreams(t *testing.T) (*instance, time.Time, int) { 183 t.Helper() 184 limits, err := validation.NewOverrides(defaultLimitsTestConfig(), nil) 185 require.NoError(t, err) 186 limiter := NewLimiter(limits, NilMetrics, &ringCountMock{count: 1}, 1) 187 indexShards := 2 188 189 // just some random values 190 cfg := defaultConfig() 191 cfg.SyncPeriod = 1 * time.Minute 192 cfg.SyncMinUtilization = 0.20 193 cfg.IndexShards = indexShards 194 195 instance, err := newInstance(cfg, defaultPeriodConfigs, "test", limiter, loki_runtime.DefaultTenantConfigs(), noopWAL{}, NilMetrics, &OnceSwitch{}, nil) 196 require.Nil(t, err) 197 198 currentTime := time.Now() 199 200 testStreams := []logproto.Stream{ 201 {Labels: "{app=\"test\",job=\"varlogs\"}", Entries: entries(5, currentTime)}, 202 {Labels: "{app=\"test2\",job=\"varlogs\"}", Entries: entries(5, currentTime.Add(6*time.Nanosecond))}, 203 } 204 205 for _, testStream := range testStreams { 206 stream, err := instance.getOrCreateStream(testStream, recordPool.GetRecord()) 207 require.NoError(t, err) 208 chunk := newStream(cfg, limiter, "fake", 0, nil, true, NilMetrics).NewChunk() 209 for _, entry := range testStream.Entries { 210 err = chunk.Append(&entry) 211 require.NoError(t, err) 212 } 213 stream.chunks = append(stream.chunks, chunkDesc{chunk: chunk}) 214 } 215 216 return instance, currentTime, indexShards 217 } 218 219 func Test_LabelQuery(t *testing.T) { 220 instance, currentTime, _ := setupTestStreams(t) 221 start := &[]time.Time{currentTime.Add(11 * time.Nanosecond)}[0] 222 end := &[]time.Time{currentTime.Add(12 * time.Nanosecond)}[0] 223 m, err := labels.NewMatcher(labels.MatchEqual, "app", "test") 224 require.NoError(t, err) 225 226 tests := []struct { 227 name string 228 req *logproto.LabelRequest 229 expectedResponse logproto.LabelResponse 230 matchers []*labels.Matcher 231 }{ 232 { 233 "label names - no matchers", 234 &logproto.LabelRequest{ 235 Start: start, 236 End: end, 237 }, 238 logproto.LabelResponse{ 239 Values: []string{"app", "job"}, 240 }, 241 nil, 242 }, 243 { 244 "label names - with matcher", 245 &logproto.LabelRequest{ 246 Start: start, 247 End: end, 248 }, 249 logproto.LabelResponse{ 250 Values: []string{"app", "job"}, 251 }, 252 []*labels.Matcher{m}, 253 }, 254 { 255 "label values - no matchers", 256 &logproto.LabelRequest{ 257 Name: "app", 258 Values: true, 259 Start: start, 260 End: end, 261 }, 262 logproto.LabelResponse{ 263 Values: []string{"test", "test2"}, 264 }, 265 nil, 266 }, 267 { 268 "label values - with matcher", 269 &logproto.LabelRequest{ 270 Name: "app", 271 Values: true, 272 Start: start, 273 End: end, 274 }, 275 logproto.LabelResponse{ 276 Values: []string{"test"}, 277 }, 278 []*labels.Matcher{m}, 279 }, 280 } 281 282 for _, tc := range tests { 283 t.Run(tc.name, func(t *testing.T) { 284 resp, err := instance.Label(context.Background(), tc.req, tc.matchers...) 285 require.NoError(t, err) 286 287 require.Equal(t, tc.expectedResponse.Values, resp.Values) 288 }) 289 } 290 } 291 292 func Test_SeriesQuery(t *testing.T) { 293 instance, currentTime, indexShards := setupTestStreams(t) 294 295 tests := []struct { 296 name string 297 req *logproto.SeriesRequest 298 expectedResponse []logproto.SeriesIdentifier 299 }{ 300 { 301 "non overlapping request", 302 &logproto.SeriesRequest{ 303 Start: currentTime.Add(11 * time.Nanosecond), 304 End: currentTime.Add(12 * time.Nanosecond), 305 Groups: []string{`{job="varlogs"}`}, 306 }, 307 []logproto.SeriesIdentifier{}, 308 }, 309 { 310 "overlapping request", 311 &logproto.SeriesRequest{ 312 Start: currentTime.Add(1 * time.Nanosecond), 313 End: currentTime.Add(7 * time.Nanosecond), 314 Groups: []string{`{job="varlogs"}`}, 315 }, 316 []logproto.SeriesIdentifier{ 317 {Labels: map[string]string{"app": "test", "job": "varlogs"}}, 318 {Labels: map[string]string{"app": "test2", "job": "varlogs"}}, 319 }, 320 }, 321 { 322 "overlapping request with shard param", 323 &logproto.SeriesRequest{ 324 Start: currentTime.Add(1 * time.Nanosecond), 325 End: currentTime.Add(7 * time.Nanosecond), 326 Groups: []string{`{job="varlogs"}`}, 327 Shards: []string{astmapper.ShardAnnotation{ 328 Shard: 1, 329 Of: indexShards, 330 }.String()}, 331 }, 332 []logproto.SeriesIdentifier{ 333 // Separated by shard number 334 {Labels: map[string]string{"app": "test2", "job": "varlogs"}}, 335 }, 336 }, 337 { 338 "request end time overlaps stream start time", 339 &logproto.SeriesRequest{ 340 Start: currentTime.Add(1 * time.Nanosecond), 341 End: currentTime.Add(6 * time.Nanosecond), 342 Groups: []string{`{job="varlogs"}`}, 343 }, 344 []logproto.SeriesIdentifier{ 345 {Labels: map[string]string{"app": "test", "job": "varlogs"}}, 346 }, 347 }, 348 { 349 "request start time overlaps stream end time", 350 &logproto.SeriesRequest{ 351 Start: currentTime.Add(10 * time.Nanosecond), 352 End: currentTime.Add(11 * time.Nanosecond), 353 Groups: []string{`{job="varlogs"}`}, 354 }, 355 []logproto.SeriesIdentifier{ 356 {Labels: map[string]string{"app": "test2", "job": "varlogs"}}, 357 }, 358 }, 359 } 360 361 for _, tc := range tests { 362 t.Run(tc.name, func(t *testing.T) { 363 resp, err := instance.Series(context.Background(), tc.req) 364 require.NoError(t, err) 365 366 sort.Slice(resp.Series, func(i, j int) bool { 367 return resp.Series[i].String() < resp.Series[j].String() 368 }) 369 sort.Slice(tc.expectedResponse, func(i, j int) bool { 370 return tc.expectedResponse[i].String() < tc.expectedResponse[j].String() 371 }) 372 require.Equal(t, tc.expectedResponse, resp.Series) 373 }) 374 } 375 } 376 377 func entries(n int, t time.Time) []logproto.Entry { 378 result := make([]logproto.Entry, 0, n) 379 for i := 0; i < n; i++ { 380 result = append(result, logproto.Entry{Timestamp: t, Line: fmt.Sprintf("hello %d", i)}) 381 t = t.Add(time.Nanosecond) 382 } 383 return result 384 } 385 386 var labelNames = []string{"app", "instance", "namespace", "user", "cluster"} 387 388 func makeRandomLabels() labels.Labels { 389 ls := labels.NewBuilder(nil) 390 for _, ln := range labelNames { 391 ls.Set(ln, fmt.Sprintf("%d", rand.Int31())) 392 } 393 return ls.Labels() 394 } 395 396 func Benchmark_PushInstance(b *testing.B) { 397 limits, err := validation.NewOverrides(defaultLimitsTestConfig(), nil) 398 require.NoError(b, err) 399 limiter := NewLimiter(limits, NilMetrics, &ringCountMock{count: 1}, 1) 400 401 i, _ := newInstance(&Config{IndexShards: 1}, defaultPeriodConfigs, "test", limiter, loki_runtime.DefaultTenantConfigs(), noopWAL{}, NilMetrics, &OnceSwitch{}, nil) 402 ctx := context.Background() 403 404 for n := 0; n < b.N; n++ { 405 _ = i.Push(ctx, &logproto.PushRequest{ 406 Streams: []logproto.Stream{ 407 { 408 Labels: `{cpu="10",endpoint="https",instance="10.253.57.87:9100",job="node-exporter",mode="idle",namespace="observability",pod="node-exporter-l454v",service="node-exporter"}`, 409 Entries: []logproto.Entry{ 410 {Timestamp: time.Now(), Line: "1"}, 411 {Timestamp: time.Now(), Line: "2"}, 412 {Timestamp: time.Now(), Line: "3"}, 413 }, 414 }, 415 { 416 Labels: `{cpu="35",endpoint="https",instance="10.253.57.87:9100",job="node-exporter",mode="idle",namespace="observability",pod="node-exporter-l454v",service="node-exporter"}`, 417 Entries: []logproto.Entry{ 418 {Timestamp: time.Now(), Line: "1"}, 419 {Timestamp: time.Now(), Line: "2"}, 420 {Timestamp: time.Now(), Line: "3"}, 421 }, 422 }, 423 { 424 Labels: `{cpu="89",endpoint="https",instance="10.253.57.87:9100",job="node-exporter",mode="idle",namespace="observability",pod="node-exporter-l454v",service="node-exporter"}`, 425 Entries: []logproto.Entry{ 426 {Timestamp: time.Now(), Line: "1"}, 427 {Timestamp: time.Now(), Line: "2"}, 428 {Timestamp: time.Now(), Line: "3"}, 429 }, 430 }, 431 }, 432 }) 433 } 434 } 435 436 func Benchmark_instance_addNewTailer(b *testing.B) { 437 l := defaultLimitsTestConfig() 438 l.MaxLocalStreamsPerUser = 100000 439 limits, err := validation.NewOverrides(l, nil) 440 require.NoError(b, err) 441 limiter := NewLimiter(limits, NilMetrics, &ringCountMock{count: 1}, 1) 442 443 ctx := context.Background() 444 445 inst, _ := newInstance(&Config{}, defaultPeriodConfigs, "test", limiter, loki_runtime.DefaultTenantConfigs(), noopWAL{}, NilMetrics, &OnceSwitch{}, nil) 446 t, err := newTailer("foo", `{namespace="foo",pod="bar",instance=~"10.*"}`, nil, 10) 447 require.NoError(b, err) 448 for i := 0; i < 10000; i++ { 449 require.NoError(b, inst.Push(ctx, &logproto.PushRequest{ 450 Streams: []logproto.Stream{}, 451 })) 452 } 453 b.Run("addNewTailer", func(b *testing.B) { 454 for n := 0; n < b.N; n++ { 455 _ = inst.addNewTailer(context.Background(), t) 456 } 457 }) 458 lbs := makeRandomLabels() 459 b.Run("addTailersToNewStream", func(b *testing.B) { 460 for n := 0; n < b.N; n++ { 461 inst.addTailersToNewStream(newStream(nil, limiter, "fake", 0, lbs, true, NilMetrics)) 462 } 463 }) 464 } 465 466 func Benchmark_OnceSwitch(b *testing.B) { 467 threads := runtime.GOMAXPROCS(0) 468 469 // limit threads 470 if threads > 4 { 471 threads = 4 472 } 473 474 for n := 0; n < b.N; n++ { 475 x := &OnceSwitch{} 476 var wg sync.WaitGroup 477 for i := 0; i < threads; i++ { 478 wg.Add(1) 479 go func() { 480 for i := 0; i < 1000; i++ { 481 x.Trigger() 482 } 483 wg.Done() 484 }() 485 } 486 wg.Wait() 487 } 488 } 489 490 func Test_Iterator(t *testing.T) { 491 instance := defaultInstance(t) 492 493 it, err := instance.Query(context.TODO(), 494 logql.SelectLogParams{ 495 QueryRequest: &logproto.QueryRequest{ 496 Selector: `{job="3"} | logfmt`, 497 Limit: uint32(2), 498 Start: time.Unix(0, 0), 499 End: time.Unix(0, 100000000), 500 Direction: logproto.BACKWARD, 501 }, 502 }, 503 ) 504 require.NoError(t, err) 505 506 // assert the order is preserved. 507 var res *logproto.QueryResponse 508 require.NoError(t, 509 sendBatches(context.TODO(), it, 510 fakeQueryServer( 511 func(qr *logproto.QueryResponse) error { 512 res = qr 513 return nil 514 }, 515 ), 516 int32(2)), 517 ) 518 require.Equal(t, 2, len(res.Streams)) 519 // each entry translated into a unique stream 520 require.Equal(t, 1, len(res.Streams[0].Entries)) 521 require.Equal(t, 1, len(res.Streams[1].Entries)) 522 // sort by entries we expect 9 and 8 this is because readbatch uses a map to build the response. 523 // map have no order guarantee 524 sort.Slice(res.Streams, func(i, j int) bool { 525 return res.Streams[i].Entries[0].Timestamp.UnixNano() > res.Streams[j].Entries[0].Timestamp.UnixNano() 526 }) 527 require.Equal(t, int64(9), res.Streams[0].Entries[0].Timestamp.UnixNano()) 528 require.Equal(t, int64(8), res.Streams[1].Entries[0].Timestamp.UnixNano()) 529 } 530 531 type testFilter struct{} 532 533 func (t *testFilter) ForRequest(ctx context.Context) chunk.Filterer { 534 return t 535 } 536 537 func (t *testFilter) ShouldFilter(lbs labels.Labels) bool { 538 return lbs.Get("log_stream") == "dispatcher" 539 } 540 541 func Test_ChunkFilter(t *testing.T) { 542 instance := defaultInstance(t) 543 instance.chunkFilter = &testFilter{} 544 545 it, err := instance.Query(context.TODO(), 546 logql.SelectLogParams{ 547 QueryRequest: &logproto.QueryRequest{ 548 Selector: `{job="3"}`, 549 Limit: uint32(2), 550 Start: time.Unix(0, 0), 551 End: time.Unix(0, 100000000), 552 Direction: logproto.BACKWARD, 553 }, 554 }, 555 ) 556 require.NoError(t, err) 557 defer it.Close() 558 559 for it.Next() { 560 require.NoError(t, it.Error()) 561 lbs, err := syntax.ParseLabels(it.Labels()) 562 require.NoError(t, err) 563 require.NotEqual(t, "dispatcher", lbs.Get("log_stream")) 564 } 565 } 566 567 func Test_QueryWithDelete(t *testing.T) { 568 instance := defaultInstance(t) 569 570 it, err := instance.Query(context.TODO(), 571 logql.SelectLogParams{ 572 QueryRequest: &logproto.QueryRequest{ 573 Selector: `{job="3"}`, 574 Limit: uint32(2), 575 Start: time.Unix(0, 0), 576 End: time.Unix(0, 100000000), 577 Direction: logproto.BACKWARD, 578 Deletes: []*logproto.Delete{ 579 { 580 Selector: `{log_stream="worker"}`, 581 Start: 0, 582 End: 10, 583 }, 584 { 585 Selector: `{log_stream="dispatcher"}`, 586 Start: 0, 587 End: 5, 588 }, 589 { 590 Selector: `{log_stream="dispatcher"} |= "9"`, 591 Start: 0, 592 End: 10, 593 }, 594 }, 595 }, 596 }, 597 ) 598 require.NoError(t, err) 599 defer it.Close() 600 601 var logs []string 602 for it.Next() { 603 logs = append(logs, it.Entry().Line) 604 } 605 606 require.Equal(t, logs, []string{`msg="dispatcher_7"`}) 607 } 608 609 func Test_QuerySampleWithDelete(t *testing.T) { 610 instance := defaultInstance(t) 611 612 it, err := instance.QuerySample(context.TODO(), 613 logql.SelectSampleParams{ 614 SampleQueryRequest: &logproto.SampleQueryRequest{ 615 Selector: `count_over_time({job="3"}[5m])`, 616 Start: time.Unix(0, 0), 617 End: time.Unix(0, 100000000), 618 Deletes: []*logproto.Delete{ 619 { 620 Selector: `{log_stream="worker"}`, 621 Start: 0, 622 End: 10, 623 }, 624 { 625 Selector: `{log_stream="dispatcher"}`, 626 Start: 0, 627 End: 5, 628 }, 629 { 630 Selector: `{log_stream="dispatcher"} |= "9"`, 631 Start: 0, 632 End: 10, 633 }, 634 }, 635 }, 636 }, 637 ) 638 require.NoError(t, err) 639 defer it.Close() 640 641 var samples []float64 642 for it.Next() { 643 samples = append(samples, it.Sample().Value) 644 } 645 646 require.Equal(t, samples, []float64{1.}) 647 } 648 649 func defaultInstance(t *testing.T) *instance { 650 ingesterConfig := defaultIngesterTestConfig(t) 651 defaultLimits := defaultLimitsTestConfig() 652 overrides, err := validation.NewOverrides(defaultLimits, nil) 653 require.NoError(t, err) 654 instance, err := newInstance( 655 &ingesterConfig, 656 defaultPeriodConfigs, 657 "fake", 658 NewLimiter(overrides, NilMetrics, &ringCountMock{count: 1}, 1), 659 loki_runtime.DefaultTenantConfigs(), 660 noopWAL{}, 661 NilMetrics, 662 nil, 663 nil, 664 ) 665 require.Nil(t, err) 666 insertData(t, instance) 667 668 return instance 669 } 670 671 func insertData(t *testing.T, instance *instance) { 672 for i := 0; i < 10; i++ { 673 // nolint 674 stream := "dispatcher" 675 if i%2 == 0 { 676 stream = "worker" 677 } 678 require.NoError(t, 679 instance.Push(context.TODO(), &logproto.PushRequest{ 680 Streams: []logproto.Stream{ 681 { 682 Labels: fmt.Sprintf(`{host="agent", log_stream="%s",job="3"}`, stream), 683 Entries: []logproto.Entry{ 684 {Timestamp: time.Unix(0, int64(i)), Line: fmt.Sprintf(`msg="%s_%d"`, stream, i)}, 685 }, 686 }, 687 }, 688 }), 689 ) 690 } 691 } 692 693 type fakeQueryServer func(*logproto.QueryResponse) error 694 695 func (f fakeQueryServer) Send(res *logproto.QueryResponse) error { 696 return f(res) 697 } 698 func (f fakeQueryServer) Context() context.Context { return context.TODO() }