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