mirror of
https://github.com/prometheus/prometheus.git
synced 2025-08-05 13:47:10 +02:00
step() is a new keyword introduced to represent the query step width in duration expressions. min(a,b) and max(a,b) return the min and max from two duration expressions. Signed-off-by: Julien Pivotto <291750+roidelapluie@users.noreply.github.com>
258 lines
7.3 KiB
Go
258 lines
7.3 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 (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/prometheus/prometheus/promql/parser"
|
|
)
|
|
|
|
func TestDurationVisitor(t *testing.T) {
|
|
// Enable experimental duration expression parsing.
|
|
parser.ExperimentalDurationExpr = true
|
|
t.Cleanup(func() {
|
|
parser.ExperimentalDurationExpr = false
|
|
})
|
|
complexExpr := `sum_over_time(
|
|
rate(metric[5m] offset 1h)[10m:30s] offset 2h
|
|
) +
|
|
avg_over_time(
|
|
metric[1h + 30m] offset -1h
|
|
) *
|
|
count_over_time(
|
|
metric[2h * 0.5]
|
|
)`
|
|
|
|
expr, err := parser.ParseExpr(complexExpr)
|
|
require.NoError(t, err)
|
|
|
|
err = parser.Walk(&durationVisitor{}, expr, nil)
|
|
require.NoError(t, err)
|
|
|
|
// Verify different parts of the expression have correct durations.
|
|
// This is a binary expression at the top level.
|
|
binExpr, ok := expr.(*parser.BinaryExpr)
|
|
require.True(t, ok, "Expected binary expression at top level")
|
|
|
|
// Left side should be sum_over_time with subquery.
|
|
leftCall, ok := binExpr.LHS.(*parser.Call)
|
|
require.True(t, ok, "Expected call expression on left side")
|
|
require.Equal(t, "sum_over_time", leftCall.Func.Name)
|
|
|
|
// Extract the subquery from sum_over_time.
|
|
sumSubquery, ok := leftCall.Args[0].(*parser.SubqueryExpr)
|
|
require.True(t, ok, "Expected subquery in sum_over_time")
|
|
require.Equal(t, 10*time.Minute, sumSubquery.Range)
|
|
require.Equal(t, 30*time.Second, sumSubquery.Step)
|
|
require.Equal(t, 2*time.Hour, sumSubquery.OriginalOffset)
|
|
|
|
// Extract the rate call inside the subquery.
|
|
rateCall, ok := sumSubquery.Expr.(*parser.Call)
|
|
require.True(t, ok, "Expected rate call in subquery")
|
|
require.Equal(t, "rate", rateCall.Func.Name)
|
|
|
|
// Extract the matrix selector from rate.
|
|
rateMatrix, ok := rateCall.Args[0].(*parser.MatrixSelector)
|
|
require.True(t, ok, "Expected matrix selector in rate")
|
|
require.Equal(t, 5*time.Minute, rateMatrix.Range)
|
|
require.Equal(t, 1*time.Hour, rateMatrix.VectorSelector.(*parser.VectorSelector).OriginalOffset)
|
|
|
|
// Right side should be another binary expression (multiplication).
|
|
rightBinExpr, ok := binExpr.RHS.(*parser.BinaryExpr)
|
|
require.True(t, ok, "Expected binary expression on right side")
|
|
|
|
// Left side of multiplication should be avg_over_time.
|
|
avgCall, ok := rightBinExpr.LHS.(*parser.Call)
|
|
require.True(t, ok, "Expected call expression on left side of multiplication")
|
|
require.Equal(t, "avg_over_time", avgCall.Func.Name)
|
|
|
|
// Extract the matrix selector from avg_over_time.
|
|
avgMatrix, ok := avgCall.Args[0].(*parser.MatrixSelector)
|
|
require.True(t, ok, "Expected matrix selector in avg_over_time")
|
|
require.Equal(t, 90*time.Minute, avgMatrix.Range) // 1h + 30m
|
|
require.Equal(t, -1*time.Hour, avgMatrix.VectorSelector.(*parser.VectorSelector).OriginalOffset)
|
|
|
|
// Right side of multiplication should be count_over_time.
|
|
countCall, ok := rightBinExpr.RHS.(*parser.Call)
|
|
require.True(t, ok, "Expected call expression on right side of multiplication")
|
|
require.Equal(t, "count_over_time", countCall.Func.Name)
|
|
|
|
// Extract the matrix selector from count_over_time.
|
|
countMatrix, ok := countCall.Args[0].(*parser.MatrixSelector)
|
|
require.True(t, ok, "Expected matrix selector in count_over_time")
|
|
require.Equal(t, 1*time.Hour, countMatrix.Range) // 2h * 0.5
|
|
}
|
|
|
|
func TestCalculateDuration(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
expr parser.Expr
|
|
expected time.Duration
|
|
errorMessage string
|
|
allowedNegative bool
|
|
}{
|
|
{
|
|
name: "addition",
|
|
expr: &parser.DurationExpr{
|
|
LHS: &parser.NumberLiteral{Val: 5},
|
|
RHS: &parser.NumberLiteral{Val: 10},
|
|
Op: parser.ADD,
|
|
},
|
|
expected: 15 * time.Second,
|
|
},
|
|
{
|
|
name: "subtraction",
|
|
expr: &parser.DurationExpr{
|
|
LHS: &parser.NumberLiteral{Val: 15},
|
|
RHS: &parser.NumberLiteral{Val: 5},
|
|
Op: parser.SUB,
|
|
},
|
|
expected: 10 * time.Second,
|
|
},
|
|
{
|
|
name: "subtraction with negative",
|
|
expr: &parser.DurationExpr{
|
|
LHS: &parser.NumberLiteral{Val: 5},
|
|
RHS: &parser.NumberLiteral{Val: 10},
|
|
Op: parser.SUB,
|
|
},
|
|
errorMessage: "duration must be greater than 0",
|
|
},
|
|
{
|
|
name: "multiplication",
|
|
expr: &parser.DurationExpr{
|
|
LHS: &parser.NumberLiteral{Val: 5},
|
|
RHS: &parser.NumberLiteral{Val: 3},
|
|
Op: parser.MUL,
|
|
},
|
|
expected: 15 * time.Second,
|
|
},
|
|
{
|
|
name: "division",
|
|
expr: &parser.DurationExpr{
|
|
LHS: &parser.NumberLiteral{Val: 15},
|
|
RHS: &parser.NumberLiteral{Val: 3},
|
|
Op: parser.DIV,
|
|
},
|
|
expected: 5 * time.Second,
|
|
},
|
|
{
|
|
name: "modulo with numbers",
|
|
expr: &parser.DurationExpr{
|
|
LHS: &parser.NumberLiteral{Val: 17},
|
|
RHS: &parser.NumberLiteral{Val: 5},
|
|
Op: parser.MOD,
|
|
},
|
|
expected: 2 * time.Second,
|
|
},
|
|
{
|
|
name: "power",
|
|
expr: &parser.DurationExpr{
|
|
LHS: &parser.NumberLiteral{Val: 2},
|
|
RHS: &parser.NumberLiteral{Val: 3},
|
|
Op: parser.POW,
|
|
},
|
|
expected: 8 * time.Second,
|
|
},
|
|
{
|
|
name: "complex expression",
|
|
expr: &parser.DurationExpr{
|
|
LHS: &parser.DurationExpr{
|
|
LHS: &parser.NumberLiteral{Val: 2},
|
|
RHS: &parser.DurationExpr{
|
|
LHS: &parser.NumberLiteral{Val: 3},
|
|
RHS: &parser.NumberLiteral{Val: 4},
|
|
Op: parser.ADD,
|
|
},
|
|
Op: parser.MUL,
|
|
},
|
|
RHS: &parser.NumberLiteral{Val: 1},
|
|
Op: parser.SUB,
|
|
},
|
|
expected: 13 * time.Second,
|
|
},
|
|
{
|
|
name: "unary negative",
|
|
expr: &parser.DurationExpr{
|
|
RHS: &parser.NumberLiteral{Val: 5},
|
|
Op: parser.SUB,
|
|
},
|
|
expected: -5 * time.Second,
|
|
allowedNegative: true,
|
|
},
|
|
{
|
|
name: "step",
|
|
expr: &parser.DurationExpr{
|
|
Op: parser.STEP,
|
|
},
|
|
expected: 1 * time.Second,
|
|
},
|
|
{
|
|
name: "step multiplication",
|
|
expr: &parser.DurationExpr{
|
|
LHS: &parser.DurationExpr{
|
|
Op: parser.STEP,
|
|
},
|
|
RHS: &parser.NumberLiteral{Val: 3},
|
|
Op: parser.MUL,
|
|
},
|
|
expected: 3 * time.Second,
|
|
},
|
|
{
|
|
name: "division by zero",
|
|
expr: &parser.DurationExpr{
|
|
LHS: &parser.NumberLiteral{Val: 5},
|
|
RHS: &parser.DurationExpr{
|
|
LHS: &parser.NumberLiteral{Val: 5},
|
|
RHS: &parser.NumberLiteral{Val: 5},
|
|
Op: parser.SUB,
|
|
},
|
|
Op: parser.DIV,
|
|
},
|
|
errorMessage: "division by zero",
|
|
},
|
|
{
|
|
name: "modulo by zero",
|
|
expr: &parser.DurationExpr{
|
|
LHS: &parser.NumberLiteral{Val: 5},
|
|
RHS: &parser.DurationExpr{
|
|
LHS: &parser.NumberLiteral{Val: 5},
|
|
RHS: &parser.NumberLiteral{Val: 5},
|
|
Op: parser.SUB,
|
|
},
|
|
Op: parser.MOD,
|
|
},
|
|
errorMessage: "modulo by zero",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
v := &durationVisitor{step: 1 * time.Second}
|
|
result, err := v.calculateDuration(tt.expr, tt.allowedNegative)
|
|
if tt.errorMessage != "" {
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), tt.errorMessage)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|