web/api: add limit param on series, labels, label-values (#13396)

Support limit parameter in queries to restrict output data to the specified size, on the following endpoints:

/api/v1/series
/api/v1/labels
/api/v1/label/:name:/values

Signed-off-by: Pranshu Srivastava <rexagod@gmail.com>
Signed-off-by: Kartikay <kartikay_2101ce32@iitp.ac.in>
This commit is contained in:
Kartikay 2024-02-29 21:01:13 +05:30 committed by GitHub
parent ab9b770aea
commit 8736772053
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 85 additions and 3 deletions

View file

@ -256,6 +256,7 @@ URL query parameters:
series to return. At least one `match[]` argument must be provided.
- `start=<rfc3339 | unix_timestamp>`: Start timestamp.
- `end=<rfc3339 | unix_timestamp>`: End timestamp.
- `limit=<number>`: 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=<rfc3339 | unix_timestamp>`: End timestamp. Optional.
- `match[]=<series_selector>`: Repeated series selector argument that selects the
series from which to read the label names. Optional.
- `limit=<number>`: 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=<rfc3339 | unix_timestamp>`: End timestamp. Optional.
- `match[]=<series_selector>`: Repeated series selector argument that selects the
series from which to read the label values. Optional.
- `limit=<number>`: Maximum number of returned series. Optional.
The `data` section of the JSON response is a list of string label values.

View file

@ -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
}

View file

@ -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__"},
},
}...)
}