github.com/thanos-io/thanos@v0.32.5/pkg/queryfrontend/queryinstant_codec_test.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package queryfrontend 5 6 import ( 7 "bytes" 8 "context" 9 "io" 10 "net/http" 11 "testing" 12 13 "github.com/prometheus/common/model" 14 "github.com/prometheus/prometheus/model/labels" 15 "github.com/weaveworks/common/httpgrpc" 16 17 "github.com/efficientgo/core/testutil" 18 "github.com/thanos-io/thanos/internal/cortex/cortexpb" 19 "github.com/thanos-io/thanos/internal/cortex/querier/queryrange" 20 queryv1 "github.com/thanos-io/thanos/pkg/api/query" 21 "github.com/thanos-io/thanos/pkg/compact" 22 ) 23 24 func TestQueryInstantCodec_DecodeRequest(t *testing.T) { 25 for _, tc := range []struct { 26 name string 27 url string 28 partialResponse bool 29 expectedError error 30 expectedRequest *ThanosQueryInstantRequest 31 }{ 32 { 33 name: "cannot parse time", 34 url: "/api/v1/query?time=foo", 35 partialResponse: false, 36 expectedError: httpgrpc.Errorf(http.StatusBadRequest, `cannot parse "foo" to a valid timestamp`), 37 }, 38 { 39 name: "parse time", 40 url: "/api/v1/query?time=123", 41 partialResponse: false, 42 expectedRequest: &ThanosQueryInstantRequest{ 43 Path: "/api/v1/query", 44 Time: 123000, 45 Dedup: true, 46 StoreMatchers: [][]*labels.Matcher{}, 47 }, 48 }, 49 { 50 name: "parse query", 51 url: "/api/v1/query?time=123&query=up", 52 partialResponse: false, 53 expectedRequest: &ThanosQueryInstantRequest{ 54 Path: "/api/v1/query", 55 Query: "up", 56 Time: 123000, 57 Dedup: true, 58 StoreMatchers: [][]*labels.Matcher{}, 59 }, 60 }, 61 { 62 name: "cannot parse dedup", 63 url: "/api/v1/query?dedup=bar", 64 partialResponse: false, 65 expectedError: httpgrpc.Errorf(http.StatusBadRequest, "cannot parse parameter dedup"), 66 }, 67 { 68 name: "cannot parse downsampling resolution", 69 url: "/api/v1/query?max_source_resolution=bar", 70 partialResponse: false, 71 expectedError: httpgrpc.Errorf(http.StatusBadRequest, "cannot parse parameter max_source_resolution"), 72 }, 73 { 74 name: "negative downsampling resolution", 75 url: "/api/v1/query?max_source_resolution=-1", 76 partialResponse: false, 77 expectedError: httpgrpc.Errorf(http.StatusBadRequest, "negative max_source_resolution is not accepted. Try a positive integer"), 78 }, 79 { 80 name: "auto downsampling enabled", 81 url: "/api/v1/query?max_source_resolution=auto", 82 expectedRequest: &ThanosQueryInstantRequest{ 83 Path: "/api/v1/query", 84 AutoDownsampling: true, 85 Dedup: true, 86 StoreMatchers: [][]*labels.Matcher{}, 87 }, 88 }, 89 { 90 name: "cannot parse partial_response", 91 url: "/api/v1/query?partial_response=bar", 92 partialResponse: false, 93 expectedError: httpgrpc.Errorf(http.StatusBadRequest, "cannot parse parameter partial_response"), 94 }, 95 { 96 name: "partial_response default to true", 97 url: "/api/v1/query", 98 partialResponse: true, 99 expectedRequest: &ThanosQueryInstantRequest{ 100 Path: "/api/v1/query", 101 Dedup: true, 102 PartialResponse: true, 103 StoreMatchers: [][]*labels.Matcher{}, 104 }, 105 }, 106 { 107 name: "partial_response default to false, but set to true in query", 108 url: "/api/v1/query?partial_response=true", 109 partialResponse: false, 110 expectedRequest: &ThanosQueryInstantRequest{ 111 Path: "/api/v1/query", 112 Dedup: true, 113 PartialResponse: true, 114 StoreMatchers: [][]*labels.Matcher{}, 115 }, 116 }, 117 { 118 name: "replicaLabels", 119 url: "/api/v1/query?replicaLabels[]=foo&replicaLabels[]=bar", 120 partialResponse: false, 121 expectedRequest: &ThanosQueryInstantRequest{ 122 Path: "/api/v1/query", 123 Dedup: true, 124 ReplicaLabels: []string{"foo", "bar"}, 125 StoreMatchers: [][]*labels.Matcher{}, 126 }, 127 }, 128 { 129 name: "storeMatchers", 130 url: `/api/v1/query?storeMatch[]={__address__="localhost:10901", cluster="test"}`, 131 partialResponse: false, 132 expectedRequest: &ThanosQueryInstantRequest{ 133 Path: "/api/v1/query", 134 Dedup: true, 135 StoreMatchers: [][]*labels.Matcher{ 136 { 137 labels.MustNewMatcher(labels.MatchEqual, "__address__", "localhost:10901"), 138 labels.MustNewMatcher(labels.MatchEqual, "cluster", "test"), 139 }, 140 }, 141 }, 142 }, 143 { 144 name: "lookback_delta", 145 url: "/api/v1/query?lookback_delta=1000", 146 partialResponse: false, 147 expectedRequest: &ThanosQueryInstantRequest{ 148 Path: "/api/v1/query", 149 Dedup: true, 150 LookbackDelta: 1000000, 151 StoreMatchers: [][]*labels.Matcher{}, 152 }, 153 }, 154 } { 155 t.Run(tc.name, func(t *testing.T) { 156 r, err := http.NewRequest(http.MethodGet, tc.url, nil) 157 testutil.Ok(t, err) 158 159 codec := NewThanosQueryInstantCodec(tc.partialResponse) 160 req, err := codec.DecodeRequest(context.Background(), r, nil) 161 if tc.expectedError != nil { 162 testutil.Equals(t, err, tc.expectedError) 163 } else { 164 testutil.Ok(t, err) 165 testutil.Equals(t, req, tc.expectedRequest) 166 } 167 }) 168 } 169 } 170 171 func TestQueryInstantCodec_EncodeRequest(t *testing.T) { 172 for _, tc := range []struct { 173 name string 174 expectedError error 175 checkFunc func(r *http.Request) bool 176 req queryrange.Request 177 }{ 178 { 179 name: "prometheus request, invalid format", 180 req: &queryrange.PrometheusRequest{}, 181 expectedError: httpgrpc.Errorf(http.StatusBadRequest, "invalid request format"), 182 }, 183 { 184 name: "empty thanos request", 185 req: &ThanosQueryInstantRequest{}, 186 checkFunc: func(r *http.Request) bool { 187 return r.FormValue("time") == "" && r.FormValue("query") == "" 188 }, 189 }, 190 { 191 name: "query set", 192 req: &ThanosQueryInstantRequest{Query: "up"}, 193 checkFunc: func(r *http.Request) bool { 194 return r.FormValue("query") == "up" 195 }, 196 }, 197 { 198 name: "time set", 199 req: &ThanosQueryInstantRequest{Time: 123000}, 200 checkFunc: func(r *http.Request) bool { 201 return r.FormValue("time") == "123" 202 }, 203 }, 204 { 205 name: "query and time set", 206 req: &ThanosQueryInstantRequest{Time: 123000, Query: "foo"}, 207 checkFunc: func(r *http.Request) bool { 208 return r.FormValue("time") == "123" && r.FormValue("query") == "foo" 209 }, 210 }, 211 { 212 name: "Dedup disabled", 213 req: &ThanosQueryInstantRequest{ 214 Dedup: false, 215 }, 216 checkFunc: func(r *http.Request) bool { 217 return r.FormValue(queryv1.DedupParam) == "false" 218 }, 219 }, 220 { 221 name: "Partial response set to true", 222 req: &ThanosQueryInstantRequest{ 223 PartialResponse: true, 224 }, 225 checkFunc: func(r *http.Request) bool { 226 return r.FormValue(queryv1.PartialResponseParam) == "true" 227 }, 228 }, 229 { 230 name: "Downsampling resolution set to 5m", 231 req: &ThanosQueryInstantRequest{ 232 MaxSourceResolution: int64(compact.ResolutionLevel5m), 233 }, 234 checkFunc: func(r *http.Request) bool { 235 return r.FormValue(queryv1.MaxSourceResolutionParam) == "300" 236 }, 237 }, 238 { 239 name: "Downsampling resolution set to 1h", 240 req: &ThanosQueryInstantRequest{ 241 MaxSourceResolution: int64(compact.ResolutionLevel1h), 242 }, 243 checkFunc: func(r *http.Request) bool { 244 return r.FormValue(queryv1.MaxSourceResolutionParam) == "3600" 245 }, 246 }, 247 } { 248 t.Run(tc.name, func(t *testing.T) { 249 // Default partial response value doesn't matter when encoding requests. 250 codec := NewThanosQueryInstantCodec(false) 251 r, err := codec.EncodeRequest(context.TODO(), tc.req) 252 if tc.expectedError != nil { 253 testutil.Equals(t, err, tc.expectedError) 254 } else { 255 testutil.Ok(t, err) 256 testutil.Equals(t, tc.checkFunc(r), true) 257 } 258 }) 259 } 260 } 261 262 func TestMergeResponse(t *testing.T) { 263 codec := NewThanosQueryInstantCodec(false) 264 defaultReq := &queryrange.PrometheusRequest{ 265 Query: "sum(up)", 266 } 267 for _, tc := range []struct { 268 name string 269 req *queryrange.PrometheusRequest 270 resps []queryrange.Response 271 expectedResp queryrange.Response 272 expectedErr error 273 }{ 274 { 275 name: "empty response", 276 req: defaultReq, 277 resps: []queryrange.Response{}, 278 expectedResp: &queryrange.PrometheusInstantQueryResponse{ 279 Status: queryrange.StatusSuccess, 280 Data: queryrange.PrometheusInstantQueryData{ 281 ResultType: model.ValVector.String(), 282 Result: queryrange.PrometheusInstantQueryResult{ 283 Result: &queryrange.PrometheusInstantQueryResult_Vector{}, 284 }, 285 }, 286 }, 287 }, 288 { 289 name: "one response", 290 req: defaultReq, 291 resps: []queryrange.Response{ 292 &queryrange.PrometheusInstantQueryResponse{ 293 Status: queryrange.StatusSuccess, 294 Data: queryrange.PrometheusInstantQueryData{ 295 ResultType: model.ValVector.String(), 296 Result: queryrange.PrometheusInstantQueryResult{ 297 Result: &queryrange.PrometheusInstantQueryResult_Vector{ 298 Vector: &queryrange.Vector{ 299 Samples: []*queryrange.Sample{ 300 { 301 Timestamp: 0, 302 SampleValue: 1, 303 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 304 "__name__": "up", 305 })), 306 }, 307 }, 308 }, 309 }, 310 }, 311 }, 312 }, 313 }, 314 expectedResp: &queryrange.PrometheusInstantQueryResponse{ 315 Status: queryrange.StatusSuccess, 316 Data: queryrange.PrometheusInstantQueryData{ 317 ResultType: model.ValVector.String(), 318 Result: queryrange.PrometheusInstantQueryResult{ 319 Result: &queryrange.PrometheusInstantQueryResult_Vector{ 320 Vector: &queryrange.Vector{ 321 Samples: []*queryrange.Sample{ 322 { 323 Timestamp: 0, 324 SampleValue: 1, 325 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 326 "__name__": "up", 327 })), 328 }, 329 }, 330 }, 331 }, 332 }, 333 }, 334 }, 335 }, 336 { 337 name: "merge two responses with sort", 338 req: &queryrange.PrometheusRequest{ 339 Query: "1 + sort(topk(1, up))", 340 }, 341 resps: []queryrange.Response{ 342 &queryrange.PrometheusInstantQueryResponse{ 343 Status: queryrange.StatusSuccess, 344 Data: queryrange.PrometheusInstantQueryData{ 345 ResultType: model.ValVector.String(), 346 Result: queryrange.PrometheusInstantQueryResult{ 347 Result: &queryrange.PrometheusInstantQueryResult_Vector{ 348 Vector: &queryrange.Vector{ 349 Samples: []*queryrange.Sample{ 350 { 351 Timestamp: 0, 352 SampleValue: 1, 353 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 354 "__name__": "up", 355 "job": "foo", 356 })), 357 }, 358 }, 359 }, 360 }, 361 }, 362 }, 363 }, 364 &queryrange.PrometheusInstantQueryResponse{ 365 Status: queryrange.StatusSuccess, 366 Data: queryrange.PrometheusInstantQueryData{ 367 ResultType: model.ValVector.String(), 368 Result: queryrange.PrometheusInstantQueryResult{ 369 Result: &queryrange.PrometheusInstantQueryResult_Vector{ 370 Vector: &queryrange.Vector{ 371 Samples: []*queryrange.Sample{ 372 { 373 Timestamp: 0, 374 SampleValue: 2, 375 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 376 "__name__": "up", 377 "job": "bar", 378 })), 379 }, 380 }, 381 }, 382 }, 383 }, 384 }, 385 }, 386 }, 387 expectedResp: &queryrange.PrometheusInstantQueryResponse{ 388 Status: queryrange.StatusSuccess, 389 Data: queryrange.PrometheusInstantQueryData{ 390 ResultType: model.ValVector.String(), 391 Result: queryrange.PrometheusInstantQueryResult{ 392 Result: &queryrange.PrometheusInstantQueryResult_Vector{ 393 Vector: &queryrange.Vector{ 394 Samples: []*queryrange.Sample{ 395 { 396 Timestamp: 0, 397 SampleValue: 1, 398 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 399 "__name__": "up", 400 "job": "foo", 401 })), 402 }, 403 { 404 Timestamp: 0, 405 SampleValue: 2, 406 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 407 "__name__": "up", 408 "job": "bar", 409 })), 410 }, 411 }, 412 }, 413 }, 414 }, 415 }, 416 }, 417 }, 418 { 419 name: "merge two responses with topk", 420 req: &queryrange.PrometheusRequest{ 421 Query: "topk(10, sort(up)) by (job)", 422 }, 423 resps: []queryrange.Response{ 424 &queryrange.PrometheusInstantQueryResponse{ 425 Status: queryrange.StatusSuccess, 426 Data: queryrange.PrometheusInstantQueryData{ 427 ResultType: model.ValVector.String(), 428 Result: queryrange.PrometheusInstantQueryResult{ 429 Result: &queryrange.PrometheusInstantQueryResult_Vector{ 430 Vector: &queryrange.Vector{ 431 Samples: []*queryrange.Sample{ 432 { 433 Timestamp: 0, 434 SampleValue: 1, 435 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 436 "__name__": "up", 437 "job": "foo", 438 })), 439 }, 440 }, 441 }, 442 }, 443 }, 444 }, 445 }, 446 &queryrange.PrometheusInstantQueryResponse{ 447 Status: queryrange.StatusSuccess, 448 Data: queryrange.PrometheusInstantQueryData{ 449 ResultType: model.ValVector.String(), 450 Result: queryrange.PrometheusInstantQueryResult{ 451 Result: &queryrange.PrometheusInstantQueryResult_Vector{ 452 Vector: &queryrange.Vector{ 453 Samples: []*queryrange.Sample{ 454 { 455 Timestamp: 0, 456 SampleValue: 2, 457 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 458 "__name__": "up", 459 "job": "bar", 460 })), 461 }, 462 }, 463 }, 464 }, 465 }, 466 }, 467 }, 468 }, 469 expectedResp: &queryrange.PrometheusInstantQueryResponse{ 470 Status: queryrange.StatusSuccess, 471 Data: queryrange.PrometheusInstantQueryData{ 472 ResultType: model.ValVector.String(), 473 Result: queryrange.PrometheusInstantQueryResult{ 474 Result: &queryrange.PrometheusInstantQueryResult_Vector{ 475 Vector: &queryrange.Vector{ 476 Samples: []*queryrange.Sample{ 477 { 478 Timestamp: 0, 479 SampleValue: 1, 480 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 481 "__name__": "up", 482 "job": "foo", 483 })), 484 }, 485 { 486 Timestamp: 0, 487 SampleValue: 2, 488 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 489 "__name__": "up", 490 "job": "bar", 491 })), 492 }, 493 }, 494 }, 495 }, 496 }, 497 }, 498 }, 499 }, 500 { 501 name: "merge two responses", 502 req: defaultReq, 503 resps: []queryrange.Response{ 504 &queryrange.PrometheusInstantQueryResponse{ 505 Status: queryrange.StatusSuccess, 506 Data: queryrange.PrometheusInstantQueryData{ 507 ResultType: model.ValVector.String(), 508 Result: queryrange.PrometheusInstantQueryResult{ 509 Result: &queryrange.PrometheusInstantQueryResult_Vector{ 510 Vector: &queryrange.Vector{ 511 Samples: []*queryrange.Sample{ 512 { 513 Timestamp: 0, 514 SampleValue: 1, 515 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 516 "__name__": "up", 517 "job": "foo", 518 })), 519 }, 520 }, 521 }, 522 }, 523 }, 524 }, 525 }, 526 &queryrange.PrometheusInstantQueryResponse{ 527 Status: queryrange.StatusSuccess, 528 Data: queryrange.PrometheusInstantQueryData{ 529 ResultType: model.ValVector.String(), 530 Result: queryrange.PrometheusInstantQueryResult{ 531 Result: &queryrange.PrometheusInstantQueryResult_Vector{ 532 Vector: &queryrange.Vector{ 533 Samples: []*queryrange.Sample{ 534 { 535 Timestamp: 0, 536 SampleValue: 2, 537 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 538 "__name__": "up", 539 "job": "bar", 540 })), 541 }, 542 }, 543 }, 544 }, 545 }, 546 }, 547 }, 548 }, 549 expectedResp: &queryrange.PrometheusInstantQueryResponse{ 550 Status: queryrange.StatusSuccess, 551 Data: queryrange.PrometheusInstantQueryData{ 552 ResultType: model.ValVector.String(), 553 Result: queryrange.PrometheusInstantQueryResult{ 554 Result: &queryrange.PrometheusInstantQueryResult_Vector{ 555 Vector: &queryrange.Vector{ 556 Samples: []*queryrange.Sample{ 557 { 558 Timestamp: 0, 559 SampleValue: 2, 560 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 561 "__name__": "up", 562 "job": "bar", 563 })), 564 }, 565 { 566 Timestamp: 0, 567 SampleValue: 1, 568 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 569 "__name__": "up", 570 "job": "foo", 571 })), 572 }, 573 }, 574 }, 575 }, 576 }, 577 }, 578 }, 579 }, 580 { 581 name: "merge multiple responses with same label sets, won't happen if sharding is enabled on downstream querier", 582 req: defaultReq, 583 resps: []queryrange.Response{ 584 &queryrange.PrometheusInstantQueryResponse{ 585 Status: queryrange.StatusSuccess, 586 Data: queryrange.PrometheusInstantQueryData{ 587 ResultType: model.ValVector.String(), 588 Result: queryrange.PrometheusInstantQueryResult{ 589 Result: &queryrange.PrometheusInstantQueryResult_Vector{ 590 Vector: &queryrange.Vector{ 591 Samples: []*queryrange.Sample{ 592 { 593 Timestamp: 0, 594 SampleValue: 1, 595 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 596 "__name__": "up", 597 "job": "foo", 598 })), 599 }, 600 }, 601 }, 602 }, 603 }, 604 }, 605 }, 606 &queryrange.PrometheusInstantQueryResponse{ 607 Status: queryrange.StatusSuccess, 608 Data: queryrange.PrometheusInstantQueryData{ 609 ResultType: model.ValVector.String(), 610 Result: queryrange.PrometheusInstantQueryResult{ 611 Result: &queryrange.PrometheusInstantQueryResult_Vector{ 612 Vector: &queryrange.Vector{ 613 Samples: []*queryrange.Sample{ 614 { 615 Timestamp: 1, 616 SampleValue: 2, 617 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 618 "__name__": "up", 619 "job": "foo", 620 })), 621 }, 622 }, 623 }, 624 }, 625 }, 626 }, 627 }, 628 }, 629 expectedResp: &queryrange.PrometheusInstantQueryResponse{ 630 Status: queryrange.StatusSuccess, 631 Data: queryrange.PrometheusInstantQueryData{ 632 ResultType: model.ValVector.String(), 633 Result: queryrange.PrometheusInstantQueryResult{ 634 Result: &queryrange.PrometheusInstantQueryResult_Vector{ 635 Vector: &queryrange.Vector{ 636 Samples: []*queryrange.Sample{ 637 { 638 Timestamp: 1, 639 SampleValue: 2, 640 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 641 "__name__": "up", 642 "job": "foo", 643 })), 644 }, 645 }, 646 }, 647 }, 648 }, 649 }, 650 }, 651 }, 652 { 653 name: "responses don't contain vector, return empty vector", 654 req: defaultReq, 655 resps: []queryrange.Response{ 656 &queryrange.PrometheusInstantQueryResponse{ 657 Status: queryrange.StatusSuccess, 658 Data: queryrange.PrometheusInstantQueryData{ 659 ResultType: model.ValScalar.String(), 660 Result: queryrange.PrometheusInstantQueryResult{ 661 Result: &queryrange.PrometheusInstantQueryResult_Scalar{ 662 Scalar: &cortexpb.Sample{ 663 TimestampMs: 0, 664 Value: 1, 665 }, 666 }, 667 }, 668 }, 669 }, 670 &queryrange.PrometheusInstantQueryResponse{ 671 Status: queryrange.StatusSuccess, 672 Data: queryrange.PrometheusInstantQueryData{ 673 ResultType: model.ValScalar.String(), 674 Result: queryrange.PrometheusInstantQueryResult{ 675 Result: &queryrange.PrometheusInstantQueryResult_Scalar{ 676 Scalar: &cortexpb.Sample{ 677 TimestampMs: 0, 678 Value: 2, 679 }, 680 }, 681 }, 682 }, 683 }, 684 }, 685 expectedResp: &queryrange.PrometheusInstantQueryResponse{ 686 Status: queryrange.StatusSuccess, 687 Data: queryrange.PrometheusInstantQueryData{ 688 ResultType: model.ValVector.String(), 689 Result: queryrange.PrometheusInstantQueryResult{ 690 Result: &queryrange.PrometheusInstantQueryResult_Vector{ 691 Vector: &queryrange.Vector{ 692 Samples: []*queryrange.Sample{}, 693 }, 694 }, 695 }, 696 }, 697 }, 698 }, 699 { 700 name: "merge two matrix responses with non-duplicate samples", 701 req: defaultReq, 702 resps: []queryrange.Response{ 703 &queryrange.PrometheusInstantQueryResponse{ 704 Status: queryrange.StatusSuccess, 705 Data: queryrange.PrometheusInstantQueryData{ 706 ResultType: model.ValMatrix.String(), 707 Result: queryrange.PrometheusInstantQueryResult{ 708 Result: &queryrange.PrometheusInstantQueryResult_Matrix{ 709 Matrix: &queryrange.Matrix{ 710 SampleStreams: []*queryrange.SampleStream{ 711 { 712 Samples: []cortexpb.Sample{{TimestampMs: 1, Value: 2}}, 713 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 714 "__name__": "up", 715 "job": "bar", 716 })), 717 }, 718 { 719 Samples: []cortexpb.Sample{{TimestampMs: 1, Value: 2}}, 720 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 721 "__name__": "up", 722 "job": "foo", 723 })), 724 }, 725 }, 726 }, 727 }, 728 }, 729 }, 730 }, 731 &queryrange.PrometheusInstantQueryResponse{ 732 Status: queryrange.StatusSuccess, 733 Data: queryrange.PrometheusInstantQueryData{ 734 ResultType: model.ValMatrix.String(), 735 Result: queryrange.PrometheusInstantQueryResult{ 736 Result: &queryrange.PrometheusInstantQueryResult_Matrix{ 737 Matrix: &queryrange.Matrix{ 738 SampleStreams: []*queryrange.SampleStream{ 739 { 740 Samples: []cortexpb.Sample{{TimestampMs: 2, Value: 3}}, 741 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 742 "__name__": "up", 743 "job": "bar", 744 })), 745 }, 746 { 747 Samples: []cortexpb.Sample{{TimestampMs: 2, Value: 3}}, 748 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 749 "__name__": "up", 750 "job": "foo", 751 })), 752 }, 753 }, 754 }, 755 }, 756 }, 757 }, 758 }, 759 }, 760 expectedResp: &queryrange.PrometheusInstantQueryResponse{ 761 Status: queryrange.StatusSuccess, 762 Data: queryrange.PrometheusInstantQueryData{ 763 ResultType: model.ValMatrix.String(), 764 Result: queryrange.PrometheusInstantQueryResult{ 765 Result: &queryrange.PrometheusInstantQueryResult_Matrix{ 766 Matrix: &queryrange.Matrix{ 767 SampleStreams: []*queryrange.SampleStream{ 768 { 769 Samples: []cortexpb.Sample{{TimestampMs: 1, Value: 2}, {TimestampMs: 2, Value: 3}}, 770 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 771 "__name__": "up", 772 "job": "bar", 773 })), 774 }, 775 { 776 Samples: []cortexpb.Sample{{TimestampMs: 1, Value: 2}, {TimestampMs: 2, Value: 3}}, 777 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 778 "__name__": "up", 779 "job": "foo", 780 })), 781 }, 782 }, 783 }, 784 }, 785 }, 786 }, 787 }, 788 }, 789 { 790 name: "merge two matrix responses with duplicate samples", 791 req: defaultReq, 792 resps: []queryrange.Response{ 793 &queryrange.PrometheusInstantQueryResponse{ 794 Status: queryrange.StatusSuccess, 795 Data: queryrange.PrometheusInstantQueryData{ 796 ResultType: model.ValMatrix.String(), 797 Result: queryrange.PrometheusInstantQueryResult{ 798 Result: &queryrange.PrometheusInstantQueryResult_Matrix{ 799 Matrix: &queryrange.Matrix{ 800 SampleStreams: []*queryrange.SampleStream{ 801 { 802 Samples: []cortexpb.Sample{{TimestampMs: 1, Value: 2}}, 803 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 804 "__name__": "up", 805 "job": "bar", 806 })), 807 }, 808 { 809 Samples: []cortexpb.Sample{{TimestampMs: 1, Value: 2}}, 810 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 811 "__name__": "up", 812 "job": "foo", 813 })), 814 }, 815 }, 816 }, 817 }, 818 }, 819 }, 820 }, 821 &queryrange.PrometheusInstantQueryResponse{ 822 Status: queryrange.StatusSuccess, 823 Data: queryrange.PrometheusInstantQueryData{ 824 ResultType: model.ValMatrix.String(), 825 Result: queryrange.PrometheusInstantQueryResult{ 826 Result: &queryrange.PrometheusInstantQueryResult_Matrix{ 827 Matrix: &queryrange.Matrix{ 828 SampleStreams: []*queryrange.SampleStream{ 829 { 830 Samples: []cortexpb.Sample{{TimestampMs: 1, Value: 2}}, 831 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 832 "__name__": "up", 833 "job": "bar", 834 })), 835 }, 836 { 837 Samples: []cortexpb.Sample{{TimestampMs: 1, Value: 2}}, 838 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 839 "__name__": "up", 840 "job": "foo", 841 })), 842 }, 843 }, 844 }, 845 }, 846 }, 847 }, 848 }, 849 }, 850 expectedResp: &queryrange.PrometheusInstantQueryResponse{ 851 Status: queryrange.StatusSuccess, 852 Data: queryrange.PrometheusInstantQueryData{ 853 ResultType: model.ValMatrix.String(), 854 Result: queryrange.PrometheusInstantQueryResult{ 855 Result: &queryrange.PrometheusInstantQueryResult_Matrix{ 856 Matrix: &queryrange.Matrix{ 857 SampleStreams: []*queryrange.SampleStream{ 858 { 859 Samples: []cortexpb.Sample{{TimestampMs: 1, Value: 2}}, 860 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 861 "__name__": "up", 862 "job": "bar", 863 })), 864 }, 865 { 866 Samples: []cortexpb.Sample{{TimestampMs: 1, Value: 2}}, 867 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 868 "__name__": "up", 869 "job": "foo", 870 })), 871 }, 872 }, 873 }, 874 }, 875 }, 876 }, 877 }, 878 }, 879 } { 880 t.Run(tc.name, func(t *testing.T) { 881 resp, err := codec.MergeResponse(tc.req, tc.resps...) 882 testutil.Equals(t, err, tc.expectedErr) 883 testutil.Equals(t, resp, tc.expectedResp) 884 }) 885 } 886 } 887 888 func TestDecodeResponse(t *testing.T) { 889 codec := NewThanosQueryInstantCodec(false) 890 headers := []*queryrange.PrometheusResponseHeader{ 891 {Name: "Content-Type", Values: []string{"application/json"}}, 892 } 893 for _, tc := range []struct { 894 name string 895 body string 896 expectedResponse queryrange.Response 897 expectedErr error 898 }{ 899 { 900 name: "with explanation", 901 body: `{ 902 "status":"success", 903 "data":{ 904 "resultType":"vector", 905 "result":[], 906 "explanation": { 907 "name":"[*concurrencyOperator(buff=2)]", 908 "children":[{"name":"[*aggregate] sum by ([])", "children": []}] 909 } 910 } 911 }`, 912 expectedResponse: &queryrange.PrometheusInstantQueryResponse{ 913 Status: queryrange.StatusSuccess, 914 Headers: headers, 915 Data: queryrange.PrometheusInstantQueryData{ 916 Explanation: &queryrange.Explanation{ 917 Name: "[*concurrencyOperator(buff=2)]", 918 Children: []*queryrange.Explanation{ 919 { 920 Name: "[*aggregate] sum by ([])", 921 Children: []*queryrange.Explanation{}, 922 }, 923 }, 924 }, 925 ResultType: model.ValVector.String(), 926 Result: queryrange.PrometheusInstantQueryResult{ 927 Result: &queryrange.PrometheusInstantQueryResult_Vector{ 928 Vector: &queryrange.Vector{ 929 Samples: []*queryrange.Sample{}, 930 }, 931 }, 932 }, 933 }, 934 }, 935 }, 936 { 937 name: "empty vector", 938 body: `{ 939 "status": "success", 940 "data": { 941 "resultType": "vector", 942 "result": [ 943 944 ] 945 } 946 }`, 947 expectedResponse: &queryrange.PrometheusInstantQueryResponse{ 948 Status: queryrange.StatusSuccess, 949 Headers: headers, 950 Data: queryrange.PrometheusInstantQueryData{ 951 ResultType: model.ValVector.String(), 952 Result: queryrange.PrometheusInstantQueryResult{ 953 Result: &queryrange.PrometheusInstantQueryResult_Vector{ 954 Vector: &queryrange.Vector{ 955 Samples: []*queryrange.Sample{}, 956 }, 957 }, 958 }, 959 }, 960 }, 961 }, 962 { 963 name: "vector", 964 body: `{ 965 "status": "success", 966 "data": { 967 "resultType": "vector", 968 "result": [ 969 { 970 "metric": { 971 "__name__": "up", 972 "instance": "localhost:9090", 973 "job": "prometheus" 974 }, 975 "value": [ 976 1661020672.043, 977 "1" 978 ] 979 } 980 ] 981 } 982 }`, 983 expectedResponse: &queryrange.PrometheusInstantQueryResponse{ 984 Status: queryrange.StatusSuccess, 985 Headers: headers, 986 Data: queryrange.PrometheusInstantQueryData{ 987 ResultType: model.ValVector.String(), 988 Result: queryrange.PrometheusInstantQueryResult{ 989 Result: &queryrange.PrometheusInstantQueryResult_Vector{ 990 Vector: &queryrange.Vector{ 991 Samples: []*queryrange.Sample{ 992 { 993 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 994 "__name__": "up", 995 "instance": "localhost:9090", 996 "job": "prometheus", 997 })), 998 Timestamp: 1661020672043, 999 SampleValue: 1, 1000 }, 1001 }, 1002 }, 1003 }, 1004 }, 1005 }, 1006 }, 1007 }, 1008 { 1009 name: "scalar", 1010 body: `{ 1011 "status": "success", 1012 "data": { 1013 "resultType": "scalar", 1014 "result": [ 1015 1661020145.547, 1016 "1" 1017 ] 1018 } 1019 }`, 1020 expectedResponse: &queryrange.PrometheusInstantQueryResponse{ 1021 Status: queryrange.StatusSuccess, 1022 Headers: headers, 1023 Data: queryrange.PrometheusInstantQueryData{ 1024 ResultType: model.ValScalar.String(), 1025 Result: queryrange.PrometheusInstantQueryResult{ 1026 Result: &queryrange.PrometheusInstantQueryResult_Scalar{ 1027 Scalar: &cortexpb.Sample{TimestampMs: 1661020145547, Value: 1}, 1028 }, 1029 }, 1030 }, 1031 }, 1032 }, 1033 { 1034 name: "string", 1035 body: `{ 1036 "status": "success", 1037 "data": { 1038 "resultType": "string", 1039 "result": [ 1040 1661020232.424, 1041 "test" 1042 ] 1043 } 1044 }`, 1045 expectedResponse: &queryrange.PrometheusInstantQueryResponse{ 1046 Status: queryrange.StatusSuccess, 1047 Headers: headers, 1048 Data: queryrange.PrometheusInstantQueryData{ 1049 ResultType: model.ValString.String(), 1050 Result: queryrange.PrometheusInstantQueryResult{ 1051 Result: &queryrange.PrometheusInstantQueryResult_StringSample{ 1052 StringSample: &queryrange.StringSample{TimestampMs: 1661020232424, Value: "test"}, 1053 }, 1054 }, 1055 }, 1056 }, 1057 }, 1058 { 1059 name: "empty matrix", 1060 body: `{ 1061 "status": "success", 1062 "data": { 1063 "resultType": "matrix", 1064 "result": [ 1065 1066 ] 1067 } 1068 }`, 1069 expectedResponse: &queryrange.PrometheusInstantQueryResponse{ 1070 Status: queryrange.StatusSuccess, 1071 Headers: headers, 1072 Data: queryrange.PrometheusInstantQueryData{ 1073 ResultType: model.ValMatrix.String(), 1074 Result: queryrange.PrometheusInstantQueryResult{ 1075 Result: &queryrange.PrometheusInstantQueryResult_Matrix{ 1076 Matrix: &queryrange.Matrix{ 1077 SampleStreams: []*queryrange.SampleStream{}, 1078 }, 1079 }, 1080 }, 1081 }, 1082 }, 1083 }, 1084 { 1085 name: "matrix", 1086 body: `{ 1087 "status": "success", 1088 "data": { 1089 "resultType": "matrix", 1090 "result": [ 1091 { 1092 "metric": { 1093 "__name__": "up", 1094 "instance": "localhost:9090", 1095 "job": "prometheus" 1096 }, 1097 "values": [ 1098 [ 1099 1661020250.310, 1100 "1" 1101 ], 1102 [ 1103 1661020265.309, 1104 "1" 1105 ], 1106 [ 1107 1661020280.309, 1108 "1" 1109 ], 1110 [ 1111 1661020295.310, 1112 "1" 1113 ] 1114 ] 1115 } 1116 ] 1117 } 1118 }`, 1119 expectedResponse: &queryrange.PrometheusInstantQueryResponse{ 1120 Status: queryrange.StatusSuccess, 1121 Headers: headers, 1122 Data: queryrange.PrometheusInstantQueryData{ 1123 ResultType: model.ValMatrix.String(), 1124 Result: queryrange.PrometheusInstantQueryResult{ 1125 Result: &queryrange.PrometheusInstantQueryResult_Matrix{ 1126 Matrix: &queryrange.Matrix{ 1127 SampleStreams: []*queryrange.SampleStream{ 1128 { 1129 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 1130 "__name__": "up", 1131 "instance": "localhost:9090", 1132 "job": "prometheus", 1133 })), 1134 Samples: []cortexpb.Sample{ 1135 {TimestampMs: 1661020250310, Value: 1}, 1136 {TimestampMs: 1661020265309, Value: 1}, 1137 {TimestampMs: 1661020280309, Value: 1}, 1138 {TimestampMs: 1661020295310, Value: 1}, 1139 }, 1140 }, 1141 }, 1142 }, 1143 }, 1144 }, 1145 }, 1146 }, 1147 }, 1148 { 1149 name: "matrix with multiple metrics", 1150 body: `{ 1151 "status": "success", 1152 "data": { 1153 "resultType": "matrix", 1154 "result": [ 1155 { 1156 "metric": { 1157 "__name__": "prometheus_http_requests_total", 1158 "code": "200", 1159 "handler": "/favicon.ico", 1160 "instance": "localhost:9090", 1161 "job": "prometheus" 1162 }, 1163 "values": [ 1164 [ 1165 1661020430.311, 1166 "1" 1167 ], 1168 [ 1169 1661020445.312, 1170 "1" 1171 ], 1172 [ 1173 1661020460.313, 1174 "1" 1175 ], 1176 [ 1177 1661020475.313, 1178 "1" 1179 ] 1180 ] 1181 }, 1182 { 1183 "metric": { 1184 "__name__": "prometheus_http_requests_total", 1185 "code": "200", 1186 "handler": "/metrics", 1187 "instance": "localhost:9090", 1188 "job": "prometheus" 1189 }, 1190 "values": [ 1191 [ 1192 1661020430.311, 1193 "33" 1194 ], 1195 [ 1196 1661020445.312, 1197 "34" 1198 ], 1199 [ 1200 1661020460.313, 1201 "35" 1202 ], 1203 [ 1204 1661020475.313, 1205 "36" 1206 ] 1207 ] 1208 } 1209 ] 1210 } 1211 }`, 1212 expectedResponse: &queryrange.PrometheusInstantQueryResponse{ 1213 Status: queryrange.StatusSuccess, 1214 Headers: headers, 1215 Data: queryrange.PrometheusInstantQueryData{ 1216 ResultType: model.ValMatrix.String(), 1217 Result: queryrange.PrometheusInstantQueryResult{ 1218 Result: &queryrange.PrometheusInstantQueryResult_Matrix{ 1219 Matrix: &queryrange.Matrix{ 1220 SampleStreams: []*queryrange.SampleStream{ 1221 { 1222 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 1223 "__name__": "prometheus_http_requests_total", 1224 "code": "200", 1225 "handler": "/favicon.ico", 1226 "instance": "localhost:9090", 1227 "job": "prometheus", 1228 })), 1229 Samples: []cortexpb.Sample{ 1230 {TimestampMs: 1661020430311, Value: 1}, 1231 {TimestampMs: 1661020445312, Value: 1}, 1232 {TimestampMs: 1661020460313, Value: 1}, 1233 {TimestampMs: 1661020475313, Value: 1}, 1234 }, 1235 }, 1236 { 1237 Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{ 1238 "__name__": "prometheus_http_requests_total", 1239 "code": "200", 1240 "handler": "/metrics", 1241 "instance": "localhost:9090", 1242 "job": "prometheus", 1243 })), 1244 Samples: []cortexpb.Sample{ 1245 {TimestampMs: 1661020430311, Value: 33}, 1246 {TimestampMs: 1661020445312, Value: 34}, 1247 {TimestampMs: 1661020460313, Value: 35}, 1248 {TimestampMs: 1661020475313, Value: 36}, 1249 }, 1250 }, 1251 }, 1252 }, 1253 }, 1254 }, 1255 }, 1256 }, 1257 }, 1258 } { 1259 resp := &http.Response{ 1260 StatusCode: 200, 1261 Header: http.Header{"Content-Type": []string{"application/json"}}, 1262 Body: io.NopCloser(bytes.NewBuffer([]byte(tc.body))), 1263 } 1264 gotResponse, err := codec.DecodeResponse(context.Background(), resp, nil) 1265 testutil.Equals(t, tc.expectedErr, err) 1266 testutil.Equals(t, tc.expectedResponse, gotResponse) 1267 } 1268 } 1269 1270 func Test_sortPlanForQuery(t *testing.T) { 1271 tc := []struct { 1272 query string 1273 expectedPlan sortPlan 1274 err bool 1275 }{ 1276 { 1277 query: "invalid(10, up)", 1278 expectedPlan: mergeOnly, 1279 err: true, 1280 }, 1281 { 1282 query: "topk(10, up)", 1283 expectedPlan: mergeOnly, 1284 err: false, 1285 }, 1286 { 1287 query: "bottomk(10, up)", 1288 expectedPlan: mergeOnly, 1289 err: false, 1290 }, 1291 { 1292 query: "1 + topk(10, up)", 1293 expectedPlan: sortByLabels, 1294 err: false, 1295 }, 1296 { 1297 query: "1 + sort_desc(sum by (job) (up) )", 1298 expectedPlan: sortByValuesDesc, 1299 err: false, 1300 }, 1301 { 1302 query: "sort(topk by (job) (10, up))", 1303 expectedPlan: sortByValuesAsc, 1304 err: false, 1305 }, 1306 { 1307 query: "topk(5, up) by (job) + sort_desc(up)", 1308 expectedPlan: sortByValuesDesc, 1309 err: false, 1310 }, 1311 { 1312 query: "sort(up) + topk(5, up) by (job)", 1313 expectedPlan: sortByValuesAsc, 1314 err: false, 1315 }, 1316 { 1317 query: "sum(up) by (job)", 1318 expectedPlan: sortByLabels, 1319 err: false, 1320 }, 1321 } 1322 1323 for _, tc := range tc { 1324 t.Run(tc.query, func(t *testing.T) { 1325 p, err := sortPlanForQuery(tc.query) 1326 if tc.err { 1327 testutil.NotOk(t, err) 1328 } else { 1329 testutil.Ok(t, err) 1330 testutil.Equals(t, tc.expectedPlan, p) 1331 } 1332 }) 1333 } 1334 }