mirror of
https://github.com/prometheus/prometheus.git
synced 2025-01-12 14:27:27 -08:00
[BUGFIX] PromQL: Corrects the behaviour of some operator and aggregators with Native Histograms (#15245)
Some checks failed
buf.build / lint and publish (push) Has been cancelled
CI / Go tests (push) Has been cancelled
CI / More Go tests (push) Has been cancelled
CI / Go tests with previous Go version (push) Has been cancelled
CI / UI tests (push) Has been cancelled
CI / Go tests on Windows (push) Has been cancelled
CI / Mixins tests (push) Has been cancelled
CI / Build Prometheus for common architectures (0) (push) Has been cancelled
CI / Build Prometheus for common architectures (1) (push) Has been cancelled
CI / Build Prometheus for common architectures (2) (push) Has been cancelled
CI / Build Prometheus for all architectures (0) (push) Has been cancelled
CI / Build Prometheus for all architectures (1) (push) Has been cancelled
CI / Build Prometheus for all architectures (10) (push) Has been cancelled
CI / Build Prometheus for all architectures (11) (push) Has been cancelled
CI / Build Prometheus for all architectures (2) (push) Has been cancelled
CI / Build Prometheus for all architectures (3) (push) Has been cancelled
CI / Build Prometheus for all architectures (4) (push) Has been cancelled
CI / Build Prometheus for all architectures (5) (push) Has been cancelled
CI / Build Prometheus for all architectures (6) (push) Has been cancelled
CI / Build Prometheus for all architectures (7) (push) Has been cancelled
CI / Build Prometheus for all architectures (8) (push) Has been cancelled
CI / Build Prometheus for all architectures (9) (push) Has been cancelled
CI / Check generated parser (push) Has been cancelled
CI / golangci-lint (push) Has been cancelled
CI / fuzzing (push) Has been cancelled
CI / codeql (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Has been cancelled
CI / Report status of build Prometheus for all architectures (push) Has been cancelled
CI / Publish main branch artifacts (push) Has been cancelled
CI / Publish release artefacts (push) Has been cancelled
CI / Publish UI on npm Registry (push) Has been cancelled
Some checks failed
buf.build / lint and publish (push) Has been cancelled
CI / Go tests (push) Has been cancelled
CI / More Go tests (push) Has been cancelled
CI / Go tests with previous Go version (push) Has been cancelled
CI / UI tests (push) Has been cancelled
CI / Go tests on Windows (push) Has been cancelled
CI / Mixins tests (push) Has been cancelled
CI / Build Prometheus for common architectures (0) (push) Has been cancelled
CI / Build Prometheus for common architectures (1) (push) Has been cancelled
CI / Build Prometheus for common architectures (2) (push) Has been cancelled
CI / Build Prometheus for all architectures (0) (push) Has been cancelled
CI / Build Prometheus for all architectures (1) (push) Has been cancelled
CI / Build Prometheus for all architectures (10) (push) Has been cancelled
CI / Build Prometheus for all architectures (11) (push) Has been cancelled
CI / Build Prometheus for all architectures (2) (push) Has been cancelled
CI / Build Prometheus for all architectures (3) (push) Has been cancelled
CI / Build Prometheus for all architectures (4) (push) Has been cancelled
CI / Build Prometheus for all architectures (5) (push) Has been cancelled
CI / Build Prometheus for all architectures (6) (push) Has been cancelled
CI / Build Prometheus for all architectures (7) (push) Has been cancelled
CI / Build Prometheus for all architectures (8) (push) Has been cancelled
CI / Build Prometheus for all architectures (9) (push) Has been cancelled
CI / Check generated parser (push) Has been cancelled
CI / golangci-lint (push) Has been cancelled
CI / fuzzing (push) Has been cancelled
CI / codeql (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Has been cancelled
CI / Report status of build Prometheus for all architectures (push) Has been cancelled
CI / Publish main branch artifacts (push) Has been cancelled
CI / Publish release artefacts (push) Has been cancelled
CI / Publish UI on npm Registry (push) Has been cancelled
PromQL: Correct the behaviour of some operator and aggregators with Native Histograms --------- Signed-off-by: Neeraj Gartia <neerajgartia211002@gmail.com>
This commit is contained in:
parent
c599d37668
commit
789c9b1a5e
172
promql/engine.go
172
promql/engine.go
|
@ -2793,77 +2793,84 @@ func scalarBinop(op parser.ItemType, lhs, rhs float64) float64 {
|
|||
|
||||
// vectorElemBinop evaluates a binary operation between two Vector elements.
|
||||
func vectorElemBinop(op parser.ItemType, lhs, rhs float64, hlhs, hrhs *histogram.FloatHistogram, pos posrange.PositionRange) (float64, *histogram.FloatHistogram, bool, error) {
|
||||
switch op {
|
||||
case parser.ADD:
|
||||
if hlhs != nil && hrhs != nil {
|
||||
res, err := hlhs.Copy().Add(hrhs)
|
||||
if err != nil {
|
||||
return 0, nil, false, err
|
||||
opName := parser.ItemTypeStr[op]
|
||||
switch {
|
||||
case hlhs == nil && hrhs == nil:
|
||||
{
|
||||
switch op {
|
||||
case parser.ADD:
|
||||
return lhs + rhs, nil, true, nil
|
||||
case parser.SUB:
|
||||
return lhs - rhs, nil, true, nil
|
||||
case parser.MUL:
|
||||
return lhs * rhs, nil, true, nil
|
||||
case parser.DIV:
|
||||
return lhs / rhs, nil, true, nil
|
||||
case parser.POW:
|
||||
return math.Pow(lhs, rhs), nil, true, nil
|
||||
case parser.MOD:
|
||||
return math.Mod(lhs, rhs), nil, true, nil
|
||||
case parser.EQLC:
|
||||
return lhs, nil, lhs == rhs, nil
|
||||
case parser.NEQ:
|
||||
return lhs, nil, lhs != rhs, nil
|
||||
case parser.GTR:
|
||||
return lhs, nil, lhs > rhs, nil
|
||||
case parser.LSS:
|
||||
return lhs, nil, lhs < rhs, nil
|
||||
case parser.GTE:
|
||||
return lhs, nil, lhs >= rhs, nil
|
||||
case parser.LTE:
|
||||
return lhs, nil, lhs <= rhs, nil
|
||||
case parser.ATAN2:
|
||||
return math.Atan2(lhs, rhs), nil, true, nil
|
||||
}
|
||||
return 0, res.Compact(0), true, nil
|
||||
}
|
||||
if hlhs == nil && hrhs == nil {
|
||||
return lhs + rhs, nil, true, nil
|
||||
}
|
||||
if hlhs != nil {
|
||||
return 0, nil, false, annotations.NewIncompatibleTypesInBinOpInfo("histogram", "+", "float", pos)
|
||||
}
|
||||
return 0, nil, false, annotations.NewIncompatibleTypesInBinOpInfo("float", "+", "histogram", pos)
|
||||
case parser.SUB:
|
||||
if hlhs != nil && hrhs != nil {
|
||||
res, err := hlhs.Copy().Sub(hrhs)
|
||||
if err != nil {
|
||||
return 0, nil, false, err
|
||||
case hlhs == nil && hrhs != nil:
|
||||
{
|
||||
switch op {
|
||||
case parser.MUL:
|
||||
return 0, hrhs.Copy().Mul(lhs).Compact(0), true, 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:
|
||||
return 0, nil, false, annotations.NewIncompatibleTypesInBinOpInfo("float", opName, "histogram", pos)
|
||||
}
|
||||
return 0, res.Compact(0), true, nil
|
||||
}
|
||||
if hlhs == nil && hrhs == nil {
|
||||
return lhs - rhs, nil, true, nil
|
||||
}
|
||||
if hlhs != nil {
|
||||
return 0, nil, false, annotations.NewIncompatibleTypesInBinOpInfo("histogram", "-", "float", pos)
|
||||
}
|
||||
return 0, nil, false, annotations.NewIncompatibleTypesInBinOpInfo("float", "-", "histogram", pos)
|
||||
case parser.MUL:
|
||||
if hlhs != nil && hrhs == nil {
|
||||
return 0, hlhs.Copy().Mul(rhs), true, nil
|
||||
}
|
||||
if hlhs == nil && hrhs != nil {
|
||||
return 0, hrhs.Copy().Mul(lhs), true, nil
|
||||
}
|
||||
if hlhs != nil && hrhs != nil {
|
||||
return 0, nil, false, annotations.NewIncompatibleTypesInBinOpInfo("histogram", "*", "histogram", pos)
|
||||
}
|
||||
return lhs * rhs, nil, true, nil
|
||||
case parser.DIV:
|
||||
if hlhs != nil && hrhs == nil {
|
||||
return 0, hlhs.Copy().Div(rhs), true, nil
|
||||
}
|
||||
if hrhs != nil {
|
||||
if hlhs != nil {
|
||||
return 0, nil, false, annotations.NewIncompatibleTypesInBinOpInfo("histogram", "/", "histogram", pos)
|
||||
case hlhs != nil && hrhs == nil:
|
||||
{
|
||||
switch op {
|
||||
case parser.MUL:
|
||||
return 0, hlhs.Copy().Mul(rhs).Compact(0), true, nil
|
||||
case parser.DIV:
|
||||
return 0, hlhs.Copy().Div(rhs).Compact(0), true, 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, annotations.NewIncompatibleTypesInBinOpInfo("histogram", opName, "float", pos)
|
||||
}
|
||||
}
|
||||
case hlhs != nil && hrhs != nil:
|
||||
{
|
||||
switch op {
|
||||
case parser.ADD:
|
||||
res, err := hlhs.Copy().Add(hrhs)
|
||||
if err != nil {
|
||||
return 0, nil, false, err
|
||||
}
|
||||
return 0, res.Compact(0), true, nil
|
||||
case parser.SUB:
|
||||
res, err := hlhs.Copy().Sub(hrhs)
|
||||
if err != nil {
|
||||
return 0, nil, false, err
|
||||
}
|
||||
return 0, res.Compact(0), true, nil
|
||||
case parser.EQLC:
|
||||
// This operation expects that both histograms are compacted.
|
||||
return 0, hlhs, hlhs.Equals(hrhs), nil
|
||||
case parser.NEQ:
|
||||
// This operation expects that both histograms are compacted.
|
||||
return 0, hlhs, !hlhs.Equals(hrhs), nil
|
||||
case parser.MUL, parser.DIV, parser.POW, parser.MOD, parser.GTR, parser.LSS, parser.GTE, parser.LTE, parser.ATAN2:
|
||||
return 0, nil, false, annotations.NewIncompatibleTypesInBinOpInfo("histogram", opName, "histogram", pos)
|
||||
}
|
||||
return 0, nil, false, annotations.NewIncompatibleTypesInBinOpInfo("float", "/", "histogram", pos)
|
||||
}
|
||||
return lhs / rhs, nil, true, nil
|
||||
case parser.POW:
|
||||
return math.Pow(lhs, rhs), nil, true, nil
|
||||
case parser.MOD:
|
||||
return math.Mod(lhs, rhs), nil, true, nil
|
||||
case parser.EQLC:
|
||||
return lhs, nil, lhs == rhs, nil
|
||||
case parser.NEQ:
|
||||
return lhs, nil, lhs != rhs, nil
|
||||
case parser.GTR:
|
||||
return lhs, nil, lhs > rhs, nil
|
||||
case parser.LSS:
|
||||
return lhs, nil, lhs < rhs, nil
|
||||
case parser.GTE:
|
||||
return lhs, nil, lhs >= rhs, nil
|
||||
case parser.LTE:
|
||||
return lhs, nil, lhs <= rhs, nil
|
||||
case parser.ATAN2:
|
||||
return math.Atan2(lhs, rhs), nil, true, nil
|
||||
}
|
||||
panic(fmt.Errorf("operator %q not allowed for operations between Vectors", op))
|
||||
}
|
||||
|
@ -2925,16 +2932,34 @@ func (ev *evaluator) aggregation(e *parser.AggregateExpr, q float64, inputMatrix
|
|||
case h != nil:
|
||||
// Ignore histograms for STDVAR and STDDEV.
|
||||
group.seen = false
|
||||
if op == parser.STDVAR {
|
||||
annos.Add(annotations.NewHistogramIgnoredInAggregationInfo("stdvar", e.Expr.PositionRange()))
|
||||
} else {
|
||||
annos.Add(annotations.NewHistogramIgnoredInAggregationInfo("stddev", e.Expr.PositionRange()))
|
||||
}
|
||||
case math.IsNaN(f), math.IsInf(f, 0):
|
||||
group.floatValue = math.NaN()
|
||||
default:
|
||||
group.floatValue = 0
|
||||
}
|
||||
case parser.QUANTILE:
|
||||
if h != nil {
|
||||
group.seen = false
|
||||
annos.Add(annotations.NewHistogramIgnoredInAggregationInfo("quantile", e.Expr.PositionRange()))
|
||||
}
|
||||
group.heap = make(vectorByValueHeap, 1)
|
||||
group.heap[0] = Sample{F: f}
|
||||
case parser.GROUP:
|
||||
group.floatValue = 1
|
||||
case parser.MIN, parser.MAX:
|
||||
if h != nil {
|
||||
group.seen = false
|
||||
if op == parser.MIN {
|
||||
annos.Add(annotations.NewHistogramIgnoredInAggregationInfo("min", e.Expr.PositionRange()))
|
||||
} else {
|
||||
annos.Add(annotations.NewHistogramIgnoredInAggregationInfo("max", e.Expr.PositionRange()))
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
@ -3033,11 +3058,19 @@ func (ev *evaluator) aggregation(e *parser.AggregateExpr, q float64, inputMatrix
|
|||
// Do nothing. Required to avoid the panic in `default:` below.
|
||||
|
||||
case parser.MAX:
|
||||
if h != nil {
|
||||
annos.Add(annotations.NewHistogramIgnoredInAggregationInfo("min", e.Expr.PositionRange()))
|
||||
continue
|
||||
}
|
||||
if group.floatValue < f || math.IsNaN(group.floatValue) {
|
||||
group.floatValue = f
|
||||
}
|
||||
|
||||
case parser.MIN:
|
||||
if h != nil {
|
||||
annos.Add(annotations.NewHistogramIgnoredInAggregationInfo("max", e.Expr.PositionRange()))
|
||||
continue
|
||||
}
|
||||
if group.floatValue > f || math.IsNaN(group.floatValue) {
|
||||
group.floatValue = f
|
||||
}
|
||||
|
@ -3051,9 +3084,18 @@ func (ev *evaluator) aggregation(e *parser.AggregateExpr, q float64, inputMatrix
|
|||
delta := f - group.floatMean
|
||||
group.floatMean += delta / group.groupCount
|
||||
group.floatValue += delta * (f - group.floatMean)
|
||||
if op == parser.STDVAR {
|
||||
annos.Add(annotations.NewHistogramIgnoredInAggregationInfo("stdvar", e.Expr.PositionRange()))
|
||||
} else {
|
||||
annos.Add(annotations.NewHistogramIgnoredInAggregationInfo("stddev", e.Expr.PositionRange()))
|
||||
}
|
||||
}
|
||||
|
||||
case parser.QUANTILE:
|
||||
if h != nil {
|
||||
annos.Add(annotations.NewHistogramIgnoredInAggregationInfo("quantile", e.Expr.PositionRange()))
|
||||
continue
|
||||
}
|
||||
group.heap = append(group.heap, Sample{F: f})
|
||||
|
||||
default:
|
||||
|
|
35
promql/promqltest/testdata/aggregators.test
vendored
35
promql/promqltest/testdata/aggregators.test
vendored
|
@ -229,13 +229,28 @@ load 5m
|
|||
http_requests{job="api-server", instance="0", group="canary"} NaN
|
||||
http_requests{job="api-server", instance="1", group="canary"} 3
|
||||
http_requests{job="api-server", instance="2", group="canary"} 4
|
||||
http_requests_histogram{job="api-server", instance="3", group="canary"} {{schema:2 count:4 sum:10 buckets:[1 0 0 0 1 0 0 1 1]}}
|
||||
|
||||
eval instant at 0m max(http_requests)
|
||||
{} 4
|
||||
|
||||
# The histogram is ignored here so the result doesn't change but it has an info annotation now.
|
||||
eval_info instant at 0m max({job="api-server"})
|
||||
{} 4
|
||||
|
||||
# The histogram is ignored here so there is no result but it has an info annotation now.
|
||||
eval_info instant at 0m max(http_requests_histogram)
|
||||
|
||||
eval instant at 0m min(http_requests)
|
||||
{} 1
|
||||
|
||||
# The histogram is ignored here so the result doesn't change but it has an info annotation now.
|
||||
eval_info instant at 0m min({job="api-server"})
|
||||
{} 1
|
||||
|
||||
# The histogram is ignored here so there is no result but it has an info annotation now.
|
||||
eval_info instant at 0m min(http_requests_histogram)
|
||||
|
||||
eval instant at 0m max by (group) (http_requests)
|
||||
{group="production"} 2
|
||||
{group="canary"} 4
|
||||
|
@ -380,6 +395,7 @@ load 10s
|
|||
data{test="uneven samples",point="a"} 0
|
||||
data{test="uneven samples",point="b"} 1
|
||||
data{test="uneven samples",point="c"} 4
|
||||
data_histogram{test="histogram sample", point="c"} {{schema:2 count:4 sum:10 buckets:[1 0 0 0 1 0 0 1 1]}}
|
||||
foo .8
|
||||
|
||||
eval instant at 1m quantile without(point)(0.8, data)
|
||||
|
@ -387,6 +403,15 @@ eval instant at 1m quantile without(point)(0.8, data)
|
|||
{test="three samples"} 1.6
|
||||
{test="uneven samples"} 2.8
|
||||
|
||||
# The histogram is ignored here so the result doesn't change but it has an info annotation now.
|
||||
eval_info instant at 1m quantile without(point)(0.8, {__name__=~"data(_histogram)?"})
|
||||
{test="two samples"} 0.8
|
||||
{test="three samples"} 1.6
|
||||
{test="uneven samples"} 2.8
|
||||
|
||||
# The histogram is ignored here so there is no result but it has an info annotation now.
|
||||
eval_info instant at 1m quantile(0.8, data_histogram)
|
||||
|
||||
# Bug #5276.
|
||||
eval instant at 1m quantile without(point)(scalar(foo), data)
|
||||
{test="two samples"} 0.8
|
||||
|
@ -581,12 +606,18 @@ load 5m
|
|||
series{label="b"} 2
|
||||
series{label="c"} {{schema:1 sum:15 count:10 buckets:[3 2 5 7 9]}}
|
||||
|
||||
eval instant at 0m stddev(series)
|
||||
# The histogram is ignored here so the result doesn't change but it has an info annotation now.
|
||||
eval_info instant at 0m stddev(series)
|
||||
{} 0.5
|
||||
|
||||
eval instant at 0m stdvar(series)
|
||||
eval_info instant at 0m stdvar(series)
|
||||
{} 0.25
|
||||
|
||||
# The histogram is ignored here so there is no result but it has an info annotation now.
|
||||
eval_info instant at 0m stddev({label="c"})
|
||||
|
||||
eval_info instant at 0m stdvar({label="c"})
|
||||
|
||||
eval instant at 0m stddev by (label) (series)
|
||||
{label="a"} 0
|
||||
{label="b"} 0
|
||||
|
|
|
@ -1184,3 +1184,5 @@ eval instant at 3m sum_over_time(histogram_sum_over_time[4m:1m])
|
|||
|
||||
eval instant at 3m avg_over_time(histogram_sum_over_time[4m:1m])
|
||||
{} {{schema:0 count:26.75 sum:1172.8 z_bucket:3.5 z_bucket_w:0.001 buckets:[0.75 2 0.5 1.25 0.75 0.5 0.5] n_buckets:[0.5 1.5 2 1 3.75 2.25 0 0 0 2.5 2.5 1]}}
|
||||
|
||||
clear
|
||||
|
|
21
promql/promqltest/testdata/operators.test
vendored
21
promql/promqltest/testdata/operators.test
vendored
|
@ -7,6 +7,7 @@ load 5m
|
|||
http_requests{job="app-server", instance="1", group="production"} 0+60x10
|
||||
http_requests{job="app-server", instance="0", group="canary"} 0+70x10
|
||||
http_requests{job="app-server", instance="1", group="canary"} 0+80x10
|
||||
http_requests_histogram{job="app-server", instance="1", group="production"} {{schema:1 sum:15 count:10 buckets:[3 2 5 7 9]}}x11
|
||||
|
||||
load 5m
|
||||
vector_matching_a{l="x"} 0+1x100
|
||||
|
@ -287,6 +288,26 @@ eval instant at 50m 1 == bool 1
|
|||
eval instant at 50m http_requests{job="api-server", instance="0", group="production"} == bool 100
|
||||
{job="api-server", instance="0", group="production"} 1
|
||||
|
||||
# The histogram is ignored here so the result doesn't change but it has an info annotation now.
|
||||
eval_info instant at 5m {job="app-server"} == 80
|
||||
http_requests{group="canary", instance="1", job="app-server"} 80
|
||||
|
||||
eval_info instant at 5m http_requests_histogram != 80
|
||||
|
||||
eval_info instant at 5m http_requests_histogram > 80
|
||||
|
||||
eval_info instant at 5m http_requests_histogram < 80
|
||||
|
||||
eval_info instant at 5m http_requests_histogram >= 80
|
||||
|
||||
eval_info instant at 5m http_requests_histogram <= 80
|
||||
|
||||
# Should produce valid results in case of (in)equality between two histograms.
|
||||
eval instant at 5m http_requests_histogram == http_requests_histogram
|
||||
http_requests_histogram{job="app-server", instance="1", group="production"} {{schema:1 sum:15 count:10 buckets:[3 2 5 7 9]}}
|
||||
|
||||
eval instant at 5m http_requests_histogram != http_requests_histogram
|
||||
|
||||
# group_left/group_right.
|
||||
|
||||
clear
|
||||
|
|
|
@ -147,6 +147,7 @@ var (
|
|||
PossibleNonCounterInfo = fmt.Errorf("%w: metric might not be a counter, name does not end in _total/_sum/_count/_bucket:", PromQLInfo)
|
||||
HistogramQuantileForcedMonotonicityInfo = fmt.Errorf("%w: input to histogram_quantile needed to be fixed for monotonicity (see https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_quantile) for metric name", PromQLInfo)
|
||||
IncompatibleTypesInBinOpInfo = fmt.Errorf("%w: incompatible sample types encountered for binary operator", PromQLInfo)
|
||||
HistogramIgnoredInAggregationInfo = fmt.Errorf("%w: ignored histogram in", PromQLInfo)
|
||||
)
|
||||
|
||||
type annoErr struct {
|
||||
|
@ -283,3 +284,12 @@ func NewIncompatibleTypesInBinOpInfo(lhsType, operator, rhsType string, pos posr
|
|||
Err: fmt.Errorf("%w %q: %s %s %s", IncompatibleTypesInBinOpInfo, operator, lhsType, operator, rhsType),
|
||||
}
|
||||
}
|
||||
|
||||
// NewHistogramIgnoredInAggregationInfo is used when a histogram is ignored by
|
||||
// an aggregation operator that cannot handle histograms.
|
||||
func NewHistogramIgnoredInAggregationInfo(aggregation string, pos posrange.PositionRange) error {
|
||||
return annoErr{
|
||||
PositionRange: pos,
|
||||
Err: fmt.Errorf("%w %s aggregation", HistogramIgnoredInAggregationInfo, aggregation),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue