prometheus/promql/durations.go
George Krajcsovits 4662722a8e
chore(promql): improve doc string in promql/durations.go (#16863)
Signed-off-by: György Krajcsovits <gyorgy.krajcsovits@grafana.com>
2025-07-11 12:07:54 +02:00

161 lines
4.7 KiB
Go

// Copyright 2025 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package promql
import (
"fmt"
"math"
"time"
"github.com/prometheus/prometheus/promql/parser"
)
// durationVisitor is a visitor that calculates the actual value of
// duration expressions in AST nodes. For example the query
// "http_requests_total offset (1h / 2)" is represented in the AST
// as a VectorSelector with OriginalOffset 0 and the duration expression
// in OriginalOffsetExpr representing (1h / 2). This visitor evaluates
// such duration expression, setting OriginalOffset to 30m.
type durationVisitor struct {
step time.Duration
}
// Visit finds any duration expressions in AST Nodes and modifies the Node to
// store the concrete value. Note that parser.Walk does NOT traverse the
// duration expressions such as OriginalOffsetExpr so we make our own recursive
// call on those to evaluate the result.
func (v *durationVisitor) Visit(node parser.Node, _ []parser.Node) (parser.Visitor, error) {
switch n := node.(type) {
case *parser.VectorSelector:
if n.OriginalOffsetExpr != nil {
duration, err := v.calculateDuration(n.OriginalOffsetExpr, true)
if err != nil {
return nil, err
}
n.OriginalOffset = duration
}
case *parser.MatrixSelector:
if n.RangeExpr != nil {
duration, err := v.calculateDuration(n.RangeExpr, false)
if err != nil {
return nil, err
}
n.Range = duration
}
case *parser.SubqueryExpr:
if n.OriginalOffsetExpr != nil {
duration, err := v.calculateDuration(n.OriginalOffsetExpr, true)
if err != nil {
return nil, err
}
n.OriginalOffset = duration
}
if n.StepExpr != nil {
duration, err := v.calculateDuration(n.StepExpr, false)
if err != nil {
return nil, err
}
n.Step = duration
}
if n.RangeExpr != nil {
duration, err := v.calculateDuration(n.RangeExpr, false)
if err != nil {
return nil, err
}
n.Range = duration
}
}
return v, nil
}
// calculateDuration returns the float value of a duration expression as
// time.Duration or an error if the duration is invalid.
func (v *durationVisitor) calculateDuration(expr parser.Expr, allowedNegative bool) (time.Duration, error) {
duration, err := v.evaluateDurationExpr(expr)
if err != nil {
return 0, err
}
if duration <= 0 && !allowedNegative {
return 0, fmt.Errorf("%d:%d: duration must be greater than 0", expr.PositionRange().Start, expr.PositionRange().End)
}
if duration > 1<<63-1 || duration < -1<<63 {
return 0, fmt.Errorf("%d:%d: duration is out of range", expr.PositionRange().Start, expr.PositionRange().End)
}
return time.Duration(duration*1000) * time.Millisecond, nil
}
// evaluateDurationExpr recursively evaluates a duration expression to a float64 value.
func (v *durationVisitor) evaluateDurationExpr(expr parser.Expr) (float64, error) {
switch n := expr.(type) {
case *parser.NumberLiteral:
return n.Val, nil
case *parser.DurationExpr:
var lhs, rhs float64
var err error
if n.LHS != nil {
lhs, err = v.evaluateDurationExpr(n.LHS)
if err != nil {
return 0, err
}
}
if n.RHS != nil {
rhs, err = v.evaluateDurationExpr(n.RHS)
if err != nil {
return 0, err
}
}
switch n.Op {
case parser.STEP:
return float64(v.step.Seconds()), nil
case parser.MIN:
return math.Min(lhs, rhs), nil
case parser.MAX:
return math.Max(lhs, rhs), nil
case parser.ADD:
if n.LHS == nil {
// Unary positive duration expression.
return rhs, nil
}
return lhs + rhs, nil
case parser.SUB:
if n.LHS == nil {
// Unary negative duration expression.
return -rhs, nil
}
return lhs - rhs, nil
case parser.MUL:
return lhs * rhs, nil
case parser.DIV:
if rhs == 0 {
return 0, fmt.Errorf("%d:%d: division by zero", expr.PositionRange().Start, expr.PositionRange().End)
}
return lhs / rhs, nil
case parser.MOD:
if rhs == 0 {
return 0, fmt.Errorf("%d:%d: modulo by zero", expr.PositionRange().Start, expr.PositionRange().End)
}
return math.Mod(lhs, rhs), nil
case parser.POW:
return math.Pow(lhs, rhs), nil
default:
return 0, fmt.Errorf("unexpected duration expression operator %q", n.Op)
}
default:
return 0, fmt.Errorf("unexpected duration expression type %T", n)
}
}