[FEATURE] PromQL: Implements idelta and irate with histograms (#15853)
Some checks are pending
buf.build / lint and publish (push) Waiting to run
CI / Go tests (push) Waiting to run
CI / More Go tests (push) Waiting to run
CI / Go tests with previous Go version (push) Waiting to run
CI / UI tests (push) Waiting to run
CI / Go tests on Windows (push) Waiting to run
CI / Mixins tests (push) Waiting to run
CI / Build Prometheus for common architectures (0) (push) Waiting to run
CI / Build Prometheus for common architectures (1) (push) Waiting to run
CI / Build Prometheus for common architectures (2) (push) Waiting to run
CI / Build Prometheus for all architectures (0) (push) Waiting to run
CI / Build Prometheus for all architectures (1) (push) Waiting to run
CI / Build Prometheus for all architectures (10) (push) Waiting to run
CI / Build Prometheus for all architectures (11) (push) Waiting to run
CI / Build Prometheus for all architectures (2) (push) Waiting to run
CI / Build Prometheus for all architectures (3) (push) Waiting to run
CI / Build Prometheus for all architectures (4) (push) Waiting to run
CI / Build Prometheus for all architectures (5) (push) Waiting to run
CI / Build Prometheus for all architectures (6) (push) Waiting to run
CI / Build Prometheus for all architectures (7) (push) Waiting to run
CI / Build Prometheus for all architectures (8) (push) Waiting to run
CI / Build Prometheus for all architectures (9) (push) Waiting to run
CI / Report status of build Prometheus for all architectures (push) Blocked by required conditions
CI / Check generated parser (push) Waiting to run
CI / golangci-lint (push) Waiting to run
CI / fuzzing (push) Waiting to run
CI / codeql (push) Waiting to run
CI / Publish main branch artifacts (push) Blocked by required conditions
CI / Publish release artefacts (push) Blocked by required conditions
CI / Publish UI on npm Registry (push) Blocked by required conditions
Scorecards supply-chain security / Scorecards analysis (push) Waiting to run

Add native histogram support to idelta and irate functions

---------

Signed-off-by: Neeraj Gartia <neerajgartia211002@gmail.com>
This commit is contained in:
Neeraj Gartia 2025-02-01 03:41:03 +05:30 committed by GitHub
parent cb3b17a14c
commit 130cd024e0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 142 additions and 17 deletions

View file

@ -286,46 +286,115 @@ func funcIncrease(vals []parser.Value, args parser.Expressions, enh *EvalNodeHel
// === irate(node parser.ValueTypeMatrix) (Vector, Annotations) ===
func funcIrate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
return instantValue(vals, enh.Out, true)
return instantValue(vals, args, enh.Out, true)
}
// === idelta(node model.ValMatrix) (Vector, Annotations) ===
func funcIdelta(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
return instantValue(vals, enh.Out, false)
return instantValue(vals, args, enh.Out, false)
}
func instantValue(vals []parser.Value, out Vector, isRate bool) (Vector, annotations.Annotations) {
samples := vals[0].(Matrix)[0]
func instantValue(vals []parser.Value, args parser.Expressions, out Vector, isRate bool) (Vector, annotations.Annotations) {
var (
samples = vals[0].(Matrix)[0]
metricName = samples.Metric.Get(labels.MetricName)
ss = make([]Sample, 0, 2)
annos annotations.Annotations
)
// No sense in trying to compute a rate without at least two points. Drop
// this Vector element.
// TODO: add RangeTooShortWarning
if len(samples.Floats) < 2 {
if len(samples.Floats)+len(samples.Histograms) < 2 {
return out, nil
}
lastSample := samples.Floats[len(samples.Floats)-1]
previousSample := samples.Floats[len(samples.Floats)-2]
var resultValue float64
if isRate && lastSample.F < previousSample.F {
// Counter reset.
resultValue = lastSample.F
} else {
resultValue = lastSample.F - previousSample.F
// Add the last 2 float samples if they exist.
for i := max(0, len(samples.Floats)-2); i < len(samples.Floats); i++ {
ss = append(ss, Sample{
F: samples.Floats[i].F,
T: samples.Floats[i].T,
})
}
sampledInterval := lastSample.T - previousSample.T
// Add the last 2 histogram samples into their correct position if they exist.
for i := max(0, len(samples.Histograms)-2); i < len(samples.Histograms); i++ {
s := Sample{
H: samples.Histograms[i].H,
T: samples.Histograms[i].T,
}
switch {
case len(ss) == 0:
ss = append(ss, s)
case len(ss) == 1:
if s.T < ss[0].T {
ss = append([]Sample{s}, ss...)
} else {
ss = append(ss, s)
}
case s.T < ss[0].T:
// s is older than 1st, so discard it.
case s.T > ss[1].T:
// s is newest, so add it as 2nd and make the old 2nd the new 1st.
ss[0] = ss[1]
ss[1] = s
default:
// In all other cases, we just make s the new 1st.
// This establishes a correct order, even in the (irregular)
// case of equal timestamps.
ss[0] = s
}
}
resultSample := ss[1]
sampledInterval := ss[1].T - ss[0].T
if sampledInterval == 0 {
// Avoid dividing by 0.
return out, nil
}
switch {
case ss[1].H == nil && ss[0].H == nil:
if !isRate || ss[1].F >= ss[0].F {
// Gauge or counter without reset.
resultSample.F = ss[1].F - ss[0].F
}
// In case of a counter reset, we leave resultSample at
// its current value, which is already ss[1].
case ss[1].H != nil && ss[0].H != nil:
resultSample.H = ss[1].H.Copy()
// irate should only be applied to counters.
if isRate && (ss[1].H.CounterResetHint == histogram.GaugeType || ss[0].H.CounterResetHint == histogram.GaugeType) {
annos.Add(annotations.NewNativeHistogramNotCounterWarning(metricName, args.PositionRange()))
}
// idelta should only be applied to gauges.
if !isRate && (ss[1].H.CounterResetHint != histogram.GaugeType || ss[0].H.CounterResetHint != histogram.GaugeType) {
annos.Add(annotations.NewNativeHistogramNotGaugeWarning(metricName, args.PositionRange()))
}
if !isRate || !ss[1].H.DetectReset(ss[0].H) {
_, err := resultSample.H.Sub(ss[0].H)
if errors.Is(err, histogram.ErrHistogramsIncompatibleSchema) {
return out, annos.Add(annotations.NewMixedExponentialCustomHistogramsWarning(metricName, args.PositionRange()))
} else if errors.Is(err, histogram.ErrHistogramsIncompatibleBounds) {
return out, annos.Add(annotations.NewIncompatibleCustomBucketsHistogramsWarning(metricName, args.PositionRange()))
}
}
resultSample.H.CounterResetHint = histogram.GaugeType
resultSample.H.Compact(0)
default:
// Mix of a float and a histogram.
return out, annos.Add(annotations.NewMixedFloatsHistogramsWarning(metricName, args.PositionRange()))
}
if isRate {
// Convert to per-second.
resultValue /= float64(sampledInterval) / 1000
if resultSample.H == nil {
resultSample.F /= float64(sampledInterval) / 1000
} else {
resultSample.H.Div(float64(sampledInterval) / 1000)
}
}
return append(out, Sample{F: resultValue}), nil
return append(out, resultSample), annos
}
// Calculate the trend value at the given index i in raw data d.

View file

@ -218,6 +218,13 @@ clear
load 5m
http_requests_total{path="/foo"} 0+10x10
http_requests_total{path="/bar"} 0+10x5 0+10x5
http_requests_histogram{path="/a"} {{sum:2 count:2}}+{{sum:3 count:3}}x5
http_requests_histogram{path="/b"} 0 0 {{sum:1 count:1}} {{sum:4 count:4}}
http_requests_histogram{path="/c"} 0 0 {{sum:1 count:1}} {{sum:4 count:4 counter_reset_hint:gauge}}
http_requests_histogram{path="/d"} 0 0 {{sum:1 count:1 counter_reset_hint:gauge}} {{sum:4 count:4}}
http_requests_histogram{path="/e"} 0 1 2 {{sum:4 count:4}}
http_requests_histogram{path="/f"} 0 0 {{sum:1 count:1}} {{schema:-53 sum:3 count:3 custom_values:[5 10] buckets:[3]}}
http_requests_histogram{path="/g"} 0 0 {{schema:-53 sum:3 count:3 custom_values:[1] buckets:[3]}} {{schema:-53 sum:3 count:3 custom_values:[5 10] buckets:[3]}}
eval instant at 50m irate(http_requests_total[50m])
{path="/foo"} .03333333333333333333
@ -228,6 +235,28 @@ eval instant at 30m irate(http_requests_total[50m])
{path="/foo"} .03333333333333333333
{path="/bar"} 0
eval instant at 20m irate(http_requests_histogram{path="/a"}[20m])
{path="/a"} {{sum:0.01 count:0.01 counter_reset_hint:gauge}}
eval instant at 20m irate(http_requests_histogram{path="/b"}[20m])
{path="/b"} {{sum:0.01 count:0.01 counter_reset_hint:gauge}}
eval instant at 20m irate(http_requests_histogram{path="/b"}[6m])
eval_warn instant at 20m irate(http_requests_histogram{path="/c"}[20m])
{path="/c"} {{sum:0.01 count:0.01 counter_reset_hint:gauge}}
eval_warn instant at 20m irate(http_requests_histogram{path="/d"}[20m])
{path="/d"} {{sum:0.01 count:0.01 counter_reset_hint:gauge}}
eval_warn instant at 20m irate(http_requests_histogram{path="/e"}[20m])
eval instant at 20m irate(http_requests_histogram{path="/f"}[20m])
{path="/f"} {{schema:-53 sum:0.01 count:0.01 custom_values:[5 10] buckets:[0.01]}}
eval instant at 20m irate(http_requests_histogram{path="/g"}[20m])
{path="/g"} {{schema:-53 sum:0.01 count:0.01 custom_values:[5 10] buckets:[0.01]}}
clear
# Tests for delta().
@ -259,11 +288,38 @@ clear
load 5m
http_requests{path="/foo"} 0 50 100 150
http_requests{path="/bar"} 0 50 100 50
http_requests_histogram{path="/a"} {{sum:2 count:2 counter_reset_hint:gauge}}+{{sum:1 count:3 counter_reset_hint:gauge}}x5
http_requests_histogram{path="/b"} 0 0 {{sum:1 count:1 counter_reset_hint:gauge}} {{sum:2 count:2 counter_reset_hint:gauge}}
http_requests_histogram{path="/c"} 0 0 {{sum:1 count:1}} {{sum:2 count:2 counter_reset_hint:gauge}}
http_requests_histogram{path="/d"} 0 0 {{sum:1 count:1 counter_reset_hint:gauge}} {{sum:2 count:2}}
http_requests_histogram{path="/e"} 0 1 2 {{sum:1 count:2 counter_reset_hint:gauge}}
http_requests_histogram{path="/f"} 0 0 {{sum:1 count:1 counter_reset_hint:gauge}} {{schema:-53 sum:1 count:1 custom_values:[5 10] buckets:[1] counter_reset_hint:gauge}}
http_requests_histogram{path="/g"} 0 0 {{schema:-53 sum:1 count:1 custom_values:[1] buckets:[2] counter_reset_hint:gauge}} {{schema:-53 sum:1 count:1 custom_values:[5 10] buckets:[1] counter_reset_hint:gauge}}
eval instant at 20m idelta(http_requests[20m])
{path="/foo"} 50
{path="/bar"} -50
eval instant at 20m idelta(http_requests_histogram{path="/a"}[20m])
{path="/a"} {{sum:1 count:3 counter_reset_hint:gauge}}
eval instant at 20m idelta(http_requests_histogram{path="/b"}[20m])
{path="/b"} {{sum:1 count:1 counter_reset_hint:gauge}}
eval instant at 20m idelta(http_requests_histogram{path="/b"}[6m])
eval_warn instant at 20m idelta(http_requests_histogram{path="/c"}[20m])
{path="/c"} {{sum:1 count:1 counter_reset_hint:gauge}}
eval_warn instant at 20m idelta(http_requests_histogram{path="/d"}[20m])
{path="/d"} {{sum:1 count:1 counter_reset_hint:gauge}}
eval_warn instant at 20m idelta(http_requests_histogram{path="/e"}[20m])
eval_warn instant at 20m idelta(http_requests_histogram{path="/f"}[20m])
eval_warn instant at 20m idelta(http_requests_histogram{path="/g"}[20m])
clear
# Tests for deriv() and predict_linear().