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  }