github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/querier/queryrange/split_by_interval_test.go (about) 1 package queryrange 2 3 import ( 4 "context" 5 "fmt" 6 "runtime" 7 "strconv" 8 "sync" 9 "testing" 10 "time" 11 12 "github.com/stretchr/testify/require" 13 "github.com/weaveworks/common/user" 14 15 "github.com/grafana/loki/pkg/logqlmodel/stats" 16 "github.com/grafana/loki/pkg/querier/queryrange/queryrangebase" 17 18 "github.com/grafana/loki/pkg/loghttp" 19 "github.com/grafana/loki/pkg/logproto" 20 ) 21 22 var nilMetrics = NewSplitByMetrics(nil) 23 24 func Test_splitQuery(t *testing.T) { 25 buildLokiRequest := func(start, end time.Time) queryrangebase.Request { 26 return &LokiRequest{ 27 Query: "foo", 28 Limit: 1, 29 Step: 2, 30 StartTs: start, 31 EndTs: end, 32 Direction: logproto.BACKWARD, 33 Path: "/path", 34 } 35 } 36 37 buildLokiRequestWithInterval := func(start, end time.Time) queryrangebase.Request { 38 return &LokiRequest{ 39 Query: "foo", 40 Limit: 1, 41 Interval: 2, 42 StartTs: start, 43 EndTs: end, 44 Direction: logproto.BACKWARD, 45 Path: "/path", 46 } 47 } 48 49 buildLokiSeriesRequest := func(start, end time.Time) queryrangebase.Request { 50 return &LokiSeriesRequest{ 51 Match: []string{"match1"}, 52 StartTs: start, 53 EndTs: end, 54 Path: "/series", 55 Shards: []string{"shard1"}, 56 } 57 } 58 59 buildLokiLabelNamesRequest := func(start, end time.Time) queryrangebase.Request { 60 return &LokiLabelNamesRequest{ 61 StartTs: start, 62 EndTs: end, 63 Path: "/labels", 64 } 65 } 66 67 type interval struct { 68 start, end time.Time 69 } 70 for requestType, tc := range map[string]struct { 71 requestBuilderFunc func(start, end time.Time) queryrangebase.Request 72 endTimeInclusive bool 73 }{ 74 "LokiRequest": { 75 buildLokiRequest, 76 false, 77 }, 78 "LokiRequestWithInterval": { 79 buildLokiRequestWithInterval, 80 false, 81 }, 82 "LokiSeriesRequest": { 83 buildLokiSeriesRequest, 84 true, 85 }, 86 "LokiLabelNamesRequest": { 87 buildLokiLabelNamesRequest, 88 true, 89 }, 90 } { 91 expectedSplitGap := time.Duration(0) 92 if tc.endTimeInclusive { 93 expectedSplitGap = time.Millisecond 94 } 95 for name, intervals := range map[string]struct { 96 inp interval 97 expected []interval 98 }{ 99 "no_change": { 100 inp: interval{ 101 start: time.Unix(0, 0), 102 end: time.Unix(0, (1 * time.Hour).Nanoseconds()), 103 }, 104 expected: []interval{ 105 { 106 start: time.Unix(0, 0), 107 end: time.Unix(0, (1 * time.Hour).Nanoseconds()), 108 }, 109 }, 110 }, 111 "align_start": { 112 inp: interval{ 113 start: time.Unix(0, (5 * time.Minute).Nanoseconds()), 114 end: time.Unix(0, (2 * time.Hour).Nanoseconds()), 115 }, 116 expected: []interval{ 117 { 118 start: time.Unix(0, (5 * time.Minute).Nanoseconds()), 119 end: time.Unix(0, (1 * time.Hour).Nanoseconds()).Add(-expectedSplitGap), 120 }, 121 { 122 start: time.Unix(0, (1 * time.Hour).Nanoseconds()), 123 end: time.Unix(0, (2 * time.Hour).Nanoseconds()), 124 }, 125 }, 126 }, 127 "align_end": { 128 inp: interval{ 129 start: time.Unix(0, 0), 130 end: time.Unix(0, (115 * time.Minute).Nanoseconds()), 131 }, 132 expected: []interval{ 133 { 134 start: time.Unix(0, 0), 135 end: time.Unix(0, (1 * time.Hour).Nanoseconds()).Add(-expectedSplitGap), 136 }, 137 { 138 start: time.Unix(0, (1 * time.Hour).Nanoseconds()), 139 end: time.Unix(0, (115 * time.Minute).Nanoseconds()), 140 }, 141 }, 142 }, 143 "align_both": { 144 inp: interval{ 145 start: time.Unix(0, (5 * time.Minute).Nanoseconds()), 146 end: time.Unix(0, (175 * time.Minute).Nanoseconds()), 147 }, 148 expected: []interval{ 149 { 150 start: time.Unix(0, (5 * time.Minute).Nanoseconds()), 151 end: time.Unix(0, (1 * time.Hour).Nanoseconds()).Add(-expectedSplitGap), 152 }, 153 { 154 start: time.Unix(0, (1 * time.Hour).Nanoseconds()), 155 end: time.Unix(0, (2 * time.Hour).Nanoseconds()).Add(-expectedSplitGap), 156 }, 157 { 158 start: time.Unix(0, (2 * time.Hour).Nanoseconds()), 159 end: time.Unix(0, (175 * time.Minute).Nanoseconds()), 160 }, 161 }, 162 }, 163 "no_align": { 164 inp: interval{ 165 start: time.Unix(0, (5 * time.Minute).Nanoseconds()), 166 end: time.Unix(0, (55 * time.Minute).Nanoseconds()), 167 }, 168 expected: []interval{ 169 { 170 start: time.Unix(0, (5 * time.Minute).Nanoseconds()), 171 end: time.Unix(0, (55 * time.Minute).Nanoseconds()), 172 }, 173 }, 174 }, 175 } { 176 t.Run(fmt.Sprintf("%s - %s", name, requestType), func(t *testing.T) { 177 inp := tc.requestBuilderFunc(intervals.inp.start, intervals.inp.end) 178 var want []queryrangebase.Request 179 for _, interval := range intervals.expected { 180 want = append(want, tc.requestBuilderFunc(interval.start, interval.end)) 181 } 182 splits, err := splitByTime(inp, time.Hour) 183 require.NoError(t, err) 184 require.Equal(t, want, splits) 185 }) 186 } 187 } 188 } 189 190 func Test_splitMetricQuery(t *testing.T) { 191 const seconds = 1e3 // 1e3 milliseconds per second. 192 193 for i, tc := range []struct { 194 input queryrangebase.Request 195 expected []queryrangebase.Request 196 interval time.Duration 197 }{ 198 // the step is lower than the interval therefore we should split only once. 199 { 200 input: &LokiRequest{ 201 StartTs: time.Unix(0, 0), 202 EndTs: time.Unix(0, 60*time.Minute.Nanoseconds()), 203 Step: 15 * seconds, 204 Query: `rate({app="foo"}[1m])`, 205 }, 206 expected: []queryrangebase.Request{ 207 &LokiRequest{ 208 StartTs: time.Unix(0, 0), 209 EndTs: time.Unix(0, 60*time.Minute.Nanoseconds()), 210 Step: 15 * seconds, 211 Query: `rate({app="foo"}[1m])`, 212 }, 213 }, 214 interval: 24 * time.Hour, 215 }, 216 { 217 input: &LokiRequest{ 218 StartTs: time.Unix(0, 0), 219 EndTs: time.Unix(60*60, 0), 220 Step: 15 * seconds, 221 Query: `rate({app="foo"}[1m])`, 222 }, 223 expected: []queryrangebase.Request{ 224 &LokiRequest{ 225 StartTs: time.Unix(0, 0), 226 EndTs: time.Unix(60*60, 0), 227 Step: 15 * seconds, 228 Query: `rate({app="foo"}[1m])`, 229 }, 230 }, 231 interval: 3 * time.Hour, 232 }, 233 { 234 input: &LokiRequest{ 235 StartTs: time.Unix(0, 0), 236 EndTs: time.Unix(24*3600, 0), 237 Step: 15 * seconds, 238 Query: `rate({app="foo"}[1m])`, 239 }, 240 expected: []queryrangebase.Request{ 241 &LokiRequest{ 242 StartTs: time.Unix(0, 0), 243 EndTs: time.Unix(24*3600, 0), 244 Step: 15 * seconds, 245 Query: `rate({app="foo"}[1m])`, 246 }, 247 }, 248 interval: 24 * time.Hour, 249 }, 250 { 251 input: &LokiRequest{ 252 StartTs: time.Unix(0, 0), 253 EndTs: time.Unix(3*3600, 0), 254 Step: 15 * seconds, 255 Query: `rate({app="foo"}[1m])`, 256 }, 257 expected: []queryrangebase.Request{ 258 &LokiRequest{ 259 StartTs: time.Unix(0, 0), 260 EndTs: time.Unix(3*3600, 0), 261 Step: 15 * seconds, 262 Query: `rate({app="foo"}[1m])`, 263 }, 264 }, 265 interval: 3 * time.Hour, 266 }, 267 { 268 input: &LokiRequest{ 269 StartTs: time.Unix(0, 0), 270 EndTs: time.Unix(2*24*3600, 0), 271 Step: 15 * seconds, 272 Query: `rate({app="foo"}[1m])`, 273 }, 274 expected: []queryrangebase.Request{ 275 &LokiRequest{ 276 StartTs: time.Unix(0, 0), 277 EndTs: time.Unix((24*3600)-15, 0), 278 Step: 15 * seconds, 279 Query: `rate({app="foo"}[1m])`, 280 }, 281 &LokiRequest{ 282 StartTs: time.Unix((24 * 3600), 0), 283 EndTs: time.Unix((2 * 24 * 3600), 0), 284 Step: 15 * seconds, 285 Query: `rate({app="foo"}[1m])`, 286 }, 287 }, 288 interval: 24 * time.Hour, 289 }, 290 { 291 input: &LokiRequest{ 292 StartTs: time.Unix(0, 0), 293 EndTs: time.Unix(2*3*3600, 0), 294 Step: 15 * seconds, 295 Query: `rate({app="foo"}[1m])`, 296 }, 297 expected: []queryrangebase.Request{ 298 &LokiRequest{ 299 StartTs: time.Unix(0, 0), 300 EndTs: time.Unix((3*3600)-15, 0), 301 Step: 15 * seconds, 302 Query: `rate({app="foo"}[1m])`, 303 }, 304 &LokiRequest{ 305 StartTs: time.Unix((3 * 3600), 0), 306 EndTs: time.Unix((2 * 3 * 3600), 0), 307 Step: 15 * seconds, 308 Query: `rate({app="foo"}[1m])`, 309 }, 310 }, 311 interval: 3 * time.Hour, 312 }, 313 { 314 input: &LokiRequest{ 315 StartTs: time.Unix(3*3600, 0), 316 EndTs: time.Unix(3*24*3600, 0), 317 Step: 15 * seconds, 318 Query: `rate({app="foo"}[1m])`, 319 }, 320 expected: []queryrangebase.Request{ 321 &LokiRequest{ 322 StartTs: time.Unix(3*3600, 0), 323 EndTs: time.Unix((24*3600)-15, 0), 324 Step: 15 * seconds, 325 Query: `rate({app="foo"}[1m])`, 326 }, 327 &LokiRequest{ 328 StartTs: time.Unix(24*3600, 0), 329 EndTs: time.Unix((2*24*3600)-15, 0), 330 Step: 15 * seconds, 331 Query: `rate({app="foo"}[1m])`, 332 }, 333 &LokiRequest{ 334 StartTs: time.Unix(2*24*3600, 0), 335 EndTs: time.Unix(3*24*3600, 0), 336 Step: 15 * seconds, 337 Query: `rate({app="foo"}[1m])`, 338 }, 339 }, 340 interval: 24 * time.Hour, 341 }, 342 { 343 input: &LokiRequest{ 344 StartTs: time.Unix(2*3600, 0), 345 EndTs: time.Unix(3*3*3600, 0), 346 Step: 15 * seconds, 347 Query: `rate({app="foo"}[1m])`, 348 }, 349 expected: []queryrangebase.Request{ 350 &LokiRequest{ 351 StartTs: time.Unix(2*3600, 0), 352 EndTs: time.Unix((3*3600)-15, 0), 353 Step: 15 * seconds, 354 Query: `rate({app="foo"}[1m])`, 355 }, 356 &LokiRequest{ 357 StartTs: time.Unix(3*3600, 0), 358 EndTs: time.Unix((2*3*3600)-15, 0), 359 Step: 15 * seconds, 360 Query: `rate({app="foo"}[1m])`, 361 }, 362 &LokiRequest{ 363 StartTs: time.Unix(2*3*3600, 0), 364 EndTs: time.Unix(3*3*3600, 0), 365 Step: 15 * seconds, 366 Query: `rate({app="foo"}[1m])`, 367 }, 368 }, 369 interval: 3 * time.Hour, 370 }, 371 372 // step not a multiple of interval 373 // start time already step aligned 374 { 375 input: &LokiRequest{ 376 StartTs: time.Unix(2*3600-9, 0), // 2h mod 17s = 9s 377 EndTs: time.Unix(3*3*3600, 0), 378 Step: 17 * seconds, 379 Query: `rate({app="foo"}[1m])`, 380 }, 381 expected: []queryrangebase.Request{ 382 &LokiRequest{ 383 StartTs: time.Unix(2*3600-9, 0), 384 EndTs: time.Unix((3*3600)-5, 0), // 3h mod 17s = 5s 385 Step: 17 * seconds, 386 Query: `rate({app="foo"}[1m])`, 387 }, 388 &LokiRequest{ 389 StartTs: time.Unix((3*3600)+12, 0), 390 EndTs: time.Unix((2*3*3600)-10, 0), // 6h mod 17s = 10s 391 Step: 17 * seconds, 392 Query: `rate({app="foo"}[1m])`, 393 }, 394 &LokiRequest{ 395 StartTs: time.Unix(2*3*3600+7, 0), 396 EndTs: time.Unix(3*3*3600+2, 0), // 9h mod 17s = 2s 397 Step: 17 * seconds, 398 Query: `rate({app="foo"}[1m])`, 399 }, 400 }, 401 interval: 3 * time.Hour, 402 }, 403 // end time already step aligned 404 { 405 input: &LokiRequest{ 406 StartTs: time.Unix(2*3600, 0), 407 EndTs: time.Unix(3*3*3600+2, 0), // 9h mod 17s = 2s 408 Step: 17 * seconds, 409 Query: `rate({app="foo"}[1m])`, 410 }, 411 expected: []queryrangebase.Request{ 412 &LokiRequest{ 413 StartTs: time.Unix(2*3600-9, 0), // 2h mod 17s = 9s 414 EndTs: time.Unix((3*3600)-5, 0), // 3h mod 17s = 5s 415 Step: 17 * seconds, 416 Query: `rate({app="foo"}[1m])`, 417 }, 418 &LokiRequest{ 419 StartTs: time.Unix((3*3600)+12, 0), 420 EndTs: time.Unix((2*3*3600)-10, 0), // 6h mod 17s = 10s 421 Step: 17 * seconds, 422 Query: `rate({app="foo"}[1m])`, 423 }, 424 &LokiRequest{ 425 StartTs: time.Unix(2*3*3600+7, 0), 426 EndTs: time.Unix(3*3*3600+2, 0), 427 Step: 17 * seconds, 428 Query: `rate({app="foo"}[1m])`, 429 }, 430 }, 431 interval: 3 * time.Hour, 432 }, 433 // start & end time not aligned with step 434 { 435 input: &LokiRequest{ 436 StartTs: time.Unix(2*3600, 0), 437 EndTs: time.Unix(3*3*3600, 0), 438 Step: 17 * seconds, 439 Query: `rate({app="foo"}[1m])`, 440 }, 441 expected: []queryrangebase.Request{ 442 &LokiRequest{ 443 StartTs: time.Unix(2*3600-9, 0), // 2h mod 17s = 9s 444 EndTs: time.Unix((3*3600)-5, 0), // 3h mod 17s = 5s 445 Step: 17 * seconds, 446 Query: `rate({app="foo"}[1m])`, 447 }, 448 &LokiRequest{ 449 StartTs: time.Unix((3*3600)+12, 0), 450 EndTs: time.Unix((2*3*3600)-10, 0), // 6h mod 17s = 10s 451 Step: 17 * seconds, 452 Query: `rate({app="foo"}[1m])`, 453 }, 454 &LokiRequest{ 455 StartTs: time.Unix(2*3*3600+7, 0), 456 EndTs: time.Unix(3*3*3600+2, 0), // 9h mod 17s = 2s 457 Step: 17 * seconds, 458 Query: `rate({app="foo"}[1m])`, 459 }, 460 }, 461 interval: 3 * time.Hour, 462 }, 463 464 // step larger than split interval 465 { 466 input: &LokiRequest{ 467 StartTs: time.Unix(0, 0), 468 EndTs: time.Unix(25*3600, 0), 469 Step: 6 * 3600 * seconds, 470 Query: `rate({app="foo"}[1m])`, 471 }, 472 expected: []queryrangebase.Request{ 473 &LokiRequest{ 474 StartTs: time.Unix(0, 0), 475 EndTs: time.Unix(6*3600, 0), 476 Step: 6 * 3600 * seconds, 477 Query: `rate({app="foo"}[1m])`, 478 }, 479 &LokiRequest{ 480 StartTs: time.Unix(6*3600, 0), 481 EndTs: time.Unix(12*3600, 0), 482 Step: 6 * 3600 * seconds, 483 Query: `rate({app="foo"}[1m])`, 484 }, 485 &LokiRequest{ 486 StartTs: time.Unix(12*3600, 0), 487 EndTs: time.Unix(18*3600, 0), 488 Step: 6 * 3600 * seconds, 489 Query: `rate({app="foo"}[1m])`, 490 }, 491 &LokiRequest{ 492 StartTs: time.Unix(18*3600, 0), 493 EndTs: time.Unix(24*3600, 0), 494 Step: 6 * 3600 * seconds, 495 Query: `rate({app="foo"}[1m])`, 496 }, 497 &LokiRequest{ 498 StartTs: time.Unix(24*3600, 0), 499 EndTs: time.Unix(30*3600, 0), 500 Step: 6 * 3600 * seconds, 501 Query: `rate({app="foo"}[1m])`, 502 }, 503 }, 504 interval: 15 * time.Minute, 505 }, 506 { 507 input: &LokiRequest{ 508 StartTs: time.Unix(1*3600, 0), 509 EndTs: time.Unix(3*3600, 0), 510 Step: 6 * 3600 * seconds, 511 Query: `rate({app="foo"}[1m])`, 512 }, 513 expected: []queryrangebase.Request{ 514 &LokiRequest{ 515 StartTs: time.Unix(0, 0), 516 EndTs: time.Unix(6*3600, 0), 517 Step: 6 * 3600 * seconds, 518 Query: `rate({app="foo"}[1m])`, 519 }, 520 }, 521 interval: 15 * time.Minute, 522 }, 523 // reduce split by to 6h instead of 1h 524 { 525 input: &LokiRequest{ 526 StartTs: time.Unix(2*3600, 0), 527 EndTs: time.Unix(3*3*3600, 0), 528 Step: 15 * seconds, 529 Query: `rate({app="foo"}[6h])`, 530 }, 531 expected: []queryrangebase.Request{ 532 &LokiRequest{ 533 StartTs: time.Unix(2*3600, 0), 534 EndTs: time.Unix((6*3600)-15, 0), 535 Step: 15 * seconds, 536 Query: `rate({app="foo"}[6h])`, 537 }, 538 &LokiRequest{ 539 StartTs: time.Unix(6*3600, 0), 540 EndTs: time.Unix(3*3*3600, 0), 541 Step: 15 * seconds, 542 Query: `rate({app="foo"}[6h])`, 543 }, 544 }, 545 interval: 1 * time.Hour, 546 }, 547 // range vector too large we don't want to split it 548 { 549 input: &LokiRequest{ 550 StartTs: time.Unix(2*3600, 0), 551 EndTs: time.Unix(3*3*3600, 0), 552 Step: 15 * seconds, 553 Query: `rate({app="foo"}[7d])`, 554 }, 555 expected: []queryrangebase.Request{ 556 &LokiRequest{ 557 StartTs: time.Unix(2*3600, 0), 558 EndTs: time.Unix(3*3*3600, 0), 559 Step: 15 * seconds, 560 Query: `rate({app="foo"}[7d])`, 561 }, 562 }, 563 interval: 15 * time.Minute, 564 }, 565 } { 566 t.Run(strconv.Itoa(i), func(t *testing.T) { 567 splits, err := splitMetricByTime(tc.input, tc.interval) 568 require.NoError(t, err) 569 for i, s := range splits { 570 s := s.(*LokiRequest) 571 t.Logf(" want: %d start:%s end:%s \n", i, s.StartTs, s.EndTs) 572 } 573 require.Equal(t, tc.expected, splits) 574 }) 575 } 576 } 577 578 func Test_splitByInterval_Do(t *testing.T) { 579 ctx := user.InjectOrgID(context.Background(), "1") 580 next := queryrangebase.HandlerFunc(func(_ context.Context, r queryrangebase.Request) (queryrangebase.Response, error) { 581 return &LokiResponse{ 582 Status: loghttp.QueryStatusSuccess, 583 Direction: r.(*LokiRequest).Direction, 584 Limit: r.(*LokiRequest).Limit, 585 Version: uint32(loghttp.VersionV1), 586 Data: LokiData{ 587 ResultType: loghttp.ResultTypeStream, 588 Result: []logproto.Stream{ 589 { 590 Labels: `{foo="bar", level="debug"}`, 591 Entries: []logproto.Entry{ 592 {Timestamp: time.Unix(0, r.(*LokiRequest).StartTs.UnixNano()), Line: fmt.Sprintf("%d", r.(*LokiRequest).StartTs.UnixNano())}, 593 }, 594 }, 595 }, 596 }, 597 }, nil 598 }) 599 600 l := WithSplitByLimits(fakeLimits{maxQueryParallelism: 1}, time.Hour) 601 split := SplitByIntervalMiddleware( 602 l, 603 LokiCodec, 604 splitByTime, 605 nilMetrics, 606 ).Wrap(next) 607 608 tests := []struct { 609 name string 610 req *LokiRequest 611 want *LokiResponse 612 }{ 613 { 614 "backward", 615 &LokiRequest{ 616 StartTs: time.Unix(0, 0), 617 EndTs: time.Unix(0, (4 * time.Hour).Nanoseconds()), 618 Query: "", 619 Limit: 1000, 620 Step: 1, 621 Direction: logproto.BACKWARD, 622 Path: "/api/prom/query_range", 623 }, 624 &LokiResponse{ 625 Status: loghttp.QueryStatusSuccess, 626 Direction: logproto.BACKWARD, 627 Limit: 1000, 628 Version: 1, 629 Statistics: stats.Result{Summary: stats.Summary{Subqueries: 4}}, 630 Data: LokiData{ 631 ResultType: loghttp.ResultTypeStream, 632 Result: []logproto.Stream{ 633 { 634 Labels: `{foo="bar", level="debug"}`, 635 Entries: []logproto.Entry{ 636 {Timestamp: time.Unix(0, 3*time.Hour.Nanoseconds()), Line: fmt.Sprintf("%d", 3*time.Hour.Nanoseconds())}, 637 {Timestamp: time.Unix(0, 2*time.Hour.Nanoseconds()), Line: fmt.Sprintf("%d", 2*time.Hour.Nanoseconds())}, 638 {Timestamp: time.Unix(0, time.Hour.Nanoseconds()), Line: fmt.Sprintf("%d", time.Hour.Nanoseconds())}, 639 {Timestamp: time.Unix(0, 0), Line: fmt.Sprintf("%d", 0)}, 640 }, 641 }, 642 }, 643 }, 644 }, 645 }, 646 { 647 "forward", 648 &LokiRequest{ 649 StartTs: time.Unix(0, 0), 650 EndTs: time.Unix(0, (4 * time.Hour).Nanoseconds()), 651 Query: "", 652 Limit: 1000, 653 Step: 1, 654 Direction: logproto.FORWARD, 655 Path: "/api/prom/query_range", 656 }, 657 &LokiResponse{ 658 Status: loghttp.QueryStatusSuccess, 659 Direction: logproto.FORWARD, 660 Statistics: stats.Result{Summary: stats.Summary{Subqueries: 4}}, 661 Limit: 1000, 662 Version: 1, 663 Data: LokiData{ 664 ResultType: loghttp.ResultTypeStream, 665 Result: []logproto.Stream{ 666 { 667 Labels: `{foo="bar", level="debug"}`, 668 Entries: []logproto.Entry{ 669 {Timestamp: time.Unix(0, 0), Line: fmt.Sprintf("%d", 0)}, 670 {Timestamp: time.Unix(0, time.Hour.Nanoseconds()), Line: fmt.Sprintf("%d", time.Hour.Nanoseconds())}, 671 {Timestamp: time.Unix(0, 2*time.Hour.Nanoseconds()), Line: fmt.Sprintf("%d", 2*time.Hour.Nanoseconds())}, 672 {Timestamp: time.Unix(0, 3*time.Hour.Nanoseconds()), Line: fmt.Sprintf("%d", 3*time.Hour.Nanoseconds())}, 673 }, 674 }, 675 }, 676 }, 677 }, 678 }, 679 { 680 "forward limited", 681 &LokiRequest{ 682 StartTs: time.Unix(0, 0), 683 EndTs: time.Unix(0, (4 * time.Hour).Nanoseconds()), 684 Query: "", 685 Limit: 2, 686 Step: 1, 687 Direction: logproto.FORWARD, 688 Path: "/api/prom/query_range", 689 }, 690 &LokiResponse{ 691 Status: loghttp.QueryStatusSuccess, 692 Direction: logproto.FORWARD, 693 Limit: 2, 694 Version: 1, 695 Statistics: stats.Result{Summary: stats.Summary{Subqueries: 2}}, 696 Data: LokiData{ 697 ResultType: loghttp.ResultTypeStream, 698 Result: []logproto.Stream{ 699 { 700 Labels: `{foo="bar", level="debug"}`, 701 Entries: []logproto.Entry{ 702 {Timestamp: time.Unix(0, 0), Line: fmt.Sprintf("%d", 0)}, 703 {Timestamp: time.Unix(0, time.Hour.Nanoseconds()), Line: fmt.Sprintf("%d", time.Hour.Nanoseconds())}, 704 }, 705 }, 706 }, 707 }, 708 }, 709 }, 710 { 711 "backward limited", 712 &LokiRequest{ 713 StartTs: time.Unix(0, 0), 714 EndTs: time.Unix(0, (4 * time.Hour).Nanoseconds()), 715 Query: "", 716 Limit: 2, 717 Step: 1, 718 Direction: logproto.BACKWARD, 719 Path: "/api/prom/query_range", 720 }, 721 &LokiResponse{ 722 Status: loghttp.QueryStatusSuccess, 723 Direction: logproto.BACKWARD, 724 Limit: 2, 725 Version: 1, 726 Statistics: stats.Result{Summary: stats.Summary{Subqueries: 2}}, 727 Data: LokiData{ 728 ResultType: loghttp.ResultTypeStream, 729 Result: []logproto.Stream{ 730 { 731 Labels: `{foo="bar", level="debug"}`, 732 Entries: []logproto.Entry{ 733 {Timestamp: time.Unix(0, 3*time.Hour.Nanoseconds()), Line: fmt.Sprintf("%d", 3*time.Hour.Nanoseconds())}, 734 {Timestamp: time.Unix(0, 2*time.Hour.Nanoseconds()), Line: fmt.Sprintf("%d", 2*time.Hour.Nanoseconds())}, 735 }, 736 }, 737 }, 738 }, 739 }, 740 }, 741 } 742 743 for _, tt := range tests { 744 t.Run(tt.name, func(t *testing.T) { 745 res, err := split.Do(ctx, tt.req) 746 require.NoError(t, err) 747 require.Equal(t, tt.want, res) 748 }) 749 } 750 } 751 752 func Test_series_splitByInterval_Do(t *testing.T) { 753 ctx := user.InjectOrgID(context.Background(), "1") 754 next := queryrangebase.HandlerFunc(func(_ context.Context, r queryrangebase.Request) (queryrangebase.Response, error) { 755 return &LokiSeriesResponse{ 756 Status: "success", 757 Version: uint32(loghttp.VersionV1), 758 Data: []logproto.SeriesIdentifier{ 759 { 760 Labels: map[string]string{"filename": "/var/hostlog/apport.log", "job": "varlogs"}, 761 }, 762 { 763 Labels: map[string]string{"filename": "/var/hostlog/test.log", "job": "varlogs"}, 764 }, 765 { 766 Labels: map[string]string{"filename": "/var/hostlog/test.log", "job": "varlogs"}, 767 }, 768 }, 769 }, nil 770 }) 771 772 l := WithSplitByLimits(fakeLimits{maxQueryParallelism: 1}, time.Hour) 773 split := SplitByIntervalMiddleware( 774 l, 775 LokiCodec, 776 splitByTime, 777 nilMetrics, 778 ).Wrap(next) 779 780 tests := []struct { 781 name string 782 req *LokiSeriesRequest 783 want *LokiSeriesResponse 784 }{ 785 { 786 "backward", 787 &LokiSeriesRequest{ 788 StartTs: time.Unix(0, 0), 789 EndTs: time.Unix(0, (4 * time.Hour).Nanoseconds()), 790 Match: []string{`{job="varlogs"}`}, 791 Path: "/loki/api/v1/series", 792 }, 793 &LokiSeriesResponse{ 794 Status: "success", 795 Version: 1, 796 Data: []logproto.SeriesIdentifier{ 797 { 798 Labels: map[string]string{"filename": "/var/hostlog/apport.log", "job": "varlogs"}, 799 }, 800 { 801 Labels: map[string]string{"filename": "/var/hostlog/test.log", "job": "varlogs"}, 802 }, 803 }, 804 }, 805 }, 806 } 807 808 for _, tt := range tests { 809 t.Run(tt.name, func(t *testing.T) { 810 res, err := split.Do(ctx, tt.req) 811 require.NoError(t, err) 812 require.Equal(t, tt.want, res) 813 }) 814 } 815 } 816 817 func Test_ExitEarly(t *testing.T) { 818 ctx := user.InjectOrgID(context.Background(), "1") 819 820 var callCt int 821 var mtx sync.Mutex 822 823 next := queryrangebase.HandlerFunc(func(_ context.Context, r queryrangebase.Request) (queryrangebase.Response, error) { 824 time.Sleep(time.Millisecond) // artificial delay to minimize race condition exposure in test 825 826 mtx.Lock() 827 defer mtx.Unlock() 828 callCt++ 829 830 return &LokiResponse{ 831 Status: loghttp.QueryStatusSuccess, 832 Direction: r.(*LokiRequest).Direction, 833 Limit: r.(*LokiRequest).Limit, 834 Version: uint32(loghttp.VersionV1), 835 Data: LokiData{ 836 ResultType: loghttp.ResultTypeStream, 837 Result: []logproto.Stream{ 838 { 839 Labels: `{foo="bar", level="debug"}`, 840 Entries: []logproto.Entry{ 841 { 842 Timestamp: time.Unix(0, r.(*LokiRequest).StartTs.UnixNano()), 843 Line: fmt.Sprintf("%d", r.(*LokiRequest).StartTs.UnixNano()), 844 }, 845 }, 846 }, 847 }, 848 }, 849 }, nil 850 }) 851 852 l := WithSplitByLimits(fakeLimits{maxQueryParallelism: 1}, time.Hour) 853 split := SplitByIntervalMiddleware( 854 l, 855 LokiCodec, 856 splitByTime, 857 nilMetrics, 858 ).Wrap(next) 859 860 req := &LokiRequest{ 861 StartTs: time.Unix(0, 0), 862 EndTs: time.Unix(0, (4 * time.Hour).Nanoseconds()), 863 Query: "", 864 Limit: 2, 865 Step: 1, 866 Direction: logproto.FORWARD, 867 Path: "/api/prom/query_range", 868 } 869 870 expected := &LokiResponse{ 871 Status: loghttp.QueryStatusSuccess, 872 Direction: logproto.FORWARD, 873 Limit: 2, 874 Version: 1, 875 Statistics: stats.Result{ 876 Summary: stats.Summary{ 877 Subqueries: 2, 878 }, 879 }, 880 Data: LokiData{ 881 ResultType: loghttp.ResultTypeStream, 882 Result: []logproto.Stream{ 883 { 884 Labels: `{foo="bar", level="debug"}`, 885 Entries: []logproto.Entry{ 886 { 887 Timestamp: time.Unix(0, 0), 888 Line: fmt.Sprintf("%d", 0), 889 }, 890 { 891 Timestamp: time.Unix(0, time.Hour.Nanoseconds()), 892 Line: fmt.Sprintf("%d", time.Hour.Nanoseconds()), 893 }, 894 }, 895 }, 896 }, 897 }, 898 } 899 900 res, err := split.Do(ctx, req) 901 902 require.Equal(t, int(req.Limit), callCt) 903 require.NoError(t, err) 904 require.Equal(t, expected, res) 905 } 906 907 func Test_DoesntDeadlock(t *testing.T) { 908 n := 10 909 910 next := queryrangebase.HandlerFunc(func(_ context.Context, r queryrangebase.Request) (queryrangebase.Response, error) { 911 return &LokiResponse{ 912 Status: loghttp.QueryStatusSuccess, 913 Direction: r.(*LokiRequest).Direction, 914 Limit: r.(*LokiRequest).Limit, 915 Version: uint32(loghttp.VersionV1), 916 Data: LokiData{ 917 ResultType: loghttp.ResultTypeStream, 918 Result: []logproto.Stream{ 919 { 920 Labels: `{foo="bar", level="debug"}`, 921 Entries: []logproto.Entry{ 922 { 923 Timestamp: time.Unix(0, r.(*LokiRequest).StartTs.UnixNano()), 924 Line: fmt.Sprintf("%d", r.(*LokiRequest).StartTs.UnixNano()), 925 }, 926 }, 927 }, 928 }, 929 }, 930 }, nil 931 }) 932 933 l := WithSplitByLimits(fakeLimits{maxQueryParallelism: n}, time.Hour) 934 split := SplitByIntervalMiddleware( 935 l, 936 LokiCodec, 937 splitByTime, 938 nilMetrics, 939 ).Wrap(next) 940 941 // split into n requests w/ n/2 limit, ensuring unused responses are cleaned up properly 942 req := &LokiRequest{ 943 StartTs: time.Unix(0, 0), 944 EndTs: time.Unix(0, (time.Duration(n) * time.Hour).Nanoseconds()), 945 Query: "", 946 Limit: uint32(n / 2), 947 Step: 1, 948 Direction: logproto.FORWARD, 949 Path: "/api/prom/query_range", 950 } 951 952 ctx := user.InjectOrgID(context.Background(), "1") 953 954 startingGoroutines := runtime.NumGoroutine() 955 956 // goroutines shouldn't blow up across 100 rounds 957 for i := 0; i < 100; i++ { 958 res, err := split.Do(ctx, req) 959 require.NoError(t, err) 960 require.Equal(t, 1, len(res.(*LokiResponse).Data.Result)) 961 require.Equal(t, n/2, len(res.(*LokiResponse).Data.Result[0].Entries)) 962 963 } 964 runtime.GC() 965 endingGoroutines := runtime.NumGoroutine() 966 967 // give runtime a bit of slack when catching up -- this isn't an exact science :( 968 // Allow for 1% increase in goroutines 969 require.LessOrEqual(t, endingGoroutines, startingGoroutines*101/100) 970 }