Repository: prometheus/prometheus
Pull request #12088 (2023-03-07 23:30:51)

Add support for lookbackdelta per query via the API
<!--
    Don't forget!

    - Please sign CNCF's Developer Certificate of Origin and sign-off your commits by adding the -s / --sign-off flag to `git commit`. See https://github.com/apps/dco for more information.

    - If the PR adds or changes a behaviour or fixes a bug of an exported API it would need a unit/e2e test.

    - Where possible use only exported APIs for tests to simplify the review and make it as close as possible to an actual library usage.

    - No tests are needed for internal implementation changes.

    - Performance improvements would need a benchmark test to prove it.

    - All exposed objects should have a comment.

    - All comments should start with a capital letter and end with a full stop.
 -->



Patch:
diff --git a/web/api/v1/api.go b/web/api/v1/api.go
index 7c4ef1908..157786ccb 100644
--- a/web/api/v1/api.go
+++ b/web/api/v1/api.go
@@ -413,7 +413,10 @@ func (api *API) query(r *http.Request) (result apiFuncResult) {
 		defer cancel()
 	}
 
-	opts := extractQueryOpts(r)
+	opts, err := extractQueryOpts(r)
+	if err != nil {
+		return apiFuncResult{nil, &apiError{errorBadData, err}, nil, nil}
+	}
 	qry, err := api.QueryEngine.NewInstantQuery(api.Queryable, opts, r.FormValue("query"), ts)
 	if err != nil {
 		return invalidParamError(err, "query")
@@ -458,10 +461,18 @@ func (api *API) formatQuery(r *http.Request) (result apiFuncResult) {
 	return apiFuncResult{expr.Pretty(0), nil, nil, nil}
 }
 
-func extractQueryOpts(r *http.Request) *promql.QueryOpts {
-	return &promql.QueryOpts{
+func extractQueryOpts(r *http.Request) (*promql.QueryOpts, error) {
+	opts := &promql.QueryOpts{
 		EnablePerStepStats: r.FormValue("stats") == "all",
 	}
+	if strDuration := r.FormValue("lookback_delta"); strDuration != "" {
+		duration, err := parseDuration(strDuration)
+		if err != nil {
+			return nil, fmt.Errorf("error parsing lookback delta duration: %w", err)
+		}
+		opts.LookbackDelta = duration
+	}
+	return opts, nil
 }
 
 func (api *API) queryRange(r *http.Request) (result apiFuncResult) {
@@ -505,7 +516,10 @@ func (api *API) queryRange(r *http.Request) (result apiFuncResult) {
 		defer cancel()
 	}
 
-	opts := extractQueryOpts(r)
+	opts, err := extractQueryOpts(r)
+	if err != nil {
+		return apiFuncResult{nil, &apiError{errorBadData, err}, nil, nil}
+	}
 	qry, err := api.QueryEngine.NewRangeQuery(api.Queryable, opts, r.FormValue("query"), start, end, step)
 	if err != nil {
 		return apiFuncResult{nil, &apiError{errorBadData, err}, nil, nil}


Test patch:
diff --git a/web/api/v1/api_test.go b/web/api/v1/api_test.go
index 80bc11087..4a1e4d67d 100644
--- a/web/api/v1/api_test.go
+++ b/web/api/v1/api_test.go
@@ -3410,3 +3410,66 @@ func TestGetGlobalURL(t *testing.T) {
 		})
 	}
 }
+
+func TestExtractQueryOpts(t *testing.T) {
+	tests := []struct {
+		name   string
+		form   url.Values
+		expect *promql.QueryOpts
+		err    error
+	}{
+		{
+			name: "with stats all",
+			form: url.Values{
+				"stats": []string{"all"},
+			},
+			expect: &promql.QueryOpts{
+				EnablePerStepStats: true,
+			},
+			err: nil,
+		},
+		{
+			name: "with stats none",
+			form: url.Values{
+				"stats": []string{"none"},
+			},
+			expect: &promql.QueryOpts{
+				EnablePerStepStats: false,
+			},
+			err: nil,
+		},
+		{
+			name: "with lookback delta",
+			form: url.Values{
+				"stats":          []string{"all"},
+				"lookback_delta": []string{"30s"},
+			},
+			expect: &promql.QueryOpts{
+				EnablePerStepStats: true,
+				LookbackDelta:      30 * time.Second,
+			},
+			err: nil,
+		},
+		{
+			name: "with invalid lookback delta",
+			form: url.Values{
+				"lookback_delta": []string{"invalid"},
+			},
+			expect: nil,
+			err:    errors.New(`error parsing lookback delta duration: cannot parse "invalid" to a valid duration`),
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			req := &http.Request{Form: test.form}
+			opts, err := extractQueryOpts(req)
+			require.Equal(t, test.expect, opts)
+			if test.err == nil {
+				require.NoError(t, err)
+			} else {
+				require.Equal(t, test.err.Error(), err.Error())
+			}
+		})
+	}
+}