github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/logcli/query/query_test.go (about) 1 package query 2 3 import ( 4 "bytes" 5 "context" 6 "os" 7 "path/filepath" 8 "reflect" 9 "strings" 10 "testing" 11 "time" 12 13 "github.com/go-kit/log" 14 "github.com/gorilla/websocket" 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17 18 "github.com/grafana/loki/pkg/logcli/output" 19 "github.com/grafana/loki/pkg/loghttp" 20 "github.com/grafana/loki/pkg/logproto" 21 "github.com/grafana/loki/pkg/logql" 22 "github.com/grafana/loki/pkg/loki" 23 "github.com/grafana/loki/pkg/storage" 24 "github.com/grafana/loki/pkg/storage/chunk/client/local" 25 "github.com/grafana/loki/pkg/storage/config" 26 "github.com/grafana/loki/pkg/storage/stores/indexshipper" 27 "github.com/grafana/loki/pkg/storage/stores/shipper" 28 "github.com/grafana/loki/pkg/util/marshal" 29 ) 30 31 func Test_commonLabels(t *testing.T) { 32 type args struct { 33 lss []loghttp.LabelSet 34 } 35 tests := []struct { 36 name string 37 args args 38 want loghttp.LabelSet 39 }{ 40 { 41 "Extract common labels source > target", 42 args{ 43 []loghttp.LabelSet{mustParseLabels(t, `{foo="bar", bar="foo"}`), mustParseLabels(t, `{bar="foo", foo="foo", baz="baz"}`)}, 44 }, 45 mustParseLabels(t, `{bar="foo"}`), 46 }, 47 { 48 "Extract common labels source > target", 49 args{ 50 []loghttp.LabelSet{mustParseLabels(t, `{foo="bar", bar="foo"}`), mustParseLabels(t, `{bar="foo", foo="bar", baz="baz"}`)}, 51 }, 52 mustParseLabels(t, `{foo="bar", bar="foo"}`), 53 }, 54 { 55 "Extract common labels source < target", 56 args{ 57 []loghttp.LabelSet{mustParseLabels(t, `{foo="bar", bar="foo"}`), mustParseLabels(t, `{bar="foo"}`)}, 58 }, 59 mustParseLabels(t, `{bar="foo"}`), 60 }, 61 { 62 "Extract common labels source < target no common", 63 args{ 64 []loghttp.LabelSet{mustParseLabels(t, `{foo="bar", bar="foo"}`), mustParseLabels(t, `{fo="bar"}`)}, 65 }, 66 loghttp.LabelSet{}, 67 }, 68 { 69 "Extract common labels source = target no common", 70 args{ 71 []loghttp.LabelSet{mustParseLabels(t, `{foo="bar"}`), mustParseLabels(t, `{fooo="bar"}`)}, 72 }, 73 loghttp.LabelSet{}, 74 }, 75 } 76 for _, tt := range tests { 77 t.Run(tt.name, func(t *testing.T) { 78 var streams []loghttp.Stream 79 80 for _, lss := range tt.args.lss { 81 streams = append(streams, loghttp.Stream{ 82 Entries: nil, 83 Labels: lss, 84 }) 85 } 86 87 if got := commonLabels(streams); !reflect.DeepEqual(got, tt.want) { 88 t.Errorf("commonLabels() = %v, want %v", got, tt.want) 89 } 90 }) 91 } 92 } 93 94 func Test_subtract(t *testing.T) { 95 type args struct { 96 a loghttp.LabelSet 97 b loghttp.LabelSet 98 } 99 tests := []struct { 100 name string 101 args args 102 want loghttp.LabelSet 103 }{ 104 { 105 "Subtract labels source > target", 106 args{ 107 mustParseLabels(t, `{foo="bar", bar="foo"}`), 108 mustParseLabels(t, `{bar="foo", foo="foo", baz="baz"}`), 109 }, 110 mustParseLabels(t, `{foo="bar"}`), 111 }, 112 { 113 "Subtract labels source < target", 114 args{ 115 mustParseLabels(t, `{foo="bar", bar="foo"}`), 116 mustParseLabels(t, `{bar="foo"}`), 117 }, 118 mustParseLabels(t, `{foo="bar"}`), 119 }, 120 { 121 "Subtract labels source < target no sub", 122 args{ 123 mustParseLabels(t, `{foo="bar", bar="foo"}`), 124 mustParseLabels(t, `{fo="bar"}`), 125 }, 126 mustParseLabels(t, `{bar="foo", foo="bar"}`), 127 }, 128 { 129 "Subtract labels source = target no sub", 130 args{ 131 mustParseLabels(t, `{foo="bar"}`), 132 mustParseLabels(t, `{fiz="buz"}`), 133 }, 134 mustParseLabels(t, `{foo="bar"}`), 135 }, 136 { 137 "Subtract labels source > target no sub", 138 args{ 139 mustParseLabels(t, `{foo="bar"}`), 140 mustParseLabels(t, `{fiz="buz", foo="baz"}`), 141 }, 142 mustParseLabels(t, `{foo="bar"}`), 143 }, 144 { 145 "Subtract labels source > target no sub", 146 args{ 147 mustParseLabels(t, `{a="b", foo="bar", baz="baz", fizz="fizz"}`), 148 mustParseLabels(t, `{foo="bar", baz="baz", buzz="buzz", fizz="fizz"}`), 149 }, 150 mustParseLabels(t, `{a="b"}`), 151 }, 152 } 153 for _, tt := range tests { 154 t.Run(tt.name, func(t *testing.T) { 155 if got := subtract(tt.args.a, tt.args.b); !reflect.DeepEqual(got, tt.want) { 156 t.Errorf("subtract() = %v, want %v", got, tt.want) 157 } 158 }) 159 } 160 } 161 162 func Test_batch(t *testing.T) { 163 tests := []struct { 164 name string 165 streams []logproto.Stream 166 start, end time.Time 167 limit, batch int 168 labelMatcher string 169 forward bool 170 expectedCalls int 171 expected []string 172 }{ 173 { 174 name: "super simple forward", 175 streams: []logproto.Stream{ 176 { 177 Labels: "{test=\"simple\"}", 178 Entries: []logproto.Entry{ 179 {Timestamp: time.Unix(1, 0), Line: "line1"}, 180 {Timestamp: time.Unix(2, 0), Line: "line2"}, 181 {Timestamp: time.Unix(3, 0), Line: "line3"}, // End timestamp is exclusive 182 }, 183 }, 184 }, 185 start: time.Unix(1, 0), 186 end: time.Unix(3, 0), 187 limit: 10, 188 batch: 10, 189 labelMatcher: "{test=\"simple\"}", 190 forward: true, 191 expectedCalls: 2, // Client doesn't know if the server hit a limit or there were no results so we have to query until there is no results, in this case 2 calls 192 expected: []string{ 193 "line1", 194 "line2", 195 }, 196 }, 197 { 198 name: "super simple backward", 199 streams: []logproto.Stream{ 200 { 201 Labels: "{test=\"simple\"}", 202 Entries: []logproto.Entry{ 203 {Timestamp: time.Unix(1, 0), Line: "line1"}, 204 {Timestamp: time.Unix(2, 0), Line: "line2"}, 205 {Timestamp: time.Unix(3, 0), Line: "line3"}, // End timestamp is exclusive 206 }, 207 }, 208 }, 209 start: time.Unix(1, 0), 210 end: time.Unix(3, 0), 211 limit: 10, 212 batch: 10, 213 labelMatcher: "{test=\"simple\"}", 214 forward: false, 215 expectedCalls: 2, 216 expected: []string{ 217 "line2", 218 "line1", 219 }, 220 }, 221 { 222 name: "single stream forward batch", 223 streams: []logproto.Stream{ 224 { 225 Labels: "{test=\"simple\"}", 226 Entries: []logproto.Entry{ 227 {Timestamp: time.Unix(1, 0), Line: "line1"}, 228 {Timestamp: time.Unix(2, 0), Line: "line2"}, 229 {Timestamp: time.Unix(3, 0), Line: "line3"}, 230 {Timestamp: time.Unix(4, 0), Line: "line4"}, 231 {Timestamp: time.Unix(5, 0), Line: "line5"}, 232 {Timestamp: time.Unix(6, 0), Line: "line6"}, 233 {Timestamp: time.Unix(7, 0), Line: "line7"}, 234 {Timestamp: time.Unix(8, 0), Line: "line8"}, 235 {Timestamp: time.Unix(9, 0), Line: "line9"}, 236 {Timestamp: time.Unix(10, 0), Line: "line10"}, 237 }, 238 }, 239 }, 240 start: time.Unix(1, 0), 241 end: time.Unix(11, 0), 242 limit: 9, 243 batch: 2, 244 labelMatcher: "{test=\"simple\"}", 245 forward: true, 246 // Our batchsize is 2 but each query will also return the overlapping last element from the 247 // previous batch, as such we only get one item per call so we make a lot of calls 248 // Call one: line1 line2 249 // Call two: line2 line3 250 // Call three: line3 line4 251 // Call four: line4 line5 252 // Call five: line5 line6 253 // Call six: line6 line7 254 // Call seven: line7 line8 255 // Call eight: line8 line9 256 expectedCalls: 8, 257 expected: []string{ 258 "line1", "line2", "line3", "line4", "line5", "line6", "line7", "line8", "line9", 259 }, 260 }, 261 { 262 name: "single stream backward batch", 263 streams: []logproto.Stream{ 264 { 265 Labels: "{test=\"simple\"}", 266 Entries: []logproto.Entry{ 267 {Timestamp: time.Unix(1, 0), Line: "line1"}, 268 {Timestamp: time.Unix(2, 0), Line: "line2"}, 269 {Timestamp: time.Unix(3, 0), Line: "line3"}, 270 {Timestamp: time.Unix(4, 0), Line: "line4"}, 271 {Timestamp: time.Unix(5, 0), Line: "line5"}, 272 {Timestamp: time.Unix(6, 0), Line: "line6"}, 273 {Timestamp: time.Unix(7, 0), Line: "line7"}, 274 {Timestamp: time.Unix(8, 0), Line: "line8"}, 275 {Timestamp: time.Unix(9, 0), Line: "line9"}, 276 {Timestamp: time.Unix(10, 0), Line: "line10"}, 277 }, 278 }, 279 }, 280 start: time.Unix(1, 0), 281 end: time.Unix(11, 0), 282 limit: 9, 283 batch: 2, 284 labelMatcher: "{test=\"simple\"}", 285 forward: false, 286 expectedCalls: 8, 287 expected: []string{ 288 "line10", "line9", "line8", "line7", "line6", "line5", "line4", "line3", "line2", 289 }, 290 }, 291 { 292 name: "two streams forward batch", 293 streams: []logproto.Stream{ 294 { 295 Labels: "{test=\"one\"}", 296 Entries: []logproto.Entry{ 297 {Timestamp: time.Unix(1, 0), Line: "line1"}, 298 {Timestamp: time.Unix(2, 0), Line: "line2"}, 299 {Timestamp: time.Unix(3, 0), Line: "line3"}, 300 {Timestamp: time.Unix(4, 0), Line: "line4"}, 301 {Timestamp: time.Unix(5, 0), Line: "line5"}, 302 {Timestamp: time.Unix(6, 0), Line: "line6"}, 303 {Timestamp: time.Unix(7, 0), Line: "line7"}, 304 {Timestamp: time.Unix(8, 0), Line: "line8"}, 305 {Timestamp: time.Unix(9, 0), Line: "line9"}, 306 {Timestamp: time.Unix(10, 0), Line: "line10"}, 307 }, 308 }, 309 { 310 Labels: "{test=\"two\"}", 311 Entries: []logproto.Entry{ 312 {Timestamp: time.Unix(1, 1000), Line: "s2line1"}, 313 {Timestamp: time.Unix(2, 1000), Line: "s2line2"}, 314 {Timestamp: time.Unix(3, 1000), Line: "s2line3"}, 315 {Timestamp: time.Unix(4, 1000), Line: "s2line4"}, 316 {Timestamp: time.Unix(5, 1000), Line: "s2line5"}, 317 {Timestamp: time.Unix(6, 1000), Line: "s2line6"}, 318 {Timestamp: time.Unix(7, 1000), Line: "s2line7"}, 319 {Timestamp: time.Unix(8, 1000), Line: "s2line8"}, 320 {Timestamp: time.Unix(9, 1000), Line: "s2line9"}, 321 {Timestamp: time.Unix(10, 1000), Line: "s2line10"}, 322 }, 323 }, 324 }, 325 start: time.Unix(1, 0), 326 end: time.Unix(11, 0), 327 limit: 12, 328 batch: 3, 329 labelMatcher: "{test=~\"one|two\"}", 330 forward: true, 331 // Six calls 332 // 1 line1, s2line1, line2 333 // 2 line2, s2line2, line3 334 // 3 line3, s2line3, line4 335 // 4 line4, s2line4, line5 336 // 5 line5, s2line5, line6 337 // 6 line6, s2line6 338 expectedCalls: 6, 339 expected: []string{ 340 "line1", "s2line1", "line2", "s2line2", "line3", "s2line3", "line4", "s2line4", "line5", "s2line5", "line6", "s2line6", 341 }, 342 }, 343 { 344 name: "two streams backward batch", 345 streams: []logproto.Stream{ 346 { 347 Labels: "{test=\"one\"}", 348 Entries: []logproto.Entry{ 349 {Timestamp: time.Unix(1, 0), Line: "line1"}, 350 {Timestamp: time.Unix(2, 0), Line: "line2"}, 351 {Timestamp: time.Unix(3, 0), Line: "line3"}, 352 {Timestamp: time.Unix(4, 0), Line: "line4"}, 353 {Timestamp: time.Unix(5, 0), Line: "line5"}, 354 {Timestamp: time.Unix(6, 0), Line: "line6"}, 355 {Timestamp: time.Unix(7, 0), Line: "line7"}, 356 {Timestamp: time.Unix(8, 0), Line: "line8"}, 357 {Timestamp: time.Unix(9, 0), Line: "line9"}, 358 {Timestamp: time.Unix(10, 0), Line: "line10"}, 359 }, 360 }, 361 { 362 Labels: "{test=\"two\"}", 363 Entries: []logproto.Entry{ 364 {Timestamp: time.Unix(1, 1000), Line: "s2line1"}, 365 {Timestamp: time.Unix(2, 1000), Line: "s2line2"}, 366 {Timestamp: time.Unix(3, 1000), Line: "s2line3"}, 367 {Timestamp: time.Unix(4, 1000), Line: "s2line4"}, 368 {Timestamp: time.Unix(5, 1000), Line: "s2line5"}, 369 {Timestamp: time.Unix(6, 1000), Line: "s2line6"}, 370 {Timestamp: time.Unix(7, 1000), Line: "s2line7"}, 371 {Timestamp: time.Unix(8, 1000), Line: "s2line8"}, 372 {Timestamp: time.Unix(9, 1000), Line: "s2line9"}, 373 {Timestamp: time.Unix(10, 1000), Line: "s2line10"}, 374 }, 375 }, 376 }, 377 start: time.Unix(1, 0), 378 end: time.Unix(11, 0), 379 limit: 12, 380 batch: 3, 381 labelMatcher: "{test=~\"one|two\"}", 382 forward: false, 383 expectedCalls: 6, 384 expected: []string{ 385 "s2line10", "line10", "s2line9", "line9", "s2line8", "line8", "s2line7", "line7", "s2line6", "line6", "s2line5", "line5", 386 }, 387 }, 388 { 389 name: "single stream forward batch identical timestamps", 390 streams: []logproto.Stream{ 391 { 392 Labels: "{test=\"simple\"}", 393 Entries: []logproto.Entry{ 394 {Timestamp: time.Unix(1, 0), Line: "line1"}, 395 {Timestamp: time.Unix(2, 0), Line: "line2"}, 396 {Timestamp: time.Unix(3, 0), Line: "line3"}, 397 {Timestamp: time.Unix(4, 0), Line: "line4"}, 398 {Timestamp: time.Unix(5, 0), Line: "line5"}, 399 {Timestamp: time.Unix(6, 0), Line: "line6"}, 400 {Timestamp: time.Unix(6, 0), Line: "line6a"}, 401 {Timestamp: time.Unix(7, 0), Line: "line7"}, 402 {Timestamp: time.Unix(8, 0), Line: "line8"}, 403 {Timestamp: time.Unix(9, 0), Line: "line9"}, 404 {Timestamp: time.Unix(10, 0), Line: "line10"}, 405 }, 406 }, 407 }, 408 start: time.Unix(1, 0), 409 end: time.Unix(11, 0), 410 limit: 9, 411 batch: 4, 412 labelMatcher: "{test=\"simple\"}", 413 forward: true, 414 // Our batchsize is 2 but each query will also return the overlapping last element from the 415 // previous batch, as such we only get one item per call so we make a lot of calls 416 // Call one: line1 line2 line3 line4 417 // Call two: line4 line5 line6 line6a 418 // Call three: line6 line6a line7 line8 <- notice line 6 and 6a share the same timestamp so they get returned as overlap in the next query. 419 expectedCalls: 3, 420 expected: []string{ 421 "line1", "line2", "line3", "line4", "line5", "line6", "line6a", "line7", "line8", 422 }, 423 }, 424 { 425 name: "single stream backward batch identical timestamps", 426 streams: []logproto.Stream{ 427 { 428 Labels: "{test=\"simple\"}", 429 Entries: []logproto.Entry{ 430 {Timestamp: time.Unix(1, 0), Line: "line1"}, 431 {Timestamp: time.Unix(2, 0), Line: "line2"}, 432 {Timestamp: time.Unix(3, 0), Line: "line3"}, 433 {Timestamp: time.Unix(4, 0), Line: "line4"}, 434 {Timestamp: time.Unix(5, 0), Line: "line5"}, 435 {Timestamp: time.Unix(6, 0), Line: "line6"}, 436 {Timestamp: time.Unix(6, 0), Line: "line6a"}, 437 {Timestamp: time.Unix(6, 0), Line: "line6b"}, 438 {Timestamp: time.Unix(7, 0), Line: "line7"}, 439 {Timestamp: time.Unix(8, 0), Line: "line8"}, 440 {Timestamp: time.Unix(9, 0), Line: "line9"}, 441 {Timestamp: time.Unix(10, 0), Line: "line10"}, 442 }, 443 }, 444 }, 445 start: time.Unix(1, 0), 446 end: time.Unix(11, 0), 447 limit: 11, 448 batch: 4, 449 labelMatcher: "{test=\"simple\"}", 450 forward: false, 451 // Our batchsize is 2 but each query will also return the overlapping last element from the 452 // previous batch, as such we only get one item per call so we make a lot of calls 453 // Call one: line10 line9 line8 line7 454 // Call two: line7 line6b line6a line6 455 // Call three: line6b line6a line6 line5 456 // Call four: line5 line5 line3 line2 457 expectedCalls: 4, 458 expected: []string{ 459 "line10", "line9", "line8", "line7", "line6b", "line6a", "line6", "line5", "line4", "line3", "line2", 460 }, 461 }, 462 } 463 for _, tt := range tests { 464 t.Run(tt.name, func(t *testing.T) { 465 tc := newTestQueryClient(tt.streams...) 466 writer := &bytes.Buffer{} 467 out := output.NewRaw(writer, nil) 468 q := Query{ 469 QueryString: tt.labelMatcher, 470 Start: tt.start, 471 End: tt.end, 472 Limit: tt.limit, 473 BatchSize: tt.batch, 474 Forward: tt.forward, 475 Step: 0, 476 Interval: 0, 477 Quiet: false, 478 NoLabels: false, 479 IgnoreLabelsKey: nil, 480 ShowLabelsKey: nil, 481 FixedLabelsLen: 0, 482 LocalConfig: "", 483 } 484 q.DoQuery(tc, out, false) 485 split := strings.Split(writer.String(), "\n") 486 // Remove the last entry because there is always a newline after the last line which 487 // leaves an entry element in the list of lines. 488 if len(split) > 0 { 489 split = split[:len(split)-1] 490 } 491 assert.Equal(t, tt.expected, split) 492 assert.Equal(t, tt.expectedCalls, tc.queryRangeCalls) 493 }) 494 } 495 } 496 497 func mustParseLabels(t *testing.T, s string) loghttp.LabelSet { 498 t.Helper() 499 l, err := marshal.NewLabelSet(s) 500 require.NoErrorf(t, err, "Failed to parse %q", s) 501 502 return l 503 } 504 505 type testQueryClient struct { 506 engine *logql.Engine 507 queryRangeCalls int 508 } 509 510 func newTestQueryClient(testStreams ...logproto.Stream) *testQueryClient { 511 q := logql.NewMockQuerier(0, testStreams) 512 e := logql.NewEngine(logql.EngineOpts{}, q, logql.NoLimits, log.NewNopLogger()) 513 return &testQueryClient{ 514 engine: e, 515 queryRangeCalls: 0, 516 } 517 } 518 519 func (t *testQueryClient) Query(queryStr string, limit int, time time.Time, direction logproto.Direction, quiet bool) (*loghttp.QueryResponse, error) { 520 panic("implement me") 521 } 522 523 func (t *testQueryClient) QueryRange(queryStr string, limit int, from, through time.Time, direction logproto.Direction, step, interval time.Duration, quiet bool) (*loghttp.QueryResponse, error) { 524 525 params := logql.NewLiteralParams(queryStr, from, through, step, interval, direction, uint32(limit), nil) 526 527 v, err := t.engine.Query(params).Exec(context.Background()) 528 if err != nil { 529 return nil, err 530 } 531 532 value, err := marshal.NewResultValue(v.Data) 533 if err != nil { 534 return nil, err 535 } 536 537 q := &loghttp.QueryResponse{ 538 Status: "success", 539 Data: loghttp.QueryResponseData{ 540 ResultType: value.Type(), 541 Result: value, 542 Statistics: v.Statistics, 543 }, 544 } 545 t.queryRangeCalls++ 546 return q, nil 547 } 548 549 func (t *testQueryClient) ListLabelNames(quiet bool, from, through time.Time) (*loghttp.LabelResponse, error) { 550 panic("implement me") 551 } 552 553 func (t *testQueryClient) ListLabelValues(name string, quiet bool, from, through time.Time) (*loghttp.LabelResponse, error) { 554 panic("implement me") 555 } 556 557 func (t *testQueryClient) Series(matchers []string, from, through time.Time, quiet bool) (*loghttp.SeriesResponse, error) { 558 panic("implement me") 559 } 560 561 func (t *testQueryClient) LiveTailQueryConn(queryStr string, delayFor time.Duration, limit int, start time.Time, quiet bool) (*websocket.Conn, error) { 562 panic("implement me") 563 } 564 565 func (t *testQueryClient) GetOrgID() string { 566 panic("implement me") 567 } 568 569 var schemaConfigContents = `schema_config: 570 configs: 571 - from: 2020-05-15 572 store: boltdb-shipper 573 object_store: gcs 574 schema: v10 575 index: 576 prefix: index_ 577 period: 168h 578 - from: 2020-07-31 579 store: boltdb-shipper 580 object_store: gcs 581 schema: v11 582 index: 583 prefix: index_ 584 period: 24h 585 ` 586 587 func TestLoadFromURL(t *testing.T) { 588 tmpDir := t.TempDir() 589 590 conf := loki.Config{ 591 StorageConfig: storage.Config{ 592 FSConfig: local.FSConfig{ 593 Directory: tmpDir, 594 }, 595 }, 596 } 597 598 // Missing SharedStoreType should error 599 cm := storage.NewClientMetrics() 600 client, err := GetObjectClient(conf, cm) 601 require.Error(t, err) 602 require.Nil(t, client) 603 604 conf.StorageConfig.BoltDBShipperConfig = shipper.Config{ 605 Config: indexshipper.Config{ 606 SharedStoreType: config.StorageTypeFileSystem, 607 }, 608 } 609 610 client, err = GetObjectClient(conf, cm) 611 require.NoError(t, err) 612 require.NotNil(t, client) 613 614 // Missing schema.config file should error 615 schemaConfig, err := LoadSchemaUsingObjectClient(client, SchemaConfigFilename) 616 require.Error(t, err) 617 require.Nil(t, schemaConfig) 618 619 err = os.WriteFile( 620 filepath.Join(tmpDir, SchemaConfigFilename), 621 []byte(schemaConfigContents), 622 0666, 623 ) 624 require.NoError(t, err) 625 626 schemaConfig, err = LoadSchemaUsingObjectClient(client, SchemaConfigFilename) 627 628 require.NoError(t, err) 629 require.NotNil(t, schemaConfig) 630 }