mirror of
https://github.com/prometheus/prometheus.git
synced 2026-02-03 20:39:32 -05:00
PromQL: Add histogram_quantiles variadic function
Signed-off-by: Linas Medziunas <linas.medziunas@gmail.com>
This commit is contained in:
parent
f4b8840f51
commit
dd4783e6c2
5 changed files with 226 additions and 4 deletions
|
|
@ -1159,6 +1159,9 @@ type EvalNodeHelper struct {
|
|||
// funcHistogramQuantile and funcHistogramFraction for classic histograms.
|
||||
signatureToMetricWithBuckets map[string]*metricWithBuckets
|
||||
nativeHistogramSamples []Sample
|
||||
// funcHistogramQuantiles for histograms.
|
||||
quantileStrs map[float64]string
|
||||
signatureToLabelsWithQuantile map[string]map[float64]labels.Labels
|
||||
|
||||
lb *labels.Builder
|
||||
lblBuf []byte
|
||||
|
|
@ -1246,6 +1249,35 @@ func (enh *EvalNodeHelper) resetHistograms(inVec Vector, arg parser.Expr) annota
|
|||
return annos
|
||||
}
|
||||
|
||||
func (enh *EvalNodeHelper) getOrCreateLblsWithQuantile(lbls labels.Labels, quantileLabel string, q float64) labels.Labels {
|
||||
if enh.signatureToLabelsWithQuantile == nil {
|
||||
enh.signatureToLabelsWithQuantile = make(map[string]map[float64]labels.Labels)
|
||||
}
|
||||
|
||||
enh.lblBuf = lbls.Bytes(enh.lblBuf)
|
||||
cachedLbls, ok := enh.signatureToLabelsWithQuantile[string(enh.lblBuf)]
|
||||
if !ok {
|
||||
cachedLbls = make(map[float64]labels.Labels, len(enh.quantileStrs))
|
||||
enh.signatureToLabelsWithQuantile[string(enh.lblBuf)] = cachedLbls
|
||||
}
|
||||
|
||||
cachedLblsWithQuantile, ok := cachedLbls[q]
|
||||
if !ok {
|
||||
quantileStr := "NaN"
|
||||
if !math.IsNaN(q) {
|
||||
// Cannot do map lookup by NaN key.
|
||||
quantileStr = enh.quantileStrs[q]
|
||||
}
|
||||
cachedLblsWithQuantile = labels.NewBuilder(lbls).
|
||||
Set(quantileLabel, quantileStr).
|
||||
Labels()
|
||||
|
||||
cachedLbls[q] = cachedLblsWithQuantile
|
||||
}
|
||||
|
||||
return cachedLblsWithQuantile
|
||||
}
|
||||
|
||||
// rangeEval evaluates the given expressions, and then for each step calls
|
||||
// the given funcCall with the values computed for each expression at that
|
||||
// step. The return value is the combination into time series of all the
|
||||
|
|
@ -4072,7 +4104,7 @@ func detectHistogramStatsDecoding(expr parser.Expr) {
|
|||
// further up (the latter wouldn't make sense,
|
||||
// but no harm in detecting it).
|
||||
n.SkipHistogramBuckets = true
|
||||
case "histogram_quantile", "histogram_fraction":
|
||||
case "histogram_quantile", "histogram_quantiles", "histogram_fraction":
|
||||
// If we ever see a function that needs the
|
||||
// whole histogram, we will not skip the
|
||||
// buckets.
|
||||
|
|
|
|||
|
|
@ -1612,8 +1612,8 @@ func funcHistogramQuantile(vectorVals []Vector, _ Matrix, args parser.Expression
|
|||
inVec := vectorVals[1]
|
||||
var annos annotations.Annotations
|
||||
|
||||
if math.IsNaN(q) || q < 0 || q > 1 {
|
||||
annos.Add(annotations.NewInvalidQuantileWarning(q, args[0].PositionRange()))
|
||||
if err := validateQuantile(q, args[0]); err != nil {
|
||||
annos.Add(err)
|
||||
}
|
||||
annos.Merge(enh.resetHistograms(inVec, args[1]))
|
||||
|
||||
|
|
@ -1662,6 +1662,89 @@ func funcHistogramQuantile(vectorVals []Vector, _ Matrix, args parser.Expression
|
|||
return enh.Out, annos
|
||||
}
|
||||
|
||||
func validateQuantile(q float64, arg parser.Expr) error {
|
||||
if math.IsNaN(q) || q < 0 || q > 1 {
|
||||
return annotations.NewInvalidQuantileWarning(q, arg.PositionRange())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// === histogram_quantiles(Vector parser.ValueTypeVector, label parser.ValueTypeString, q0 parser.ValueTypeScalar, qs parser.ValueTypeScalar...) (Vector, Annotations) ===
|
||||
func funcHistogramQuantiles(vectorVals []Vector, _ Matrix, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
|
||||
var (
|
||||
inVec = vectorVals[0]
|
||||
quantileLabel = args[1].(*parser.StringLiteral).Val
|
||||
numQuantiles = len(vectorVals[2:])
|
||||
qs = make([]float64, 0, numQuantiles)
|
||||
|
||||
annos annotations.Annotations
|
||||
)
|
||||
|
||||
if enh.quantileStrs == nil {
|
||||
enh.quantileStrs = make(map[float64]string, numQuantiles)
|
||||
}
|
||||
for i := 2; i < len(vectorVals); i++ {
|
||||
q := vectorVals[i][0].F
|
||||
|
||||
if err := validateQuantile(q, args[i]); err != nil {
|
||||
annos.Add(err)
|
||||
}
|
||||
|
||||
if _, ok := enh.quantileStrs[q]; !ok {
|
||||
enh.quantileStrs[q] = fmt.Sprintf("%g", q)
|
||||
}
|
||||
qs = append(qs, q)
|
||||
}
|
||||
|
||||
annos.Merge(enh.resetHistograms(inVec, args[0]))
|
||||
|
||||
for _, q := range qs {
|
||||
// Deal with the native histograms.
|
||||
for _, sample := range enh.nativeHistogramSamples {
|
||||
if sample.H == nil {
|
||||
// Native histogram conflicts with classic histogram at the same timestamp, ignore.
|
||||
continue
|
||||
}
|
||||
if !enh.enableDelayedNameRemoval {
|
||||
sample.Metric = sample.Metric.DropReserved(schema.IsMetadataLabel)
|
||||
}
|
||||
hq, hqAnnos := HistogramQuantile(q, sample.H, sample.Metric.Get(model.MetricNameLabel), args[0].PositionRange())
|
||||
annos.Merge(hqAnnos)
|
||||
enh.Out = append(enh.Out, Sample{
|
||||
Metric: enh.getOrCreateLblsWithQuantile(sample.Metric, quantileLabel, q),
|
||||
F: hq,
|
||||
DropName: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Deal with classic histograms that have already been filtered for conflicting native histograms.
|
||||
for _, mb := range enh.signatureToMetricWithBuckets {
|
||||
if len(mb.buckets) > 0 {
|
||||
hq, forcedMonotonicity, _ := BucketQuantile(q, mb.buckets)
|
||||
if forcedMonotonicity {
|
||||
if enh.enableDelayedNameRemoval {
|
||||
annos.Add(annotations.NewHistogramQuantileForcedMonotonicityInfo(mb.metric.Get(labels.MetricName), args[0].PositionRange()))
|
||||
} else {
|
||||
annos.Add(annotations.NewHistogramQuantileForcedMonotonicityInfo("", args[0].PositionRange()))
|
||||
}
|
||||
}
|
||||
|
||||
if !enh.enableDelayedNameRemoval {
|
||||
mb.metric = mb.metric.DropReserved(schema.IsMetadataLabel)
|
||||
}
|
||||
|
||||
enh.Out = append(enh.Out, Sample{
|
||||
Metric: enh.getOrCreateLblsWithQuantile(mb.metric, quantileLabel, q),
|
||||
F: hq,
|
||||
DropName: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return enh.Out, annos
|
||||
}
|
||||
|
||||
// pickFirstSampleIndex returns the index of the last sample before
|
||||
// or at the range start, or 0 if none exist before the range start.
|
||||
// If the vector selector is not anchored, it always returns 0.
|
||||
|
|
@ -1982,6 +2065,7 @@ var FunctionCalls = map[string]FunctionCall{
|
|||
"histogram_count": funcHistogramCount,
|
||||
"histogram_fraction": funcHistogramFraction,
|
||||
"histogram_quantile": funcHistogramQuantile,
|
||||
"histogram_quantiles": funcHistogramQuantiles,
|
||||
"histogram_sum": funcHistogramSum,
|
||||
"histogram_stddev": funcHistogramStdDev,
|
||||
"histogram_stdvar": funcHistogramStdVar,
|
||||
|
|
|
|||
|
|
@ -208,6 +208,13 @@ var Functions = map[string]*Function{
|
|||
ArgTypes: []ValueType{ValueTypeScalar, ValueTypeVector},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"histogram_quantiles": {
|
||||
Name: "histogram_quantiles",
|
||||
ArgTypes: []ValueType{ValueTypeVector, ValueTypeString, ValueTypeScalar, ValueTypeScalar},
|
||||
Variadic: 10,
|
||||
ReturnType: ValueTypeVector,
|
||||
Experimental: true,
|
||||
},
|
||||
"double_exponential_smoothing": {
|
||||
Name: "double_exponential_smoothing",
|
||||
ArgTypes: []ValueType{ValueTypeMatrix, ValueTypeScalar, ValueTypeScalar},
|
||||
|
|
|
|||
86
promql/promqltest/testdata/histograms.test
vendored
86
promql/promqltest/testdata/histograms.test
vendored
|
|
@ -221,6 +221,40 @@ eval instant at 50m histogram_quantile(1, testhistogram3_bucket)
|
|||
{start="positive"} 1
|
||||
{start="negative"} -0.1
|
||||
|
||||
eval instant at 50m histogram_quantiles(testhistogram3, "q", 0, 0.25, 0.5, 0.75, 1)
|
||||
expect no_warn
|
||||
{q="0", start="positive"} 0
|
||||
{q="0", start="negative"} -0.25
|
||||
{q="0.25", start="positive"} 0.055
|
||||
{q="0.25", start="negative"} -0.225
|
||||
{q="0.5", start="positive"} 0.125
|
||||
{q="0.5", start="negative"} -0.2
|
||||
{q="0.75", start="positive"} 0.45
|
||||
{q="0.75", start="negative"} -0.15
|
||||
{q="1", start="positive"} 1
|
||||
{q="1", start="negative"} -0.1
|
||||
|
||||
eval instant at 50m histogram_quantiles(testhistogram3_bucket, "q", 0, 0.25, 0.5, 0.75, 1)
|
||||
expect no_warn
|
||||
{q="0", start="positive"} 0
|
||||
{q="0", start="negative"} -0.25
|
||||
{q="0.25", start="positive"} 0.055
|
||||
{q="0.25", start="negative"} -0.225
|
||||
{q="0.5", start="positive"} 0.125
|
||||
{q="0.5", start="negative"} -0.2
|
||||
{q="0.75", start="positive"} 0.45
|
||||
{q="0.75", start="negative"} -0.15
|
||||
{q="1", start="positive"} 1
|
||||
{q="1", start="negative"} -0.1
|
||||
|
||||
# Break label set uniqueness.
|
||||
|
||||
eval instant at 50m histogram_quantiles(testhistogram3, "start", 0, 0.25, 0.5, 0.75, 1)
|
||||
expect fail
|
||||
|
||||
eval instant at 50m histogram_quantiles(testhistogram3_bucket, "start", 0, 0.25, 0.5, 0.75, 1)
|
||||
expect fail
|
||||
|
||||
# Quantile too low.
|
||||
|
||||
eval instant at 50m histogram_quantile(-0.1, testhistogram)
|
||||
|
|
@ -233,6 +267,16 @@ eval instant at 50m histogram_quantile(-0.1, testhistogram_bucket)
|
|||
{start="positive"} -Inf
|
||||
{start="negative"} -Inf
|
||||
|
||||
eval instant at 50m histogram_quantiles(testhistogram, "q", -0.1)
|
||||
expect warn
|
||||
{q="-0.1", start="positive"} -Inf
|
||||
{q="-0.1", start="negative"} -Inf
|
||||
|
||||
eval instant at 50m histogram_quantiles(testhistogram_bucket, "q", -0.1)
|
||||
expect warn
|
||||
{q="-0.1", start="positive"} -Inf
|
||||
{q="-0.1", start="negative"} -Inf
|
||||
|
||||
# Quantile too high.
|
||||
|
||||
eval instant at 50m histogram_quantile(1.01, testhistogram)
|
||||
|
|
@ -245,6 +289,16 @@ eval instant at 50m histogram_quantile(1.01, testhistogram_bucket)
|
|||
{start="positive"} +Inf
|
||||
{start="negative"} +Inf
|
||||
|
||||
eval instant at 50m histogram_quantiles(testhistogram, "q", 1.01)
|
||||
expect warn
|
||||
{q="1.01", start="positive"} +Inf
|
||||
{q="1.01", start="negative"} +Inf
|
||||
|
||||
eval instant at 50m histogram_quantiles(testhistogram_bucket, "q", 1.01)
|
||||
expect warn
|
||||
{q="1.01", start="positive"} +Inf
|
||||
{q="1.01", start="negative"} +Inf
|
||||
|
||||
# Quantile invalid.
|
||||
|
||||
eval instant at 50m histogram_quantile(NaN, testhistogram)
|
||||
|
|
@ -257,9 +311,22 @@ eval instant at 50m histogram_quantile(NaN, testhistogram_bucket)
|
|||
{start="positive"} NaN
|
||||
{start="negative"} NaN
|
||||
|
||||
eval instant at 50m histogram_quantiles(testhistogram, "q", NaN)
|
||||
expect warn
|
||||
{q="NaN", start="positive"} NaN
|
||||
{q="NaN", start="negative"} NaN
|
||||
|
||||
eval instant at 50m histogram_quantiles(testhistogram_bucket, "q", NaN)
|
||||
expect warn
|
||||
{q="NaN", start="positive"} NaN
|
||||
{q="NaN", start="negative"} NaN
|
||||
|
||||
eval instant at 50m histogram_quantile(NaN, non_existent)
|
||||
expect warn msg: PromQL warning: quantile value should be between 0 and 1, got NaN
|
||||
|
||||
eval instant at 50m histogram_quantiles(non_existent, "q", NaN)
|
||||
expect warn msg: PromQL warning: quantile value should be between 0 and 1, got NaN
|
||||
|
||||
# Quantile value in lowest bucket.
|
||||
|
||||
eval instant at 50m histogram_quantile(0, testhistogram)
|
||||
|
|
@ -590,6 +657,12 @@ eval instant at 50m histogram_quantile(0.99, nonmonotonic_bucket)
|
|||
expect info
|
||||
{} 979.75
|
||||
|
||||
eval instant at 50m histogram_quantiles(nonmonotonic_bucket, "q", 0.01, 0.5, 0.99)
|
||||
expect info
|
||||
{q="0.01"} 0.0045
|
||||
{q="0.5"} 8.5
|
||||
{q="0.99"} 979.75
|
||||
|
||||
# Buckets with different representations of the same upper bound.
|
||||
eval instant at 50m histogram_quantile(0.5, rate(mixed_bucket[10m]))
|
||||
{instance="ins1", job="job1"} 0.15
|
||||
|
|
@ -625,9 +698,15 @@ load_with_nhcb 5m
|
|||
eval instant at 50m histogram_quantile(0.99, {__name__=~"request_duration_seconds\\d*_bucket"})
|
||||
expect fail
|
||||
|
||||
eval instant at 50m histogram_quantiles({__name__=~"request_duration_seconds\\d*_bucket"}, "q", 0.99)
|
||||
expect fail
|
||||
|
||||
eval instant at 50m histogram_quantile(0.99, {__name__=~"request_duration_seconds\\d*"})
|
||||
expect fail
|
||||
|
||||
eval instant at 50m histogram_quantiles({__name__=~"request_duration_seconds\\d*"}, "q", 0.99)
|
||||
expect fail
|
||||
|
||||
# Histogram with constant buckets.
|
||||
load_with_nhcb 1m
|
||||
const_histogram_bucket{le="0.0"} 1 1 1 1 1
|
||||
|
|
@ -689,7 +768,7 @@ eval instant at 10m histogram_sum(increase(histogram_with_reset[15m]))
|
|||
|
||||
clear
|
||||
|
||||
# Test histogram_quantile and histogram_fraction with conflicting classic and native histograms.
|
||||
# Test histogram_quantile(s) and histogram_fraction with conflicting classic and native histograms.
|
||||
load 1m
|
||||
series{host="a"} {{schema:0 sum:5 count:4 buckets:[9 2 1]}}
|
||||
series{host="a", le="0.1"} 2
|
||||
|
|
@ -704,6 +783,11 @@ eval instant at 0 histogram_quantile(0.8, series)
|
|||
expect warn msg: PromQL warning: vector contains a mix of classic and native histograms for metric name "series"
|
||||
# Should return no results.
|
||||
|
||||
eval instant at 0 histogram_quantiles(series, "q", 0.1, 0.2)
|
||||
expect no_info
|
||||
expect warn msg: PromQL warning: vector contains a mix of classic and native histograms for metric name "series"
|
||||
# Should return no results.
|
||||
|
||||
eval instant at 0 histogram_fraction(-Inf, 1, series)
|
||||
expect no_info
|
||||
expect warn msg: PromQL warning: vector contains a mix of classic and native histograms for metric name "series"
|
||||
|
|
|
|||
|
|
@ -55,6 +55,10 @@ eval instant at 1m histogram_quantile(0.5, single_histogram)
|
|||
expect no_info
|
||||
{} 1.414213562373095
|
||||
|
||||
eval instant at 1m histogram_quantiles(single_histogram, "q", 0.5)
|
||||
expect no_info
|
||||
{q="0.5"} 1.414213562373095
|
||||
|
||||
clear
|
||||
|
||||
# Repeat the same histogram 10 times.
|
||||
|
|
@ -1463,6 +1467,11 @@ eval instant at 1m histogram_quantile(0.81, histogram_nan)
|
|||
{case="100% NaNs"} NaN
|
||||
{case="20% NaNs"} NaN
|
||||
|
||||
eval instant at 1m histogram_quantiles(histogram_nan, "q", 0.81)
|
||||
expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN for metric name "histogram_nan"
|
||||
{case="100% NaNs", q="0.81"} NaN
|
||||
{case="20% NaNs", q="0.81"} NaN
|
||||
|
||||
eval instant at 1m histogram_quantile(0.8, histogram_nan{case="100% NaNs"})
|
||||
expect info msg: PromQL info: input to histogram_quantile has NaN observations, result is NaN for metric name "histogram_nan"
|
||||
{case="100% NaNs"} NaN
|
||||
|
|
@ -1608,6 +1617,9 @@ eval instant at 1m histogram_quantile(0.5, myHistogram2)
|
|||
eval instant at 1m histogram_quantile(0.5, mixedHistogram)
|
||||
expect warn msg: PromQL warning: vector contains a mix of classic and native histograms for metric name "mixedHistogram"
|
||||
|
||||
eval instant at 1m histogram_quantiles(mixedHistogram, "q", 0.5)
|
||||
expect warn msg: PromQL warning: vector contains a mix of classic and native histograms for metric name "mixedHistogram"
|
||||
|
||||
clear
|
||||
|
||||
# A counter reset only in a bucket. Sub-queries still need to detect
|
||||
|
|
@ -1677,6 +1689,9 @@ eval instant at 1m histogram_count(histogram unless histogram_quantile(0.5, hist
|
|||
eval instant at 1m histogram_quantile(0.5, histogram unless histogram_count(histogram) == 0)
|
||||
{} 3.1748021039363987
|
||||
|
||||
eval instant at 1m histogram_quantiles(histogram unless histogram_count(histogram) == 0, "q", 0.5)
|
||||
{q="0.5"} 3.1748021039363987
|
||||
|
||||
clear
|
||||
|
||||
# Regression test for:
|
||||
|
|
|
|||
Loading…
Reference in a new issue