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:
Ganesh Vernekar 2021-02-09 21:33:16 +05:30 committed by GitHub
parent b9f0baf6ff
commit 86c71856e8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 917 additions and 501 deletions

View file

@ -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).

View file

@ -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

View file

@ -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
}

View file

@ -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)

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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{

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -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
}

View file

@ -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}
}