github.com/thanos-io/thanos@v0.32.5/pkg/queryfrontend/labels_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 "encoding/json" 10 "fmt" 11 io "io" 12 "net/http" 13 "testing" 14 "time" 15 16 "github.com/prometheus/prometheus/model/labels" 17 "github.com/weaveworks/common/httpgrpc" 18 19 "github.com/efficientgo/core/testutil" 20 "github.com/thanos-io/thanos/internal/cortex/querier/queryrange" 21 queryv1 "github.com/thanos-io/thanos/pkg/api/query" 22 "github.com/thanos-io/thanos/pkg/store/labelpb" 23 ) 24 25 func TestLabelsCodec_DecodeRequest(t *testing.T) { 26 for _, tc := range []struct { 27 name string 28 url string 29 partialResponse bool 30 expectedError error 31 expectedRequest ThanosRequestStoreMatcherGetter 32 }{ 33 { 34 name: "label_names cannot parse start", 35 url: "/api/v1/labels?start=foo", 36 partialResponse: false, 37 expectedError: httpgrpc.Errorf(http.StatusBadRequest, `cannot parse "foo" to a valid timestamp`), 38 }, 39 { 40 name: "label_values cannot parse start", 41 url: "/api/v1/label/__name__/values?start=foo", 42 partialResponse: false, 43 expectedError: httpgrpc.Errorf(http.StatusBadRequest, `cannot parse "foo" to a valid timestamp`), 44 }, 45 { 46 name: "series cannot parse start", 47 url: "/api/v1/series?start=foo", 48 partialResponse: false, 49 expectedError: httpgrpc.Errorf(http.StatusBadRequest, `cannot parse "foo" to a valid timestamp`), 50 }, 51 { 52 name: "label_names cannot parse end", 53 url: "/api/v1/labels?start=123&end=bar", 54 partialResponse: false, 55 expectedError: httpgrpc.Errorf(http.StatusBadRequest, `cannot parse "bar" to a valid timestamp`), 56 }, 57 { 58 name: "label_values cannot parse end", 59 url: "/api/v1/label/__name__/values?start=123&end=bar", 60 partialResponse: false, 61 expectedError: httpgrpc.Errorf(http.StatusBadRequest, `cannot parse "bar" to a valid timestamp`), 62 }, 63 { 64 name: "series cannot parse end", 65 url: "/api/v1/series?start=123&end=bar", 66 partialResponse: false, 67 expectedError: httpgrpc.Errorf(http.StatusBadRequest, `cannot parse "bar" to a valid timestamp`), 68 }, 69 { 70 name: "label_names end before start", 71 url: "/api/v1/labels?start=123&end=0", 72 partialResponse: false, 73 expectedError: errEndBeforeStart, 74 }, 75 { 76 name: "label_values end before start", 77 url: "/api/v1/label/__name__/values?start=123&end=0", 78 partialResponse: false, 79 expectedError: errEndBeforeStart, 80 }, 81 { 82 name: "series end before start", 83 url: "/api/v1/series?start=123&end=0", 84 partialResponse: false, 85 expectedError: errEndBeforeStart, 86 }, 87 { 88 name: "cannot parse partial_response", 89 url: "/api/v1/labels?start=123&end=456&partial_response=boo", 90 partialResponse: false, 91 expectedError: httpgrpc.Errorf(http.StatusBadRequest, "cannot parse parameter partial_response"), 92 }, 93 { 94 name: "label_names partial_response default to true", 95 url: `/api/v1/labels?start=123&end=456&match[]={foo="bar"}`, 96 partialResponse: true, 97 expectedRequest: &ThanosLabelsRequest{ 98 Path: "/api/v1/labels", 99 Start: 123000, 100 End: 456000, 101 PartialResponse: true, 102 Matchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}}, 103 StoreMatchers: [][]*labels.Matcher{}, 104 }, 105 }, 106 { 107 name: "label_values partial_response default to true", 108 url: `/api/v1/label/__name__/values?start=123&end=456&match[]={foo="bar"}`, 109 partialResponse: true, 110 expectedRequest: &ThanosLabelsRequest{ 111 Path: "/api/v1/label/__name__/values", 112 Start: 123000, 113 End: 456000, 114 PartialResponse: true, 115 Label: "__name__", 116 Matchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}}, 117 StoreMatchers: [][]*labels.Matcher{}, 118 }, 119 }, 120 { 121 name: "series partial_response default to true", 122 url: `/api/v1/series?start=123&end=456&match[]={foo="bar"}`, 123 partialResponse: true, 124 expectedRequest: &ThanosSeriesRequest{ 125 Path: "/api/v1/series", 126 Start: 123000, 127 End: 456000, 128 PartialResponse: true, 129 Dedup: true, 130 Matchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}}, 131 StoreMatchers: [][]*labels.Matcher{}, 132 }, 133 }, 134 { 135 name: "partial_response default to false, but set to true in query", 136 url: `/api/v1/labels?start=123&end=456&partial_response=true&match[]={foo="bar"}`, 137 partialResponse: false, 138 expectedRequest: &ThanosLabelsRequest{ 139 Path: "/api/v1/labels", 140 Start: 123000, 141 End: 456000, 142 PartialResponse: true, 143 Matchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}}, 144 StoreMatchers: [][]*labels.Matcher{}, 145 }, 146 }, 147 { 148 name: "storeMatchers", 149 url: `/api/v1/labels?start=123&end=456&storeMatch[]={__address__="localhost:10901", cluster="test"}`, 150 partialResponse: false, 151 expectedRequest: &ThanosLabelsRequest{ 152 Path: "/api/v1/labels", 153 Start: 123000, 154 End: 456000, 155 Matchers: [][]*labels.Matcher{}, 156 StoreMatchers: [][]*labels.Matcher{ 157 { 158 labels.MustNewMatcher(labels.MatchEqual, "__address__", "localhost:10901"), 159 labels.MustNewMatcher(labels.MatchEqual, "cluster", "test"), 160 }, 161 }, 162 }, 163 }, 164 { 165 name: "series dedup set to false", 166 url: `/api/v1/series?start=123&dedup=false&end=456&match[]={foo="bar"}`, 167 partialResponse: false, 168 expectedRequest: &ThanosSeriesRequest{ 169 Path: "/api/v1/series", 170 Start: 123000, 171 End: 456000, 172 Dedup: false, 173 Matchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}}, 174 StoreMatchers: [][]*labels.Matcher{}, 175 }, 176 }, 177 { 178 name: "series replicaLabels", 179 url: "/api/v1/series?start=123&end=456&replicaLabels[]=foo&replicaLabels[]=bar", 180 partialResponse: false, 181 expectedRequest: &ThanosSeriesRequest{ 182 Path: "/api/v1/series", 183 Start: 123000, 184 End: 456000, 185 Dedup: true, 186 ReplicaLabels: []string{"foo", "bar"}, 187 Matchers: [][]*labels.Matcher{}, 188 StoreMatchers: [][]*labels.Matcher{}, 189 }, 190 }, 191 } { 192 t.Run(tc.name, func(t *testing.T) { 193 r, err := http.NewRequest(http.MethodGet, tc.url, nil) 194 testutil.Ok(t, err) 195 196 codec := NewThanosLabelsCodec(tc.partialResponse, 2*time.Hour) 197 req, err := codec.DecodeRequest(context.Background(), r, nil) 198 if tc.expectedError != nil { 199 testutil.Equals(t, tc.expectedError, err) 200 } else { 201 testutil.Ok(t, err) 202 testutil.Equals(t, tc.expectedRequest, req) 203 } 204 }) 205 } 206 } 207 208 func TestLabelsCodec_EncodeRequest(t *testing.T) { 209 const ( 210 start = "start" 211 end = "end" 212 startTime = "123" 213 endTime = "456" 214 trueStr = "true" 215 ) 216 for _, tc := range []struct { 217 name string 218 expectedError error 219 checkFunc func(r *http.Request) bool 220 req queryrange.Request 221 }{ 222 { 223 name: "prometheus request, invalid format", 224 req: &queryrange.PrometheusRequest{}, 225 expectedError: httpgrpc.Errorf(http.StatusInternalServerError, "invalid request format"), 226 }, 227 { 228 name: "thanos query range request, invalid format", 229 req: &ThanosQueryRangeRequest{}, 230 expectedError: httpgrpc.Errorf(http.StatusInternalServerError, "invalid request format"), 231 }, 232 { 233 name: "thanos labels names request", 234 req: &ThanosLabelsRequest{Start: 123000, End: 456000, Path: "/api/v1/labels"}, 235 checkFunc: func(r *http.Request) bool { 236 return r.FormValue(start) == startTime && 237 r.FormValue(end) == endTime && 238 r.URL.Path == "/api/v1/labels" 239 }, 240 }, 241 { 242 name: "thanos labels values request", 243 req: &ThanosLabelsRequest{Start: 123000, End: 456000, Path: "/api/v1/label/__name__/values", Label: "__name__"}, 244 checkFunc: func(r *http.Request) bool { 245 return r.URL.Query().Get(start) == startTime && 246 r.URL.Query().Get(end) == endTime && 247 r.URL.Path == "/api/v1/label/__name__/values" 248 }, 249 }, 250 { 251 name: "thanos labels values request, partial response set to true", 252 req: &ThanosLabelsRequest{Start: 123000, End: 456000, Path: "/api/v1/label/__name__/values", Label: "__name__", PartialResponse: true}, 253 checkFunc: func(r *http.Request) bool { 254 return r.URL.Query().Get(start) == startTime && 255 r.URL.Query().Get(end) == endTime && 256 r.URL.Path == "/api/v1/label/__name__/values" && 257 r.URL.Query().Get(queryv1.PartialResponseParam) == trueStr 258 }, 259 }, 260 { 261 name: "thanos series request with empty matchers", 262 req: &ThanosSeriesRequest{Start: 123000, End: 456000, Path: "/api/v1/series"}, 263 checkFunc: func(r *http.Request) bool { 264 return r.FormValue(start) == startTime && 265 r.FormValue(end) == endTime && 266 r.URL.Path == "/api/v1/series" 267 }, 268 }, 269 { 270 name: "thanos series request", 271 req: &ThanosSeriesRequest{ 272 Start: 123000, 273 End: 456000, 274 Path: "/api/v1/series", 275 Matchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "cluster", "test")}}, 276 }, 277 checkFunc: func(r *http.Request) bool { 278 return r.FormValue(start) == startTime && 279 r.FormValue(end) == endTime && 280 r.FormValue(queryv1.MatcherParam) == `{cluster="test"}` && 281 r.URL.Path == "/api/v1/series" 282 }, 283 }, 284 { 285 name: "thanos series request, dedup to true", 286 req: &ThanosSeriesRequest{ 287 Start: 123000, 288 End: 456000, 289 Path: "/api/v1/series", 290 Dedup: true, 291 }, 292 checkFunc: func(r *http.Request) bool { 293 return r.FormValue(start) == startTime && 294 r.FormValue(end) == endTime && 295 r.FormValue(queryv1.DedupParam) == trueStr && 296 r.URL.Path == "/api/v1/series" 297 }, 298 }, 299 } { 300 t.Run(tc.name, func(t *testing.T) { 301 // Default partial response value doesn't matter when encoding requests. 302 codec := NewThanosLabelsCodec(false, time.Hour*2) 303 r, err := codec.EncodeRequest(context.TODO(), tc.req) 304 if tc.expectedError != nil { 305 testutil.Equals(t, tc.expectedError, err) 306 } else { 307 testutil.Ok(t, err) 308 testutil.Equals(t, true, tc.checkFunc(r)) 309 } 310 }) 311 } 312 } 313 314 func TestLabelsCodec_DecodeResponse(t *testing.T) { 315 labelResponse := &ThanosLabelsResponse{ 316 Status: "success", 317 Data: []string{"__name__"}, 318 } 319 labelsData, err := json.Marshal(labelResponse) 320 testutil.Ok(t, err) 321 322 labelResponseWithHeaders := &ThanosLabelsResponse{ 323 Status: "success", 324 Data: []string{"__name__"}, 325 Headers: []*ResponseHeader{{Name: cacheControlHeader, Values: []string{noStoreValue}}}, 326 } 327 labelsDataWithHeaders, err := json.Marshal(labelResponseWithHeaders) 328 testutil.Ok(t, err) 329 330 seriesResponse := &ThanosSeriesResponse{ 331 Status: "success", 332 Data: []labelpb.ZLabelSet{{Labels: []labelpb.ZLabel{{Name: "foo", Value: "bar"}}}}, 333 } 334 seriesData, err := json.Marshal(seriesResponse) 335 testutil.Ok(t, err) 336 337 seriesResponseWithHeaders := &ThanosSeriesResponse{ 338 Status: "success", 339 Data: []labelpb.ZLabelSet{{Labels: []labelpb.ZLabel{{Name: "foo", Value: "bar"}}}}, 340 Headers: []*ResponseHeader{{Name: cacheControlHeader, Values: []string{noStoreValue}}}, 341 } 342 seriesDataWithHeaders, err := json.Marshal(seriesResponseWithHeaders) 343 testutil.Ok(t, err) 344 345 for _, tc := range []struct { 346 name string 347 expectedError error 348 res http.Response 349 req queryrange.Request 350 expectedResponse queryrange.Response 351 }{ 352 { 353 name: "prometheus request, invalid for labelsCodec", 354 req: &queryrange.PrometheusRequest{}, 355 res: http.Response{StatusCode: 200, Body: io.NopCloser(bytes.NewBuffer([]byte("foo")))}, 356 expectedError: httpgrpc.Errorf(http.StatusInternalServerError, "invalid request type"), 357 }, 358 { 359 name: "thanos query range request, invalid for labelsCodec", 360 req: &ThanosQueryRangeRequest{}, 361 res: http.Response{StatusCode: 200, Body: io.NopCloser(bytes.NewBuffer([]byte("foo")))}, 362 expectedError: httpgrpc.Errorf(http.StatusInternalServerError, "invalid request type"), 363 }, 364 { 365 name: "thanos labels request", 366 req: &ThanosLabelsRequest{}, 367 res: http.Response{StatusCode: 200, Body: io.NopCloser(bytes.NewBuffer(labelsData))}, 368 expectedResponse: labelResponse, 369 }, 370 { 371 name: "thanos labels request with HTTP headers", 372 req: &ThanosLabelsRequest{}, 373 res: http.Response{ 374 StatusCode: 200, Body: io.NopCloser(bytes.NewBuffer(labelsDataWithHeaders)), 375 Header: map[string][]string{ 376 cacheControlHeader: {noStoreValue}, 377 }, 378 }, 379 expectedResponse: labelResponseWithHeaders, 380 }, 381 { 382 name: "thanos series request", 383 req: &ThanosSeriesRequest{}, 384 res: http.Response{StatusCode: 200, Body: io.NopCloser(bytes.NewBuffer(seriesData))}, 385 expectedResponse: seriesResponse, 386 }, 387 { 388 name: "thanos series request with HTTP headers", 389 req: &ThanosSeriesRequest{}, 390 res: http.Response{ 391 StatusCode: 200, Body: io.NopCloser(bytes.NewBuffer(seriesDataWithHeaders)), 392 Header: map[string][]string{ 393 cacheControlHeader: {noStoreValue}, 394 }, 395 }, 396 expectedResponse: seriesResponseWithHeaders, 397 }, 398 } { 399 t.Run(tc.name, func(t *testing.T) { 400 // Default partial response value doesn't matter when encoding requests. 401 codec := NewThanosLabelsCodec(false, time.Hour*2) 402 r, err := codec.DecodeResponse(context.TODO(), &tc.res, tc.req) 403 if tc.expectedError != nil { 404 testutil.Equals(t, err, tc.expectedError) 405 } else { 406 testutil.Ok(t, err) 407 testutil.Equals(t, tc.expectedResponse, r) 408 } 409 }) 410 } 411 } 412 413 func TestLabelsCodec_MergeResponse(t *testing.T) { 414 for _, tc := range []struct { 415 name string 416 expectedError error 417 responses []queryrange.Response 418 expectedResponse queryrange.Response 419 }{ 420 { 421 name: "Prometheus range query response format, not valid", 422 responses: []queryrange.Response{ 423 &queryrange.PrometheusResponse{Status: "success"}, 424 }, 425 expectedError: httpgrpc.Errorf(http.StatusInternalServerError, "invalid response format"), 426 }, 427 { 428 name: "Empty response", 429 responses: nil, 430 expectedResponse: &ThanosLabelsResponse{Status: queryrange.StatusSuccess, Data: []string{}}, 431 }, 432 { 433 name: "One label response", 434 responses: []queryrange.Response{ 435 &ThanosLabelsResponse{Status: "success", Data: []string{"localhost:9090", "localhost:9091"}}, 436 }, 437 expectedResponse: &ThanosLabelsResponse{Status: "success", Data: []string{"localhost:9090", "localhost:9091"}}, 438 }, 439 { 440 name: "One label response and two empty responses", 441 responses: []queryrange.Response{ 442 &ThanosLabelsResponse{Status: queryrange.StatusSuccess, Data: []string{}}, 443 &ThanosLabelsResponse{Status: "success", Data: []string{"localhost:9090", "localhost:9091"}}, 444 &ThanosLabelsResponse{Status: queryrange.StatusSuccess, Data: []string{}}, 445 }, 446 expectedResponse: &ThanosLabelsResponse{Status: "success", Data: []string{"localhost:9090", "localhost:9091"}}, 447 }, 448 { 449 name: "Multiple duplicate label responses", 450 responses: []queryrange.Response{ 451 &ThanosLabelsResponse{Status: "success", Data: []string{"localhost:9090", "localhost:9091"}}, 452 &ThanosLabelsResponse{Status: "success", Data: []string{"localhost:9091", "localhost:9092"}}, 453 &ThanosLabelsResponse{Status: "success", Data: []string{"localhost:9092", "localhost:9093"}}, 454 }, 455 expectedResponse: &ThanosLabelsResponse{Status: "success", 456 Data: []string{"localhost:9090", "localhost:9091", "localhost:9092", "localhost:9093"}}, 457 }, 458 // This case shouldn't happen because the responses from Querier are sorted. 459 { 460 name: "Multiple unordered label responses", 461 responses: []queryrange.Response{ 462 &ThanosLabelsResponse{Status: "success", Data: []string{"localhost:9093", "localhost:9092"}}, 463 &ThanosLabelsResponse{Status: "success", Data: []string{"localhost:9091", "localhost:9090"}}, 464 }, 465 expectedResponse: &ThanosLabelsResponse{Status: "success", 466 Data: []string{"localhost:9090", "localhost:9091", "localhost:9092", "localhost:9093"}}, 467 }, 468 { 469 name: "One series response", 470 responses: []queryrange.Response{ 471 &ThanosSeriesResponse{Status: "success", Data: []labelpb.ZLabelSet{{Labels: []labelpb.ZLabel{{Name: "foo", Value: "bar"}}}}}, 472 }, 473 expectedResponse: &ThanosSeriesResponse{Status: "success", Data: []labelpb.ZLabelSet{{Labels: []labelpb.ZLabel{{Name: "foo", Value: "bar"}}}}}, 474 }, 475 { 476 name: "One series response and two empty responses", 477 responses: []queryrange.Response{ 478 &ThanosSeriesResponse{Status: queryrange.StatusSuccess}, 479 &ThanosSeriesResponse{Status: "success", Data: []labelpb.ZLabelSet{{Labels: []labelpb.ZLabel{{Name: "foo", Value: "bar"}}}}}, 480 &ThanosSeriesResponse{Status: queryrange.StatusSuccess}, 481 }, 482 expectedResponse: &ThanosSeriesResponse{Status: "success", Data: []labelpb.ZLabelSet{{Labels: []labelpb.ZLabel{{Name: "foo", Value: "bar"}}}}}, 483 }, 484 { 485 name: "Multiple duplicate series responses", 486 responses: []queryrange.Response{ 487 &ThanosSeriesResponse{Status: "success", Data: []labelpb.ZLabelSet{{Labels: []labelpb.ZLabel{{Name: "foo", Value: "bar"}}}}}, 488 &ThanosSeriesResponse{Status: "success", Data: []labelpb.ZLabelSet{{Labels: []labelpb.ZLabel{{Name: "foo", Value: "bar"}}}}}, 489 &ThanosSeriesResponse{Status: "success", Data: []labelpb.ZLabelSet{{Labels: []labelpb.ZLabel{{Name: "foo", Value: "bar"}}}}}, 490 }, 491 expectedResponse: &ThanosSeriesResponse{Status: "success", Data: []labelpb.ZLabelSet{{Labels: []labelpb.ZLabel{{Name: "foo", Value: "bar"}}}}}, 492 }, 493 { 494 name: "Multiple unordered series responses", 495 responses: []queryrange.Response{ 496 &ThanosSeriesResponse{Status: "success", Data: []labelpb.ZLabelSet{ 497 {Labels: []labelpb.ZLabel{{Name: "foo", Value: "bar"}}}, 498 {Labels: []labelpb.ZLabel{{Name: "test", Value: "aaa"}, {Name: "instance", Value: "localhost:9090"}}}, 499 }}, 500 &ThanosSeriesResponse{Status: "success", Data: []labelpb.ZLabelSet{ 501 {Labels: []labelpb.ZLabel{{Name: "foo", Value: "aaa"}}}, 502 {Labels: []labelpb.ZLabel{{Name: "test", Value: "bbb"}, {Name: "instance", Value: "localhost:9091"}}}, 503 }}, 504 }, 505 expectedResponse: &ThanosSeriesResponse{Status: "success", Data: []labelpb.ZLabelSet{ 506 {Labels: []labelpb.ZLabel{{Name: "foo", Value: "aaa"}}}, 507 {Labels: []labelpb.ZLabel{{Name: "foo", Value: "bar"}}}, 508 {Labels: []labelpb.ZLabel{{Name: "test", Value: "aaa"}, {Name: "instance", Value: "localhost:9090"}}}, 509 {Labels: []labelpb.ZLabel{{Name: "test", Value: "bbb"}, {Name: "instance", Value: "localhost:9091"}}}, 510 }}, 511 }, 512 } { 513 t.Run(tc.name, func(t *testing.T) { 514 // Default partial response value doesn't matter when encoding requests. 515 codec := NewThanosLabelsCodec(false, time.Hour*2) 516 r, err := codec.MergeResponse(nil, tc.responses...) 517 if tc.expectedError != nil { 518 testutil.Equals(t, err, tc.expectedError) 519 } else { 520 testutil.Ok(t, err) 521 testutil.Equals(t, tc.expectedResponse, r) 522 } 523 }) 524 } 525 } 526 527 func BenchmarkLabelsCodecEncodeAndDecodeRequest(b *testing.B) { 528 codec := NewThanosLabelsCodec(false, time.Hour*2) 529 ctx := context.TODO() 530 531 b.Run("SeriesRequest", func(b *testing.B) { 532 req := &ThanosSeriesRequest{ 533 Start: 123000, 534 End: 456000, 535 Path: "/api/v1/series", 536 Dedup: true, 537 } 538 539 b.ReportAllocs() 540 b.ResetTimer() 541 542 for n := 0; n < b.N; n++ { 543 reqEnc, err := codec.EncodeRequest(ctx, req) 544 testutil.Ok(b, err) 545 _, err = codec.DecodeRequest(ctx, reqEnc, nil) 546 testutil.Ok(b, err) 547 } 548 }) 549 550 b.Run("LabelsRequest", func(b *testing.B) { 551 req := &ThanosLabelsRequest{ 552 Path: "/api/v1/labels", 553 Start: 123000, 554 End: 456000, 555 PartialResponse: true, 556 Matchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}}, 557 StoreMatchers: [][]*labels.Matcher{}, 558 } 559 560 b.ReportAllocs() 561 b.ResetTimer() 562 563 for n := 0; n < b.N; n++ { 564 reqEnc, err := codec.EncodeRequest(ctx, req) 565 testutil.Ok(b, err) 566 _, err = codec.DecodeRequest(ctx, reqEnc, nil) 567 testutil.Ok(b, err) 568 } 569 }) 570 } 571 572 func BenchmarkLabelsCodecDecodeResponse(b *testing.B) { 573 codec := NewThanosLabelsCodec(false, time.Hour*2) 574 ctx := context.TODO() 575 576 b.Run("SeriesResponse", func(b *testing.B) { 577 seriesData, err := json.Marshal(&ThanosSeriesResponse{ 578 Status: "success", 579 Data: []labelpb.ZLabelSet{{Labels: []labelpb.ZLabel{{Name: "foo", Value: "bar"}}}}, 580 }) 581 testutil.Ok(b, err) 582 583 b.ReportAllocs() 584 b.ResetTimer() 585 586 for n := 0; n < b.N; n++ { 587 _, err := codec.DecodeResponse( 588 ctx, 589 makeResponse(seriesData, false), 590 &ThanosSeriesRequest{}) 591 testutil.Ok(b, err) 592 } 593 }) 594 595 b.Run("SeriesResponseWithHeaders", func(b *testing.B) { 596 seriesDataWithHeaders, err := json.Marshal(&ThanosSeriesResponse{ 597 Status: "success", 598 Data: []labelpb.ZLabelSet{{Labels: []labelpb.ZLabel{{Name: "foo", Value: "bar"}}}}, 599 Headers: []*ResponseHeader{{Name: cacheControlHeader, Values: []string{noStoreValue}}}, 600 }) 601 testutil.Ok(b, err) 602 603 b.ReportAllocs() 604 b.ResetTimer() 605 606 for n := 0; n < b.N; n++ { 607 _, err := codec.DecodeResponse( 608 ctx, 609 makeResponse(seriesDataWithHeaders, true), 610 &ThanosSeriesRequest{}) 611 testutil.Ok(b, err) 612 } 613 }) 614 615 b.Run("LabelsResponse", func(b *testing.B) { 616 labelsData, err := json.Marshal(&ThanosLabelsResponse{ 617 Status: "success", 618 Data: []string{"__name__"}, 619 }) 620 testutil.Ok(b, err) 621 622 b.ReportAllocs() 623 b.ResetTimer() 624 625 for n := 0; n < b.N; n++ { 626 _, err := codec.DecodeResponse( 627 ctx, 628 makeResponse(labelsData, false), 629 &ThanosLabelsRequest{}) 630 testutil.Ok(b, err) 631 } 632 }) 633 634 b.Run("LabelsResponseWithHeaders", func(b *testing.B) { 635 labelsDataWithHeaders, err := json.Marshal(&ThanosLabelsResponse{ 636 Status: "success", 637 Data: []string{"__name__"}, 638 Headers: []*ResponseHeader{{Name: cacheControlHeader, Values: []string{noStoreValue}}}, 639 }) 640 testutil.Ok(b, err) 641 642 b.ReportAllocs() 643 b.ResetTimer() 644 645 for n := 0; n < b.N; n++ { 646 _, err := codec.DecodeResponse( 647 ctx, 648 makeResponse(labelsDataWithHeaders, true), 649 &ThanosLabelsRequest{}) 650 testutil.Ok(b, err) 651 } 652 }) 653 } 654 655 func BenchmarkLabelsCodecMergeResponses_1(b *testing.B) { 656 benchmarkMergeResponses(b, 1) 657 } 658 659 func BenchmarkLabelsCodecMergeResponses_10(b *testing.B) { 660 benchmarkMergeResponses(b, 10) 661 } 662 663 func BenchmarkLabelsCodecMergeResponses_100(b *testing.B) { 664 benchmarkMergeResponses(b, 100) 665 } 666 667 func BenchmarkLabelsCodecMergeResponses_1000(b *testing.B) { 668 benchmarkMergeResponses(b, 1000) 669 } 670 671 func benchmarkMergeResponses(b *testing.B, size int) { 672 codec := NewThanosLabelsCodec(false, time.Hour*2) 673 queryResLabel, queryResSeries := makeQueryRangeResponses(size) 674 675 b.Run("SeriesResponses", func(b *testing.B) { 676 b.ReportAllocs() 677 b.ResetTimer() 678 679 for i := 0; i < b.N; i++ { 680 _, _ = codec.MergeResponse(nil, queryResSeries...) 681 } 682 }) 683 684 b.Run("LabelsResponses", func(b *testing.B) { 685 b.ReportAllocs() 686 b.ResetTimer() 687 688 for i := 0; i < b.N; i++ { 689 _, _ = codec.MergeResponse(nil, queryResLabel...) 690 } 691 }) 692 693 } 694 695 func makeQueryRangeResponses(size int) ([]queryrange.Response, []queryrange.Response) { 696 labelResp := make([]queryrange.Response, 0, size) 697 seriesResp := make([]queryrange.Response, 0, size*2) 698 699 // Generate with some duplicated values. 700 for i := 0; i < size; i++ { 701 labelResp = append(labelResp, &ThanosLabelsResponse{ 702 Status: "success", 703 Data: []string{fmt.Sprintf("data-%d", i), fmt.Sprintf("data-%d", i+1)}, 704 }) 705 706 seriesResp = append( 707 seriesResp, 708 &ThanosSeriesResponse{ 709 Status: "success", 710 Data: []labelpb.ZLabelSet{{Labels: []labelpb.ZLabel{{Name: fmt.Sprintf("foo-%d", i), Value: fmt.Sprintf("bar-%d", i)}}}}, 711 }, 712 &ThanosSeriesResponse{ 713 Status: "success", 714 Data: []labelpb.ZLabelSet{{Labels: []labelpb.ZLabel{{Name: fmt.Sprintf("foo-%d", i+1), Value: fmt.Sprintf("bar-%d", i+1)}}}}, 715 }, 716 ) 717 } 718 719 return labelResp, seriesResp 720 } 721 722 func makeResponse(data []byte, withHeader bool) *http.Response { 723 r := &http.Response{ 724 StatusCode: 200, Body: io.NopCloser(bytes.NewBuffer(data)), 725 } 726 727 if withHeader { 728 r.Header = map[string][]string{ 729 cacheControlHeader: {noStoreValue}, 730 } 731 } 732 733 return r 734 }