diff --git a/docs/querying/api.md b/docs/querying/api.md index 04ba3e8c6..46e79181e 100644 --- a/docs/querying/api.md +++ b/docs/querying/api.md @@ -256,6 +256,7 @@ URL query parameters: series to return. At least one `match[]` argument must be provided. - `start=`: Start timestamp. - `end=`: End timestamp. +- `limit=`: Maximum number of returned series. Optional. You can URL-encode these parameters directly in the request body by using the `POST` method and `Content-Type: application/x-www-form-urlencoded` header. This is useful when specifying a large @@ -306,6 +307,7 @@ URL query parameters: - `end=`: End timestamp. Optional. - `match[]=`: Repeated series selector argument that selects the series from which to read the label names. Optional. +- `limit=`: Maximum number of returned series. Optional. The `data` section of the JSON response is a list of string label names. @@ -356,6 +358,7 @@ URL query parameters: - `end=`: End timestamp. Optional. - `match[]=`: Repeated series selector argument that selects the series from which to read the label values. Optional. +- `limit=`: Maximum number of returned series. Optional. The `data` section of the JSON response is a list of string label values. diff --git a/web/api/v1/api.go b/web/api/v1/api.go index 59e20004c..48938fce1 100644 --- a/web/api/v1/api.go +++ b/web/api/v1/api.go @@ -644,6 +644,11 @@ func returnAPIError(err error) *apiError { } func (api *API) labelNames(r *http.Request) apiFuncResult { + limit, err := parseLimitParam(r.FormValue("limit")) + if err != nil { + return invalidParamError(err, "limit") + } + start, err := parseTimeParam(r, "start", MinTime) if err != nil { return invalidParamError(err, "start") @@ -703,6 +708,11 @@ func (api *API) labelNames(r *http.Request) apiFuncResult { if names == nil { names = []string{} } + + if len(names) >= limit { + names = names[:limit] + warnings = warnings.Add(errors.New("results truncated due to limit")) + } return apiFuncResult{names, nil, warnings, nil} } @@ -714,6 +724,11 @@ func (api *API) labelValues(r *http.Request) (result apiFuncResult) { return apiFuncResult{nil, &apiError{errorBadData, fmt.Errorf("invalid label name: %q", name)}, nil, nil} } + limit, err := parseLimitParam(r.FormValue("limit")) + if err != nil { + return invalidParamError(err, "limit") + } + start, err := parseTimeParam(r, "start", MinTime) if err != nil { return invalidParamError(err, "start") @@ -783,6 +798,11 @@ func (api *API) labelValues(r *http.Request) (result apiFuncResult) { slices.Sort(vals) + if len(vals) >= limit { + vals = vals[:limit] + warnings = warnings.Add(errors.New("results truncated due to limit")) + } + return apiFuncResult{vals, nil, warnings, closer} } @@ -809,6 +829,11 @@ func (api *API) series(r *http.Request) (result apiFuncResult) { return apiFuncResult{nil, &apiError{errorBadData, errors.New("no match[] parameter provided")}, nil, nil} } + limit, err := parseLimitParam(r.FormValue("limit")) + if err != nil { + return invalidParamError(err, "limit") + } + start, err := parseTimeParam(r, "start", MinTime) if err != nil { return invalidParamError(err, "start") @@ -860,11 +885,17 @@ func (api *API) series(r *http.Request) (result apiFuncResult) { } metrics := []labels.Labels{} - for set.Next() { - metrics = append(metrics, set.At().Labels()) - } warnings := set.Warnings() + + for set.Next() { + metrics = append(metrics, set.At().Labels()) + + if len(metrics) >= limit { + warnings.Add(errors.New("results truncated due to limit")) + return apiFuncResult{metrics, nil, warnings, closer} + } + } if set.Err() != nil { return apiFuncResult{nil, returnAPIError(set.Err()), warnings, closer} } @@ -1867,3 +1898,20 @@ OUTER: } return matcherSets, nil } + +func parseLimitParam(limitStr string) (limit int, err error) { + limit = math.MaxInt + if limitStr == "" { + return limit, nil + } + + limit, err = strconv.Atoi(limitStr) + if err != nil { + return limit, err + } + if limit <= 0 { + return limit, errors.New("limit must be positive") + } + + return limit, nil +} diff --git a/web/api/v1/api_test.go b/web/api/v1/api_test.go index d3013b2ee..eae4b9c64 100644 --- a/web/api/v1/api_test.go +++ b/web/api/v1/api_test.go @@ -1389,6 +1389,16 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E }, }, // Missing match[] query params in series requests. + { + endpoint: api.series, + query: url.Values{ + "match[]": []string{"test_metric1"}, + "limit": []string{"1"}, + }, + response: []labels.Labels{ + labels.FromStrings("__name__", "test_metric1", "foo", "bar"), + }, + }, { endpoint: api.series, errType: errorBadData, @@ -2660,6 +2670,19 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E "boo", }, }, + { + endpoint: api.labelValues, + params: map[string]string{ + "name": "foo", + }, + query: url.Values{ + "match[]": []string{"test_metric4"}, + "limit": []string{"1"}, + }, + response: []string{ + "bar", + }, + }, // Label names. { endpoint: api.labelNames, @@ -2799,6 +2822,14 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E }, response: []string{"__name__", "foo"}, }, + { + endpoint: api.labelNames, + query: url.Values{ + "match[]": []string{"test_metric2"}, + "limit": []string{"1"}, + }, + response: []string{"__name__"}, + }, }...) }