diff --git a/src/query/api/v1/handler/prometheus/handleroptions/fetch_options.go b/src/query/api/v1/handler/prometheus/handleroptions/fetch_options.go index a812d19828..d30e631be8 100644 --- a/src/query/api/v1/handler/prometheus/handleroptions/fetch_options.go +++ b/src/query/api/v1/handler/prometheus/handleroptions/fetch_options.go @@ -134,6 +134,20 @@ func ParseLimit(req *http.Request, header, formValue string, defaultLimit int) ( return defaultLimit, nil } +// ParseInstanceMultiple parses request instance multiple from header. +func ParseInstanceMultiple(req *http.Request, defaultValue float32) (float32, error) { + if str := req.Header.Get(headers.LimitInstanceMultipleHeader); str != "" { + v, err := strconv.ParseFloat(str, 32) + if err != nil { + err = fmt.Errorf( + "could not parse instance multiple: input=%s, err=%w", str, err) + return 0, err + } + return float32(v), nil + } + return defaultValue, nil +} + // ParseRequireExhaustive parses request limit require exhaustive from header or // query string. func ParseRequireExhaustive(req *http.Request, defaultValue bool) (bool, error) { @@ -214,9 +228,13 @@ func (b fetchOptionsBuilder) newFetchOptions( if err != nil { return nil, nil, err } - fetchOpts.SeriesLimit = seriesLimit - fetchOpts.InstanceMultiple = b.opts.Limits.InstanceMultiple + + instanceMultiple, err := ParseInstanceMultiple(req, b.opts.Limits.InstanceMultiple) + if err != nil { + return nil, nil, err + } + fetchOpts.InstanceMultiple = instanceMultiple docsLimit, err := ParseLimit(req, headers.LimitMaxDocsHeader, "docsLimit", b.opts.Limits.DocsLimit) diff --git a/src/query/api/v1/handler/prometheus/handleroptions/fetch_options_test.go b/src/query/api/v1/handler/prometheus/handleroptions/fetch_options_test.go index f2b4dfae2c..312d8589f3 100644 --- a/src/query/api/v1/handler/prometheus/handleroptions/fetch_options_test.go +++ b/src/query/api/v1/handler/prometheus/handleroptions/fetch_options_test.go @@ -475,3 +475,20 @@ func TestTimeoutParseWithGetRequestParam(t *testing.T) { assert.NoError(t, err) assert.Equal(t, timeout, time.Millisecond) } + +func TestInstanceMultiple(t *testing.T) { + req := httptest.NewRequest("GET", "/", nil) + m, err := ParseInstanceMultiple(req, 2.0) + require.NoError(t, err) + require.Equal(t, float32(2.0), m) + + req.Header.Set(headers.LimitInstanceMultipleHeader, "3.0") + m, err = ParseInstanceMultiple(req, 2.0) + require.NoError(t, err) + require.Equal(t, float32(3.0), m) + + req.Header.Set(headers.LimitInstanceMultipleHeader, "blah") + _, err = ParseInstanceMultiple(req, 2.0) + require.Error(t, err) + require.Contains(t, err.Error(), "could not parse instance multiple") +} diff --git a/src/x/headers/headers.go b/src/x/headers/headers.go index 59cede447d..76368b7320 100644 --- a/src/x/headers/headers.go +++ b/src/x/headers/headers.go @@ -84,6 +84,9 @@ const ( // the number of time series returned by each storage node. LimitMaxSeriesHeader = M3HeaderPrefix + "Limit-Max-Series" + // LimitInstanceMultipleHeader overrides the PerQueryLimitsConfiguration.InstanceMultiple for the request. + LimitInstanceMultipleHeader = M3HeaderPrefix + "Limit-Instance-Multiple" + // LimitMaxDocsHeader is the M3 limit docs header that limits // the number of docs returned by each storage node. LimitMaxDocsHeader = M3HeaderPrefix + "Limit-Max-Docs"