mirror of https://github.com/prometheus/prometheus
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>pull/8464/head
parent
b9f0baf6ff
commit
86c71856e8
|
@ -15,11 +15,12 @@ They may be enabled by default in future versions.
|
||||||
|
|
||||||
`--enable-feature=promql-at-modifier`
|
`--enable-feature=promql-at-modifier`
|
||||||
|
|
||||||
This feature lets you specify the evaluation time for instant vector selectors,
|
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).
|
range vector selectors, and subqueries. More details can be found [here](querying/basics.md#-modifier).
|
||||||
|
|
||||||
## Remote Write Receiver
|
## Remote Write Receiver
|
||||||
|
|
||||||
`--enable-feature=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).
|
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
|
### @ modifier
|
||||||
|
|
||||||
The `@` modifier allows changing the evaluation time for individual instant
|
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.
|
is a unix timestamp and described with a float literal.
|
||||||
|
|
||||||
For example, the following expression returns the value of
|
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)
|
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
|
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.
|
modifier time irrespective of which modifier is written first.
|
||||||
These 2 queries will produce the same result.
|
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
|
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
|
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
|
## Subquery
|
||||||
|
|
||||||
|
|
|
@ -373,7 +373,7 @@ func (ng *Engine) newQuery(q storage.Queryable, expr parser.Expr, start, end tim
|
||||||
}
|
}
|
||||||
|
|
||||||
es := &parser.EvalStmt{
|
es := &parser.EvalStmt{
|
||||||
Expr: WrapWithStepInvariantExpr(expr),
|
Expr: PreprocessExpr(expr, start, end),
|
||||||
Start: start,
|
Start: start,
|
||||||
End: end,
|
End: end,
|
||||||
Interval: interval,
|
Interval: interval,
|
||||||
|
@ -387,6 +387,8 @@ func (ng *Engine) newQuery(q storage.Queryable, expr parser.Expr, start, end tim
|
||||||
return qry, nil
|
return qry, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ErrValidationAtModifierDisabled = errors.New("@ modifier is disabled")
|
||||||
|
|
||||||
func (ng *Engine) validateOpts(expr parser.Expr) error {
|
func (ng *Engine) validateOpts(expr parser.Expr) error {
|
||||||
if ng.enableAtModifier {
|
if ng.enableAtModifier {
|
||||||
return nil
|
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 {
|
parser.Inspect(expr, func(node parser.Node, path []parser.Node) error {
|
||||||
switch n := node.(type) {
|
switch n := node.(type) {
|
||||||
case *parser.VectorSelector:
|
case *parser.VectorSelector:
|
||||||
if n.Timestamp != nil {
|
if n.Timestamp != nil || n.StartOrEnd == parser.START || n.StartOrEnd == parser.END {
|
||||||
validationErr = errors.New("@ modifier is disabled")
|
validationErr = ErrValidationAtModifierDisabled
|
||||||
return validationErr
|
return validationErr
|
||||||
}
|
}
|
||||||
|
|
||||||
case *parser.MatrixSelector:
|
case *parser.MatrixSelector:
|
||||||
if n.VectorSelector.(*parser.VectorSelector).Timestamp != nil {
|
vs := n.VectorSelector.(*parser.VectorSelector)
|
||||||
validationErr = errors.New("@ modifier is disabled")
|
if vs.Timestamp != nil || vs.StartOrEnd == parser.START || vs.StartOrEnd == parser.END {
|
||||||
|
validationErr = ErrValidationAtModifierDisabled
|
||||||
return validationErr
|
return validationErr
|
||||||
}
|
}
|
||||||
|
|
||||||
case *parser.SubqueryExpr:
|
case *parser.SubqueryExpr:
|
||||||
if n.Timestamp != nil {
|
if n.Timestamp != nil || n.StartOrEnd == parser.START || n.StartOrEnd == parser.END {
|
||||||
validationErr = errors.New("@ modifier is disabled")
|
validationErr = ErrValidationAtModifierDisabled
|
||||||
return validationErr
|
return validationErr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2311,29 +2314,35 @@ func unwrapStepInvariantExpr(e parser.Expr) parser.Expr {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
// WrapWithStepInvariantExpr wraps all possible parts of the given
|
// PreprocessExpr wraps all possible step invariant parts of the given expression with
|
||||||
// expression with StepInvariantExpr wherever valid.
|
// StepInvariantExpr. It also resolves the preprocessors.
|
||||||
func WrapWithStepInvariantExpr(expr parser.Expr) parser.Expr {
|
func PreprocessExpr(expr parser.Expr, start, end time.Time) parser.Expr {
|
||||||
isStepInvariant := wrapWithStepInvariantExprHelper(expr)
|
isStepInvariant := preprocessExprHelper(expr, start, end)
|
||||||
if isStepInvariant {
|
if isStepInvariant {
|
||||||
return newStepInvariantExpr(expr)
|
return newStepInvariantExpr(expr)
|
||||||
}
|
}
|
||||||
return expr
|
return expr
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrapWithStepInvariantExprHelper wraps the child nodes of the expression
|
// preprocessExprHelper wraps the child nodes of the expression
|
||||||
// with a StepInvariantExpr wherever valid. The returned boolean is true if the
|
// with a StepInvariantExpr wherever it's step invariant. The returned boolean is true if the
|
||||||
// passed expression qualifies to be wrapped by StepInvariantExpr.
|
// 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) {
|
switch n := expr.(type) {
|
||||||
case *parser.VectorSelector:
|
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
|
return n.Timestamp != nil
|
||||||
|
|
||||||
case *parser.AggregateExpr:
|
case *parser.AggregateExpr:
|
||||||
return wrapWithStepInvariantExprHelper(n.Expr)
|
return preprocessExprHelper(n.Expr, start, end)
|
||||||
|
|
||||||
case *parser.BinaryExpr:
|
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 {
|
if isInvariant1 && isInvariant2 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -2352,7 +2361,7 @@ func wrapWithStepInvariantExprHelper(expr parser.Expr) bool {
|
||||||
isStepInvariant := !ok
|
isStepInvariant := !ok
|
||||||
isStepInvariantSlice := make([]bool, len(n.Args))
|
isStepInvariantSlice := make([]bool, len(n.Args))
|
||||||
for i := range 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]
|
isStepInvariant = isStepInvariant && isStepInvariantSlice[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2370,7 +2379,7 @@ func wrapWithStepInvariantExprHelper(expr parser.Expr) bool {
|
||||||
return false
|
return false
|
||||||
|
|
||||||
case *parser.MatrixSelector:
|
case *parser.MatrixSelector:
|
||||||
return n.VectorSelector.(*parser.VectorSelector).Timestamp != nil
|
return preprocessExprHelper(n.VectorSelector, start, end)
|
||||||
|
|
||||||
case *parser.SubqueryExpr:
|
case *parser.SubqueryExpr:
|
||||||
// Since we adjust offset for the @ modifier evaluation,
|
// 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
|
// Hence we wrap the inside of subquery irrespective of
|
||||||
// @ on subquery (given it is also step invariant) so that
|
// @ on subquery (given it is also step invariant) so that
|
||||||
// it is evaluated only once w.r.t. the start time of subquery.
|
// 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 {
|
if isInvariant {
|
||||||
n.Expr = newStepInvariantExpr(n.Expr)
|
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
|
return n.Timestamp != nil
|
||||||
|
|
||||||
case *parser.ParenExpr:
|
case *parser.ParenExpr:
|
||||||
return wrapWithStepInvariantExprHelper(n.Expr)
|
return preprocessExprHelper(n.Expr, start, end)
|
||||||
|
|
||||||
case *parser.UnaryExpr:
|
case *parser.UnaryExpr:
|
||||||
return wrapWithStepInvariantExprHelper(n.Expr)
|
return preprocessExprHelper(n.Expr, start, end)
|
||||||
|
|
||||||
case *parser.StringLiteral, *parser.NumberLiteral:
|
case *parser.StringLiteral, *parser.NumberLiteral:
|
||||||
return true
|
return true
|
||||||
|
@ -2441,3 +2455,9 @@ func setOffsetForAtModifier(evalTime int64, expr parser.Expr) {
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeInt64Pointer(val int64) *int64 {
|
||||||
|
valp := new(int64)
|
||||||
|
*valp = val
|
||||||
|
return valp
|
||||||
|
}
|
||||||
|
|
|
@ -1038,6 +1038,24 @@ load 1ms
|
||||||
Metric: lblstopk2,
|
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.
|
// Tests for https://github.com/prometheus/prometheus/issues/8433.
|
||||||
// The trick here is that the query range should be > lookback delta.
|
// 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 {
|
var testCases = []struct {
|
||||||
input string // The input to be parsed.
|
input string // The input to be parsed.
|
||||||
expected parser.Expr // The expected expression AST.
|
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) {
|
t.Run(test.input, func(t *testing.T) {
|
||||||
expr, err := parser.ParseExpr(test.input)
|
expr, err := parser.ParseExpr(test.input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
expr = WrapWithStepInvariantExpr(expr)
|
expr = PreprocessExpr(expr, startTime, endTime)
|
||||||
require.Equal(t, test.expected, expr, "error on input '%s'", test.input)
|
require.Equal(t, test.expected, expr, "error on input '%s'", test.input)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -2124,16 +2258,44 @@ func TestEngineOptsValidation(t *testing.T) {
|
||||||
opts EngineOpts
|
opts EngineOpts
|
||||||
query string
|
query string
|
||||||
fail bool
|
fail bool
|
||||||
expError string
|
expError error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
opts: EngineOpts{EnableAtModifier: false},
|
opts: EngineOpts{EnableAtModifier: false},
|
||||||
query: "metric @ 100",
|
query: "metric @ 100", fail: true, expError: ErrValidationAtModifierDisabled,
|
||||||
fail: true,
|
}, {
|
||||||
expError: "@ modifier is disabled",
|
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},
|
opts: EngineOpts{EnableAtModifier: true},
|
||||||
query: "metric @ 100",
|
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))
|
_, 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)
|
_, err2 := eng.NewRangeQuery(nil, c.query, time.Unix(0, 0), time.Unix(10, 0), time.Second)
|
||||||
if c.fail {
|
if c.fail {
|
||||||
require.Equal(t, c.expError, err1.Error())
|
require.Equal(t, c.expError, err1)
|
||||||
require.Equal(t, c.expError, err2.Error())
|
require.Equal(t, c.expError, err2)
|
||||||
} else {
|
} else {
|
||||||
require.Nil(t, err1)
|
require.Nil(t, err1)
|
||||||
require.Nil(t, err2)
|
require.Nil(t, err2)
|
||||||
|
|
|
@ -133,9 +133,10 @@ type SubqueryExpr struct {
|
||||||
// Offset is the offset used during the query execution
|
// Offset is the offset used during the query execution
|
||||||
// which is calculated using the original offset, at modifier time,
|
// which is calculated using the original offset, at modifier time,
|
||||||
// eval time, and subquery offsets in the AST tree.
|
// eval time, and subquery offsets in the AST tree.
|
||||||
Offset time.Duration
|
Offset time.Duration
|
||||||
Timestamp *int64
|
Timestamp *int64
|
||||||
Step time.Duration
|
StartOrEnd ItemType // Set when @ is used with start() or end()
|
||||||
|
Step time.Duration
|
||||||
|
|
||||||
EndPos Pos
|
EndPos Pos
|
||||||
}
|
}
|
||||||
|
@ -191,6 +192,7 @@ type VectorSelector struct {
|
||||||
// eval time, and subquery offsets in the AST tree.
|
// eval time, and subquery offsets in the AST tree.
|
||||||
Offset time.Duration
|
Offset time.Duration
|
||||||
Timestamp *int64
|
Timestamp *int64
|
||||||
|
StartOrEnd ItemType // Set when @ is used with start() or end()
|
||||||
LabelMatchers []*labels.Matcher
|
LabelMatchers []*labels.Matcher
|
||||||
|
|
||||||
// The unexpanded seriesSet populated at query preparation time.
|
// The unexpanded seriesSet populated at query preparation time.
|
||||||
|
|
|
@ -116,6 +116,13 @@ ON
|
||||||
WITHOUT
|
WITHOUT
|
||||||
%token keywordsEnd
|
%token keywordsEnd
|
||||||
|
|
||||||
|
// Preprocessors.
|
||||||
|
%token preprocessorStart
|
||||||
|
%token <item>
|
||||||
|
START
|
||||||
|
END
|
||||||
|
%token preprocessorEnd
|
||||||
|
|
||||||
|
|
||||||
// Start symbols for the generated parser.
|
// Start symbols for the generated parser.
|
||||||
%token startSymbolsStart
|
%token startSymbolsStart
|
||||||
|
@ -131,7 +138,7 @@ START_METRIC_SELECTOR
|
||||||
%type <matchers> label_match_list
|
%type <matchers> label_match_list
|
||||||
%type <matcher> label_matcher
|
%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 <labels> label_set label_set_list metric
|
||||||
%type <label> label_set_item
|
%type <label> label_set_item
|
||||||
|
@ -391,11 +398,17 @@ step_invariant_expr: expr AT signed_or_unsigned_number
|
||||||
yylex.(*parser).setTimestamp($1, $3)
|
yylex.(*parser).setTimestamp($1, $3)
|
||||||
$$ = $1
|
$$ = $1
|
||||||
}
|
}
|
||||||
|
| expr AT at_modifier_preprocessors LEFT_PAREN RIGHT_PAREN
|
||||||
|
{
|
||||||
|
yylex.(*parser).setAtModifierPreprocessor($1, $3)
|
||||||
|
$$ = $1
|
||||||
|
}
|
||||||
| expr AT error
|
| expr AT error
|
||||||
{ yylex.(*parser).unexpected("@", "timestamp"); $$ = $1 }
|
{ yylex.(*parser).unexpected("@", "timestamp"); $$ = $1 }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
at_modifier_preprocessors: START | END;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Subquery and range selectors.
|
* 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_left": GROUP_LEFT,
|
||||||
"group_right": GROUP_RIGHT,
|
"group_right": GROUP_RIGHT,
|
||||||
"bool": BOOL,
|
"bool": BOOL,
|
||||||
|
|
||||||
|
// Preprocessors.
|
||||||
|
"start": START,
|
||||||
|
"end": END,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ItemTypeStr is the default string representations for common Items. It does not
|
// ItemTypeStr is the default string representations for common Items. It does not
|
||||||
|
|
|
@ -276,6 +276,9 @@ var tests = []struct {
|
||||||
}, {
|
}, {
|
||||||
input: `unless`,
|
input: `unless`,
|
||||||
expected: []Item{{LUNLESS, 0, `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",
|
name: "selectors",
|
||||||
tests: []testCase{
|
tests: []testCase{
|
||||||
|
|
|
@ -726,29 +726,12 @@ func (p *parser) setTimestamp(e Node, ts float64) {
|
||||||
var timestampp **int64
|
var timestampp **int64
|
||||||
var endPosp *Pos
|
var endPosp *Pos
|
||||||
|
|
||||||
switch s := e.(type) {
|
timestampp, _, endPosp, ok := p.getAtModifierVars(e)
|
||||||
case *VectorSelector:
|
if !ok {
|
||||||
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")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if *timestampp != nil {
|
if timestampp != nil {
|
||||||
p.addParseErrf(e.PositionRange(), "@ <timestamp> may not be set multiple times")
|
|
||||||
} else if timestampp != nil {
|
|
||||||
*timestampp = new(int64)
|
*timestampp = new(int64)
|
||||||
**timestampp = timestamp.FromFloatSeconds(ts)
|
**timestampp = timestamp.FromFloatSeconds(ts)
|
||||||
}
|
}
|
||||||
|
@ -756,6 +739,57 @@ func (p *parser) setTimestamp(e Node, ts float64) {
|
||||||
*endPosp = p.lastClosing
|
*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 {
|
func MustLabelMatcher(mt labels.MatchType, name, val string) *labels.Matcher {
|
||||||
m, err := labels.NewMatcher(mt, name, val)
|
m, err := labels.NewMatcher(mt, name, val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -3059,6 +3059,112 @@ var testExpr = []struct {
|
||||||
fail: true,
|
fail: true,
|
||||||
errMsg: `1:15: parse error: ranges only allowed for vector selectors`,
|
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 {
|
func makeInt64Pointer(val int64) *int64 {
|
||||||
|
|
|
@ -122,16 +122,21 @@ func (node *MatrixSelector) String() string {
|
||||||
at := ""
|
at := ""
|
||||||
if vecSelector.Timestamp != nil {
|
if vecSelector.Timestamp != nil {
|
||||||
at = fmt.Sprintf(" @ %.3f", float64(*vecSelector.Timestamp)/1000.0)
|
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.
|
// 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.OriginalOffset = 0
|
||||||
vecSelector.Timestamp = nil
|
vecSelector.Timestamp = nil
|
||||||
|
vecSelector.StartOrEnd = 0
|
||||||
|
|
||||||
str := fmt.Sprintf("%s[%s]%s%s", vecSelector.String(), model.Duration(node.Range), at, offset)
|
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
|
return str
|
||||||
}
|
}
|
||||||
|
@ -148,6 +153,10 @@ func (node *SubqueryExpr) String() string {
|
||||||
at := ""
|
at := ""
|
||||||
if node.Timestamp != nil {
|
if node.Timestamp != nil {
|
||||||
at = fmt.Sprintf(" @ %.3f", float64(*node.Timestamp)/1000.0)
|
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)
|
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 := ""
|
at := ""
|
||||||
if node.Timestamp != nil {
|
if node.Timestamp != nil {
|
||||||
at = fmt.Sprintf(" @ %.3f", float64(*node.Timestamp)/1000.0)
|
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 {
|
if len(labelStrings) == 0 {
|
||||||
|
|
|
@ -98,6 +98,26 @@ func TestExprString(t *testing.T) {
|
||||||
{
|
{
|
||||||
in: `a{b!~"c"}[1m]`,
|
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 {
|
for _, test := range inputs {
|
||||||
|
|
|
@ -779,9 +779,3 @@ func (ll *LazyLoader) Close() {
|
||||||
ll.T.Fatalf("closing test storage: %s", err)
|
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)
|
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 {
|
if err != nil {
|
||||||
return invalidParamError(err, "query")
|
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)
|
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 {
|
if err != nil {
|
||||||
return apiFuncResult{nil, &apiError{errorBadData, err}, nil, nil}
|
return apiFuncResult{nil, &apiError{errorBadData, err}, nil, nil}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue