mirror of
https://github.com/prometheus/prometheus.git
synced 2025-08-05 13:47:10 +02:00
161 lines
4.7 KiB
Go
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)
|
|
}
|
|
}
|