From 352e36a7601cd720254d1a40d3cc07babce0cc82 Mon Sep 17 00:00:00 2001 From: Julien Pivotto <291750+roidelapluie@users.noreply.github.com> Date: Mon, 29 Jun 2026 14:18:15 +0200 Subject: [PATCH] promql: enable duration expressions by default Duration expressions no longer require the promql-duration-expr feature flag, which is now a no-op in both prometheus and promtool. The parser option (ExperimentalDurationExpr) is kept and enabled by default in the binaries, so downstream consumers of the parser can still keep it off. Move the documentation from the feature flags page into the PromQL querying basics. Signed-off-by: Julien Pivotto <291750+roidelapluie@users.noreply.github.com> --- cmd/prometheus/main.go | 8 ++- cmd/prometheus/testdata/features.json | 2 +- cmd/promtool/main.go | 10 ++-- cmd/promtool/main_test.go | 2 +- docs/command-line/prometheus.md | 2 +- docs/command-line/promtool.md | 2 +- docs/feature_flags.md | 60 ------------------- docs/querying/basics.md | 59 ++++++++++++++++++ docs/querying/functions.md | 3 +- web/ui/mantine-ui/src/promql/functionDocs.tsx | 48 +++++---------- 10 files changed, 91 insertions(+), 105 deletions(-) diff --git a/cmd/prometheus/main.go b/cmd/prometheus/main.go index 6795c6bb4c..7ad72270e7 100644 --- a/cmd/prometheus/main.go +++ b/cmd/prometheus/main.go @@ -261,8 +261,7 @@ func (c *flagConfig) setFeatureListOptions(logger *slog.Logger) error { c.parserOpts.EnableExperimentalFunctions = true logger.Info("Experimental PromQL functions enabled.") case "promql-duration-expr": - c.parserOpts.ExperimentalDurationExpr = true - logger.Info("Experimental duration expression parsing enabled.") + logger.Warn("This option for --enable-feature is now permanently enabled and therefore a no-op.", "option", o) 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": @@ -391,6 +390,9 @@ func main() { FeatureRegistry: features.DefaultRegistry, }, promslogConfig: promslog.Config{}, + // Duration expressions are enabled by default; the promql-duration-expr + // feature flag is now a no-op. + parserOpts: parser.Options{ExperimentalDurationExpr: true}, scrape: scrape.Options{ FeatureRegistry: features.DefaultRegistry, }, @@ -636,7 +638,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-duration-expr, promql-experimental-functions, promql-extended-range-selectors, promql-per-step-stats, search-api, st-storage, st-synthesis, 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-experimental-functions, promql-extended-range-selectors, promql-per-step-stats, search-api, st-storage, st-synthesis, 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 1cfce7709c..c49a5f43c1 100644 --- a/cmd/prometheus/testdata/features.json +++ b/cmd/prometheus/testdata/features.json @@ -32,7 +32,7 @@ "bool": true, "by": true, "delayed_name_removal": false, - "duration_expr": false, + "duration_expr": true, "fill": false, "fill_left": false, "fill_right": false, diff --git a/cmd/promtool/main.go b/cmd/promtool/main.go index a6dd4e0c05..385e8077d7 100644 --- a/cmd/promtool/main.go +++ b/cmd/promtool/main.go @@ -64,8 +64,10 @@ import ( var ( promqlEnableDelayedNameRemoval = false - promtoolParserOpts parser.Options - logger = promslog.New(&promslog.Config{}) + // Duration expressions are enabled by default; the promql-duration-expr + // feature flag is now a no-op. + promtoolParserOpts = parser.Options{ExperimentalDurationExpr: true} + logger = promslog.New(&promslog.Config{}) ) func init() { @@ -320,7 +322,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-duration-expr, 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-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 +360,7 @@ func main() { case "promql-delayed-name-removal": promqlEnableDelayedNameRemoval = true case "promql-duration-expr": - promtoolParserOpts.ExperimentalDurationExpr = true + // This feature is now permanently enabled and therefore a no-op. case "promql-extended-range-selectors": promtoolParserOpts.EnableExtendedRangeSelectors = true case "": diff --git a/cmd/promtool/main_test.go b/cmd/promtool/main_test.go index e42ca69441..7a15cf1c0b 100644 --- a/cmd/promtool/main_test.go +++ b/cmd/promtool/main_test.go @@ -702,7 +702,7 @@ func TestCheckRulesWithFeatureFlag(t *testing.T) { // As opposed to TestCheckRules calling CheckRules directly we run promtool // so the feature flag parsing can be tested. - args := []string{"-test.main", "--enable-feature=promql-experimental-functions", "--enable-feature=promql-duration-expr", "--enable-feature=promql-extended-range-selectors", "check", "rules", "testdata/features.yml"} + args := []string{"-test.main", "--enable-feature=promql-experimental-functions", "--enable-feature=promql-extended-range-selectors", "check", "rules", "testdata/features.yml"} tool := exec.Command(promtoolPath, args...) err := tool.Run() require.NoError(t, err) diff --git a/docs/command-line/prometheus.md b/docs/command-line/prometheus.md index 5ec738e768..a9f6d033ba 100644 --- a/docs/command-line/prometheus.md +++ b/docs/command-line/prometheus.md @@ -63,7 +63,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-duration-expr, promql-experimental-functions, promql-extended-range-selectors, promql-per-step-stats, search-api, st-storage, st-synthesis, 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-experimental-functions, promql-extended-range-selectors, promql-per-step-stats, search-api, st-storage, st-synthesis, 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 06c6f87874..dc9e3074ce 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-duration-expr, 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-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 8b500dba50..cd83ab91d6 100644 --- a/docs/feature_flags.md +++ b/docs/feature_flags.md @@ -214,66 +214,6 @@ 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_of(, )` and `max_of(, )` select between two duration expressions. -`min_of` returns the smaller of the two, which is useful for capping a duration at a maximum value. -`max_of` returns the larger of the two, which is useful for enforcing a minimum value. -For example, `max_of(step(), 5s)` ensures the duration is never shorter than `5s`, while `min_of(range(), 1h)` caps the duration at `1h`. - -**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_of(step(), 5s)` is equivalent to the larger of the query step width and `5s`. -* `min_of(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 e7f1173af4..b2ce7e757e 100644 --- a/docs/querying/basics.md +++ b/docs/querying/basics.md @@ -174,6 +174,65 @@ Examples: 12h34m56s # Equivalent to 45296s and thus 45296. 54s321ms # Equivalent to 54.321. +### Duration expressions + +Arithmetic expressions can be used wherever a time duration is expected, that is +in [range vector selectors](#range-vector-selectors) and in +[offset durations](#offset-modifier). + +In range vectors: + + 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 a duration expression, you must wrap the expression in +parentheses. Without parentheses, only the first duration value is used in the +offset calculation. + +The following operators are supported, following the usual precedence rules: + +* `+` – addition +* `-` – subtraction +* `*` – multiplication +* `/` – division +* `%` – modulo +* `^` – exponentiation + +The following functions can be used inside duration expressions: + +* `step()` resolves to the step width of a [range query](api.md#range-queries), + and to `0s` for an [instant query](api.md#instant-queries). +* `range()` resolves to the full range of a range query (end time − start time), + and to `0s` for an instant query. 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_of(, )` returns the smaller of the two durations, + which is useful for capping a duration at a maximum value. +* `max_of(, )` returns the larger of the two durations, + which is useful for enforcing a minimum value. + +For example, `max_of(step(), 5s)` ensures the duration is never shorter than +`5s`, while `min_of(range(), 1h)` caps the duration at `1h`. + +**Note**: Duration expressions are not supported in the [`@` modifier](#modifier). + +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_of(step(), 5s)` is equivalent to the larger of the query step width and `5s`. +* `min_of(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. diff --git a/docs/querying/functions.md b/docs/querying/functions.md index 5900da4c62..4361cfe72f 100644 --- a/docs/querying/functions.md +++ b/docs/querying/functions.md @@ -979,8 +979,7 @@ 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 (available when `--enable-feature=promql-duration-expr` is set) -to ensure that the sample selected is within the range step. +in range queries to ensure that the sample selected is within the range step. ## Trigonometric Functions diff --git a/web/ui/mantine-ui/src/promql/functionDocs.tsx b/web/ui/mantine-ui/src/promql/functionDocs.tsx index df47550052..a66cd6223e 100644 --- a/web/ui/mantine-ui/src/promql/functionDocs.tsx +++ b/web/ui/mantine-ui/src/promql/functionDocs.tsx @@ -589,8 +589,7 @@ 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 (available when --enable-feature=promql-duration-expr is set) to ensure that the - sample selected is within the range step. + in range queries to ensure that the sample selected is within the range step.

), @@ -909,8 +908,7 @@ 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 (available when --enable-feature=promql-duration-expr is set) to ensure that the - sample selected is within the range step. + in range queries to ensure that the sample selected is within the range step.

), @@ -1240,8 +1238,7 @@ 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 (available when --enable-feature=promql-duration-expr is set) to ensure that the - sample selected is within the range step. + in range queries to ensure that the sample selected is within the range step.

), @@ -2072,8 +2069,7 @@ 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 (available when --enable-feature=promql-duration-expr is set) to ensure that the - sample selected is within the range step. + in range queries to ensure that the sample selected is within the range step.

), @@ -2223,8 +2219,7 @@ 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 (available when --enable-feature=promql-duration-expr is set) to ensure that the - sample selected is within the range step. + in range queries to ensure that the sample selected is within the range step.

), @@ -2349,8 +2344,7 @@ 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 (available when --enable-feature=promql-duration-expr is set) to ensure that the - sample selected is within the range step. + in range queries to ensure that the sample selected is within the range step.

), @@ -2475,8 +2469,7 @@ 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 (available when --enable-feature=promql-duration-expr is set) to ensure that the - sample selected is within the range step. + in range queries to ensure that the sample selected is within the range step.

), @@ -2691,8 +2684,7 @@ 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 (available when --enable-feature=promql-duration-expr is set) to ensure that the - sample selected is within the range step. + in range queries to ensure that the sample selected is within the range step.

), @@ -2801,8 +2793,7 @@ 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 (available when --enable-feature=promql-duration-expr is set) to ensure that the - sample selected is within the range step. + in range queries to ensure that the sample selected is within the range step.

), @@ -3315,8 +3306,7 @@ 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 (available when --enable-feature=promql-duration-expr is set) to ensure that the - sample selected is within the range step. + in range queries to ensure that the sample selected is within the range step.

), @@ -3425,8 +3415,7 @@ 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 (available when --enable-feature=promql-duration-expr is set) to ensure that the - sample selected is within the range step. + in range queries to ensure that the sample selected is within the range step.

), @@ -3551,8 +3540,7 @@ 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 (available when --enable-feature=promql-duration-expr is set) to ensure that the - sample selected is within the range step. + in range queries to ensure that the sample selected is within the range step.

), @@ -3817,8 +3805,7 @@ 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 (available when --enable-feature=promql-duration-expr is set) to ensure that the - sample selected is within the range step. + in range queries to ensure that the sample selected is within the range step.

), @@ -3927,8 +3914,7 @@ 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 (available when --enable-feature=promql-duration-expr is set) to ensure that the - sample selected is within the range step. + in range queries to ensure that the sample selected is within the range step.

), @@ -4037,8 +4023,7 @@ 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 (available when --enable-feature=promql-duration-expr is set) to ensure that the - sample selected is within the range step. + in range queries to ensure that the sample selected is within the range step.

), @@ -4147,8 +4132,7 @@ 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 (available when --enable-feature=promql-duration-expr is set) to ensure that the - sample selected is within the range step. + in range queries to ensure that the sample selected is within the range step.

),