mirror of
https://github.com/prometheus/prometheus.git
synced 2024-11-09 23:24:05 -08:00
Add start() and end() pre-processors for @ modifier (#8425)
* Add start() and end() pre-processors for @ modifier Signed-off-by: Ganesh Vernekar <cs15btech11018@iith.ac.in> * Fix reviews Signed-off-by: Ganesh Vernekar <cs15btech11018@iith.ac.in> * Fix review comments Signed-off-by: Ganesh Vernekar <cs15btech11018@iith.ac.in> * Fix review comments Signed-off-by: Ganesh Vernekar <cs15btech11018@iith.ac.in>
This commit is contained in:
parent
b9f0baf6ff
commit
86c71856e8
|
@ -15,11 +15,12 @@ They may be enabled by default in future versions.
|
|||
|
||||
`--enable-feature=promql-at-modifier`
|
||||
|
||||
This feature lets you specify the evaluation time for instant vector selectors,
|
||||
range vector selectors, and subqueries. More details can be found [here](querying/basics.md#@-modifier).
|
||||
The `@` modifier lets you specify the evaluation time for instant vector selectors,
|
||||
range vector selectors, and subqueries. More details can be found [here](querying/basics.md#-modifier).
|
||||
|
||||
## Remote Write Receiver
|
||||
|
||||
`--enable-feature=remote-write-receiver`
|
||||
|
||||
The remote write receiver allows Prometheus to accept remote write requests from other Prometheus servers. More details can be found [here](storage.md#overview).
|
||||
|
||||
|
|
|
@ -207,7 +207,7 @@ The same works for range vectors. This returns the 5-minute rate that
|
|||
### @ modifier
|
||||
|
||||
The `@` modifier allows changing the evaluation time for individual instant
|
||||
and range vectors in a query. The time supplied to `@` modifier
|
||||
and range vectors in a query. The time supplied to the `@` modifier
|
||||
is a unix timestamp and described with a float literal.
|
||||
|
||||
For example, the following expression returns the value of
|
||||
|
@ -229,9 +229,9 @@ The same works for range vectors. This returns the 5-minute rate that
|
|||
|
||||
rate(http_requests_total[5m] @ 1609746000)
|
||||
|
||||
`@` modifier supports all representation of float literals described
|
||||
The `@` modifier supports all representation of float literals described
|
||||
above within the limits of `int64`. It can also be used along
|
||||
with `offset` modifier where the offset is applied relative to the `@`
|
||||
with the `offset` modifier where the offset is applied relative to the `@`
|
||||
modifier time irrespective of which modifier is written first.
|
||||
These 2 queries will produce the same result.
|
||||
|
||||
|
@ -242,7 +242,16 @@ These 2 queries will produce the same result.
|
|||
|
||||
This modifier is disabled by default since it breaks the invariant that PromQL
|
||||
does not look ahead of the evaluation time for samples. It can be enabled by setting
|
||||
`--enable-feature=promql-at-modifier` flag. It will be enabled by default in the future.
|
||||
`--enable-feature=promql-at-modifier` flag. See [disabled features](../disabled_features.md) for more details about this flag.
|
||||
|
||||
Additionally, `start()` and `end()` can also be used as values for the `@` modifier as special values.
|
||||
|
||||
For a range query, they resolve to the start and end of the range query respectively and remain the same for all steps.
|
||||
|
||||
For an instant query, `start()` and `end()` both resolve to the evaluation time.
|
||||
|
||||
http_requests_total @ start()
|
||||
rate(http_requests_total[5m] @ end())
|
||||
|
||||
## Subquery
|
||||
|
||||
|
|
|
@ -373,7 +373,7 @@ func (ng *Engine) newQuery(q storage.Queryable, expr parser.Expr, start, end tim
|
|||
}
|
||||
|
||||
es := &parser.EvalStmt{
|
||||
Expr: WrapWithStepInvariantExpr(expr),
|
||||
Expr: PreprocessExpr(expr, start, end),
|
||||
Start: start,
|
||||
End: end,
|
||||
Interval: interval,
|
||||
|
@ -387,6 +387,8 @@ func (ng *Engine) newQuery(q storage.Queryable, expr parser.Expr, start, end tim
|
|||
return qry, nil
|
||||
}
|
||||
|
||||
var ErrValidationAtModifierDisabled = errors.New("@ modifier is disabled")
|
||||
|
||||
func (ng *Engine) validateOpts(expr parser.Expr) error {
|
||||
if ng.enableAtModifier {
|
||||
return nil
|
||||
|
@ -396,20 +398,21 @@ func (ng *Engine) validateOpts(expr parser.Expr) error {
|
|||
parser.Inspect(expr, func(node parser.Node, path []parser.Node) error {
|
||||
switch n := node.(type) {
|
||||
case *parser.VectorSelector:
|
||||
if n.Timestamp != nil {
|
||||
validationErr = errors.New("@ modifier is disabled")
|
||||
if n.Timestamp != nil || n.StartOrEnd == parser.START || n.StartOrEnd == parser.END {
|
||||
validationErr = ErrValidationAtModifierDisabled
|
||||
return validationErr
|
||||
}
|
||||
|
||||
case *parser.MatrixSelector:
|
||||
if n.VectorSelector.(*parser.VectorSelector).Timestamp != nil {
|
||||
validationErr = errors.New("@ modifier is disabled")
|
||||
vs := n.VectorSelector.(*parser.VectorSelector)
|
||||
if vs.Timestamp != nil || vs.StartOrEnd == parser.START || vs.StartOrEnd == parser.END {
|
||||
validationErr = ErrValidationAtModifierDisabled
|
||||
return validationErr
|
||||
}
|
||||
|
||||
case *parser.SubqueryExpr:
|
||||
if n.Timestamp != nil {
|
||||
validationErr = errors.New("@ modifier is disabled")
|
||||
if n.Timestamp != nil || n.StartOrEnd == parser.START || n.StartOrEnd == parser.END {
|
||||
validationErr = ErrValidationAtModifierDisabled
|
||||
return validationErr
|
||||
}
|
||||
}
|
||||
|
@ -2311,29 +2314,35 @@ func unwrapStepInvariantExpr(e parser.Expr) parser.Expr {
|
|||
return e
|
||||
}
|
||||
|
||||
// WrapWithStepInvariantExpr wraps all possible parts of the given
|
||||
// expression with StepInvariantExpr wherever valid.
|
||||
func WrapWithStepInvariantExpr(expr parser.Expr) parser.Expr {
|
||||
isStepInvariant := wrapWithStepInvariantExprHelper(expr)
|
||||
// PreprocessExpr wraps all possible step invariant parts of the given expression with
|
||||
// StepInvariantExpr. It also resolves the preprocessors.
|
||||
func PreprocessExpr(expr parser.Expr, start, end time.Time) parser.Expr {
|
||||
isStepInvariant := preprocessExprHelper(expr, start, end)
|
||||
if isStepInvariant {
|
||||
return newStepInvariantExpr(expr)
|
||||
}
|
||||
return expr
|
||||
}
|
||||
|
||||
// wrapWithStepInvariantExprHelper wraps the child nodes of the expression
|
||||
// with a StepInvariantExpr wherever valid. The returned boolean is true if the
|
||||
// preprocessExprHelper wraps the child nodes of the expression
|
||||
// with a StepInvariantExpr wherever it's step invariant. The returned boolean is true if the
|
||||
// passed expression qualifies to be wrapped by StepInvariantExpr.
|
||||
func wrapWithStepInvariantExprHelper(expr parser.Expr) bool {
|
||||
// It also resolves the preprocessors.
|
||||
func preprocessExprHelper(expr parser.Expr, start, end time.Time) bool {
|
||||
switch n := expr.(type) {
|
||||
case *parser.VectorSelector:
|
||||
if n.StartOrEnd == parser.START {
|
||||
n.Timestamp = makeInt64Pointer(timestamp.FromTime(start))
|
||||
} else if n.StartOrEnd == parser.END {
|
||||
n.Timestamp = makeInt64Pointer(timestamp.FromTime(end))
|
||||
}
|
||||
return n.Timestamp != nil
|
||||
|
||||
case *parser.AggregateExpr:
|
||||
return wrapWithStepInvariantExprHelper(n.Expr)
|
||||
return preprocessExprHelper(n.Expr, start, end)
|
||||
|
||||
case *parser.BinaryExpr:
|
||||
isInvariant1, isInvariant2 := wrapWithStepInvariantExprHelper(n.LHS), wrapWithStepInvariantExprHelper(n.RHS)
|
||||
isInvariant1, isInvariant2 := preprocessExprHelper(n.LHS, start, end), preprocessExprHelper(n.RHS, start, end)
|
||||
if isInvariant1 && isInvariant2 {
|
||||
return true
|
||||
}
|
||||
|
@ -2352,7 +2361,7 @@ func wrapWithStepInvariantExprHelper(expr parser.Expr) bool {
|
|||
isStepInvariant := !ok
|
||||
isStepInvariantSlice := make([]bool, len(n.Args))
|
||||
for i := range n.Args {
|
||||
isStepInvariantSlice[i] = wrapWithStepInvariantExprHelper(n.Args[i])
|
||||
isStepInvariantSlice[i] = preprocessExprHelper(n.Args[i], start, end)
|
||||
isStepInvariant = isStepInvariant && isStepInvariantSlice[i]
|
||||
}
|
||||
|
||||
|
@ -2370,7 +2379,7 @@ func wrapWithStepInvariantExprHelper(expr parser.Expr) bool {
|
|||
return false
|
||||
|
||||
case *parser.MatrixSelector:
|
||||
return n.VectorSelector.(*parser.VectorSelector).Timestamp != nil
|
||||
return preprocessExprHelper(n.VectorSelector, start, end)
|
||||
|
||||
case *parser.SubqueryExpr:
|
||||
// Since we adjust offset for the @ modifier evaluation,
|
||||
|
@ -2378,17 +2387,22 @@ func wrapWithStepInvariantExprHelper(expr parser.Expr) bool {
|
|||
// Hence we wrap the inside of subquery irrespective of
|
||||
// @ on subquery (given it is also step invariant) so that
|
||||
// it is evaluated only once w.r.t. the start time of subquery.
|
||||
isInvariant := wrapWithStepInvariantExprHelper(n.Expr)
|
||||
isInvariant := preprocessExprHelper(n.Expr, start, end)
|
||||
if isInvariant {
|
||||
n.Expr = newStepInvariantExpr(n.Expr)
|
||||
}
|
||||
if n.StartOrEnd == parser.START {
|
||||
n.Timestamp = makeInt64Pointer(timestamp.FromTime(start))
|
||||
} else if n.StartOrEnd == parser.END {
|
||||
n.Timestamp = makeInt64Pointer(timestamp.FromTime(end))
|
||||
}
|
||||
return n.Timestamp != nil
|
||||
|
||||
case *parser.ParenExpr:
|
||||
return wrapWithStepInvariantExprHelper(n.Expr)
|
||||
return preprocessExprHelper(n.Expr, start, end)
|
||||
|
||||
case *parser.UnaryExpr:
|
||||
return wrapWithStepInvariantExprHelper(n.Expr)
|
||||
return preprocessExprHelper(n.Expr, start, end)
|
||||
|
||||
case *parser.StringLiteral, *parser.NumberLiteral:
|
||||
return true
|
||||
|
@ -2441,3 +2455,9 @@ func setOffsetForAtModifier(evalTime int64, expr parser.Expr) {
|
|||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func makeInt64Pointer(val int64) *int64 {
|
||||
valp := new(int64)
|
||||
*valp = val
|
||||
return valp
|
||||
}
|
||||
|
|
|
@ -1038,6 +1038,24 @@ load 1ms
|
|||
Metric: lblstopk2,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
query: `metric_topk and topk(1, sum_over_time(metric_topk[50s] @ end()))`,
|
||||
start: 70, end: 100, interval: 10,
|
||||
result: Matrix{
|
||||
Series{
|
||||
Points: []Point{{V: 993, T: 70000}, {V: 992, T: 80000}, {V: 991, T: 90000}, {V: 990, T: 100000}},
|
||||
Metric: lblstopk3,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
query: `metric_topk and topk(1, sum_over_time(metric_topk[50s] @ start()))`,
|
||||
start: 100, end: 130, interval: 10,
|
||||
result: Matrix{
|
||||
Series{
|
||||
Points: []Point{{V: 990, T: 100000}, {V: 989, T: 110000}, {V: 988, T: 120000}, {V: 987, T: 130000}},
|
||||
Metric: lblstopk3,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
// Tests for https://github.com/prometheus/prometheus/issues/8433.
|
||||
// The trick here is that the query range should be > lookback delta.
|
||||
|
@ -1517,7 +1535,9 @@ func TestQueryLogger_error(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestWrapWithStepInvariantExpr(t *testing.T) {
|
||||
func TestPreprocessAndWrapWithStepInvariantExpr(t *testing.T) {
|
||||
startTime := time.Unix(1000, 0)
|
||||
endTime := time.Unix(9999, 0)
|
||||
var testCases = []struct {
|
||||
input string // The input to be parsed.
|
||||
expected parser.Expr // The expected expression AST.
|
||||
|
@ -2106,6 +2126,120 @@ func TestWrapWithStepInvariantExpr(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
input: `foo @ start()`,
|
||||
expected: &parser.StepInvariantExpr{
|
||||
Expr: &parser.VectorSelector{
|
||||
Name: "foo",
|
||||
LabelMatchers: []*labels.Matcher{
|
||||
parser.MustLabelMatcher(labels.MatchEqual, "__name__", "foo"),
|
||||
},
|
||||
PosRange: parser.PositionRange{
|
||||
Start: 0,
|
||||
End: 13,
|
||||
},
|
||||
Timestamp: makeInt64Pointer(timestamp.FromTime(startTime)),
|
||||
StartOrEnd: parser.START,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
input: `foo @ end()`,
|
||||
expected: &parser.StepInvariantExpr{
|
||||
Expr: &parser.VectorSelector{
|
||||
Name: "foo",
|
||||
LabelMatchers: []*labels.Matcher{
|
||||
parser.MustLabelMatcher(labels.MatchEqual, "__name__", "foo"),
|
||||
},
|
||||
PosRange: parser.PositionRange{
|
||||
Start: 0,
|
||||
End: 11,
|
||||
},
|
||||
Timestamp: makeInt64Pointer(timestamp.FromTime(endTime)),
|
||||
StartOrEnd: parser.END,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
input: `test[5y] @ start()`,
|
||||
expected: &parser.StepInvariantExpr{
|
||||
Expr: &parser.MatrixSelector{
|
||||
VectorSelector: &parser.VectorSelector{
|
||||
Name: "test",
|
||||
Timestamp: makeInt64Pointer(timestamp.FromTime(startTime)),
|
||||
StartOrEnd: parser.START,
|
||||
LabelMatchers: []*labels.Matcher{
|
||||
parser.MustLabelMatcher(labels.MatchEqual, "__name__", "test"),
|
||||
},
|
||||
PosRange: parser.PositionRange{
|
||||
Start: 0,
|
||||
End: 4,
|
||||
},
|
||||
},
|
||||
Range: 5 * 365 * 24 * time.Hour,
|
||||
EndPos: 18,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
input: `test[5y] @ end()`,
|
||||
expected: &parser.StepInvariantExpr{
|
||||
Expr: &parser.MatrixSelector{
|
||||
VectorSelector: &parser.VectorSelector{
|
||||
Name: "test",
|
||||
Timestamp: makeInt64Pointer(timestamp.FromTime(endTime)),
|
||||
StartOrEnd: parser.END,
|
||||
LabelMatchers: []*labels.Matcher{
|
||||
parser.MustLabelMatcher(labels.MatchEqual, "__name__", "test"),
|
||||
},
|
||||
PosRange: parser.PositionRange{
|
||||
Start: 0,
|
||||
End: 4,
|
||||
},
|
||||
},
|
||||
Range: 5 * 365 * 24 * time.Hour,
|
||||
EndPos: 16,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
input: `some_metric[10m:5s] @ start()`,
|
||||
expected: &parser.StepInvariantExpr{
|
||||
Expr: &parser.SubqueryExpr{
|
||||
Expr: &parser.VectorSelector{
|
||||
Name: "some_metric",
|
||||
LabelMatchers: []*labels.Matcher{
|
||||
parser.MustLabelMatcher(labels.MatchEqual, "__name__", "some_metric"),
|
||||
},
|
||||
PosRange: parser.PositionRange{
|
||||
Start: 0,
|
||||
End: 11,
|
||||
},
|
||||
},
|
||||
Timestamp: makeInt64Pointer(timestamp.FromTime(startTime)),
|
||||
StartOrEnd: parser.START,
|
||||
Range: 10 * time.Minute,
|
||||
Step: 5 * time.Second,
|
||||
EndPos: 29,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
input: `some_metric[10m:5s] @ end()`,
|
||||
expected: &parser.StepInvariantExpr{
|
||||
Expr: &parser.SubqueryExpr{
|
||||
Expr: &parser.VectorSelector{
|
||||
Name: "some_metric",
|
||||
LabelMatchers: []*labels.Matcher{
|
||||
parser.MustLabelMatcher(labels.MatchEqual, "__name__", "some_metric"),
|
||||
},
|
||||
PosRange: parser.PositionRange{
|
||||
Start: 0,
|
||||
End: 11,
|
||||
},
|
||||
},
|
||||
Timestamp: makeInt64Pointer(timestamp.FromTime(endTime)),
|
||||
StartOrEnd: parser.END,
|
||||
Range: 10 * time.Minute,
|
||||
Step: 5 * time.Second,
|
||||
EndPos: 27,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -2113,7 +2247,7 @@ func TestWrapWithStepInvariantExpr(t *testing.T) {
|
|||
t.Run(test.input, func(t *testing.T) {
|
||||
expr, err := parser.ParseExpr(test.input)
|
||||
require.NoError(t, err)
|
||||
expr = WrapWithStepInvariantExpr(expr)
|
||||
expr = PreprocessExpr(expr, startTime, endTime)
|
||||
require.Equal(t, test.expected, expr, "error on input '%s'", test.input)
|
||||
})
|
||||
}
|
||||
|
@ -2124,16 +2258,44 @@ func TestEngineOptsValidation(t *testing.T) {
|
|||
opts EngineOpts
|
||||
query string
|
||||
fail bool
|
||||
expError string
|
||||
expError error
|
||||
}{
|
||||
{
|
||||
opts: EngineOpts{EnableAtModifier: false},
|
||||
query: "metric @ 100",
|
||||
fail: true,
|
||||
expError: "@ modifier is disabled",
|
||||
opts: EngineOpts{EnableAtModifier: false},
|
||||
query: "metric @ 100", fail: true, expError: ErrValidationAtModifierDisabled,
|
||||
}, {
|
||||
opts: EngineOpts{EnableAtModifier: false},
|
||||
query: "rate(metric[1m] @ 100)", fail: true, expError: ErrValidationAtModifierDisabled,
|
||||
}, {
|
||||
opts: EngineOpts{EnableAtModifier: false},
|
||||
query: "rate(metric[1h:1m] @ 100)", fail: true, expError: ErrValidationAtModifierDisabled,
|
||||
}, {
|
||||
opts: EngineOpts{EnableAtModifier: false},
|
||||
query: "metric @ start()", fail: true, expError: ErrValidationAtModifierDisabled,
|
||||
}, {
|
||||
opts: EngineOpts{EnableAtModifier: false},
|
||||
query: "rate(metric[1m] @ start())", fail: true, expError: ErrValidationAtModifierDisabled,
|
||||
}, {
|
||||
opts: EngineOpts{EnableAtModifier: false},
|
||||
query: "rate(metric[1h:1m] @ start())", fail: true, expError: ErrValidationAtModifierDisabled,
|
||||
}, {
|
||||
opts: EngineOpts{EnableAtModifier: false},
|
||||
query: "metric @ end()", fail: true, expError: ErrValidationAtModifierDisabled,
|
||||
}, {
|
||||
opts: EngineOpts{EnableAtModifier: false},
|
||||
query: "rate(metric[1m] @ end())", fail: true, expError: ErrValidationAtModifierDisabled,
|
||||
}, {
|
||||
opts: EngineOpts{EnableAtModifier: false},
|
||||
query: "rate(metric[1h:1m] @ end())", fail: true, expError: ErrValidationAtModifierDisabled,
|
||||
}, {
|
||||
opts: EngineOpts{EnableAtModifier: true},
|
||||
query: "metric @ 100",
|
||||
}, {
|
||||
opts: EngineOpts{EnableAtModifier: true},
|
||||
query: "rate(metric[1m] @ start())",
|
||||
}, {
|
||||
opts: EngineOpts{EnableAtModifier: true},
|
||||
query: "rate(metric[1h:1m] @ end())",
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -2142,8 +2304,8 @@ func TestEngineOptsValidation(t *testing.T) {
|
|||
_, err1 := eng.NewInstantQuery(nil, c.query, time.Unix(10, 0))
|
||||
_, err2 := eng.NewRangeQuery(nil, c.query, time.Unix(0, 0), time.Unix(10, 0), time.Second)
|
||||
if c.fail {
|
||||
require.Equal(t, c.expError, err1.Error())
|
||||
require.Equal(t, c.expError, err2.Error())
|
||||
require.Equal(t, c.expError, err1)
|
||||
require.Equal(t, c.expError, err2)
|
||||
} else {
|
||||
require.Nil(t, err1)
|
||||
require.Nil(t, err2)
|
||||
|
|
|
@ -133,9 +133,10 @@ type SubqueryExpr struct {
|
|||
// Offset is the offset used during the query execution
|
||||
// which is calculated using the original offset, at modifier time,
|
||||
// eval time, and subquery offsets in the AST tree.
|
||||
Offset time.Duration
|
||||
Timestamp *int64
|
||||
Step time.Duration
|
||||
Offset time.Duration
|
||||
Timestamp *int64
|
||||
StartOrEnd ItemType // Set when @ is used with start() or end()
|
||||
Step time.Duration
|
||||
|
||||
EndPos Pos
|
||||
}
|
||||
|
@ -191,6 +192,7 @@ type VectorSelector struct {
|
|||
// eval time, and subquery offsets in the AST tree.
|
||||
Offset time.Duration
|
||||
Timestamp *int64
|
||||
StartOrEnd ItemType // Set when @ is used with start() or end()
|
||||
LabelMatchers []*labels.Matcher
|
||||
|
||||
// The unexpanded seriesSet populated at query preparation time.
|
||||
|
|
|
@ -116,6 +116,13 @@ ON
|
|||
WITHOUT
|
||||
%token keywordsEnd
|
||||
|
||||
// Preprocessors.
|
||||
%token preprocessorStart
|
||||
%token <item>
|
||||
START
|
||||
END
|
||||
%token preprocessorEnd
|
||||
|
||||
|
||||
// Start symbols for the generated parser.
|
||||
%token startSymbolsStart
|
||||
|
@ -131,7 +138,7 @@ START_METRIC_SELECTOR
|
|||
%type <matchers> label_match_list
|
||||
%type <matcher> label_matcher
|
||||
|
||||
%type <item> aggregate_op grouping_label match_op maybe_label metric_identifier unary_op
|
||||
%type <item> aggregate_op grouping_label match_op maybe_label metric_identifier unary_op at_modifier_preprocessors
|
||||
|
||||
%type <labels> label_set label_set_list metric
|
||||
%type <label> label_set_item
|
||||
|
@ -391,11 +398,17 @@ step_invariant_expr: expr AT signed_or_unsigned_number
|
|||
yylex.(*parser).setTimestamp($1, $3)
|
||||
$$ = $1
|
||||
}
|
||||
|
||||
| expr AT at_modifier_preprocessors LEFT_PAREN RIGHT_PAREN
|
||||
{
|
||||
yylex.(*parser).setAtModifierPreprocessor($1, $3)
|
||||
$$ = $1
|
||||
}
|
||||
| expr AT error
|
||||
{ yylex.(*parser).unexpected("@", "timestamp"); $$ = $1 }
|
||||
;
|
||||
|
||||
at_modifier_preprocessors: START | END;
|
||||
|
||||
/*
|
||||
* Subquery and range selectors.
|
||||
*/
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -121,6 +121,10 @@ var key = map[string]ItemType{
|
|||
"group_left": GROUP_LEFT,
|
||||
"group_right": GROUP_RIGHT,
|
||||
"bool": BOOL,
|
||||
|
||||
// Preprocessors.
|
||||
"start": START,
|
||||
"end": END,
|
||||
}
|
||||
|
||||
// ItemTypeStr is the default string representations for common Items. It does not
|
||||
|
|
|
@ -276,6 +276,9 @@ var tests = []struct {
|
|||
}, {
|
||||
input: `unless`,
|
||||
expected: []Item{{LUNLESS, 0, `unless`}},
|
||||
}, {
|
||||
input: `@`,
|
||||
expected: []Item{{AT, 0, `@`}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -339,6 +342,19 @@ var tests = []struct {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "preprocessors",
|
||||
tests: []testCase{
|
||||
{
|
||||
input: `start`,
|
||||
expected: []Item{{START, 0, `start`}},
|
||||
},
|
||||
{
|
||||
input: `end`,
|
||||
expected: []Item{{END, 0, `end`}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "selectors",
|
||||
tests: []testCase{
|
||||
|
|
|
@ -726,29 +726,12 @@ func (p *parser) setTimestamp(e Node, ts float64) {
|
|||
var timestampp **int64
|
||||
var endPosp *Pos
|
||||
|
||||
switch s := e.(type) {
|
||||
case *VectorSelector:
|
||||
timestampp = &s.Timestamp
|
||||
endPosp = &s.PosRange.End
|
||||
case *MatrixSelector:
|
||||
vs, ok := s.VectorSelector.(*VectorSelector)
|
||||
if !ok {
|
||||
p.addParseErrf(e.PositionRange(), "ranges only allowed for vector selectors")
|
||||
return
|
||||
}
|
||||
timestampp = &vs.Timestamp
|
||||
endPosp = &s.EndPos
|
||||
case *SubqueryExpr:
|
||||
timestampp = &s.Timestamp
|
||||
endPosp = &s.EndPos
|
||||
default:
|
||||
p.addParseErrf(e.PositionRange(), "@ modifier must be preceded by an instant selector vector or range vector selector or a subquery")
|
||||
timestampp, _, endPosp, ok := p.getAtModifierVars(e)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if *timestampp != nil {
|
||||
p.addParseErrf(e.PositionRange(), "@ <timestamp> may not be set multiple times")
|
||||
} else if timestampp != nil {
|
||||
if timestampp != nil {
|
||||
*timestampp = new(int64)
|
||||
**timestampp = timestamp.FromFloatSeconds(ts)
|
||||
}
|
||||
|
@ -756,6 +739,57 @@ func (p *parser) setTimestamp(e Node, ts float64) {
|
|||
*endPosp = p.lastClosing
|
||||
}
|
||||
|
||||
// setAtModifierPreprocessor is used to set the preprocessor for the @ modifier.
|
||||
func (p *parser) setAtModifierPreprocessor(e Node, op Item) {
|
||||
_, preprocp, endPosp, ok := p.getAtModifierVars(e)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if preprocp != nil {
|
||||
*preprocp = op.Typ
|
||||
}
|
||||
|
||||
*endPosp = p.lastClosing
|
||||
}
|
||||
|
||||
func (p *parser) getAtModifierVars(e Node) (**int64, *ItemType, *Pos, bool) {
|
||||
var (
|
||||
timestampp **int64
|
||||
preprocp *ItemType
|
||||
endPosp *Pos
|
||||
)
|
||||
switch s := e.(type) {
|
||||
case *VectorSelector:
|
||||
timestampp = &s.Timestamp
|
||||
preprocp = &s.StartOrEnd
|
||||
endPosp = &s.PosRange.End
|
||||
case *MatrixSelector:
|
||||
vs, ok := s.VectorSelector.(*VectorSelector)
|
||||
if !ok {
|
||||
p.addParseErrf(e.PositionRange(), "ranges only allowed for vector selectors")
|
||||
return nil, nil, nil, false
|
||||
}
|
||||
preprocp = &vs.StartOrEnd
|
||||
timestampp = &vs.Timestamp
|
||||
endPosp = &s.EndPos
|
||||
case *SubqueryExpr:
|
||||
preprocp = &s.StartOrEnd
|
||||
timestampp = &s.Timestamp
|
||||
endPosp = &s.EndPos
|
||||
default:
|
||||
p.addParseErrf(e.PositionRange(), "@ modifier must be preceded by an instant selector vector or range vector selector or a subquery")
|
||||
return nil, nil, nil, false
|
||||
}
|
||||
|
||||
if *timestampp != nil || (*preprocp) == START || (*preprocp) == END {
|
||||
p.addParseErrf(e.PositionRange(), "@ <timestamp> may not be set multiple times")
|
||||
return nil, nil, nil, false
|
||||
}
|
||||
|
||||
return timestampp, preprocp, endPosp, true
|
||||
}
|
||||
|
||||
func MustLabelMatcher(mt labels.MatchType, name, val string) *labels.Matcher {
|
||||
m, err := labels.NewMatcher(mt, name, val)
|
||||
if err != nil {
|
||||
|
|
|
@ -3059,6 +3059,112 @@ var testExpr = []struct {
|
|||
fail: true,
|
||||
errMsg: `1:15: parse error: ranges only allowed for vector selectors`,
|
||||
},
|
||||
// Preprocessors.
|
||||
{
|
||||
input: `foo @ start()`,
|
||||
expected: &VectorSelector{
|
||||
Name: "foo",
|
||||
StartOrEnd: START,
|
||||
LabelMatchers: []*labels.Matcher{
|
||||
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
|
||||
},
|
||||
PosRange: PositionRange{
|
||||
Start: 0,
|
||||
End: 13,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
input: `foo @ end()`,
|
||||
expected: &VectorSelector{
|
||||
Name: "foo",
|
||||
StartOrEnd: END,
|
||||
LabelMatchers: []*labels.Matcher{
|
||||
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
|
||||
},
|
||||
PosRange: PositionRange{
|
||||
Start: 0,
|
||||
End: 11,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
input: `test[5y] @ start()`,
|
||||
expected: &MatrixSelector{
|
||||
VectorSelector: &VectorSelector{
|
||||
Name: "test",
|
||||
StartOrEnd: START,
|
||||
LabelMatchers: []*labels.Matcher{
|
||||
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "test"),
|
||||
},
|
||||
PosRange: PositionRange{
|
||||
Start: 0,
|
||||
End: 4,
|
||||
},
|
||||
},
|
||||
Range: 5 * 365 * 24 * time.Hour,
|
||||
EndPos: 18,
|
||||
},
|
||||
}, {
|
||||
input: `test[5y] @ end()`,
|
||||
expected: &MatrixSelector{
|
||||
VectorSelector: &VectorSelector{
|
||||
Name: "test",
|
||||
StartOrEnd: END,
|
||||
LabelMatchers: []*labels.Matcher{
|
||||
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "test"),
|
||||
},
|
||||
PosRange: PositionRange{
|
||||
Start: 0,
|
||||
End: 4,
|
||||
},
|
||||
},
|
||||
Range: 5 * 365 * 24 * time.Hour,
|
||||
EndPos: 16,
|
||||
},
|
||||
}, {
|
||||
input: `foo[10m:6s] @ start()`,
|
||||
expected: &SubqueryExpr{
|
||||
Expr: &VectorSelector{
|
||||
Name: "foo",
|
||||
LabelMatchers: []*labels.Matcher{
|
||||
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
|
||||
},
|
||||
PosRange: PositionRange{
|
||||
Start: 0,
|
||||
End: 3,
|
||||
},
|
||||
},
|
||||
Range: 10 * time.Minute,
|
||||
Step: 6 * time.Second,
|
||||
StartOrEnd: START,
|
||||
EndPos: 21,
|
||||
},
|
||||
}, {
|
||||
input: `foo[10m:6s] @ end()`,
|
||||
expected: &SubqueryExpr{
|
||||
Expr: &VectorSelector{
|
||||
Name: "foo",
|
||||
LabelMatchers: []*labels.Matcher{
|
||||
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
|
||||
},
|
||||
PosRange: PositionRange{
|
||||
Start: 0,
|
||||
End: 3,
|
||||
},
|
||||
},
|
||||
Range: 10 * time.Minute,
|
||||
Step: 6 * time.Second,
|
||||
StartOrEnd: END,
|
||||
EndPos: 19,
|
||||
},
|
||||
}, {
|
||||
input: `start()`,
|
||||
fail: true,
|
||||
errMsg: `1:1: parse error: unexpected "start"`,
|
||||
}, {
|
||||
input: `end()`,
|
||||
fail: true,
|
||||
errMsg: `1:1: parse error: unexpected "end"`,
|
||||
},
|
||||
}
|
||||
|
||||
func makeInt64Pointer(val int64) *int64 {
|
||||
|
|
|
@ -122,16 +122,21 @@ func (node *MatrixSelector) String() string {
|
|||
at := ""
|
||||
if vecSelector.Timestamp != nil {
|
||||
at = fmt.Sprintf(" @ %.3f", float64(*vecSelector.Timestamp)/1000.0)
|
||||
} else if vecSelector.StartOrEnd == START {
|
||||
at = " @ start()"
|
||||
} else if vecSelector.StartOrEnd == END {
|
||||
at = " @ end()"
|
||||
}
|
||||
|
||||
// Do not print the @ and offset twice.
|
||||
offsetVal, atVal := vecSelector.OriginalOffset, vecSelector.Timestamp
|
||||
offsetVal, atVal, preproc := vecSelector.OriginalOffset, vecSelector.Timestamp, vecSelector.StartOrEnd
|
||||
vecSelector.OriginalOffset = 0
|
||||
vecSelector.Timestamp = nil
|
||||
vecSelector.StartOrEnd = 0
|
||||
|
||||
str := fmt.Sprintf("%s[%s]%s%s", vecSelector.String(), model.Duration(node.Range), at, offset)
|
||||
|
||||
vecSelector.OriginalOffset, vecSelector.Timestamp = offsetVal, atVal
|
||||
vecSelector.OriginalOffset, vecSelector.Timestamp, vecSelector.StartOrEnd = offsetVal, atVal, preproc
|
||||
|
||||
return str
|
||||
}
|
||||
|
@ -148,6 +153,10 @@ func (node *SubqueryExpr) String() string {
|
|||
at := ""
|
||||
if node.Timestamp != nil {
|
||||
at = fmt.Sprintf(" @ %.3f", float64(*node.Timestamp)/1000.0)
|
||||
} else if node.StartOrEnd == START {
|
||||
at = " @ start()"
|
||||
} else if node.StartOrEnd == END {
|
||||
at = " @ end()"
|
||||
}
|
||||
return fmt.Sprintf("%s[%s:%s]%s%s", node.Expr.String(), model.Duration(node.Range), step, at, offset)
|
||||
}
|
||||
|
@ -184,6 +193,10 @@ func (node *VectorSelector) String() string {
|
|||
at := ""
|
||||
if node.Timestamp != nil {
|
||||
at = fmt.Sprintf(" @ %.3f", float64(*node.Timestamp)/1000.0)
|
||||
} else if node.StartOrEnd == START {
|
||||
at = " @ start()"
|
||||
} else if node.StartOrEnd == END {
|
||||
at = " @ end()"
|
||||
}
|
||||
|
||||
if len(labelStrings) == 0 {
|
||||
|
|
|
@ -98,6 +98,26 @@ func TestExprString(t *testing.T) {
|
|||
{
|
||||
in: `a{b!~"c"}[1m]`,
|
||||
},
|
||||
{
|
||||
in: `a @ 10`,
|
||||
out: `a @ 10.000`,
|
||||
},
|
||||
{
|
||||
in: `a[1m] @ 10`,
|
||||
out: `a[1m] @ 10.000`,
|
||||
},
|
||||
{
|
||||
in: `a @ start()`,
|
||||
},
|
||||
{
|
||||
in: `a @ end()`,
|
||||
},
|
||||
{
|
||||
in: `a[1m] @ start()`,
|
||||
},
|
||||
{
|
||||
in: `a[1m] @ end()`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range inputs {
|
||||
|
|
|
@ -779,9 +779,3 @@ func (ll *LazyLoader) Close() {
|
|||
ll.T.Fatalf("closing test storage: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func makeInt64Pointer(val int64) *int64 {
|
||||
valp := new(int64)
|
||||
*valp = val
|
||||
return valp
|
||||
}
|
||||
|
|
|
@ -368,6 +368,9 @@ func (api *API) query(r *http.Request) (result apiFuncResult) {
|
|||
}
|
||||
|
||||
qry, err := api.QueryEngine.NewInstantQuery(api.Queryable, r.FormValue("query"), ts)
|
||||
if err == promql.ErrValidationAtModifierDisabled {
|
||||
err = errors.New("@ modifier is disabled, use --enable-feature=promql-at-modifier to enable it")
|
||||
}
|
||||
if err != nil {
|
||||
return invalidParamError(err, "query")
|
||||
}
|
||||
|
@ -443,6 +446,9 @@ func (api *API) queryRange(r *http.Request) (result apiFuncResult) {
|
|||
}
|
||||
|
||||
qry, err := api.QueryEngine.NewRangeQuery(api.Queryable, r.FormValue("query"), start, end, step)
|
||||
if err == promql.ErrValidationAtModifierDisabled {
|
||||
err = errors.New("@ modifier is disabled, use --enable-feature=promql-at-modifier to enable it")
|
||||
}
|
||||
if err != nil {
|
||||
return apiFuncResult{nil, &apiError{errorBadData, err}, nil, nil}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue