mirror of
https://github.com/prometheus/prometheus.git
synced 2025-03-05 20:59:13 -08:00
web/api: Add a limit parameter to /query and /query_range (#15552)
add limit param to query and rangeQuery --------- Signed-off-by: Vandit Singh <vanditsinghkv@gmail.com> Signed-off-by: Vandit Singh <107131545+Vandit1604@users.noreply.github.com> Co-authored-by: Björn Rabenstein <github@rabenste.in>
This commit is contained in:
parent
b3e30d52ce
commit
6339989e25
|
@ -86,6 +86,7 @@ URL query parameters:
|
||||||
- `time=<rfc3339 | unix_timestamp>`: Evaluation timestamp. Optional.
|
- `time=<rfc3339 | unix_timestamp>`: Evaluation timestamp. Optional.
|
||||||
- `timeout=<duration>`: Evaluation timeout. Optional. Defaults to and
|
- `timeout=<duration>`: Evaluation timeout. Optional. Defaults to and
|
||||||
is capped by the value of the `-query.timeout` flag.
|
is capped by the value of the `-query.timeout` flag.
|
||||||
|
- `limit=<number>`: Maximum number of returned series. Doesn’t affect scalars or strings but truncates the number of series for matrices and vectors. Optional. 0 means disabled.
|
||||||
|
|
||||||
The current server time is used if the `time` parameter is omitted.
|
The current server time is used if the `time` parameter is omitted.
|
||||||
|
|
||||||
|
@ -154,6 +155,7 @@ URL query parameters:
|
||||||
- `step=<duration | float>`: Query resolution step width in `duration` format or float number of seconds.
|
- `step=<duration | float>`: Query resolution step width in `duration` format or float number of seconds.
|
||||||
- `timeout=<duration>`: Evaluation timeout. Optional. Defaults to and
|
- `timeout=<duration>`: Evaluation timeout. Optional. Defaults to and
|
||||||
is capped by the value of the `-query.timeout` flag.
|
is capped by the value of the `-query.timeout` flag.
|
||||||
|
- `limit=<number>`: Maximum number of returned series. Optional. 0 means disabled.
|
||||||
|
|
||||||
You can URL-encode these parameters directly in the request body by using the `POST` method and
|
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
|
`Content-Type: application/x-www-form-urlencoded` header. This is useful when specifying a large
|
||||||
|
|
|
@ -438,6 +438,10 @@ func (api *API) options(*http.Request) apiFuncResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) query(r *http.Request) (result apiFuncResult) {
|
func (api *API) query(r *http.Request) (result apiFuncResult) {
|
||||||
|
limit, err := parseLimitParam(r.FormValue("limit"))
|
||||||
|
if err != nil {
|
||||||
|
return invalidParamError(err, "limit")
|
||||||
|
}
|
||||||
ts, err := parseTimeParam(r, "time", api.now())
|
ts, err := parseTimeParam(r, "time", api.now())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return invalidParamError(err, "time")
|
return invalidParamError(err, "time")
|
||||||
|
@ -479,6 +483,15 @@ func (api *API) query(r *http.Request) (result apiFuncResult) {
|
||||||
return apiFuncResult{nil, returnAPIError(res.Err), res.Warnings, qry.Close}
|
return apiFuncResult{nil, returnAPIError(res.Err), res.Warnings, qry.Close}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
warnings := res.Warnings
|
||||||
|
if limit > 0 {
|
||||||
|
var isTruncated bool
|
||||||
|
|
||||||
|
res, isTruncated = truncateResults(res, limit)
|
||||||
|
if isTruncated {
|
||||||
|
warnings = warnings.Add(errors.New("results truncated due to limit"))
|
||||||
|
}
|
||||||
|
}
|
||||||
// Optional stats field in response if parameter "stats" is not empty.
|
// Optional stats field in response if parameter "stats" is not empty.
|
||||||
sr := api.statsRenderer
|
sr := api.statsRenderer
|
||||||
if sr == nil {
|
if sr == nil {
|
||||||
|
@ -490,7 +503,7 @@ func (api *API) query(r *http.Request) (result apiFuncResult) {
|
||||||
ResultType: res.Value.Type(),
|
ResultType: res.Value.Type(),
|
||||||
Result: res.Value,
|
Result: res.Value,
|
||||||
Stats: qs,
|
Stats: qs,
|
||||||
}, nil, res.Warnings, qry.Close}
|
}, nil, warnings, qry.Close}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) formatQuery(r *http.Request) (result apiFuncResult) {
|
func (api *API) formatQuery(r *http.Request) (result apiFuncResult) {
|
||||||
|
@ -526,6 +539,10 @@ func extractQueryOpts(r *http.Request) (promql.QueryOpts, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) queryRange(r *http.Request) (result apiFuncResult) {
|
func (api *API) queryRange(r *http.Request) (result apiFuncResult) {
|
||||||
|
limit, err := parseLimitParam(r.FormValue("limit"))
|
||||||
|
if err != nil {
|
||||||
|
return invalidParamError(err, "limit")
|
||||||
|
}
|
||||||
start, err := parseTime(r.FormValue("start"))
|
start, err := parseTime(r.FormValue("start"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return invalidParamError(err, "start")
|
return invalidParamError(err, "start")
|
||||||
|
@ -590,6 +607,16 @@ func (api *API) queryRange(r *http.Request) (result apiFuncResult) {
|
||||||
return apiFuncResult{nil, returnAPIError(res.Err), res.Warnings, qry.Close}
|
return apiFuncResult{nil, returnAPIError(res.Err), res.Warnings, qry.Close}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
warnings := res.Warnings
|
||||||
|
if limit > 0 {
|
||||||
|
var isTruncated bool
|
||||||
|
|
||||||
|
res, isTruncated = truncateResults(res, limit)
|
||||||
|
if isTruncated {
|
||||||
|
warnings = warnings.Add(errors.New("results truncated due to limit"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Optional stats field in response if parameter "stats" is not empty.
|
// Optional stats field in response if parameter "stats" is not empty.
|
||||||
sr := api.statsRenderer
|
sr := api.statsRenderer
|
||||||
if sr == nil {
|
if sr == nil {
|
||||||
|
@ -601,7 +628,7 @@ func (api *API) queryRange(r *http.Request) (result apiFuncResult) {
|
||||||
ResultType: res.Value.Type(),
|
ResultType: res.Value.Type(),
|
||||||
Result: res.Value,
|
Result: res.Value,
|
||||||
Stats: qs,
|
Stats: qs,
|
||||||
}, nil, res.Warnings, qry.Close}
|
}, nil, warnings, qry.Close}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) queryExemplars(r *http.Request) apiFuncResult {
|
func (api *API) queryExemplars(r *http.Request) apiFuncResult {
|
||||||
|
@ -2102,3 +2129,25 @@ func toHintLimit(limit int) int {
|
||||||
}
|
}
|
||||||
return limit
|
return limit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// truncateResults truncates result for queryRange() and query().
|
||||||
|
// No truncation for other types(Scalars or Strings).
|
||||||
|
func truncateResults(result *promql.Result, limit int) (*promql.Result, bool) {
|
||||||
|
isTruncated := false
|
||||||
|
|
||||||
|
switch v := result.Value.(type) {
|
||||||
|
case promql.Matrix:
|
||||||
|
if len(v) > limit {
|
||||||
|
result.Value = v[:limit]
|
||||||
|
isTruncated = true
|
||||||
|
}
|
||||||
|
case promql.Vector:
|
||||||
|
if len(v) > limit {
|
||||||
|
result.Value = v[:limit]
|
||||||
|
isTruncated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the modified result. Unchanged for other types.
|
||||||
|
return result, isTruncated
|
||||||
|
}
|
||||||
|
|
|
@ -1164,6 +1164,49 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// Only matrix and vector responses are limited/truncated. String and scalar responses aren't truncated.
|
||||||
|
{
|
||||||
|
endpoint: api.query,
|
||||||
|
query: url.Values{
|
||||||
|
"query": []string{"2"},
|
||||||
|
"time": []string{"123.4"},
|
||||||
|
"limit": []string{"1"},
|
||||||
|
},
|
||||||
|
response: &QueryData{
|
||||||
|
ResultType: parser.ValueTypeScalar,
|
||||||
|
Result: promql.Scalar{
|
||||||
|
V: 2,
|
||||||
|
T: timestamp.FromTime(start.Add(123*time.Second + 400*time.Millisecond)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
warningsCount: 0,
|
||||||
|
},
|
||||||
|
// When limit = 0, limit is disabled.
|
||||||
|
{
|
||||||
|
endpoint: api.query,
|
||||||
|
query: url.Values{
|
||||||
|
"query": []string{"2"},
|
||||||
|
"time": []string{"123.4"},
|
||||||
|
"limit": []string{"0"},
|
||||||
|
},
|
||||||
|
response: &QueryData{
|
||||||
|
ResultType: parser.ValueTypeScalar,
|
||||||
|
Result: promql.Scalar{
|
||||||
|
V: 2,
|
||||||
|
T: timestamp.FromTime(start.Add(123*time.Second + 400*time.Millisecond)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
warningsCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
endpoint: api.query,
|
||||||
|
query: url.Values{
|
||||||
|
"query": []string{"2"},
|
||||||
|
"time": []string{"123.4"},
|
||||||
|
"limit": []string{"-1"},
|
||||||
|
},
|
||||||
|
errType: errorBadData,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
endpoint: api.query,
|
endpoint: api.query,
|
||||||
query: url.Values{
|
query: url.Values{
|
||||||
|
@ -1205,6 +1248,179 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
endpoint: api.query,
|
||||||
|
query: url.Values{
|
||||||
|
"query": []string{
|
||||||
|
`label_replace(vector(42), "foo", "bar", "", "") or label_replace(vector(3.1415), "dings", "bums", "", "")`,
|
||||||
|
},
|
||||||
|
"time": []string{"123.4"},
|
||||||
|
"limit": []string{"2"},
|
||||||
|
},
|
||||||
|
warningsCount: 0,
|
||||||
|
responseAsJSON: `{
|
||||||
|
"resultType": "vector",
|
||||||
|
"result": [
|
||||||
|
{
|
||||||
|
"metric": {
|
||||||
|
"foo": "bar"
|
||||||
|
},
|
||||||
|
"value": [123.4, "42"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"metric": {
|
||||||
|
"dings": "bums"
|
||||||
|
},
|
||||||
|
"value": [123.4, "3.1415"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
endpoint: api.query,
|
||||||
|
query: url.Values{
|
||||||
|
"query": []string{
|
||||||
|
`label_replace(vector(42), "foo", "bar", "", "") or label_replace(vector(3.1415), "dings", "bums", "", "")`,
|
||||||
|
},
|
||||||
|
"time": []string{"123.4"},
|
||||||
|
"limit": []string{"1"},
|
||||||
|
},
|
||||||
|
warningsCount: 1,
|
||||||
|
responseAsJSON: `{
|
||||||
|
"resultType": "vector",
|
||||||
|
"result": [
|
||||||
|
{
|
||||||
|
"metric": {
|
||||||
|
"foo": "bar"
|
||||||
|
},
|
||||||
|
"value": [123.4, "42"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
endpoint: api.query,
|
||||||
|
query: url.Values{
|
||||||
|
"query": []string{
|
||||||
|
`label_replace(vector(42), "foo", "bar", "", "") or label_replace(vector(3.1415), "dings", "bums", "", "")`,
|
||||||
|
},
|
||||||
|
"time": []string{"123.4"},
|
||||||
|
"limit": []string{"0"},
|
||||||
|
},
|
||||||
|
responseAsJSON: `{
|
||||||
|
"resultType": "vector",
|
||||||
|
"result": [
|
||||||
|
{
|
||||||
|
"metric": {
|
||||||
|
"foo": "bar"
|
||||||
|
},
|
||||||
|
"value": [123.4, "42"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"metric": {
|
||||||
|
"dings": "bums"
|
||||||
|
},
|
||||||
|
"value": [123.4, "3.1415"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
warningsCount: 0,
|
||||||
|
},
|
||||||
|
// limit=0 means no limit.
|
||||||
|
{
|
||||||
|
endpoint: api.queryRange,
|
||||||
|
query: url.Values{
|
||||||
|
"query": []string{
|
||||||
|
`label_replace(vector(42), "foo", "bar", "", "") or label_replace(vector(3.1415), "dings", "bums", "", "")`,
|
||||||
|
},
|
||||||
|
"start": []string{"0"},
|
||||||
|
"end": []string{"2"},
|
||||||
|
"step": []string{"1"},
|
||||||
|
"limit": []string{"0"},
|
||||||
|
},
|
||||||
|
response: &QueryData{
|
||||||
|
ResultType: parser.ValueTypeMatrix,
|
||||||
|
Result: promql.Matrix{
|
||||||
|
promql.Series{
|
||||||
|
Metric: labels.FromMap(map[string]string{"dings": "bums"}),
|
||||||
|
Floats: []promql.FPoint{
|
||||||
|
{F: 3.1415, T: timestamp.FromTime(start)},
|
||||||
|
{F: 3.1415, T: timestamp.FromTime(start.Add(1 * time.Second))},
|
||||||
|
{F: 3.1415, T: timestamp.FromTime(start.Add(2 * time.Second))},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
promql.Series{
|
||||||
|
Metric: labels.FromMap(map[string]string{"foo": "bar"}),
|
||||||
|
Floats: []promql.FPoint{
|
||||||
|
{F: 42, T: timestamp.FromTime(start)},
|
||||||
|
{F: 42, T: timestamp.FromTime(start.Add(1 * time.Second))},
|
||||||
|
{F: 42, T: timestamp.FromTime(start.Add(2 * time.Second))},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
warningsCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
endpoint: api.queryRange,
|
||||||
|
query: url.Values{
|
||||||
|
"query": []string{
|
||||||
|
`label_replace(vector(42), "foo", "bar", "", "") or label_replace(vector(3.1415), "dings", "bums", "", "")`,
|
||||||
|
},
|
||||||
|
"start": []string{"0"},
|
||||||
|
"end": []string{"2"},
|
||||||
|
"step": []string{"1"},
|
||||||
|
"limit": []string{"1"},
|
||||||
|
},
|
||||||
|
response: &QueryData{
|
||||||
|
ResultType: parser.ValueTypeMatrix,
|
||||||
|
Result: promql.Matrix{
|
||||||
|
promql.Series{
|
||||||
|
Metric: labels.FromMap(map[string]string{"dings": "bums"}),
|
||||||
|
Floats: []promql.FPoint{
|
||||||
|
{F: 3.1415, T: timestamp.FromTime(start)},
|
||||||
|
{F: 3.1415, T: timestamp.FromTime(start.Add(1 * time.Second))},
|
||||||
|
{F: 3.1415, T: timestamp.FromTime(start.Add(2 * time.Second))},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
warningsCount: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
endpoint: api.queryRange,
|
||||||
|
query: url.Values{
|
||||||
|
"query": []string{
|
||||||
|
`label_replace(vector(42), "foo", "bar", "", "") or label_replace(vector(3.1415), "dings", "bums", "", "")`,
|
||||||
|
},
|
||||||
|
"start": []string{"0"},
|
||||||
|
"end": []string{"2"},
|
||||||
|
"step": []string{"1"},
|
||||||
|
"limit": []string{"2"},
|
||||||
|
},
|
||||||
|
response: &QueryData{
|
||||||
|
ResultType: parser.ValueTypeMatrix,
|
||||||
|
Result: promql.Matrix{
|
||||||
|
promql.Series{
|
||||||
|
Metric: labels.FromMap(map[string]string{"dings": "bums"}),
|
||||||
|
Floats: []promql.FPoint{
|
||||||
|
{F: 3.1415, T: timestamp.FromTime(start)},
|
||||||
|
{F: 3.1415, T: timestamp.FromTime(start.Add(1 * time.Second))},
|
||||||
|
{F: 3.1415, T: timestamp.FromTime(start.Add(2 * time.Second))},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
promql.Series{
|
||||||
|
Metric: labels.FromMap(map[string]string{"foo": "bar"}),
|
||||||
|
Floats: []promql.FPoint{
|
||||||
|
{F: 42, T: timestamp.FromTime(start)},
|
||||||
|
{F: 42, T: timestamp.FromTime(start.Add(1 * time.Second))},
|
||||||
|
{F: 42, T: timestamp.FromTime(start.Add(2 * time.Second))},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
warningsCount: 0,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
endpoint: api.queryRange,
|
endpoint: api.queryRange,
|
||||||
query: url.Values{
|
query: url.Values{
|
||||||
|
@ -1222,7 +1438,6 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
|
||||||
{F: 1, T: timestamp.FromTime(start.Add(1 * time.Second))},
|
{F: 1, T: timestamp.FromTime(start.Add(1 * time.Second))},
|
||||||
{F: 2, T: timestamp.FromTime(start.Add(2 * time.Second))},
|
{F: 2, T: timestamp.FromTime(start.Add(2 * time.Second))},
|
||||||
},
|
},
|
||||||
// No Metric returned - use zero value for comparison.
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1235,6 +1450,17 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
|
||||||
},
|
},
|
||||||
responseAsJSON: `{"resultType":"vector","result":[]}`,
|
responseAsJSON: `{"resultType":"vector","result":[]}`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
endpoint: api.queryRange,
|
||||||
|
query: url.Values{
|
||||||
|
"query": []string{"bottomk(2, notExists)"},
|
||||||
|
"start": []string{"0"},
|
||||||
|
"end": []string{"2"},
|
||||||
|
"step": []string{"1"},
|
||||||
|
"limit": []string{"-1"},
|
||||||
|
},
|
||||||
|
errType: errorBadData,
|
||||||
|
},
|
||||||
// Test empty matrix result
|
// Test empty matrix result
|
||||||
{
|
{
|
||||||
endpoint: api.queryRange,
|
endpoint: api.queryRange,
|
||||||
|
|
Loading…
Reference in a new issue