mirror of
https://github.com/prometheus/prometheus.git
synced 2026-02-03 20:39:32 -05:00
Merge pull request #17670 from roidelapluie/roidelapluie/promql-range
Some checks are pending
buf.build / lint and publish (push) Waiting to run
CI / Go tests (push) Waiting to run
CI / More Go tests (push) Waiting to run
CI / Go tests with previous Go version (push) Waiting to run
CI / UI tests (push) Waiting to run
CI / Go tests on Windows (push) Waiting to run
CI / Mixins tests (push) Waiting to run
CI / Build Prometheus for common architectures (push) Waiting to run
CI / Build Prometheus for all architectures (push) Waiting to run
CI / Report status of build Prometheus for all architectures (push) Blocked by required conditions
CI / Check generated parser (push) Waiting to run
CI / golangci-lint (push) Waiting to run
CI / fuzzing (push) Waiting to run
CI / codeql (push) Waiting to run
CI / Publish main branch artifacts (push) Blocked by required conditions
CI / Publish release artefacts (push) Blocked by required conditions
CI / Publish UI on npm Registry (push) Blocked by required conditions
Scorecards supply-chain security / Scorecards analysis (push) Waiting to run
Some checks are pending
buf.build / lint and publish (push) Waiting to run
CI / Go tests (push) Waiting to run
CI / More Go tests (push) Waiting to run
CI / Go tests with previous Go version (push) Waiting to run
CI / UI tests (push) Waiting to run
CI / Go tests on Windows (push) Waiting to run
CI / Mixins tests (push) Waiting to run
CI / Build Prometheus for common architectures (push) Waiting to run
CI / Build Prometheus for all architectures (push) Waiting to run
CI / Report status of build Prometheus for all architectures (push) Blocked by required conditions
CI / Check generated parser (push) Waiting to run
CI / golangci-lint (push) Waiting to run
CI / fuzzing (push) Waiting to run
CI / codeql (push) Waiting to run
CI / Publish main branch artifacts (push) Blocked by required conditions
CI / Publish release artefacts (push) Blocked by required conditions
CI / Publish UI on npm Registry (push) Blocked by required conditions
Scorecards supply-chain security / Scorecards analysis (push) Waiting to run
PromQL: duration expression: add range()
This commit is contained in:
commit
48e6f6a751
13 changed files with 748 additions and 503 deletions
|
|
@ -197,7 +197,12 @@ the offset calculation.
|
|||
|
||||
`step()` can be used in duration expressions.
|
||||
For a **range query**, it resolves to the step width of the range query.
|
||||
For an **instant query**, it resolves to `0s`.
|
||||
For an **instant query**, it resolves to `0s`.
|
||||
|
||||
`range()` can be used in duration expressions.
|
||||
For a **range query**, it resolves to the full range of the query (end time - start time).
|
||||
For an **instant query**, it resolves to `0s`.
|
||||
This is particularly useful in combination with `@end()` to look back over the entire query range, e.g., `max_over_time(metric[range()] @ end())`.
|
||||
|
||||
`min(<duration>, <duration>)` and `max(<duration>, <duration>)` can be used to find the minimum or maximum of two duration expressions.
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ import (
|
|||
// in OriginalOffsetExpr representing (1h / 2). This visitor evaluates
|
||||
// such duration expression, setting OriginalOffset to 30m.
|
||||
type durationVisitor struct {
|
||||
step time.Duration
|
||||
step time.Duration
|
||||
queryRange time.Duration
|
||||
}
|
||||
|
||||
// Visit finds any duration expressions in AST Nodes and modifies the Node to
|
||||
|
|
@ -121,6 +122,8 @@ func (v *durationVisitor) evaluateDurationExpr(expr parser.Expr) (float64, error
|
|||
switch n.Op {
|
||||
case parser.STEP:
|
||||
return float64(v.step.Seconds()), nil
|
||||
case parser.RANGE:
|
||||
return float64(v.queryRange.Seconds()), nil
|
||||
case parser.MIN:
|
||||
return math.Min(lhs, rhs), nil
|
||||
case parser.MAX:
|
||||
|
|
|
|||
|
|
@ -213,6 +213,37 @@ func TestCalculateDuration(t *testing.T) {
|
|||
},
|
||||
expected: 3 * time.Second,
|
||||
},
|
||||
{
|
||||
name: "range",
|
||||
expr: &parser.DurationExpr{
|
||||
Op: parser.RANGE,
|
||||
},
|
||||
expected: 5 * time.Minute,
|
||||
},
|
||||
{
|
||||
name: "range division",
|
||||
expr: &parser.DurationExpr{
|
||||
LHS: &parser.DurationExpr{
|
||||
Op: parser.RANGE,
|
||||
},
|
||||
RHS: &parser.NumberLiteral{Val: 2},
|
||||
Op: parser.DIV,
|
||||
},
|
||||
expected: 150 * time.Second,
|
||||
},
|
||||
{
|
||||
name: "max of step and range",
|
||||
expr: &parser.DurationExpr{
|
||||
LHS: &parser.DurationExpr{
|
||||
Op: parser.STEP,
|
||||
},
|
||||
RHS: &parser.DurationExpr{
|
||||
Op: parser.RANGE,
|
||||
},
|
||||
Op: parser.MAX,
|
||||
},
|
||||
expected: 5 * time.Minute,
|
||||
},
|
||||
{
|
||||
name: "division by zero",
|
||||
expr: &parser.DurationExpr{
|
||||
|
|
@ -243,7 +274,7 @@ func TestCalculateDuration(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
v := &durationVisitor{step: 1 * time.Second}
|
||||
v := &durationVisitor{step: 1 * time.Second, queryRange: 5 * time.Minute}
|
||||
result, err := v.calculateDuration(tt.expr, tt.allowedNegative)
|
||||
if tt.errorMessage != "" {
|
||||
require.Error(t, err)
|
||||
|
|
|
|||
|
|
@ -4057,7 +4057,7 @@ func unwrapStepInvariantExpr(e parser.Expr) parser.Expr {
|
|||
func PreprocessExpr(expr parser.Expr, start, end time.Time, step time.Duration) (parser.Expr, error) {
|
||||
detectHistogramStatsDecoding(expr)
|
||||
|
||||
if err := parser.Walk(&durationVisitor{step: step}, expr, nil); err != nil {
|
||||
if err := parser.Walk(&durationVisitor{step: step, queryRange: end.Sub(start)}, expr, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -116,8 +116,8 @@ type DurationExpr struct {
|
|||
LHS, RHS Expr // The operands on the respective sides of the operator.
|
||||
Wrapped bool // Set when the duration is wrapped in parentheses.
|
||||
|
||||
StartPos posrange.Pos // For unary operations and step(), the start position of the operator.
|
||||
EndPos posrange.Pos // For step(), the end position of the operator.
|
||||
StartPos posrange.Pos // For unary operations, step(), and range(), the start position of the operator.
|
||||
EndPos posrange.Pos // For step() and range(), the end position of the operator.
|
||||
}
|
||||
|
||||
// Call represents a function call.
|
||||
|
|
@ -474,7 +474,7 @@ func (e *BinaryExpr) PositionRange() posrange.PositionRange {
|
|||
}
|
||||
|
||||
func (e *DurationExpr) PositionRange() posrange.PositionRange {
|
||||
if e.Op == STEP {
|
||||
if e.Op == STEP || e.Op == RANGE {
|
||||
return posrange.PositionRange{
|
||||
Start: e.StartPos,
|
||||
End: e.EndPos,
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ WITHOUT
|
|||
START
|
||||
END
|
||||
STEP
|
||||
RANGE
|
||||
%token preprocessorEnd
|
||||
|
||||
// Counter reset hints.
|
||||
|
|
@ -465,7 +466,7 @@ offset_expr: expr OFFSET offset_duration_expr
|
|||
$$ = $1
|
||||
}
|
||||
| expr OFFSET error
|
||||
{ yylex.(*parser).unexpected("offset", "number, duration, or step()"); $$ = $1 }
|
||||
{ yylex.(*parser).unexpected("offset", "number, duration, step(), or range()"); $$ = $1 }
|
||||
;
|
||||
|
||||
/*
|
||||
|
|
@ -575,11 +576,11 @@ subquery_expr : expr LEFT_BRACKET positive_duration_expr COLON positive_durati
|
|||
| expr LEFT_BRACKET positive_duration_expr COLON positive_duration_expr error
|
||||
{ yylex.(*parser).unexpected("subquery selector", "\"]\""); $$ = $1 }
|
||||
| expr LEFT_BRACKET positive_duration_expr COLON error
|
||||
{ yylex.(*parser).unexpected("subquery selector", "number, duration, or step() or \"]\""); $$ = $1 }
|
||||
{ yylex.(*parser).unexpected("subquery selector", "number, duration, step(), range(), or \"]\""); $$ = $1 }
|
||||
| expr LEFT_BRACKET positive_duration_expr error
|
||||
{ yylex.(*parser).unexpected("subquery or range", "\":\" or \"]\""); $$ = $1 }
|
||||
| expr LEFT_BRACKET error
|
||||
{ yylex.(*parser).unexpected("subquery or range selector", "number, duration, or step()"); $$ = $1 }
|
||||
{ yylex.(*parser).unexpected("subquery or range selector", "number, duration, step(), or range()"); $$ = $1 }
|
||||
;
|
||||
|
||||
/*
|
||||
|
|
@ -696,7 +697,7 @@ metric : metric_identifier label_set
|
|||
;
|
||||
|
||||
|
||||
metric_identifier: AVG | BOTTOMK | BY | COUNT | COUNT_VALUES | GROUP | IDENTIFIER | LAND | LOR | LUNLESS | MAX | METRIC_IDENTIFIER | MIN | OFFSET | QUANTILE | STDDEV | STDVAR | SUM | TOPK | WITHOUT | START | END | LIMITK | LIMIT_RATIO | STEP | ANCHORED | SMOOTHED;
|
||||
metric_identifier: AVG | BOTTOMK | BY | COUNT | COUNT_VALUES | GROUP | IDENTIFIER | LAND | LOR | LUNLESS | MAX | METRIC_IDENTIFIER | MIN | OFFSET | QUANTILE | STDDEV | STDVAR | SUM | TOPK | WITHOUT | START | END | LIMITK | LIMIT_RATIO | STEP | RANGE | ANCHORED | SMOOTHED;
|
||||
|
||||
label_set : LEFT_BRACE label_set_list RIGHT_BRACE
|
||||
{ $$ = labels.New($2...) }
|
||||
|
|
@ -953,7 +954,7 @@ counter_reset_hint : UNKNOWN_COUNTER_RESET | COUNTER_RESET | NOT_COUNTER_RESET |
|
|||
aggregate_op : AVG | BOTTOMK | COUNT | COUNT_VALUES | GROUP | MAX | MIN | QUANTILE | STDDEV | STDVAR | SUM | TOPK | LIMITK | LIMIT_RATIO;
|
||||
|
||||
// Inside of grouping options label names can be recognized as keywords by the lexer. This is a list of keywords that could also be a label name.
|
||||
maybe_label : AVG | BOOL | BOTTOMK | BY | COUNT | COUNT_VALUES | GROUP | GROUP_LEFT | GROUP_RIGHT | IDENTIFIER | IGNORING | LAND | LOR | LUNLESS | MAX | METRIC_IDENTIFIER | MIN | OFFSET | ON | QUANTILE | STDDEV | STDVAR | SUM | TOPK | START | END | ATAN2 | LIMITK | LIMIT_RATIO | STEP | ANCHORED | SMOOTHED;
|
||||
maybe_label : AVG | BOOL | BOTTOMK | BY | COUNT | COUNT_VALUES | GROUP | GROUP_LEFT | GROUP_RIGHT | IDENTIFIER | IGNORING | LAND | LOR | LUNLESS | MAX | METRIC_IDENTIFIER | MIN | OFFSET | ON | QUANTILE | STDDEV | STDVAR | SUM | TOPK | START | END | ATAN2 | LIMITK | LIMIT_RATIO | STEP | RANGE | ANCHORED | SMOOTHED;
|
||||
|
||||
unary_op : ADD | SUB;
|
||||
|
||||
|
|
@ -1088,6 +1089,14 @@ offset_duration_expr : number_duration_literal
|
|||
EndPos: $3.PositionRange().End,
|
||||
}
|
||||
}
|
||||
| RANGE LEFT_PAREN RIGHT_PAREN
|
||||
{
|
||||
$$ = &DurationExpr{
|
||||
Op: RANGE,
|
||||
StartPos: $1.PositionRange().Start,
|
||||
EndPos: $3.PositionRange().End,
|
||||
}
|
||||
}
|
||||
| unary_op STEP LEFT_PAREN RIGHT_PAREN
|
||||
{
|
||||
$$ = &DurationExpr{
|
||||
|
|
@ -1100,6 +1109,18 @@ offset_duration_expr : number_duration_literal
|
|||
StartPos: $1.Pos,
|
||||
}
|
||||
}
|
||||
| unary_op RANGE LEFT_PAREN RIGHT_PAREN
|
||||
{
|
||||
$$ = &DurationExpr{
|
||||
Op: $1.Typ,
|
||||
RHS: &DurationExpr{
|
||||
Op: RANGE,
|
||||
StartPos: $2.PositionRange().Start,
|
||||
EndPos: $4.PositionRange().End,
|
||||
},
|
||||
StartPos: $1.Pos,
|
||||
}
|
||||
}
|
||||
| min_max LEFT_PAREN duration_expr COMMA duration_expr RIGHT_PAREN
|
||||
{
|
||||
$$ = &DurationExpr{
|
||||
|
|
@ -1234,6 +1255,14 @@ duration_expr : number_duration_literal
|
|||
EndPos: $3.PositionRange().End,
|
||||
}
|
||||
}
|
||||
| RANGE LEFT_PAREN RIGHT_PAREN
|
||||
{
|
||||
$$ = &DurationExpr{
|
||||
Op: RANGE,
|
||||
StartPos: $1.PositionRange().Start,
|
||||
EndPos: $3.PositionRange().End,
|
||||
}
|
||||
}
|
||||
| min_max LEFT_PAREN duration_expr COMMA duration_expr RIGHT_PAREN
|
||||
{
|
||||
$$ = &DurationExpr{
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -143,6 +143,7 @@ var key = map[string]ItemType{
|
|||
"start": START,
|
||||
"end": END,
|
||||
"step": STEP,
|
||||
"range": RANGE,
|
||||
}
|
||||
|
||||
var histogramDesc = map[string]ItemType{
|
||||
|
|
@ -915,6 +916,9 @@ func (l *Lexer) scanDurationKeyword() bool {
|
|||
case "step":
|
||||
l.emit(STEP)
|
||||
return true
|
||||
case "range":
|
||||
l.emit(RANGE)
|
||||
return true
|
||||
case "min":
|
||||
l.emit(MIN)
|
||||
return true
|
||||
|
|
@ -1175,7 +1179,7 @@ func lexDurationExpr(l *Lexer) stateFn {
|
|||
case r == ',':
|
||||
l.emit(COMMA)
|
||||
return lexDurationExpr
|
||||
case r == 's' || r == 'S' || r == 'm' || r == 'M':
|
||||
case r == 's' || r == 'S' || r == 'm' || r == 'M' || r == 'r' || r == 'R':
|
||||
if l.scanDurationKeyword() {
|
||||
return lexDurationExpr
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2708,7 +2708,7 @@ var testExpr = []struct {
|
|||
errors: ParseErrors{
|
||||
ParseErr{
|
||||
PositionRange: posrange.PositionRange{Start: 4, End: 5},
|
||||
Err: errors.New("unexpected \"]\" in subquery or range selector, expected number, duration, or step()"),
|
||||
Err: errors.New("unexpected \"]\" in subquery or range selector, expected number, duration, step(), or range()"),
|
||||
Query: `foo[]`,
|
||||
},
|
||||
},
|
||||
|
|
@ -2741,7 +2741,7 @@ var testExpr = []struct {
|
|||
errors: ParseErrors{
|
||||
ParseErr{
|
||||
PositionRange: posrange.PositionRange{Start: 22, End: 22},
|
||||
Err: errors.New("unexpected end of input in offset, expected number, duration, or step()"),
|
||||
Err: errors.New("unexpected end of input in offset, expected number, duration, step(), or range()"),
|
||||
Query: `some_metric[5m] OFFSET`,
|
||||
},
|
||||
},
|
||||
|
|
@ -4698,6 +4698,100 @@ var testExpr = []struct {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `foo[range()]`,
|
||||
expected: &MatrixSelector{
|
||||
VectorSelector: &VectorSelector{
|
||||
Name: "foo",
|
||||
LabelMatchers: []*labels.Matcher{
|
||||
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
|
||||
},
|
||||
PosRange: posrange.PositionRange{Start: 0, End: 3},
|
||||
},
|
||||
RangeExpr: &DurationExpr{
|
||||
Op: RANGE,
|
||||
StartPos: 4,
|
||||
EndPos: 11,
|
||||
},
|
||||
EndPos: 12,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `foo[-range()]`,
|
||||
expected: &MatrixSelector{
|
||||
VectorSelector: &VectorSelector{
|
||||
Name: "foo",
|
||||
LabelMatchers: []*labels.Matcher{
|
||||
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
|
||||
},
|
||||
PosRange: posrange.PositionRange{Start: 0, End: 3},
|
||||
},
|
||||
RangeExpr: &DurationExpr{
|
||||
Op: SUB,
|
||||
StartPos: 4,
|
||||
RHS: &DurationExpr{Op: RANGE, StartPos: 5, EndPos: 12},
|
||||
},
|
||||
EndPos: 13,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `foo offset range()`,
|
||||
expected: &VectorSelector{
|
||||
Name: "foo",
|
||||
LabelMatchers: []*labels.Matcher{
|
||||
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
|
||||
},
|
||||
PosRange: posrange.PositionRange{Start: 0, End: 18},
|
||||
OriginalOffsetExpr: &DurationExpr{
|
||||
Op: RANGE,
|
||||
StartPos: 11,
|
||||
EndPos: 18,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `foo offset -range()`,
|
||||
expected: &VectorSelector{
|
||||
Name: "foo",
|
||||
LabelMatchers: []*labels.Matcher{
|
||||
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
|
||||
},
|
||||
PosRange: posrange.PositionRange{Start: 0, End: 19},
|
||||
OriginalOffsetExpr: &DurationExpr{
|
||||
Op: SUB,
|
||||
RHS: &DurationExpr{Op: RANGE, StartPos: 12, EndPos: 19},
|
||||
StartPos: 11,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `foo[max(range(),5s)]`,
|
||||
expected: &MatrixSelector{
|
||||
VectorSelector: &VectorSelector{
|
||||
Name: "foo",
|
||||
LabelMatchers: []*labels.Matcher{
|
||||
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
|
||||
},
|
||||
PosRange: posrange.PositionRange{Start: 0, End: 3},
|
||||
},
|
||||
RangeExpr: &DurationExpr{
|
||||
Op: MAX,
|
||||
LHS: &DurationExpr{
|
||||
Op: RANGE,
|
||||
StartPos: 8,
|
||||
EndPos: 15,
|
||||
},
|
||||
RHS: &NumberLiteral{
|
||||
Val: 5,
|
||||
Duration: true,
|
||||
PosRange: posrange.PositionRange{Start: 16, End: 18},
|
||||
},
|
||||
StartPos: 4,
|
||||
EndPos: 19,
|
||||
},
|
||||
EndPos: 20,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `foo[4s+4s:1s*2] offset (5s-8)`,
|
||||
expected: &SubqueryExpr{
|
||||
|
|
@ -4942,7 +5036,7 @@ var testExpr = []struct {
|
|||
errors: ParseErrors{
|
||||
ParseErr{
|
||||
PositionRange: posrange.PositionRange{Start: 8, End: 9},
|
||||
Err: errors.New(`unexpected "]" in subquery or range selector, expected number, duration, or step()`),
|
||||
Err: errors.New(`unexpected "]" in subquery or range selector, expected number, duration, step(), or range()`),
|
||||
Query: `foo[step]`,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -182,6 +182,8 @@ func (node *DurationExpr) writeTo(b *bytes.Buffer) {
|
|||
switch {
|
||||
case node.Op == STEP:
|
||||
b.WriteString("step()")
|
||||
case node.Op == RANGE:
|
||||
b.WriteString("range()")
|
||||
case node.Op == MIN:
|
||||
b.WriteString("min(")
|
||||
b.WriteString(node.LHS.String())
|
||||
|
|
|
|||
|
|
@ -266,6 +266,21 @@ func TestExprString(t *testing.T) {
|
|||
{
|
||||
in: "foo[200 - min(step() + 10s, -max(step() ^ 2, 3))]",
|
||||
},
|
||||
{
|
||||
in: "foo[range()]",
|
||||
},
|
||||
{
|
||||
in: "foo[-range()]",
|
||||
},
|
||||
{
|
||||
in: "foo offset range()",
|
||||
},
|
||||
{
|
||||
in: "foo offset -range()",
|
||||
},
|
||||
{
|
||||
in: "foo[max(range(), 5s)]",
|
||||
},
|
||||
{
|
||||
in: `predict_linear(foo[1h], 3000)`,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1519,6 +1519,10 @@ func (t *test) runInstantQuery(iq atModifierTestCase, cmd *evalCmd, engine promq
|
|||
|
||||
// Check query returns same result in range mode,
|
||||
// by checking against the middle step.
|
||||
// Skip this check for queries containing range() since it would resolve differently.
|
||||
if strings.Contains(iq.expr, "range()") {
|
||||
return nil
|
||||
}
|
||||
q, err = engine.NewRangeQuery(t.context, t.storage, nil, iq.expr, iq.evalTime.Add(-time.Minute), iq.evalTime.Add(time.Minute), time.Minute)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating range query for %q (line %d): %w", cmd.expr, cmd.line, err)
|
||||
|
|
|
|||
|
|
@ -225,4 +225,27 @@ eval range from 50s to 60s step 5s metric1_total offset max(3s,min(step(), 1s))+
|
|||
{} 8047 8052 8057
|
||||
|
||||
eval range from 50s to 60s step 5s metric1_total offset -(min(step(), 2s)-5)+8000
|
||||
{} 8047 8052 8057
|
||||
{} 8047 8052 8057
|
||||
|
||||
# Test range() function - resolves to query range (end - start).
|
||||
# For a range query from 50s to 60s, range() = 10s.
|
||||
eval range from 50s to 60s step 10s count_over_time(metric1_total[range()])
|
||||
{} 10 10
|
||||
|
||||
eval range from 50s to 60s step 5s count_over_time(metric1_total[range()])
|
||||
{} 10 10 10
|
||||
|
||||
eval range from 50s to 60s step 5s metric1_total offset range()
|
||||
metric1_total{} 40 45 50
|
||||
|
||||
eval range from 50s to 60s step 5s metric1_total offset min(range(), 8s)
|
||||
metric1_total{} 42 47 52
|
||||
|
||||
clear
|
||||
|
||||
load 1s
|
||||
metric1_total 0+1x100
|
||||
|
||||
# For an instant query (start == end), range() = 0s, offset 0s.
|
||||
eval instant at 50s metric1_total offset range()
|
||||
metric1_total{} 50
|
||||
|
|
|
|||
Loading…
Reference in a new issue