diff --git a/CHANGELOG.md b/CHANGELOG.md index 9052590bee..c28beaf1e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ We use _breaking :warning:_ to mark changes that are not backward compatible (re - [#4107](https://github.com/thanos-io/thanos/pull/4107) Store: `LabelNames` and `LabelValues` now support label matchers. - [#4171](https://github.com/thanos-io/thanos/pull/4171) Docker: Busybox image updated to latest (1.33.1) - +- [#4176](https://github.com/thanos-io/thanos/pull/4176) Query API: Adds optional `Stats param` to return stats for query APIs ### Fixed - ### Changed diff --git a/pkg/api/query/v1.go b/pkg/api/query/v1.go index 866862acee..87154f5c07 100644 --- a/pkg/api/query/v1.go +++ b/pkg/api/query/v1.go @@ -42,6 +42,7 @@ import ( "github.com/prometheus/prometheus/promql/parser" "github.com/prometheus/prometheus/storage" + "github.com/prometheus/prometheus/util/stats" "github.com/thanos-io/thanos/pkg/api" "github.com/thanos-io/thanos/pkg/exemplars" "github.com/thanos-io/thanos/pkg/exemplars/exemplarspb" @@ -68,6 +69,7 @@ const ( MatcherParam = "match[]" StoreMatcherParam = "storeMatch[]" Step = "step" + Stats = "stats" ) // QueryAPI is an API used by Thanos Querier. @@ -189,9 +191,9 @@ func (qapi *QueryAPI) Register(r *route.Router, tracer opentracing.Tracer, logge } type queryData struct { - ResultType parser.ValueType `json:"resultType"` - Result parser.Value `json:"result"` - + ResultType parser.ValueType `json:"resultType"` + Result parser.Value `json:"result"` + Stats *stats.QueryStats `json:"stats,omitempty"` // Additional Thanos Response field. Warnings []error `json:"warnings,omitempty"` } @@ -283,7 +285,6 @@ func (qapi *QueryAPI) parseStep(r *http.Request, defaultRangeQueryStep time.Dura } return defaultRangeQueryStep, nil } - // Default step is used this way to make it consistent with UI. d := time.Duration(math.Max(float64(rangeSeconds/250), float64(defaultRangeQueryStep/time.Second))) * time.Second return d, nil @@ -364,9 +365,15 @@ func (qapi *QueryAPI) query(r *http.Request) (interface{}, []error, *api.ApiErro return nil, nil, &api.ApiError{Typ: api.ErrorExec, Err: res.Err} } + // Optional stats field in response if parameter "stats" is not empty. + var qs *stats.QueryStats + if r.FormValue(Stats) != "" { + qs = stats.NewQueryStats(qry.Stats()) + } return &queryData{ ResultType: res.Value.Type(), Result: res.Value, + Stats: qs, }, res.Warnings, nil } @@ -478,9 +485,15 @@ func (qapi *QueryAPI) queryRange(r *http.Request) (interface{}, []error, *api.Ap return nil, nil, &api.ApiError{Typ: api.ErrorExec, Err: res.Err} } + // Optional stats field in response if parameter "stats" is not empty. + var qs *stats.QueryStats + if r.FormValue(Stats) != "" { + qs = stats.NewQueryStats(qry.Stats()) + } return &queryData{ ResultType: res.Value.Type(), Result: res.Value, + Stats: qs, }, res.Warnings, nil } diff --git a/pkg/api/query/v1_test.go b/pkg/api/query/v1_test.go index 0302733e81..8ab6990c96 100644 --- a/pkg/api/query/v1_test.go +++ b/pkg/api/query/v1_test.go @@ -44,6 +44,7 @@ import ( "github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/tsdb" "github.com/prometheus/prometheus/tsdb/tsdbutil" + "github.com/prometheus/prometheus/util/stats" "github.com/thanos-io/thanos/pkg/compact" @@ -72,8 +73,16 @@ type endpointTestCase struct { response interface{} errType baseAPI.ErrorType } +type responeCompareFunction func(interface{}, interface{}) bool -func testEndpoint(t *testing.T, test endpointTestCase, name string) bool { +// Checks if both responses have Stats present or not. +func lookupStats(a interface{}, b interface{}) bool { + ra := a.(*queryData) + rb := b.(*queryData) + return (ra.Stats == nil && rb.Stats == nil) || (ra.Stats != nil && rb.Stats != nil) +} + +func testEndpoint(t *testing.T, test endpointTestCase, name string, responseCompareFunc responeCompareFunction) bool { return t.Run(name, func(t *testing.T) { // Build a context with the correct request params. ctx := context.Background() @@ -115,7 +124,7 @@ func testEndpoint(t *testing.T, test endpointTestCase, name string) bool { t.Fatalf("Expected error of type %q but got none", test.errType) } - if !reflect.DeepEqual(resp, test.response) { + if !responseCompareFunc(resp, test.response) { t.Fatalf("Response does not match, expected:\n%+v\ngot:\n%+v", test.response, resp) } }) @@ -597,7 +606,35 @@ func TestQueryEndpoints(t *testing.T) { } for i, test := range tests { - if ok := testEndpoint(t, test, fmt.Sprintf("#%d %s", i, test.query.Encode())); !ok { + if ok := testEndpoint(t, test, fmt.Sprintf("#%d %s", i, test.query.Encode()), reflect.DeepEqual); !ok { + return + } + } + + tests = []endpointTestCase{ + { + endpoint: api.query, + query: url.Values{ + "query": []string{"2"}, + "time": []string{"123.4"}, + }, + response: &queryData{}, + }, + { + endpoint: api.query, + query: url.Values{ + "query": []string{"2"}, + "time": []string{"123.4"}, + "stats": []string{"true"}, + }, + response: &queryData{ + Stats: &stats.QueryStats{}, + }, + }, + } + + for i, test := range tests { + if ok := testEndpoint(t, test, fmt.Sprintf("#%d %s", i, test.query.Encode()), lookupStats); !ok { return } } @@ -1158,7 +1195,7 @@ func TestMetadataEndpoints(t *testing.T) { } for i, test := range tests { - if ok := testEndpoint(t, test, strings.TrimSpace(fmt.Sprintf("#%d %s", i, test.query.Encode()))); !ok { + if ok := testEndpoint(t, test, strings.TrimSpace(fmt.Sprintf("#%d %s", i, test.query.Encode())), reflect.DeepEqual); !ok { return } }