diff --git a/pkg/queryfrontend/cache.go b/pkg/queryfrontend/cache.go index 8ed069e71a..9b0492ab25 100644 --- a/pkg/queryfrontend/cache.go +++ b/pkg/queryfrontend/cache.go @@ -36,7 +36,7 @@ func (t thanosCacheKeyGenerator) GenerateCacheKey(_ string, r queryrange.Request } return fmt.Sprintf("%s:%d:%d:%d", tr.Query, tr.Step, currentInterval, i) case *ThanosLabelsRequest: - return fmt.Sprintf("%s:%d", tr.Label, currentInterval) + return fmt.Sprintf("%s:%s:%d", tr.Label, tr.Matchers, currentInterval) case *ThanosSeriesRequest: return fmt.Sprintf("%s:%d", tr.Matchers, currentInterval) } diff --git a/pkg/queryfrontend/cache_test.go b/pkg/queryfrontend/cache_test.go index 3504419e7b..641be156a0 100644 --- a/pkg/queryfrontend/cache_test.go +++ b/pkg/queryfrontend/cache_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/cortexproject/cortex/pkg/querier/queryrange" + "github.com/prometheus/prometheus/pkg/labels" "github.com/thanos-io/thanos/pkg/testutil" ) @@ -76,6 +77,61 @@ func TestGenerateCacheKey(t *testing.T) { }, expected: "up:10000:0:0", }, + { + name: "label names, no matcher", + req: &ThanosLabelsRequest{ + Start: 0, + }, + expected: ":[]:0", + }, + { + name: "label names, single matcher", + req: &ThanosLabelsRequest{ + Start: 0, + Matchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}}, + }, + expected: `:[[foo="bar"]]:0`, + }, + { + name: "label names, multiple matchers", + req: &ThanosLabelsRequest{ + Start: 0, + Matchers: [][]*labels.Matcher{ + {labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}, + {labels.MustNewMatcher(labels.MatchEqual, "baz", "qux")}, + }, + }, + expected: `:[[foo="bar"] [baz="qux"]]:0`, + }, + { + name: "label values, no matcher", + req: &ThanosLabelsRequest{ + Start: 0, + Label: "up", + }, + expected: "up:[]:0", + }, + { + name: "label values, single matcher", + req: &ThanosLabelsRequest{ + Start: 0, + Label: "up", + Matchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}}, + }, + expected: `up:[[foo="bar"]]:0`, + }, + { + name: "label values, multiple matchers", + req: &ThanosLabelsRequest{ + Start: 0, + Label: "up", + Matchers: [][]*labels.Matcher{ + {labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}, + {labels.MustNewMatcher(labels.MatchEqual, "baz", "qux")}, + }, + }, + expected: `up:[[foo="bar"] [baz="qux"]]:0`, + }, } { key := splitter.GenerateCacheKey("", tc.req) testutil.Equals(t, tc.expected, key) diff --git a/pkg/queryfrontend/labels_codec.go b/pkg/queryfrontend/labels_codec.go index 8fc0c6a240..76e34f2a6c 100644 --- a/pkg/queryfrontend/labels_codec.go +++ b/pkg/queryfrontend/labels_codec.go @@ -139,6 +139,9 @@ func (c labelsCodec) EncodeRequest(ctx context.Context, r queryrange.Request) (* "end": []string{encodeTime(thanosReq.End)}, queryv1.PartialResponseParam: []string{strconv.FormatBool(thanosReq.PartialResponse)}, } + if len(thanosReq.Matchers) > 0 { + params[queryv1.MatcherParam] = matchersToStringSlice(thanosReq.Matchers) + } if len(thanosReq.StoreMatchers) > 0 { params[queryv1.StoreMatcherParam] = matchersToStringSlice(thanosReq.StoreMatchers) } @@ -278,6 +281,11 @@ func (c labelsCodec) parseLabelsRequest(r *http.Request, op string) (queryrange. return nil, err } + result.Matchers, err = parseMatchersParam(r.Form, queryv1.MatcherParam) + if err != nil { + return nil, err + } + result.PartialResponse, err = parsePartialResponseParam(r.FormValue(queryv1.PartialResponseParam), c.partialResponse) if err != nil { return nil, err diff --git a/pkg/queryfrontend/labels_codec_test.go b/pkg/queryfrontend/labels_codec_test.go index a0e6980716..441853d599 100644 --- a/pkg/queryfrontend/labels_codec_test.go +++ b/pkg/queryfrontend/labels_codec_test.go @@ -91,19 +91,20 @@ func TestLabelsCodec_DecodeRequest(t *testing.T) { }, { name: "label_names partial_response default to true", - url: "/api/v1/labels?start=123&end=456", + url: `/api/v1/labels?start=123&end=456&match[]={foo="bar"}`, partialResponse: true, expectedRequest: &ThanosLabelsRequest{ Path: "/api/v1/labels", Start: 123000, End: 456000, PartialResponse: true, + Matchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}}, StoreMatchers: [][]*labels.Matcher{}, }, }, { name: "label_values partial_response default to true", - url: "/api/v1/label/__name__/values?start=123&end=456", + url: `/api/v1/label/__name__/values?start=123&end=456&match[]={foo="bar"}`, partialResponse: true, expectedRequest: &ThanosLabelsRequest{ Path: "/api/v1/label/__name__/values", @@ -111,6 +112,7 @@ func TestLabelsCodec_DecodeRequest(t *testing.T) { End: 456000, PartialResponse: true, Label: "__name__", + Matchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}}, StoreMatchers: [][]*labels.Matcher{}, }, }, @@ -130,13 +132,14 @@ func TestLabelsCodec_DecodeRequest(t *testing.T) { }, { name: "partial_response default to false, but set to true in query", - url: "/api/v1/labels?start=123&end=456&partial_response=true", + url: `/api/v1/labels?start=123&end=456&partial_response=true&match[]={foo="bar"}`, partialResponse: false, expectedRequest: &ThanosLabelsRequest{ Path: "/api/v1/labels", Start: 123000, End: 456000, PartialResponse: true, + Matchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}}, StoreMatchers: [][]*labels.Matcher{}, }, }, @@ -145,9 +148,10 @@ func TestLabelsCodec_DecodeRequest(t *testing.T) { url: `/api/v1/labels?start=123&end=456&storeMatch[]={__address__="localhost:10901", cluster="test"}`, partialResponse: false, expectedRequest: &ThanosLabelsRequest{ - Path: "/api/v1/labels", - Start: 123000, - End: 456000, + Path: "/api/v1/labels", + Start: 123000, + End: 456000, + Matchers: [][]*labels.Matcher{}, StoreMatchers: [][]*labels.Matcher{ { labels.MustNewMatcher(labels.MatchEqual, "__address__", "localhost:10901"), diff --git a/pkg/queryfrontend/request.go b/pkg/queryfrontend/request.go index a2ba1ec15d..c835adb21b 100644 --- a/pkg/queryfrontend/request.go +++ b/pkg/queryfrontend/request.go @@ -103,6 +103,7 @@ type ThanosLabelsRequest struct { End int64 Label string Path string + Matchers [][]*labels.Matcher StoreMatchers [][]*labels.Matcher PartialResponse bool CachingOptions queryrange.CachingOptions @@ -143,6 +144,7 @@ func (r *ThanosLabelsRequest) LogToSpan(sp opentracing.Span) { otlog.String("start", timestamp.Time(r.GetStart()).String()), otlog.String("end", timestamp.Time(r.GetEnd()).String()), otlog.Bool("partial_response", r.PartialResponse), + otlog.Object("matchers", r.Matchers), otlog.Object("storeMatchers", r.StoreMatchers), } if r.Label != "" { diff --git a/pkg/queryfrontend/roundtrip_test.go b/pkg/queryfrontend/roundtrip_test.go index d6ceff0175..982985d2b5 100644 --- a/pkg/queryfrontend/roundtrip_test.go +++ b/pkg/queryfrontend/roundtrip_test.go @@ -507,6 +507,14 @@ func TestRoundTripLabelsCacheMiddleware(t *testing.T) { End: 2 * hour, } + // Same query params as testRequest, but with Matchers + testRequestWithMatchers := &ThanosLabelsRequest{ + Path: "/api/v1/labels", + Start: 0, + End: 2 * hour, + Matchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}}, + } + // Same query params as testRequest, but with storeMatchers testRequestWithStoreMatchers := &ThanosLabelsRequest{ Path: "/api/v1/labels", @@ -522,6 +530,14 @@ func TestRoundTripLabelsCacheMiddleware(t *testing.T) { Label: "foo", } + testLabelValuesRequestFooWithMatchers := &ThanosLabelsRequest{ + Path: "/api/v1/label/foo/values", + Start: 0, + End: 2 * hour, + Label: "foo", + Matchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}}, + } + testLabelValuesRequestBar := &ThanosLabelsRequest{ Path: "/api/v1/label/bar/values", Start: 0, @@ -565,10 +581,14 @@ func TestRoundTripLabelsCacheMiddleware(t *testing.T) { }{ {name: "first request", req: testRequest, expected: 1}, {name: "same request as the first one, directly use cache", req: testRequest, expected: 1}, - {name: "storeMatchers requests won't go to cache", req: testRequestWithStoreMatchers, expected: 2}, - {name: "label values request label name foo", req: testLabelValuesRequestFoo, expected: 3}, - {name: "same label values query, use cache", req: testLabelValuesRequestFoo, expected: 3}, - {name: "label values request different label", req: testLabelValuesRequestBar, expected: 4}, + {name: "matchers requests won't go to cache", req: testRequestWithMatchers, expected: 2}, + {name: "same matchers requests, use cache", req: testRequestWithMatchers, expected: 2}, + {name: "storeMatchers requests won't go to cache", req: testRequestWithStoreMatchers, expected: 3}, + {name: "label values request label name foo", req: testLabelValuesRequestFoo, expected: 4}, + {name: "same label values query, use cache", req: testLabelValuesRequestFoo, expected: 4}, + {name: "label values query with matchers, won't go to cache", req: testLabelValuesRequestFooWithMatchers, expected: 5}, + {name: "same label values query with matchers, use cache", req: testLabelValuesRequestFooWithMatchers, expected: 5}, + {name: "label values request different label", req: testLabelValuesRequestBar, expected: 6}, { name: "request but will be partitioned", req: &ThanosLabelsRequest{ @@ -576,7 +596,7 @@ func TestRoundTripLabelsCacheMiddleware(t *testing.T) { Start: 0, End: 25 * hour, }, - expected: 6, + expected: 8, }, { name: "same query as the previous one", @@ -585,7 +605,7 @@ func TestRoundTripLabelsCacheMiddleware(t *testing.T) { Start: 0, End: 25 * hour, }, - expected: 6, + expected: 8, }, } {