diff --git a/cmd/prometheus/main.go b/cmd/prometheus/main.go index fcb655fb1e..6066a5be1d 100644 --- a/cmd/prometheus/main.go +++ b/cmd/prometheus/main.go @@ -260,7 +260,8 @@ func (c *flagConfig) setFeatureListOptions(logger *slog.Logger) error { c.parserOpts.EnableExperimentalFunctions = true logger.Info("Experimental PromQL functions enabled.") case "promql-duration-expr": - logger.Warn("This option for --enable-feature is now permanently enabled and therefore a no-op.", "option", o) + c.parserOpts.ExperimentalDurationExpr = true + logger.Info("Experimental duration expression parsing enabled.") case "native-histograms": logger.Warn("This option for --enable-feature is a no-op. To scrape native histograms, set the scrape_native_histograms scrape config setting to true.", "option", o) case "ooo-native-histograms": @@ -619,7 +620,7 @@ func main() { a.Flag("scrape.discovery-reload-interval", "Interval used by scrape manager to throttle target groups updates."). Hidden().Default("5s").SetValue(&cfg.scrape.DiscoveryReloadInterval) - a.Flag("enable-feature", "Comma separated feature names to enable. Valid options: concurrent-rule-eval, created-timestamp-zero-ingestion, delayed-compaction, exemplar-storage, extra-scrape-metrics, memory-snapshot-on-shutdown, metadata-wal-records, old-ui, otlp-deltatocumulative, otlp-native-delta-ingestion, promql-binop-fill-modifiers, promql-delayed-name-removal, promql-experimental-functions, promql-extended-range-selectors, promql-per-step-stats, st-storage, type-and-unit-labels, use-start-timestamps, use-uncached-io, xor2-encoding. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details."). + a.Flag("enable-feature", "Comma separated feature names to enable. Valid options: concurrent-rule-eval, created-timestamp-zero-ingestion, delayed-compaction, exemplar-storage, extra-scrape-metrics, memory-snapshot-on-shutdown, metadata-wal-records, old-ui, otlp-deltatocumulative, otlp-native-delta-ingestion, promql-binop-fill-modifiers, promql-delayed-name-removal, promql-duration-expr, promql-experimental-functions, promql-extended-range-selectors, promql-per-step-stats, st-storage, type-and-unit-labels, use-start-timestamps, use-uncached-io, xor2-encoding. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details."). StringsVar(&cfg.featureList) a.Flag("agent", "Run Prometheus in 'Agent mode'.").BoolVar(&agentMode) diff --git a/cmd/prometheus/testdata/features.json b/cmd/prometheus/testdata/features.json index 90bf375781..78c6b5bdc8 100644 --- a/cmd/prometheus/testdata/features.json +++ b/cmd/prometheus/testdata/features.json @@ -29,7 +29,7 @@ "bool": true, "by": true, "delayed_name_removal": false, - "duration_expr": true, + "duration_expr": false, "fill": false, "fill_left": false, "fill_right": false, diff --git a/cmd/promtool/main.go b/cmd/promtool/main.go index 88ee3be3f7..fbea5eda6a 100644 --- a/cmd/promtool/main.go +++ b/cmd/promtool/main.go @@ -320,7 +320,7 @@ func main() { promQLLabelsDeleteQuery := promQLLabelsDeleteCmd.Arg("query", "PromQL query.").Required().String() promQLLabelsDeleteName := promQLLabelsDeleteCmd.Arg("name", "Name of the label to delete.").Required().String() - featureList := app.Flag("enable-feature", "Comma separated feature names to enable. Valid options: promql-experimental-functions, promql-delayed-name-removal, promql-extended-range-selectors. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details").Default("").Strings() + featureList := app.Flag("enable-feature", "Comma separated feature names to enable. Valid options: promql-experimental-functions, promql-delayed-name-removal, promql-duration-expr, promql-extended-range-selectors. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details").Default("").Strings() documentationCmd := app.Command("write-documentation", "Generate command line documentation. Internal use.").Hidden() @@ -358,7 +358,7 @@ func main() { case "promql-delayed-name-removal": promqlEnableDelayedNameRemoval = true case "promql-duration-expr": - fmt.Printf(" WARNING: promql-duration-expr is now permanently enabled and therefore a no-op") + promtoolParserOpts.ExperimentalDurationExpr = true case "promql-extended-range-selectors": promtoolParserOpts.EnableExtendedRangeSelectors = true case "": diff --git a/docs/command-line/prometheus.md b/docs/command-line/prometheus.md index e21c4c2a67..289ded3842 100644 --- a/docs/command-line/prometheus.md +++ b/docs/command-line/prometheus.md @@ -62,7 +62,7 @@ The Prometheus monitoring server | --query.timeout | Maximum time a query may take before being aborted. Use with server mode only. | `2m` | | --query.max-concurrency | Maximum number of queries executed concurrently. Use with server mode only. | `20` | | --query.max-samples | Maximum number of samples a single query can load into memory. Note that queries will fail if they try to load more samples than this into memory, so this also limits the number of samples a query can return. Use with server mode only. | `50000000` | -| --enable-feature ... | Comma separated feature names to enable. Valid options: concurrent-rule-eval, created-timestamp-zero-ingestion, delayed-compaction, exemplar-storage, extra-scrape-metrics, memory-snapshot-on-shutdown, metadata-wal-records, old-ui, otlp-deltatocumulative, otlp-native-delta-ingestion, promql-binop-fill-modifiers, promql-delayed-name-removal, promql-experimental-functions, promql-extended-range-selectors, promql-per-step-stats, st-storage, type-and-unit-labels, use-start-timestamps, use-uncached-io, xor2-encoding. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details. | | +| --enable-feature ... | Comma separated feature names to enable. Valid options: concurrent-rule-eval, created-timestamp-zero-ingestion, delayed-compaction, exemplar-storage, extra-scrape-metrics, memory-snapshot-on-shutdown, metadata-wal-records, old-ui, otlp-deltatocumulative, otlp-native-delta-ingestion, promql-binop-fill-modifiers, promql-delayed-name-removal, promql-duration-expr, promql-experimental-functions, promql-extended-range-selectors, promql-per-step-stats, st-storage, type-and-unit-labels, use-start-timestamps, use-uncached-io, xor2-encoding. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details. | | | --agent | Run Prometheus in 'Agent mode'. | | | --log.level | Only log messages with the given severity or above. One of: [debug, info, warn, error] | `info` | | --log.format | Output format of log messages. One of: [logfmt, json] | `logfmt` | diff --git a/docs/command-line/promtool.md b/docs/command-line/promtool.md index aa606435f8..28a0d99696 100644 --- a/docs/command-line/promtool.md +++ b/docs/command-line/promtool.md @@ -12,7 +12,7 @@ Tooling for the Prometheus monitoring system. | -h, --help | Show context-sensitive help (also try --help-long and --help-man). | | --version | Show application version. | | --experimental | Enable experimental commands. | -| --enable-feature ... | Comma separated feature names to enable. Valid options: promql-experimental-functions, promql-delayed-name-removal, promql-extended-range-selectors. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details | +| --enable-feature ... | Comma separated feature names to enable. Valid options: promql-experimental-functions, promql-delayed-name-removal, promql-duration-expr, promql-extended-range-selectors. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details | diff --git a/docs/feature_flags.md b/docs/feature_flags.md index fddb4dcf6b..2806e4baa7 100644 --- a/docs/feature_flags.md +++ b/docs/feature_flags.md @@ -192,6 +192,63 @@ state is mutex guarded. Cumulative-only OTLP requests are not affected. [d2c]: https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/deltatocumulativeprocessor +## PromQL arithmetic expressions in time durations + +`--enable-feature=promql-duration-expr` + +With this flag, arithmetic expressions can be used in time durations in range queries and offset durations. + +In range queries: +``` +rate(http_requests_total[5m * 2]) # 10 minute range +rate(http_requests_total[(5+2) * 1m]) # 7 minute range +``` + +In offset durations: +``` +http_requests_total offset (1h / 2) # 30 minute offset +http_requests_total offset ((2 ^ 3) * 1m) # 8 minute offset +``` + +When using offset with duration expressions, you must wrap the expression in +parentheses. Without parentheses, only the first duration value will be used in +the offset calculation. + +`step()` can be used in duration expressions. +For a **range query**, it resolves to the step width of the range query. +For an **instant query**, it resolves to `0s`. + +`range()` can be used in duration expressions. +For a **range query**, it resolves to the full range of the query (end time - start time). +For an **instant query**, it resolves to `0s`. +This is particularly useful in combination with `@end()` to look back over the entire query range, e.g., `max_over_time(metric[range()] @ end())`. + +`min(, )` and `max(, )` can be used to find the minimum or maximum of two duration expressions. + +**Note**: Duration expressions are not supported in the @ timestamp operator. + +The following operators are supported: + +* `+` - addition +* `-` - subtraction +* `*` - multiplication +* `/` - division +* `%` - modulo +* `^` - exponentiation + +Examples of equivalent durations: + +* `5m * 2` is equivalent to `10m` or `600s` +* `10m - 1m` is equivalent to `9m` or `540s` +* `(5+2) * 1m` is equivalent to `7m` or `420s` +* `1h / 2` is equivalent to `30m` or `1800s` +* `4h % 3h` is equivalent to `1h` or `3600s` +* `(2 ^ 3) * 1m` is equivalent to `8m` or `480s` +* `step() + 1` is equivalent to the query step width increased by 1s. +* `max(step(), 5s)` is equivalent to the larger of the query step width and `5s`. +* `min(2 * step() + 5s, 5m)` is equivalent to the smaller of twice the query step increased by `5s` and `5m`. + + ## OTLP Native Delta Support `--enable-feature=otlp-native-delta-ingestion` diff --git a/docs/querying/basics.md b/docs/querying/basics.md index c3fcfee432..e7f1173af4 100644 --- a/docs/querying/basics.md +++ b/docs/querying/basics.md @@ -174,57 +174,6 @@ Examples: 12h34m56s # Equivalent to 45296s and thus 45296. 54s321ms # Equivalent to 54.321. -#### Duration expressions - -Duration expressions can be used in range selectors, subquery range and -resolution fields, and offset durations. - -Examples: - - rate(http_requests_total[5m * 2]) # 10 minute range. - rate(http_requests_total[(5 + 2) * 1m]) # 7 minute range. - http_requests_total offset (1h / 2) # 30 minute offset. - http_requests_total offset ((2 ^ 3) * 1m) # 8 minute offset. - -When using offset with duration expressions, you must wrap the expression in -parentheses. Without parentheses, only the first duration value will be used in -the offset calculation. - -`step()` can be used in duration expressions. For a range query, it resolves to -the step width of the range query. For an instant query, it resolves to `0s`. - -`range()` can be used in duration expressions. For a range query, it resolves to -the full range of the query (end time minus start time). For an instant query, -it resolves to `0s`. This is particularly useful in combination with `@ end()` -to look back over the entire query range, e.g., -`max_over_time(metric[range()] @ end())`. - -`min(, )` and `max(, )` can be used to -find the minimum or maximum of two duration expressions. - -Duration expressions are not supported in the @ timestamp operator. - -The following operators are supported: - -* `+` - addition. -* `-` - subtraction. -* `*` - multiplication. -* `/` - division. -* `%` - modulo. -* `^` - exponentiation. - -Examples of equivalent durations: - -* `5m * 2` is equivalent to `10m` or `600s`. -* `10m - 1m` is equivalent to `9m` or `540s`. -* `(5 + 2) * 1m` is equivalent to `7m` or `420s`. -* `1h / 2` is equivalent to `30m` or `1800s`. -* `4h % 3h` is equivalent to `1h` or `3600s`. -* `(2 ^ 3) * 1m` is equivalent to `8m` or `480s`. -* `step() + 1` is equivalent to the query step width increased by 1s. -* `max(step(), 5s)` is equivalent to the larger of the query step width and `5s`. -* `min(2 * step() + 5s, 5m)` is equivalent to the smaller of twice the query step increased by `5s` and `5m`. - ## Time series selectors These are the basic building-blocks that instruct PromQL what data to fetch. @@ -333,13 +282,14 @@ A workaround for this restriction is to use the `__name__` label: Range vector literals work like instant vector literals, except that they select a range of samples back from the current instant. Syntactically, a -[duration](#float-literals-and-time-durations) is appended in square brackets -(`[]`) at the end of a vector selector to specify how far back in time values -should be fetched for each resulting range vector element. Commonly, this uses -one or more time units, e.g. `[5m]`. The range is a left-open and right-closed -interval, i.e. samples with timestamps coinciding with the left boundary of the -range are excluded from the selection, while samples coinciding with the right -boundary of the range are included in the selection. +[float literal](#float-literals-and-time-durations) is appended in square +brackets (`[]`) at the end of a vector selector to specify for how many seconds +back in time values should be fetched for each resulting range vector element. +Commonly, the float literal uses the syntax with one or more time units, e.g. +`[5m]`. The range is a left-open and right-closed interval, i.e. samples with +timestamps coinciding with the left boundary of the range are excluded from the +selection, while samples coinciding with the right boundary of the range are +included in the selection. In this example, we select all the values recorded less than 5m ago for all time series that have the metric name `http_requests_total` and a `job` label @@ -429,9 +379,8 @@ Note that the `@` modifier allows a query to look ahead of its evaluation time. Subquery allows you to run an instant query for a given range and resolution. The result of a subquery is a range vector. -Syntax: ` '[' ':' [] ']' [ @ ] [ offset ]` +Syntax: ` '[' ':' [] ']' [ @ ] [ offset ]` -* ``, ``, and `offset` support duration expressions. * `` is optional. Default is the global evaluation interval. ## Operators diff --git a/docs/querying/functions.md b/docs/querying/functions.md index 6418c2955f..a175064b81 100644 --- a/docs/querying/functions.md +++ b/docs/querying/functions.md @@ -961,7 +961,8 @@ These functions act on histograms in the following way: select the first sample of `m` _within_ the 1m range, where `m offset 1m` will select the most recent sample within the lookback interval _outside and prior to_ the 1m offset. This is particularly useful with `first_over_time(m[step()])` -in range queries to ensure that the sample selected is within the range step. +in range queries (available when `--enable-feature=promql-duration-expr` is set) +to ensure that the sample selected is within the range step. ## Trigonometric Functions diff --git a/promql/durations_test.go b/promql/durations_test.go index 3b31e92dd6..b8225ca8fc 100644 --- a/promql/durations_test.go +++ b/promql/durations_test.go @@ -23,7 +23,7 @@ import ( ) func TestDurationVisitor(t *testing.T) { - p := parser.NewParser(parser.Options{}) + p := parser.NewParser(parser.Options{ExperimentalDurationExpr: true}) complexExpr := `sum_over_time( rate(metric[5m] offset 1h)[10m:30s] offset 2h ) + diff --git a/promql/parser/features.go b/promql/parser/features.go index d21c7470e6..3bd3c493f5 100644 --- a/promql/parser/features.go +++ b/promql/parser/features.go @@ -53,6 +53,6 @@ func (pql *promQLParser) RegisterFeatures(r features.Collector) { r.Set(features.PromQLFunctions, f, !fc.Experimental || pql.options.EnableExperimentalFunctions) } - // Register parser features. - r.Enable(features.PromQL, "duration_expr") + // Register experimental parser features. + r.Set(features.PromQL, "duration_expr", pql.options.ExperimentalDurationExpr) } diff --git a/promql/parser/generated_parser.y b/promql/parser/generated_parser.y index c1524f5669..34ce028c1f 100644 --- a/promql/parser/generated_parser.y +++ b/promql/parser/generated_parser.y @@ -1201,23 +1201,27 @@ offset_duration_expr : number_duration_literal } | STEP LEFT_PAREN RIGHT_PAREN { - $$ = &DurationExpr{ - Op: STEP, + de := &DurationExpr{ + Op: STEP, StartPos: $1.PositionRange().Start, - EndPos: $3.PositionRange().End, + EndPos: $3.PositionRange().End, } + yylex.(*parser).experimentalDurationExpr(de) + $$ = de } | RANGE LEFT_PAREN RIGHT_PAREN { - $$ = &DurationExpr{ - Op: RANGE, + de := &DurationExpr{ + Op: RANGE, StartPos: $1.PositionRange().Start, - EndPos: $3.PositionRange().End, + EndPos: $3.PositionRange().End, } + yylex.(*parser).experimentalDurationExpr(de) + $$ = de } | unary_op STEP LEFT_PAREN RIGHT_PAREN { - $$ = &DurationExpr{ + de := &DurationExpr{ Op: $1.Typ, RHS: &DurationExpr{ Op: STEP, @@ -1226,10 +1230,12 @@ offset_duration_expr : number_duration_literal }, StartPos: $1.Pos, } + yylex.(*parser).experimentalDurationExpr(de) + $$ = de } | unary_op RANGE LEFT_PAREN RIGHT_PAREN { - $$ = &DurationExpr{ + de := &DurationExpr{ Op: $1.Typ, RHS: &DurationExpr{ Op: RANGE, @@ -1238,20 +1244,24 @@ offset_duration_expr : number_duration_literal }, StartPos: $1.Pos, } + yylex.(*parser).experimentalDurationExpr(de) + $$ = de } | min_max LEFT_PAREN duration_expr COMMA duration_expr RIGHT_PAREN { - $$ = &DurationExpr{ + de := &DurationExpr{ Op: $1.Typ, StartPos: $1.PositionRange().Start, EndPos: $6.PositionRange().End, LHS: $3.(Expr), RHS: $5.(Expr), } + yylex.(*parser).experimentalDurationExpr(de) + $$ = de } | unary_op min_max LEFT_PAREN duration_expr COMMA duration_expr RIGHT_PAREN { - $$ = &DurationExpr{ + de := &DurationExpr{ Op: $1.Typ, StartPos: $1.Pos, EndPos: $6.PositionRange().End, @@ -1263,6 +1273,8 @@ offset_duration_expr : number_duration_literal RHS: $6.(Expr), }, } + yylex.(*parser).experimentalDurationExpr(de) + $$ = de } | unary_op LEFT_PAREN duration_expr RIGHT_PAREN %prec MUL { @@ -1327,18 +1339,22 @@ duration_expr : number_duration_literal } | duration_expr ADD duration_expr { + yylex.(*parser).experimentalDurationExpr($1.(Expr)) $$ = &DurationExpr{Op: ADD, LHS: $1.(Expr), RHS: $3.(Expr)} } | duration_expr SUB duration_expr { + yylex.(*parser).experimentalDurationExpr($1.(Expr)) $$ = &DurationExpr{Op: SUB, LHS: $1.(Expr), RHS: $3.(Expr)} } | duration_expr MUL duration_expr { + yylex.(*parser).experimentalDurationExpr($1.(Expr)) $$ = &DurationExpr{Op: MUL, LHS: $1.(Expr), RHS: $3.(Expr)} } | duration_expr DIV duration_expr { + yylex.(*parser).experimentalDurationExpr($1.(Expr)) if nl, ok := $3.(*NumberLiteral); ok && nl.Val == 0 { yylex.(*parser).addParseErrf($2.PositionRange(), "division by zero") $$ = &NumberLiteral{Val: 0} @@ -1348,6 +1364,7 @@ duration_expr : number_duration_literal } | duration_expr MOD duration_expr { + yylex.(*parser).experimentalDurationExpr($1.(Expr)) if nl, ok := $3.(*NumberLiteral); ok && nl.Val == 0 { yylex.(*parser).addParseErrf($2.PositionRange(), "modulo by zero") $$ = &NumberLiteral{Val: 0} @@ -1357,39 +1374,47 @@ duration_expr : number_duration_literal } | duration_expr POW duration_expr { + yylex.(*parser).experimentalDurationExpr($1.(Expr)) $$ = &DurationExpr{Op: POW, LHS: $1.(Expr), RHS: $3.(Expr)} } | STEP LEFT_PAREN RIGHT_PAREN { - $$ = &DurationExpr{ + de := &DurationExpr{ Op: STEP, StartPos: $1.PositionRange().Start, EndPos: $3.PositionRange().End, } + yylex.(*parser).experimentalDurationExpr(de) + $$ = de } | RANGE LEFT_PAREN RIGHT_PAREN { - $$ = &DurationExpr{ + de := &DurationExpr{ Op: RANGE, StartPos: $1.PositionRange().Start, EndPos: $3.PositionRange().End, } + yylex.(*parser).experimentalDurationExpr(de) + $$ = de } | min_max LEFT_PAREN duration_expr COMMA duration_expr RIGHT_PAREN { - $$ = &DurationExpr{ + de := &DurationExpr{ Op: $1.Typ, StartPos: $1.PositionRange().Start, EndPos: $6.PositionRange().End, LHS: $3.(Expr), RHS: $5.(Expr), } + yylex.(*parser).experimentalDurationExpr(de) + $$ = de } | paren_duration_expr ; paren_duration_expr : LEFT_PAREN duration_expr RIGHT_PAREN { + yylex.(*parser).experimentalDurationExpr($2.(Expr)) if durationExpr, ok := $2.(*DurationExpr); ok { durationExpr.Wrapped = true $$ = durationExpr diff --git a/promql/parser/generated_parser.y.go b/promql/parser/generated_parser.y.go index db7c239b55..9f9f017dfa 100644 --- a/promql/parser/generated_parser.y.go +++ b/promql/parser/generated_parser.y.go @@ -2316,25 +2316,29 @@ yydefault: case 282: yyDollar = yyS[yypt-3 : yypt+1] { - yyVAL.node = &DurationExpr{ + de := &DurationExpr{ Op: STEP, StartPos: yyDollar[1].item.PositionRange().Start, EndPos: yyDollar[3].item.PositionRange().End, } + yylex.(*parser).experimentalDurationExpr(de) + yyVAL.node = de } case 283: yyDollar = yyS[yypt-3 : yypt+1] { - yyVAL.node = &DurationExpr{ + de := &DurationExpr{ Op: RANGE, StartPos: yyDollar[1].item.PositionRange().Start, EndPos: yyDollar[3].item.PositionRange().End, } + yylex.(*parser).experimentalDurationExpr(de) + yyVAL.node = de } case 284: yyDollar = yyS[yypt-4 : yypt+1] { - yyVAL.node = &DurationExpr{ + de := &DurationExpr{ Op: yyDollar[1].item.Typ, RHS: &DurationExpr{ Op: STEP, @@ -2343,11 +2347,13 @@ yydefault: }, StartPos: yyDollar[1].item.Pos, } + yylex.(*parser).experimentalDurationExpr(de) + yyVAL.node = de } case 285: yyDollar = yyS[yypt-4 : yypt+1] { - yyVAL.node = &DurationExpr{ + de := &DurationExpr{ Op: yyDollar[1].item.Typ, RHS: &DurationExpr{ Op: RANGE, @@ -2356,22 +2362,26 @@ yydefault: }, StartPos: yyDollar[1].item.Pos, } + yylex.(*parser).experimentalDurationExpr(de) + yyVAL.node = de } case 286: yyDollar = yyS[yypt-6 : yypt+1] { - yyVAL.node = &DurationExpr{ + de := &DurationExpr{ Op: yyDollar[1].item.Typ, StartPos: yyDollar[1].item.PositionRange().Start, EndPos: yyDollar[6].item.PositionRange().End, LHS: yyDollar[3].node.(Expr), RHS: yyDollar[5].node.(Expr), } + yylex.(*parser).experimentalDurationExpr(de) + yyVAL.node = de } case 287: yyDollar = yyS[yypt-7 : yypt+1] { - yyVAL.node = &DurationExpr{ + de := &DurationExpr{ Op: yyDollar[1].item.Typ, StartPos: yyDollar[1].item.Pos, EndPos: yyDollar[6].node.PositionRange().End, @@ -2383,6 +2393,8 @@ yydefault: RHS: yyDollar[6].node.(Expr), }, } + yylex.(*parser).experimentalDurationExpr(de) + yyVAL.node = de } case 288: yyDollar = yyS[yypt-4 : yypt+1] @@ -2446,21 +2458,25 @@ yydefault: case 294: yyDollar = yyS[yypt-3 : yypt+1] { + yylex.(*parser).experimentalDurationExpr(yyDollar[1].node.(Expr)) yyVAL.node = &DurationExpr{Op: ADD, LHS: yyDollar[1].node.(Expr), RHS: yyDollar[3].node.(Expr)} } case 295: yyDollar = yyS[yypt-3 : yypt+1] { + yylex.(*parser).experimentalDurationExpr(yyDollar[1].node.(Expr)) yyVAL.node = &DurationExpr{Op: SUB, LHS: yyDollar[1].node.(Expr), RHS: yyDollar[3].node.(Expr)} } case 296: yyDollar = yyS[yypt-3 : yypt+1] { + yylex.(*parser).experimentalDurationExpr(yyDollar[1].node.(Expr)) yyVAL.node = &DurationExpr{Op: MUL, LHS: yyDollar[1].node.(Expr), RHS: yyDollar[3].node.(Expr)} } case 297: yyDollar = yyS[yypt-3 : yypt+1] { + yylex.(*parser).experimentalDurationExpr(yyDollar[1].node.(Expr)) if nl, ok := yyDollar[3].node.(*NumberLiteral); ok && nl.Val == 0 { yylex.(*parser).addParseErrf(yyDollar[2].item.PositionRange(), "division by zero") yyVAL.node = &NumberLiteral{Val: 0} @@ -2471,6 +2487,7 @@ yydefault: case 298: yyDollar = yyS[yypt-3 : yypt+1] { + yylex.(*parser).experimentalDurationExpr(yyDollar[1].node.(Expr)) if nl, ok := yyDollar[3].node.(*NumberLiteral); ok && nl.Val == 0 { yylex.(*parser).addParseErrf(yyDollar[2].item.PositionRange(), "modulo by zero") yyVAL.node = &NumberLiteral{Val: 0} @@ -2481,40 +2498,48 @@ yydefault: case 299: yyDollar = yyS[yypt-3 : yypt+1] { + yylex.(*parser).experimentalDurationExpr(yyDollar[1].node.(Expr)) yyVAL.node = &DurationExpr{Op: POW, LHS: yyDollar[1].node.(Expr), RHS: yyDollar[3].node.(Expr)} } case 300: yyDollar = yyS[yypt-3 : yypt+1] { - yyVAL.node = &DurationExpr{ + de := &DurationExpr{ Op: STEP, StartPos: yyDollar[1].item.PositionRange().Start, EndPos: yyDollar[3].item.PositionRange().End, } + yylex.(*parser).experimentalDurationExpr(de) + yyVAL.node = de } case 301: yyDollar = yyS[yypt-3 : yypt+1] { - yyVAL.node = &DurationExpr{ + de := &DurationExpr{ Op: RANGE, StartPos: yyDollar[1].item.PositionRange().Start, EndPos: yyDollar[3].item.PositionRange().End, } + yylex.(*parser).experimentalDurationExpr(de) + yyVAL.node = de } case 302: yyDollar = yyS[yypt-6 : yypt+1] { - yyVAL.node = &DurationExpr{ + de := &DurationExpr{ Op: yyDollar[1].item.Typ, StartPos: yyDollar[1].item.PositionRange().Start, EndPos: yyDollar[6].item.PositionRange().End, LHS: yyDollar[3].node.(Expr), RHS: yyDollar[5].node.(Expr), } + yylex.(*parser).experimentalDurationExpr(de) + yyVAL.node = de } case 304: yyDollar = yyS[yypt-3 : yypt+1] { + yylex.(*parser).experimentalDurationExpr(yyDollar[2].node.(Expr)) if durationExpr, ok := yyDollar[2].node.(*DurationExpr); ok { durationExpr.Wrapped = true yyVAL.node = durationExpr diff --git a/promql/parser/parse.go b/promql/parser/parse.go index 8d4491ea64..ec3e1001d9 100644 --- a/promql/parser/parse.go +++ b/promql/parser/parse.go @@ -43,6 +43,7 @@ var parserPool = sync.Pool{ // Options holds the configuration for the PromQL parser. type Options struct { EnableExperimentalFunctions bool + ExperimentalDurationExpr bool EnableExtendedRangeSelectors bool EnableBinopFillModifiers bool } @@ -1192,6 +1193,12 @@ func (p *parser) getAtModifierVars(e Node) (**int64, *ItemType, *posrange.Pos, b return timestampp, preprocp, endPosp, true } +func (p *parser) experimentalDurationExpr(e Expr) { + if !p.options.ExperimentalDurationExpr { + p.addParseErrf(e.PositionRange(), "experimental duration expression is not enabled") + } +} + func MustLabelMatcher(mt labels.MatchType, name, val string) *labels.Matcher { m, err := labels.NewMatcher(mt, name, val) if err != nil { diff --git a/promql/parser/parse_test.go b/promql/parser/parse_test.go index b3ea1bb0f4..6a6c50ee23 100644 --- a/promql/parser/parse_test.go +++ b/promql/parser/parse_test.go @@ -5327,6 +5327,7 @@ func readable(s string) string { func TestParseExpressions(t *testing.T) { optsParser := NewParser(Options{ EnableExperimentalFunctions: true, + ExperimentalDurationExpr: true, }) for _, test := range testExpr { @@ -6083,6 +6084,7 @@ func TestParseCustomFunctions(t *testing.T) { func TestNewParser(t *testing.T) { p := NewParser(Options{ EnableExperimentalFunctions: true, + ExperimentalDurationExpr: true, }) // ParseExpr should work. diff --git a/promql/parser/prettier_test.go b/promql/parser/prettier_test.go index 47e5407e30..d00bc283ec 100644 --- a/promql/parser/prettier_test.go +++ b/promql/parser/prettier_test.go @@ -670,7 +670,7 @@ func TestUnaryPretty(t *testing.T) { } func TestDurationExprPretty(t *testing.T) { - optsParser := NewParser(Options{}) + optsParser := NewParser(Options{ExperimentalDurationExpr: true}) maxCharactersPerLine = 10 inputs := []struct { in, out string diff --git a/promql/parser/printer_test.go b/promql/parser/printer_test.go index 774ff77d2f..eae91d4f88 100644 --- a/promql/parser/printer_test.go +++ b/promql/parser/printer_test.go @@ -23,6 +23,7 @@ import ( func TestExprString(t *testing.T) { optsParser := NewParser(Options{ + ExperimentalDurationExpr: true, EnableExtendedRangeSelectors: true, EnableBinopFillModifiers: true, }) diff --git a/promql/promqltest/test.go b/promql/promqltest/test.go index 0be88829bf..85c0c4f88a 100644 --- a/promql/promqltest/test.go +++ b/promql/promqltest/test.go @@ -90,6 +90,7 @@ func LoadedStorage(t testing.TB, input string) *teststorage.TestStorage { // TestParserOpts are the parser options used for all built-in test engines. var TestParserOpts = parser.Options{ EnableExperimentalFunctions: true, + ExperimentalDurationExpr: true, EnableExtendedRangeSelectors: true, EnableBinopFillModifiers: true, } diff --git a/util/fuzzing/fuzz_test.go b/util/fuzzing/fuzz_test.go index 32353c6afc..747eab1f1a 100644 --- a/util/fuzzing/fuzz_test.go +++ b/util/fuzzing/fuzz_test.go @@ -363,6 +363,7 @@ func FuzzParseExpr(f *testing.F) { p := parser.NewParser(parser.Options{ EnableExperimentalFunctions: true, + ExperimentalDurationExpr: true, EnableExtendedRangeSelectors: true, EnableBinopFillModifiers: true, }) diff --git a/web/api/v1/translate_ast_test.go b/web/api/v1/translate_ast_test.go index 50befb1962..84c4019363 100644 --- a/web/api/v1/translate_ast_test.go +++ b/web/api/v1/translate_ast_test.go @@ -22,7 +22,7 @@ import ( ) func TestTranslateASTDurationExpressions(t *testing.T) { - p := parser.NewParser(parser.Options{}) + p := parser.NewParser(parser.Options{ExperimentalDurationExpr: true}) type tc struct { name string diff --git a/web/ui/mantine-ui/src/promql/functionDocs.tsx b/web/ui/mantine-ui/src/promql/functionDocs.tsx index 375c4155d4..7ea47c02bb 100644 --- a/web/ui/mantine-ui/src/promql/functionDocs.tsx +++ b/web/ui/mantine-ui/src/promql/functionDocs.tsx @@ -589,7 +589,8 @@ const funcDocs: Record = { first sample of m within the 1m range, where m offset 1m will select the most recent sample within the lookback interval outside and prior to the 1m offset. This is particularly useful with first_over_time(m[step()]) - in range queries to ensure that the sample selected is within the range step. + in range queries (available when --enable-feature=promql-duration-expr is set) to ensure that the + sample selected is within the range step.

), @@ -908,7 +909,8 @@ const funcDocs: Record = { first sample of m within the 1m range, where m offset 1m will select the most recent sample within the lookback interval outside and prior to the 1m offset. This is particularly useful with first_over_time(m[step()]) - in range queries to ensure that the sample selected is within the range step. + in range queries (available when --enable-feature=promql-duration-expr is set) to ensure that the + sample selected is within the range step.

), @@ -1238,7 +1240,8 @@ const funcDocs: Record = { first sample of m within the 1m range, where m offset 1m will select the most recent sample within the lookback interval outside and prior to the 1m offset. This is particularly useful with first_over_time(m[step()]) - in range queries to ensure that the sample selected is within the range step. + in range queries (available when --enable-feature=promql-duration-expr is set) to ensure that the + sample selected is within the range step.

), @@ -2069,7 +2072,8 @@ const funcDocs: Record = { first sample of m within the 1m range, where m offset 1m will select the most recent sample within the lookback interval outside and prior to the 1m offset. This is particularly useful with first_over_time(m[step()]) - in range queries to ensure that the sample selected is within the range step. + in range queries (available when --enable-feature=promql-duration-expr is set) to ensure that the + sample selected is within the range step.

), @@ -2219,7 +2223,8 @@ const funcDocs: Record = { first sample of m within the 1m range, where m offset 1m will select the most recent sample within the lookback interval outside and prior to the 1m offset. This is particularly useful with first_over_time(m[step()]) - in range queries to ensure that the sample selected is within the range step. + in range queries (available when --enable-feature=promql-duration-expr is set) to ensure that the + sample selected is within the range step.

), @@ -2328,7 +2333,8 @@ const funcDocs: Record = { first sample of m within the 1m range, where m offset 1m will select the most recent sample within the lookback interval outside and prior to the 1m offset. This is particularly useful with first_over_time(m[step()]) - in range queries to ensure that the sample selected is within the range step. + in range queries (available when --enable-feature=promql-duration-expr is set) to ensure that the + sample selected is within the range step.

), @@ -2437,7 +2443,8 @@ const funcDocs: Record = { first sample of m within the 1m range, where m offset 1m will select the most recent sample within the lookback interval outside and prior to the 1m offset. This is particularly useful with first_over_time(m[step()]) - in range queries to ensure that the sample selected is within the range step. + in range queries (available when --enable-feature=promql-duration-expr is set) to ensure that the + sample selected is within the range step.

), @@ -2652,7 +2659,8 @@ const funcDocs: Record = { first sample of m within the 1m range, where m offset 1m will select the most recent sample within the lookback interval outside and prior to the 1m offset. This is particularly useful with first_over_time(m[step()]) - in range queries to ensure that the sample selected is within the range step. + in range queries (available when --enable-feature=promql-duration-expr is set) to ensure that the + sample selected is within the range step.

), @@ -2761,7 +2769,8 @@ const funcDocs: Record = { first sample of m within the 1m range, where m offset 1m will select the most recent sample within the lookback interval outside and prior to the 1m offset. This is particularly useful with first_over_time(m[step()]) - in range queries to ensure that the sample selected is within the range step. + in range queries (available when --enable-feature=promql-duration-expr is set) to ensure that the + sample selected is within the range step.

), @@ -3274,7 +3283,8 @@ const funcDocs: Record = { first sample of m within the 1m range, where m offset 1m will select the most recent sample within the lookback interval outside and prior to the 1m offset. This is particularly useful with first_over_time(m[step()]) - in range queries to ensure that the sample selected is within the range step. + in range queries (available when --enable-feature=promql-duration-expr is set) to ensure that the + sample selected is within the range step.

), @@ -3383,7 +3393,8 @@ const funcDocs: Record = { first sample of m within the 1m range, where m offset 1m will select the most recent sample within the lookback interval outside and prior to the 1m offset. This is particularly useful with first_over_time(m[step()]) - in range queries to ensure that the sample selected is within the range step. + in range queries (available when --enable-feature=promql-duration-expr is set) to ensure that the + sample selected is within the range step.

), @@ -3508,7 +3519,8 @@ const funcDocs: Record = { first sample of m within the 1m range, where m offset 1m will select the most recent sample within the lookback interval outside and prior to the 1m offset. This is particularly useful with first_over_time(m[step()]) - in range queries to ensure that the sample selected is within the range step. + in range queries (available when --enable-feature=promql-duration-expr is set) to ensure that the + sample selected is within the range step.

), @@ -3773,7 +3785,8 @@ const funcDocs: Record = { first sample of m within the 1m range, where m offset 1m will select the most recent sample within the lookback interval outside and prior to the 1m offset. This is particularly useful with first_over_time(m[step()]) - in range queries to ensure that the sample selected is within the range step. + in range queries (available when --enable-feature=promql-duration-expr is set) to ensure that the + sample selected is within the range step.

), @@ -3882,7 +3895,8 @@ const funcDocs: Record = { first sample of m within the 1m range, where m offset 1m will select the most recent sample within the lookback interval outside and prior to the 1m offset. This is particularly useful with first_over_time(m[step()]) - in range queries to ensure that the sample selected is within the range step. + in range queries (available when --enable-feature=promql-duration-expr is set) to ensure that the + sample selected is within the range step.

), @@ -3991,7 +4005,8 @@ const funcDocs: Record = { first sample of m within the 1m range, where m offset 1m will select the most recent sample within the lookback interval outside and prior to the 1m offset. This is particularly useful with first_over_time(m[step()]) - in range queries to ensure that the sample selected is within the range step. + in range queries (available when --enable-feature=promql-duration-expr is set) to ensure that the + sample selected is within the range step.

), @@ -4100,7 +4115,8 @@ const funcDocs: Record = { first sample of m within the 1m range, where m offset 1m will select the most recent sample within the lookback interval outside and prior to the 1m offset. This is particularly useful with first_over_time(m[step()]) - in range queries to ensure that the sample selected is within the range step. + in range queries (available when --enable-feature=promql-duration-expr is set) to ensure that the + sample selected is within the range step.

),