mirror of
https://github.com/prometheus/prometheus.git
synced 2026-05-28 04:02:21 -04:00
promql: Implement </ and >/ operators for trimming native histograms.
This implements the TRIM_UPPER (</) and TRIM_LOWER (>/) operators that allow removing observations below or above a threshold from a histogram. The implementation zeros out buckets outside the desired range. It also recalculates the sum, including only bucket counts within the specified threshold range. Fixes #14651. Signed-off-by: sujal shah <sujalshah28092004@gmail.com>
This commit is contained in:
parent
d9ccd70ac1
commit
e8bfcfcf1a
12 changed files with 1156 additions and 711 deletions
309
promql/engine.go
309
promql/engine.go
|
|
@ -3140,6 +3140,301 @@ func scalarBinop(op parser.ItemType, lhs, rhs float64) float64 {
|
|||
panic(fmt.Errorf("operator %q not allowed for Scalar operations", op))
|
||||
}
|
||||
|
||||
// processCustomBucket handles custom bucket processing for histogram trimming.
|
||||
// It returns the count to keep and the bucket midpoint for sum calculations.
|
||||
func processCustomBucket(
|
||||
bucket histogram.Bucket[float64],
|
||||
rhs float64,
|
||||
op parser.ItemType,
|
||||
) (keepCount, bucketMidpoint float64) {
|
||||
// Midpoint calculation
|
||||
switch {
|
||||
case math.IsInf(bucket.Lower, -1):
|
||||
// First bucket: no lower bound, assume midpoint is near upper bound.
|
||||
bucketMidpoint = bucket.Upper
|
||||
case math.IsInf(bucket.Upper, 1):
|
||||
bucketMidpoint = bucket.Lower
|
||||
default:
|
||||
bucketMidpoint = (bucket.Lower + bucket.Upper) / 2
|
||||
}
|
||||
|
||||
// Fractional keepCount calculation
|
||||
switch op {
|
||||
case parser.TRIM_UPPER:
|
||||
switch {
|
||||
case math.IsInf(bucket.Lower, -1):
|
||||
// Special case for -Inf lower bound
|
||||
if rhs >= bucket.Upper {
|
||||
// Trim point is above bucket upper bound, keep all
|
||||
keepCount = bucket.Count
|
||||
} else {
|
||||
// Trim point is within bucket or below, keep none
|
||||
keepCount = 0
|
||||
}
|
||||
case math.IsInf(bucket.Upper, 1):
|
||||
// Special case for +Inf upper bound
|
||||
if rhs <= bucket.Lower {
|
||||
// Trim point is below bucket lower bound, keep none
|
||||
keepCount = 0
|
||||
} else {
|
||||
// Trim point is within the bucket, keep a portion
|
||||
// Since we can't interpolate with +Inf, assume keep half for simplicity
|
||||
// Another approach would be to use a different interpolation scheme
|
||||
keepCount = bucket.Count * 0.5
|
||||
}
|
||||
default:
|
||||
// Normal case - finite bounds
|
||||
switch {
|
||||
case bucket.Upper <= rhs:
|
||||
// Bucket entirely below trim point - keep all
|
||||
keepCount = bucket.Count
|
||||
case bucket.Lower < rhs:
|
||||
// Bucket contains trim point - interpolate
|
||||
fraction := (rhs - bucket.Lower) / (bucket.Upper - bucket.Lower)
|
||||
keepCount = bucket.Count * fraction
|
||||
default:
|
||||
// Bucket entirely above trim point - discard
|
||||
keepCount = 0
|
||||
}
|
||||
}
|
||||
|
||||
case parser.TRIM_LOWER:
|
||||
switch {
|
||||
case math.IsInf(bucket.Upper, 1):
|
||||
// Special case for +Inf upper bound
|
||||
if rhs <= bucket.Lower {
|
||||
keepCount = bucket.Count
|
||||
} else {
|
||||
keepCount = 0
|
||||
}
|
||||
case math.IsInf(bucket.Lower, -1):
|
||||
// Special case for -Inf lower bound
|
||||
if rhs >= bucket.Upper {
|
||||
keepCount = 0
|
||||
} else {
|
||||
keepCount = bucket.Count * 0.5
|
||||
}
|
||||
default:
|
||||
switch {
|
||||
case bucket.Lower >= rhs:
|
||||
keepCount = bucket.Count
|
||||
case bucket.Upper > rhs:
|
||||
fraction := (bucket.Upper - rhs) / (bucket.Upper - bucket.Lower)
|
||||
keepCount = bucket.Count * fraction
|
||||
default:
|
||||
keepCount = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return keepCount, bucketMidpoint
|
||||
}
|
||||
|
||||
func computeBucketTrim(op parser.ItemType, bucket histogram.Bucket[float64], rhs float64, isPostive, isCustomBucket bool) (float64, float64) {
|
||||
if isCustomBucket {
|
||||
return processCustomBucket(bucket, rhs, op)
|
||||
}
|
||||
return computeExponentialTrim(bucket, rhs, isPostive, op)
|
||||
}
|
||||
|
||||
// Helper function to trim native histogram buckets.
|
||||
func trimHistogram(trimmedHist *histogram.FloatHistogram, rhs float64, op parser.ItemType) {
|
||||
updatedCount := 0.0
|
||||
origSum := trimmedHist.Sum
|
||||
removedSum := 0.0
|
||||
hasPositive, hasNegative := false, false
|
||||
isCustomBucket := trimmedHist.UsesCustomBuckets()
|
||||
|
||||
// Calculate the fraction to keep for buckets that contain the trim value
|
||||
// For TRIM_UPPER, we keep observations below the trim point (rhs)
|
||||
switch op {
|
||||
case parser.TRIM_UPPER:
|
||||
for i, iter := 0, trimmedHist.PositiveBucketIterator(); iter.Next(); i++ {
|
||||
hasPositive = true
|
||||
bucket := iter.At()
|
||||
var keepCount, bucketMidpoint float64
|
||||
keepCount, bucketMidpoint = computeBucketTrim(op, bucket, rhs, true, isCustomBucket)
|
||||
|
||||
// Bucket is entirely below the trim point - keep all
|
||||
switch {
|
||||
case bucket.Upper <= rhs:
|
||||
updatedCount += bucket.Count
|
||||
case bucket.Lower < rhs:
|
||||
// Bucket contains the trim point - interpolate
|
||||
removedCount := bucket.Count - keepCount
|
||||
removedMid := bucketMidpoint
|
||||
removedSum += removedCount * removedMid
|
||||
|
||||
updatedCount += keepCount
|
||||
trimmedHist.PositiveBuckets[i] = keepCount
|
||||
default:
|
||||
if !isCustomBucket {
|
||||
bucketMidpoint = math.Sqrt(bucket.Lower * bucket.Upper)
|
||||
}
|
||||
removedSum += bucket.Count * bucketMidpoint
|
||||
// Bucket is entirely above the trim point - discard
|
||||
trimmedHist.PositiveBuckets[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
for i, iter := 0, trimmedHist.NegativeBucketIterator(); iter.Next(); i++ {
|
||||
hasNegative = true
|
||||
bucket := iter.At()
|
||||
var keepCount, bucketMidpoint float64
|
||||
keepCount, bucketMidpoint = computeBucketTrim(op, bucket, rhs, false, isCustomBucket)
|
||||
|
||||
switch {
|
||||
case bucket.Upper <= rhs:
|
||||
updatedCount += bucket.Count
|
||||
case bucket.Lower < rhs:
|
||||
removedCount := bucket.Count - keepCount
|
||||
removedMid := bucketMidpoint
|
||||
removedSum += removedCount * removedMid
|
||||
|
||||
trimmedHist.NegativeBuckets[i] = keepCount
|
||||
updatedCount += keepCount
|
||||
default:
|
||||
bucketMidpoint = math.Sqrt(bucket.Lower * bucket.Upper)
|
||||
removedSum += bucket.Count * bucketMidpoint
|
||||
trimmedHist.NegativeBuckets[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// For TRIM_LOWER, we keep observations above the trim point (rhs)
|
||||
case parser.TRIM_LOWER:
|
||||
for i, iter := 0, trimmedHist.PositiveBucketIterator(); iter.Next(); i++ {
|
||||
hasPositive = true
|
||||
bucket := iter.At()
|
||||
var keepCount, bucketMidpoint float64
|
||||
keepCount, bucketMidpoint = computeBucketTrim(op, bucket, rhs, true, isCustomBucket)
|
||||
|
||||
switch {
|
||||
case bucket.Lower >= rhs:
|
||||
updatedCount += bucket.Count
|
||||
case bucket.Upper > rhs:
|
||||
removedCount := bucket.Count - keepCount
|
||||
removedMid := bucketMidpoint
|
||||
removedSum += removedCount * removedMid
|
||||
|
||||
trimmedHist.PositiveBuckets[i] = keepCount
|
||||
updatedCount += keepCount
|
||||
default:
|
||||
if !isCustomBucket {
|
||||
bucketMidpoint = math.Sqrt(bucket.Lower * bucket.Upper)
|
||||
}
|
||||
removedSum += bucket.Count * bucketMidpoint
|
||||
trimmedHist.PositiveBuckets[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
for i, iter := 0, trimmedHist.NegativeBucketIterator(); iter.Next(); i++ {
|
||||
hasNegative = true
|
||||
bucket := iter.At()
|
||||
var keepCount, bucketMidpoint float64
|
||||
keepCount, bucketMidpoint = computeBucketTrim(op, bucket, rhs, false, isCustomBucket)
|
||||
switch {
|
||||
case bucket.Lower >= rhs:
|
||||
updatedCount += bucket.Count
|
||||
case bucket.Upper > rhs:
|
||||
removedCount := bucket.Count - keepCount
|
||||
removedMid := bucketMidpoint
|
||||
removedSum += removedCount * removedMid
|
||||
|
||||
trimmedHist.NegativeBuckets[i] = keepCount
|
||||
updatedCount += keepCount
|
||||
default:
|
||||
bucketMidpoint = math.Sqrt(bucket.Lower * bucket.Upper)
|
||||
removedSum += bucket.Count * bucketMidpoint
|
||||
trimmedHist.NegativeBuckets[i] = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the zero count bucket
|
||||
if trimmedHist.ZeroCount > 0 {
|
||||
zeroBucket := trimmedHist.ZeroBucket()
|
||||
zLower := zeroBucket.Lower
|
||||
zUpper := zeroBucket.Upper
|
||||
|
||||
switch op {
|
||||
case parser.TRIM_UPPER:
|
||||
switch {
|
||||
case rhs < zLower:
|
||||
trimmedHist.ZeroCount = 0
|
||||
case rhs > zUpper:
|
||||
updatedCount += trimmedHist.ZeroCount
|
||||
default:
|
||||
fraction := (rhs - zLower) / (zUpper - zLower)
|
||||
keepCount := trimmedHist.ZeroCount * fraction
|
||||
trimmedHist.ZeroCount = keepCount
|
||||
updatedCount += keepCount
|
||||
}
|
||||
|
||||
case parser.TRIM_LOWER:
|
||||
switch {
|
||||
case rhs > zUpper:
|
||||
trimmedHist.ZeroCount = 0
|
||||
case rhs < zLower:
|
||||
updatedCount += trimmedHist.ZeroCount
|
||||
default:
|
||||
fraction := (zUpper - rhs) / (zUpper - zLower)
|
||||
keepCount := trimmedHist.ZeroCount * fraction
|
||||
trimmedHist.ZeroCount = keepCount
|
||||
updatedCount += keepCount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply new sum
|
||||
newSum := origSum - removedSum
|
||||
|
||||
// Clamp correction
|
||||
if !hasNegative && newSum < 0 {
|
||||
newSum = 0
|
||||
}
|
||||
if !hasPositive && newSum > 0 {
|
||||
newSum = 0
|
||||
}
|
||||
|
||||
// Update the histogram's count and sum
|
||||
trimmedHist.Count = updatedCount
|
||||
trimmedHist.Sum = newSum
|
||||
|
||||
trimmedHist.Compact(0)
|
||||
}
|
||||
|
||||
func computeExponentialTrim(bucket histogram.Bucket[float64], rhs float64, isPositive bool, op parser.ItemType) (float64, float64) {
|
||||
var fraction, bucketMidpoint, keepCount float64
|
||||
|
||||
logLower := math.Log2(math.Abs(bucket.Lower))
|
||||
logUpper := math.Log2(math.Abs(bucket.Upper))
|
||||
logRHS := math.Log2(math.Abs(rhs))
|
||||
|
||||
switch op {
|
||||
case parser.TRIM_UPPER:
|
||||
if isPositive {
|
||||
fraction = (logRHS - logLower) / (logUpper - logLower)
|
||||
bucketMidpoint = math.Sqrt(bucket.Lower * rhs)
|
||||
} else {
|
||||
fraction = 1 - ((logRHS - logUpper) / (logLower - logUpper))
|
||||
bucketMidpoint = -math.Sqrt(math.Abs(bucket.Lower) * math.Abs(rhs))
|
||||
}
|
||||
|
||||
case parser.TRIM_LOWER:
|
||||
if isPositive {
|
||||
fraction = (logUpper - logRHS) / (logUpper - logLower)
|
||||
bucketMidpoint = math.Sqrt(rhs * bucket.Upper)
|
||||
} else {
|
||||
fraction = (logRHS - logUpper) / (logLower - logUpper)
|
||||
bucketMidpoint = -math.Sqrt(math.Abs(rhs) * math.Abs(bucket.Upper))
|
||||
}
|
||||
}
|
||||
|
||||
keepCount = bucket.Count * fraction
|
||||
|
||||
return keepCount, bucketMidpoint
|
||||
}
|
||||
|
||||
// vectorElemBinop evaluates a binary operation between two Vector elements.
|
||||
func vectorElemBinop(op parser.ItemType, lhs, rhs float64, hlhs, hrhs *histogram.FloatHistogram, pos posrange.PositionRange) (res float64, resH *histogram.FloatHistogram, keep bool, info, err error) {
|
||||
switch {
|
||||
|
|
@ -3172,6 +3467,8 @@ func vectorElemBinop(op parser.ItemType, lhs, rhs float64, hlhs, hrhs *histogram
|
|||
return lhs, nil, lhs <= rhs, nil, nil
|
||||
case parser.ATAN2:
|
||||
return math.Atan2(lhs, rhs), nil, true, nil, nil
|
||||
case parser.TRIM_LOWER, parser.TRIM_UPPER:
|
||||
return 0, nil, false, nil, annotations.NewIncompatibleTypesInBinOpInfo("float", parser.ItemTypeStr[op], "float", pos)
|
||||
}
|
||||
}
|
||||
case hlhs == nil && hrhs != nil:
|
||||
|
|
@ -3179,7 +3476,7 @@ func vectorElemBinop(op parser.ItemType, lhs, rhs float64, hlhs, hrhs *histogram
|
|||
switch op {
|
||||
case parser.MUL:
|
||||
return 0, hrhs.Copy().Mul(lhs).Compact(0), true, nil, nil
|
||||
case parser.ADD, parser.SUB, parser.DIV, parser.POW, parser.MOD, parser.EQLC, parser.NEQ, parser.GTR, parser.LSS, parser.GTE, parser.LTE, parser.ATAN2:
|
||||
case parser.ADD, parser.SUB, parser.DIV, parser.POW, parser.MOD, parser.EQLC, parser.NEQ, parser.GTR, parser.TRIM_LOWER, parser.TRIM_UPPER, parser.LSS, parser.GTE, parser.LTE, parser.ATAN2:
|
||||
return 0, nil, false, nil, annotations.NewIncompatibleTypesInBinOpInfo("float", parser.ItemTypeStr[op], "histogram", pos)
|
||||
}
|
||||
}
|
||||
|
|
@ -3190,6 +3487,14 @@ func vectorElemBinop(op parser.ItemType, lhs, rhs float64, hlhs, hrhs *histogram
|
|||
return 0, hlhs.Copy().Mul(rhs).Compact(0), true, nil, nil
|
||||
case parser.DIV:
|
||||
return 0, hlhs.Copy().Div(rhs).Compact(0), true, nil, nil
|
||||
case parser.TRIM_UPPER:
|
||||
trimmedHist := hlhs.Copy()
|
||||
trimHistogram(trimmedHist, rhs, op)
|
||||
return 0, trimmedHist, true, nil, nil
|
||||
case parser.TRIM_LOWER:
|
||||
trimmedHist := hlhs.Copy()
|
||||
trimHistogram(trimmedHist, rhs, op)
|
||||
return 0, trimmedHist, true, nil, nil
|
||||
case parser.ADD, parser.SUB, parser.POW, parser.MOD, parser.EQLC, parser.NEQ, parser.GTR, parser.LSS, parser.GTE, parser.LTE, parser.ATAN2:
|
||||
return 0, nil, false, nil, annotations.NewIncompatibleTypesInBinOpInfo("histogram", parser.ItemTypeStr[op], "float", pos)
|
||||
}
|
||||
|
|
@ -3230,7 +3535,7 @@ func vectorElemBinop(op parser.ItemType, lhs, rhs float64, hlhs, hrhs *histogram
|
|||
case parser.NEQ:
|
||||
// This operation expects that both histograms are compacted.
|
||||
return 0, hlhs, !hlhs.Equals(hrhs), nil, nil
|
||||
case parser.MUL, parser.DIV, parser.POW, parser.MOD, parser.GTR, parser.LSS, parser.GTE, parser.LTE, parser.ATAN2:
|
||||
case parser.MUL, parser.DIV, parser.POW, parser.MOD, parser.GTR, parser.LSS, parser.GTE, parser.LTE, parser.ATAN2, parser.TRIM_LOWER, parser.TRIM_UPPER:
|
||||
return 0, nil, false, nil, annotations.NewIncompatibleTypesInBinOpInfo("histogram", parser.ItemTypeStr[op], "histogram", pos)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,8 @@ EQLC
|
|||
EQL_REGEX
|
||||
GTE
|
||||
GTR
|
||||
TRIM_UPPER
|
||||
TRIM_LOWER
|
||||
LAND
|
||||
LOR
|
||||
LSS
|
||||
|
|
@ -200,7 +202,7 @@ START_METRIC_SELECTOR
|
|||
// Operators are listed with increasing precedence.
|
||||
%left LOR
|
||||
%left LAND LUNLESS
|
||||
%left EQLC GTE GTR LSS LTE NEQ
|
||||
%left EQLC GTE GTR LSS LTE NEQ TRIM_UPPER TRIM_LOWER
|
||||
%left ADD SUB
|
||||
%left MUL DIV MOD ATAN2
|
||||
%right POW
|
||||
|
|
@ -291,6 +293,8 @@ binary_expr : expr ADD bin_modifier expr { $$ = yylex.(*parser).newBinar
|
|||
| expr EQLC bin_modifier expr { $$ = yylex.(*parser).newBinaryExpression($1, $2, $3, $4) }
|
||||
| expr GTE bin_modifier expr { $$ = yylex.(*parser).newBinaryExpression($1, $2, $3, $4) }
|
||||
| expr GTR bin_modifier expr { $$ = yylex.(*parser).newBinaryExpression($1, $2, $3, $4) }
|
||||
| expr TRIM_UPPER bin_modifier expr { $$ = yylex.(*parser).newBinaryExpression($1, $2, $3, $4) }
|
||||
| expr TRIM_LOWER bin_modifier expr { $$ = yylex.(*parser).newBinaryExpression($1, $2, $3, $4) }
|
||||
| expr LAND bin_modifier expr { $$ = yylex.(*parser).newBinaryExpression($1, $2, $3, $4) }
|
||||
| expr LOR bin_modifier expr { $$ = yylex.(*parser).newBinaryExpression($1, $2, $3, $4) }
|
||||
| expr LSS bin_modifier expr { $$ = yylex.(*parser).newBinaryExpression($1, $2, $3, $4) }
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -189,21 +189,23 @@ var ItemTypeStr = map[ItemType]string{
|
|||
TIMES: "x",
|
||||
SPACE: "<space>",
|
||||
|
||||
SUB: "-",
|
||||
ADD: "+",
|
||||
MUL: "*",
|
||||
MOD: "%",
|
||||
DIV: "/",
|
||||
EQLC: "==",
|
||||
NEQ: "!=",
|
||||
LTE: "<=",
|
||||
LSS: "<",
|
||||
GTE: ">=",
|
||||
GTR: ">",
|
||||
EQL_REGEX: "=~",
|
||||
NEQ_REGEX: "!~",
|
||||
POW: "^",
|
||||
AT: "@",
|
||||
SUB: "-",
|
||||
ADD: "+",
|
||||
MUL: "*",
|
||||
MOD: "%",
|
||||
DIV: "/",
|
||||
EQLC: "==",
|
||||
NEQ: "!=",
|
||||
LTE: "<=",
|
||||
LSS: "<",
|
||||
GTE: ">=",
|
||||
GTR: ">",
|
||||
TRIM_UPPER: "</",
|
||||
TRIM_LOWER: ">/",
|
||||
EQL_REGEX: "=~",
|
||||
NEQ_REGEX: "!~",
|
||||
POW: "^",
|
||||
AT: "@",
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
@ -446,6 +448,9 @@ func lexStatements(l *Lexer) stateFn {
|
|||
if t := l.peek(); t == '=' {
|
||||
l.next()
|
||||
l.emit(LTE)
|
||||
} else if t := l.peek(); t == '/' {
|
||||
l.next()
|
||||
l.emit(TRIM_UPPER)
|
||||
} else {
|
||||
l.emit(LSS)
|
||||
}
|
||||
|
|
@ -453,6 +458,9 @@ func lexStatements(l *Lexer) stateFn {
|
|||
if t := l.peek(); t == '=' {
|
||||
l.next()
|
||||
l.emit(GTE)
|
||||
} else if t := l.peek(); t == '/' {
|
||||
l.next()
|
||||
l.emit(TRIM_LOWER)
|
||||
} else {
|
||||
l.emit(GTR)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1872,3 +1872,58 @@ eval instant at 1m irate(nhcb_add_bucket[2m]) * 60
|
|||
expect no_warn
|
||||
expect no_info
|
||||
{} {{schema:-53 sum:56 count:15 custom_values:[2 3 4 6] buckets:[1 0 1 5 8] counter_reset_hint:gauge}}
|
||||
|
||||
# Test native histogram with trim operators ("</": TRIM_UPPER, ">/": TRIM_LOWER)
|
||||
load 1m
|
||||
h_test {{schema:0 sum:123.75 count:34 z_bucket:1 z_bucket_w:0.001 buckets:[2 4 8 16] n_buckets:[1 2]}}
|
||||
h_test_2 {{schema:2 sum:12.8286080906 count:28 z_bucket:1 z_bucket_w:0.001 buckets:[1 2 4 7 3] n_buckets:[1 5 3 1]}}
|
||||
cbh {{schema:-53 sum:172.5 count:15 custom_values:[5 10 15 20] buckets:[1 6 4 3 1]}}
|
||||
zero_bucket {{schema:0 sum:-6.75 z_bucket:5 z_bucket_w:0.01 buckets:[2 3] n_buckets:[1 2 3]}}
|
||||
|
||||
# Native Histogram: Exponential Bucket Interpolation Tests
|
||||
eval instant at 1m h_test_2 </ 1.13
|
||||
{__name__="h_test_2"} {{schema:2 count:13.410582181123704 sum:-9.282809901015558 z_bucket:1 z_bucket_w:0.001 buckets:[1 1.410582181123704] n_buckets:[1 5 3 1]}}
|
||||
|
||||
eval instant at 1m h_test_2 >/ 1.13
|
||||
{__name__="h_test_2"} {{schema:2 count:14.589417818876296 sum:-1.5258511531197865 z_bucket_w:0.001 offset:1 buckets:[0.589417818876296 4 7 3]}}
|
||||
|
||||
eval instant at 1m h_test_2 >/ -1.3
|
||||
{__name__="h_test_2"} {{schema:2 count:25.54213947904476 sum:13.099057472672072 z_bucket:1 z_bucket_w:0.001 buckets:[1 2 4 7 3] n_buckets:[1 5 1.54213947904476]}}
|
||||
|
||||
eval instant at 1m h_test_2 </ -1.3
|
||||
{__name__="h_test_2"} {{schema:2 count:2.45786052095524 sum:-16.03281816946792 z_bucket_w:0.001 n_offset:2 n_buckets:[1.45786052095524 1]}}
|
||||
|
||||
# Native Histogram: Linear Bucket Trimming Tests
|
||||
eval instant at 1m h_test </ 2
|
||||
{__name__="h_test"} {{count:10 sum:10.612915010152392 z_bucket:1 z_bucket_w:0.001 buckets:[2 4] n_buckets:[1 2]}}
|
||||
|
||||
eval instant at 1m h_test >/ 2
|
||||
{__name__="h_test"} {{count:24 sum:113.14339828220179 z_bucket_w:0.001 offset:2 buckets:[8 16]}}
|
||||
|
||||
eval instant at 1m h_test >/ -1
|
||||
{__name__="h_test"} {{count:32 sum:120.92157287525382 z_bucket:1 z_bucket_w:0.001 buckets:[2 4 8 16] n_buckets:[1]}}
|
||||
|
||||
eval instant at 1m h_test </ -1
|
||||
{__name__="h_test"} {{count:2 sum:2.834740417100363 z_bucket_w:0.001 n_offset:1 n_buckets:[2]}}
|
||||
|
||||
# Custom Buckets: Trim Operation Tests
|
||||
eval instant at 1m cbh </ 13
|
||||
{__name__="cbh"} {{schema:-53 count:9.4 sum:80 custom_values:[5 10 15 20] buckets:[1 6 2.4]}}
|
||||
|
||||
eval instant at 1m cbh >/ 13
|
||||
{__name__="cbh"} {{schema:-53 count:5.6 sum:92.5 custom_values:[5 10 15 20] offset:2 buckets:[1.6 3 1]}}
|
||||
|
||||
eval instant at 1m cbh </ 15
|
||||
{__name__="cbh"} {{schema:-53 count:11 sum:100 custom_values:[5 10 15 20] buckets:[1 6 4]}}
|
||||
|
||||
eval instant at 1m cbh >/ 15
|
||||
{__name__="cbh"} {{schema:-53 count:4 sum:72.5 custom_values:[5 10 15 20] offset:3 buckets:[3 1]}}
|
||||
|
||||
# Zero Bucket Edge Case: Interpolation Around Zero
|
||||
eval instant at 1m zero_bucket </ -0.005
|
||||
{__name__="zero_bucket"} {{count:7.25 sum:-12.40685424949238 z_bucket:1.25 z_bucket_w:0.01 n_buckets:[1 2 3]}}
|
||||
|
||||
eval instant at 1m zero_bucket >/ 0
|
||||
{__name__="zero_bucket"} {{count:7.5 sum:-18.77081528017131 z_bucket:2.5 z_bucket_w:0.01 buckets:[2 3]}}
|
||||
|
||||
clear
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ export enum binaryOperatorType {
|
|||
neq = "!=",
|
||||
gtr = ">",
|
||||
lss = "<",
|
||||
trimUpper = "</",
|
||||
trimLower = ">/",
|
||||
gte = ">=",
|
||||
lte = "<=",
|
||||
and = "and",
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ const binOpPrecedence = {
|
|||
[binaryOperatorType.lss]: 4,
|
||||
[binaryOperatorType.gte]: 4,
|
||||
[binaryOperatorType.lte]: 4,
|
||||
[binaryOperatorType.trimLower]: 4,
|
||||
[binaryOperatorType.trimUpper]: 4,
|
||||
[binaryOperatorType.and]: 5,
|
||||
[binaryOperatorType.or]: 6,
|
||||
[binaryOperatorType.unless]: 5,
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@ import {
|
|||
LimitK,
|
||||
LimitRatio,
|
||||
CountValues,
|
||||
TrimLower,
|
||||
TrimUpper,
|
||||
} from '@prometheus-io/lezer-promql';
|
||||
import { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete';
|
||||
import { EditorState } from '@codemirror/state';
|
||||
|
|
@ -408,7 +410,7 @@ export function analyzeCompletion(state: EditorState, node: SyntaxNode, pos: num
|
|||
// BinaryExpr( ..., Gtr , ... )
|
||||
// When the `bool` keyword is present, then the expression looks like this:
|
||||
// BinaryExpr( ..., Gtr , BoolModifier(...), ... )
|
||||
if (containsAtLeastOneChild(parent, Eql, Gte, Gtr, Lte, Lss, Neq) && !containsAtLeastOneChild(parent, BoolModifier)) {
|
||||
if (containsAtLeastOneChild(parent, Eql, Gte, Gtr, TrimLower, TrimUpper, Lte, Lss, Neq) && !containsAtLeastOneChild(parent, BoolModifier)) {
|
||||
result.push({ kind: ContextKind.Bool });
|
||||
}
|
||||
}
|
||||
|
|
@ -579,6 +581,8 @@ export function analyzeCompletion(state: EditorState, node: SyntaxNode, pos: num
|
|||
case Eql:
|
||||
case Gte:
|
||||
case Gtr:
|
||||
case TrimLower:
|
||||
case TrimUpper:
|
||||
case Lte:
|
||||
case Lss:
|
||||
case And:
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ export const binOpTerms = [
|
|||
{ label: '>=' },
|
||||
{ label: '>' },
|
||||
{ label: '<' },
|
||||
{ label: '</' },
|
||||
{ label: '>/' },
|
||||
{ label: '<=' },
|
||||
{ label: '!=' },
|
||||
{ label: 'atan2' },
|
||||
|
|
|
|||
|
|
@ -49,12 +49,14 @@ import {
|
|||
StepInvariantExpr,
|
||||
SubqueryExpr,
|
||||
Topk,
|
||||
TrimLower,
|
||||
TrimUpper,
|
||||
UnaryExpr,
|
||||
Unless,
|
||||
UnquotedLabelMatcher,
|
||||
VectorSelector,
|
||||
} from '@prometheus-io/lezer-promql';
|
||||
import { containsAtLeastOneChild } from './path-finder';
|
||||
import { containsAtLeastOneChild, containsChild } from './path-finder';
|
||||
import { getType } from './type';
|
||||
import { buildLabelMatchers } from './matcher';
|
||||
import { EditorState } from '@codemirror/state';
|
||||
|
|
@ -214,17 +216,25 @@ export class Parser {
|
|||
const lt = this.checkAST(lExpr);
|
||||
const rt = this.checkAST(rExpr);
|
||||
const boolModifierUsed = node.getChild(BoolModifier);
|
||||
const isComparisonOperator = containsAtLeastOneChild(node, Eql, Neq, Lte, Lss, Gte, Gtr);
|
||||
|
||||
const isComparisonOperator = containsAtLeastOneChild(node, Eql, Neq, Lte, Lss, Gte, Gtr, TrimLower, TrimUpper);
|
||||
const isTrimLowerOperator = containsChild(node, TrimLower);
|
||||
const isTrimUpperOperator = containsChild(node, TrimUpper);
|
||||
const isSetOperator = containsAtLeastOneChild(node, And, Or, Unless);
|
||||
|
||||
// BOOL modifier check
|
||||
if (boolModifierUsed) {
|
||||
if (!isComparisonOperator) {
|
||||
if (!isComparisonOperator || isTrimLowerOperator || isTrimUpperOperator) {
|
||||
this.addDiagnostic(node, 'bool modifier can only be used on comparison operators');
|
||||
}
|
||||
} else {
|
||||
if (isComparisonOperator && lt === ValueType.scalar && rt === ValueType.scalar) {
|
||||
this.addDiagnostic(node, 'comparisons between scalars must use BOOL modifier');
|
||||
if (isTrimLowerOperator) {
|
||||
this.addDiagnostic(node, 'operator ">/" not allowed for Scalar operations');
|
||||
} else if (isTrimUpperOperator) {
|
||||
this.addDiagnostic(node, 'operator "</" not allowed for Scalar operations');
|
||||
} else {
|
||||
this.addDiagnostic(node, 'comparisons between scalars must use BOOL modifier');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -88,6 +88,8 @@ BinaryExpr {
|
|||
expr !eql Eql binModifiers expr |
|
||||
expr !eql Gte binModifiers expr |
|
||||
expr !eql Gtr binModifiers expr |
|
||||
expr !eql TrimUpper binModifiers expr |
|
||||
expr !eql TrimLower binModifiers expr |
|
||||
expr !eql Lte binModifiers expr |
|
||||
expr !eql Lss binModifiers expr |
|
||||
expr !eql Neq binModifiers expr |
|
||||
|
|
@ -337,6 +339,8 @@ NumberDurationLiteralInDurationContext {
|
|||
Lss { "<" }
|
||||
Gte { ">=" }
|
||||
Gtr { ">" }
|
||||
TrimUpper { "</" }
|
||||
TrimLower { ">/" }
|
||||
EqlRegex { "=~" }
|
||||
EqlSingle { "=" }
|
||||
NeqRegex { "!~" }
|
||||
|
|
|
|||
|
|
@ -716,3 +716,31 @@ rate(caddy_http_requests_total[5m] smoothed)
|
|||
|
||||
==>
|
||||
PromQL(FunctionCall(FunctionIdentifier(Rate),FunctionCallBody(SmoothedExpr(MatrixSelector(VectorSelector(Identifier),NumberDurationLiteralInDurationContext),Smoothed))))
|
||||
|
||||
# TrimUpper binary operator
|
||||
|
||||
metric1 </ metric2
|
||||
|
||||
==>
|
||||
|
||||
PromQL(
|
||||
BinaryExpr(
|
||||
VectorSelector(Identifier),
|
||||
TrimUpper,
|
||||
VectorSelector(Identifier)
|
||||
)
|
||||
)
|
||||
|
||||
# TrimLower binary operator
|
||||
|
||||
metric1 >/ metric2
|
||||
|
||||
==>
|
||||
|
||||
PromQL(
|
||||
BinaryExpr(
|
||||
VectorSelector(Identifier),
|
||||
TrimLower,
|
||||
VectorSelector(Identifier)
|
||||
)
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue