Skip to content

Commit c792987

Browse files
authored
Merge branch 'prometheus:main' into promql-nhcb-use-AM
2 parents 1909516 + f379e2e commit c792987

File tree

21 files changed

+1903
-530
lines changed

21 files changed

+1903
-530
lines changed

cmd/prometheus/main.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,9 @@ func (c *flagConfig) setFeatureListOptions(logger *slog.Logger) error {
249249
case "promql-experimental-functions":
250250
parser.EnableExperimentalFunctions = true
251251
logger.Info("Experimental PromQL functions enabled.")
252+
case "promql-duration-expr":
253+
parser.ExperimentalDurationExpr = true
254+
logger.Info("Experimental duration expression parsing enabled.")
252255
case "native-histograms":
253256
c.tsdb.EnableNativeHistograms = true
254257
c.scrape.EnableNativeHistogramsIngestion = true
@@ -539,7 +542,7 @@ func main() {
539542
a.Flag("scrape.discovery-reload-interval", "Interval used by scrape manager to throttle target groups updates.").
540543
Hidden().Default("5s").SetValue(&cfg.scrape.DiscoveryReloadInterval)
541544

542-
a.Flag("enable-feature", "Comma separated feature names to enable. Valid options: exemplar-storage, expand-external-labels, memory-snapshot-on-shutdown, promql-per-step-stats, promql-experimental-functions, extra-scrape-metrics, auto-gomaxprocs, native-histograms, created-timestamp-zero-ingestion, concurrent-rule-eval, delayed-compaction, old-ui, otlp-deltatocumulative. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details.").
545+
a.Flag("enable-feature", "Comma separated feature names to enable. Valid options: exemplar-storage, expand-external-labels, memory-snapshot-on-shutdown, promql-per-step-stats, promql-experimental-functions, extra-scrape-metrics, auto-gomaxprocs, native-histograms, created-timestamp-zero-ingestion, concurrent-rule-eval, delayed-compaction, old-ui, otlp-deltatocumulative, promql-duration-expr. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details.").
543546
Default("").StringsVar(&cfg.featureList)
544547

545548
a.Flag("agent", "Run Prometheus in 'Agent mode'.").BoolVar(&agentMode)

docs/command-line/prometheus.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ The Prometheus monitoring server
6161
| <code class="text-nowrap">--query.timeout</code> | Maximum time a query may take before being aborted. Use with server mode only. | `2m` |
6262
| <code class="text-nowrap">--query.max-concurrency</code> | Maximum number of queries executed concurrently. Use with server mode only. | `20` |
6363
| <code class="text-nowrap">--query.max-samples</code> | 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` |
64-
| <code class="text-nowrap">--enable-feature</code> <code class="text-nowrap">...<code class="text-nowrap"> | Comma separated feature names to enable. Valid options: exemplar-storage, expand-external-labels, memory-snapshot-on-shutdown, promql-per-step-stats, promql-experimental-functions, extra-scrape-metrics, auto-gomaxprocs, native-histograms, created-timestamp-zero-ingestion, concurrent-rule-eval, delayed-compaction, old-ui, otlp-deltatocumulative. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details. | |
64+
| <code class="text-nowrap">--enable-feature</code> <code class="text-nowrap">...<code class="text-nowrap"> | Comma separated feature names to enable. Valid options: exemplar-storage, expand-external-labels, memory-snapshot-on-shutdown, promql-per-step-stats, promql-experimental-functions, extra-scrape-metrics, auto-gomaxprocs, native-histograms, created-timestamp-zero-ingestion, concurrent-rule-eval, delayed-compaction, old-ui, otlp-deltatocumulative, promql-duration-expr. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details. | |
6565
| <code class="text-nowrap">--agent</code> | Run Prometheus in 'Agent mode'. | |
6666
| <code class="text-nowrap">--log.level</code> | Only log messages with the given severity or above. One of: [debug, info, warn, error] | `info` |
6767
| <code class="text-nowrap">--log.format</code> | Output format of log messages. One of: [logfmt, json] | `logfmt` |

docs/feature_flags.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,4 +183,38 @@ This state is periodically ([`max_stale`][d2c]) cleared of inactive series.
183183
Enabling this _can_ have negative impact on performance, because the in-memory
184184
state is mutex guarded. Cumulative-only OTLP requests are not affected.
185185

186+
### PromQL arithmetic expressions in time durations
187+
188+
`--enable-feature=promql-duration-expr`
189+
190+
With this flag, arithmetic expressions can be used in time durations in range queries and offset durations. For example:
191+
192+
In range queries:
193+
rate(http_requests_total[5m * 2]) # 10 minute range
194+
rate(http_requests_total[(5+2) * 1m]) # 7 minute range
195+
196+
In offset durations:
197+
http_requests_total offset (1h / 2) # 30 minute offset
198+
http_requests_total offset ((2 ^ 3) * 1m) # 8 minute offset
199+
200+
Note: Duration expressions are not supported in the @ timestamp operator.
201+
202+
The following operators are supported:
203+
204+
* `+` - addition
205+
* `-` - subtraction
206+
* `*` - multiplication
207+
* `/` - division
208+
* `%` - modulo
209+
* `^` - exponentiation
210+
211+
Examples of equivalent durations:
212+
213+
* `5m * 2` is the equivalent to `10m` or `600s`
214+
* `10m - 1m` is the equivalent to `9m` or `540s`
215+
* `(5+2) * 1m` is the equivalent to `7m` or `420s`
216+
* `1h / 2` is the equivalent to `30m` or `1800s`
217+
* `4h % 3h` is the equivalent to `1h` or `3600s`
218+
* `(2 ^ 3) * 1m` is the equivalent to `8m` or `480s`
219+
186220
[d2c]: https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/deltatocumulativeprocessor

promql/durations.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Copyright 2025 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package promql
15+
16+
import (
17+
"fmt"
18+
"math"
19+
"time"
20+
21+
"github.com/prometheus/prometheus/promql/parser"
22+
)
23+
24+
// durationVisitor is a visitor that visits a duration expression and calculates the duration.
25+
type durationVisitor struct{}
26+
27+
func (v *durationVisitor) Visit(node parser.Node, _ []parser.Node) (parser.Visitor, error) {
28+
switch n := node.(type) {
29+
case *parser.VectorSelector:
30+
if n.OriginalOffsetExpr != nil {
31+
duration, err := calculateDuration(n.OriginalOffsetExpr, true)
32+
if err != nil {
33+
return nil, err
34+
}
35+
n.OriginalOffset = duration
36+
}
37+
case *parser.MatrixSelector:
38+
if n.RangeExpr != nil {
39+
duration, err := calculateDuration(n.RangeExpr, false)
40+
if err != nil {
41+
return nil, err
42+
}
43+
n.Range = duration
44+
}
45+
case *parser.SubqueryExpr:
46+
if n.OriginalOffsetExpr != nil {
47+
duration, err := calculateDuration(n.OriginalOffsetExpr, true)
48+
if err != nil {
49+
return nil, err
50+
}
51+
n.OriginalOffset = duration
52+
}
53+
if n.StepExpr != nil {
54+
duration, err := calculateDuration(n.StepExpr, false)
55+
if err != nil {
56+
return nil, err
57+
}
58+
n.Step = duration
59+
}
60+
if n.RangeExpr != nil {
61+
duration, err := calculateDuration(n.RangeExpr, false)
62+
if err != nil {
63+
return nil, err
64+
}
65+
n.Range = duration
66+
}
67+
}
68+
return v, nil
69+
}
70+
71+
// calculateDuration computes the duration from a duration expression.
72+
func calculateDuration(expr parser.Expr, allowedNegative bool) (time.Duration, error) {
73+
duration, err := evaluateDurationExpr(expr)
74+
if err != nil {
75+
return 0, err
76+
}
77+
if duration <= 0 && !allowedNegative {
78+
return 0, fmt.Errorf("%d:%d: duration must be greater than 0", expr.PositionRange().Start, expr.PositionRange().End)
79+
}
80+
if duration > 1<<63-1 || duration < -1<<63 {
81+
return 0, fmt.Errorf("%d:%d: duration is out of range", expr.PositionRange().Start, expr.PositionRange().End)
82+
}
83+
return time.Duration(duration*1000) * time.Millisecond, nil
84+
}
85+
86+
// evaluateDurationExpr recursively evaluates a duration expression to a float64 value.
87+
func evaluateDurationExpr(expr parser.Expr) (float64, error) {
88+
switch n := expr.(type) {
89+
case *parser.NumberLiteral:
90+
return n.Val, nil
91+
case *parser.DurationExpr:
92+
var lhs, rhs float64
93+
var err error
94+
95+
if n.LHS != nil {
96+
lhs, err = evaluateDurationExpr(n.LHS)
97+
if err != nil {
98+
return 0, err
99+
}
100+
}
101+
102+
rhs, err = evaluateDurationExpr(n.RHS)
103+
if err != nil {
104+
return 0, err
105+
}
106+
107+
switch n.Op {
108+
case parser.ADD:
109+
return lhs + rhs, nil
110+
case parser.SUB:
111+
if n.LHS == nil {
112+
// Unary negative duration expression.
113+
return -rhs, nil
114+
}
115+
return lhs - rhs, nil
116+
case parser.MUL:
117+
return lhs * rhs, nil
118+
case parser.DIV:
119+
if rhs == 0 {
120+
return 0, fmt.Errorf("%d:%d: division by zero", expr.PositionRange().Start, expr.PositionRange().End)
121+
}
122+
return lhs / rhs, nil
123+
case parser.MOD:
124+
if rhs == 0 {
125+
return 0, fmt.Errorf("%d:%d: modulo by zero", expr.PositionRange().Start, expr.PositionRange().End)
126+
}
127+
return math.Mod(lhs, rhs), nil
128+
case parser.POW:
129+
return math.Pow(lhs, rhs), nil
130+
default:
131+
return 0, fmt.Errorf("unexpected duration expression operator %q", n.Op)
132+
}
133+
default:
134+
return 0, fmt.Errorf("unexpected duration expression type %T", n)
135+
}
136+
}

0 commit comments

Comments
 (0)