mirror of
https://github.com/prometheus/prometheus.git
synced 2025-01-11 05:47:27 -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.
|
||||
- `timeout=<duration>`: Evaluation timeout. Optional. Defaults to and
|
||||
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.
|
||||
|
||||
|
@ -154,6 +155,7 @@ URL query parameters:
|
|||
- `step=<duration | float>`: Query resolution step width in `duration` format or float number of seconds.
|
||||
- `timeout=<duration>`: Evaluation timeout. Optional. Defaults to and
|
||||
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
|
||||
`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) {
|
||||
limit, err := parseLimitParam(r.FormValue("limit"))
|
||||
if err != nil {
|
||||
return invalidParamError(err, "limit")
|
||||
}
|
||||
ts, err := parseTimeParam(r, "time", api.now())
|
||||
if err != nil {
|
||||
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}
|
||||
}
|
||||
|
||||
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.
|
||||
sr := api.statsRenderer
|
||||
if sr == nil {
|
||||
|
@ -490,7 +503,7 @@ func (api *API) query(r *http.Request) (result apiFuncResult) {
|
|||
ResultType: res.Value.Type(),
|
||||
Result: res.Value,
|
||||
Stats: qs,
|
||||
}, nil, res.Warnings, qry.Close}
|
||||
}, nil, warnings, qry.Close}
|
||||
}
|
||||
|
||||
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) {
|
||||
limit, err := parseLimitParam(r.FormValue("limit"))
|
||||
if err != nil {
|
||||
return invalidParamError(err, "limit")
|
||||
}
|
||||
start, err := parseTime(r.FormValue("start"))
|
||||
if err != nil {
|
||||
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}
|
||||
}
|
||||
|
||||
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.
|
||||
sr := api.statsRenderer
|
||||
if sr == nil {
|
||||
|
@ -601,7 +628,7 @@ func (api *API) queryRange(r *http.Request) (result apiFuncResult) {
|
|||
ResultType: res.Value.Type(),
|
||||
Result: res.Value,
|
||||
Stats: qs,
|
||||
}, nil, res.Warnings, qry.Close}
|
||||
}, nil, warnings, qry.Close}
|
||||
}
|
||||
|
||||
func (api *API) queryExemplars(r *http.Request) apiFuncResult {
|
||||
|
@ -2102,3 +2129,25 @@ func toHintLimit(limit int) int {
|
|||
}
|
||||
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,
|
||||
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,
|
||||
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: 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":[]}`,
|
||||
},
|
||||
{
|
||||
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
|
||||
{
|
||||
endpoint: api.queryRange,
|
||||
|
|
Loading…
Reference in a new issue