diff --git a/model/histogram/float_histogram.go b/model/histogram/float_histogram.go index 92f084bdf6..1cff71bbc9 100644 --- a/model/histogram/float_histogram.go +++ b/model/histogram/float_histogram.go @@ -294,6 +294,9 @@ func (h *FloatHistogram) Mul(factor float64) *FloatHistogram { for i := range h.NegativeBuckets { h.NegativeBuckets[i] *= factor } + if factor < 0 { + h.CounterResetHint = GaugeType + } return h } @@ -317,6 +320,9 @@ func (h *FloatHistogram) Div(scalar float64) *FloatHistogram { for i := range h.NegativeBuckets { h.NegativeBuckets[i] /= scalar } + if scalar < 0 { + h.CounterResetHint = GaugeType + } return h } @@ -410,53 +416,7 @@ func (h *FloatHistogram) Add(other *FloatHistogram) (*FloatHistogram, error) { // Sub works like Add but subtracts the other histogram. func (h *FloatHistogram) Sub(other *FloatHistogram) (*FloatHistogram, error) { - if h.UsesCustomBuckets() != other.UsesCustomBuckets() { - return nil, ErrHistogramsIncompatibleSchema - } - if h.UsesCustomBuckets() && !FloatBucketsMatch(h.CustomValues, other.CustomValues) { - return nil, ErrHistogramsIncompatibleBounds - } - - if !h.UsesCustomBuckets() { - otherZeroCount := h.reconcileZeroBuckets(other) - h.ZeroCount -= otherZeroCount - } - h.Count -= other.Count - h.Sum -= other.Sum - - var ( - hPositiveSpans = h.PositiveSpans - hPositiveBuckets = h.PositiveBuckets - otherPositiveSpans = other.PositiveSpans - otherPositiveBuckets = other.PositiveBuckets - ) - - if h.UsesCustomBuckets() { - h.PositiveSpans, h.PositiveBuckets = addBuckets(h.Schema, h.ZeroThreshold, true, hPositiveSpans, hPositiveBuckets, otherPositiveSpans, otherPositiveBuckets) - return h, nil - } - - var ( - hNegativeSpans = h.NegativeSpans - hNegativeBuckets = h.NegativeBuckets - otherNegativeSpans = other.NegativeSpans - otherNegativeBuckets = other.NegativeBuckets - ) - - switch { - case other.Schema < h.Schema: - hPositiveSpans, hPositiveBuckets = reduceResolution(hPositiveSpans, hPositiveBuckets, h.Schema, other.Schema, false, true) - hNegativeSpans, hNegativeBuckets = reduceResolution(hNegativeSpans, hNegativeBuckets, h.Schema, other.Schema, false, true) - h.Schema = other.Schema - case other.Schema > h.Schema: - otherPositiveSpans, otherPositiveBuckets = reduceResolution(otherPositiveSpans, otherPositiveBuckets, other.Schema, h.Schema, false, false) - otherNegativeSpans, otherNegativeBuckets = reduceResolution(otherNegativeSpans, otherNegativeBuckets, other.Schema, h.Schema, false, false) - } - - h.PositiveSpans, h.PositiveBuckets = addBuckets(h.Schema, h.ZeroThreshold, true, hPositiveSpans, hPositiveBuckets, otherPositiveSpans, otherPositiveBuckets) - h.NegativeSpans, h.NegativeBuckets = addBuckets(h.Schema, h.ZeroThreshold, true, hNegativeSpans, hNegativeBuckets, otherNegativeSpans, otherNegativeBuckets) - - return h, nil + return h.Add(other.Copy().Mul(-1)) } // Equals returns true if the given float histogram matches exactly. diff --git a/model/histogram/float_histogram_test.go b/model/histogram/float_histogram_test.go index 34988e9d39..8772955d05 100644 --- a/model/histogram/float_histogram_test.go +++ b/model/histogram/float_histogram_test.go @@ -145,14 +145,15 @@ func TestFloatHistogramMul(t *testing.T) { }, -1, &FloatHistogram{ - ZeroThreshold: 0.01, - ZeroCount: -11, - Count: -30, - Sum: -23, - PositiveSpans: []Span{{-2, 2}, {1, 3}}, - PositiveBuckets: []float64{-1, 0, -3, -4, -7}, - NegativeSpans: []Span{{3, 2}, {3, 2}}, - NegativeBuckets: []float64{-3, -1, -5, -6}, + ZeroThreshold: 0.01, + ZeroCount: -11, + Count: -30, + Sum: -23, + PositiveSpans: []Span{{-2, 2}, {1, 3}}, + PositiveBuckets: []float64{-1, 0, -3, -4, -7}, + NegativeSpans: []Span{{3, 2}, {3, 2}}, + NegativeBuckets: []float64{-3, -1, -5, -6}, + CounterResetHint: GaugeType, }, }, { @@ -169,14 +170,15 @@ func TestFloatHistogramMul(t *testing.T) { }, -2, &FloatHistogram{ - ZeroThreshold: 0.01, - ZeroCount: -22, - Count: -60, - Sum: -46, - PositiveSpans: []Span{{-2, 2}, {1, 3}}, - PositiveBuckets: []float64{-2, 0, -6, -8, -14}, - NegativeSpans: []Span{{3, 2}, {3, 2}}, - NegativeBuckets: []float64{-6, -2, -10, -12}, + ZeroThreshold: 0.01, + ZeroCount: -22, + Count: -60, + Sum: -46, + PositiveSpans: []Span{{-2, 2}, {1, 3}}, + PositiveBuckets: []float64{-2, 0, -6, -8, -14}, + NegativeSpans: []Span{{3, 2}, {3, 2}}, + NegativeBuckets: []float64{-6, -2, -10, -12}, + CounterResetHint: GaugeType, }, }, { @@ -467,14 +469,15 @@ func TestFloatHistogramDiv(t *testing.T) { }, -1, &FloatHistogram{ - ZeroThreshold: 0.01, - ZeroCount: -5.5, - Count: -3493.3, - Sum: -2349209.324, - PositiveSpans: []Span{{-2, 1}, {2, 3}}, - PositiveBuckets: []float64{-1, -3.3, -4.2, -0.1}, - NegativeSpans: []Span{{3, 2}, {3, 2}}, - NegativeBuckets: []float64{-3.1, -3, -1.234e5, -1000}, + ZeroThreshold: 0.01, + ZeroCount: -5.5, + Count: -3493.3, + Sum: -2349209.324, + PositiveSpans: []Span{{-2, 1}, {2, 3}}, + PositiveBuckets: []float64{-1, -3.3, -4.2, -0.1}, + NegativeSpans: []Span{{3, 2}, {3, 2}}, + NegativeBuckets: []float64{-3.1, -3, -1.234e5, -1000}, + CounterResetHint: GaugeType, }, }, { @@ -491,14 +494,15 @@ func TestFloatHistogramDiv(t *testing.T) { }, -2, &FloatHistogram{ - ZeroThreshold: 0.01, - ZeroCount: -5.5, - Count: -15, - Sum: -11.5, - PositiveSpans: []Span{{-2, 2}, {1, 3}}, - PositiveBuckets: []float64{-0.5, 0, -1.5, -2, -3.5}, - NegativeSpans: []Span{{3, 2}, {3, 2}}, - NegativeBuckets: []float64{-1.5, -0.5, -2.5, -3}, + ZeroThreshold: 0.01, + ZeroCount: -5.5, + Count: -15, + Sum: -11.5, + PositiveSpans: []Span{{-2, 2}, {1, 3}}, + PositiveBuckets: []float64{-0.5, 0, -1.5, -2, -3.5}, + NegativeSpans: []Span{{3, 2}, {3, 2}}, + NegativeBuckets: []float64{-1.5, -0.5, -2.5, -3}, + CounterResetHint: GaugeType, }, }, { @@ -2379,14 +2383,15 @@ func TestFloatHistogramSub(t *testing.T) { NegativeBuckets: []float64{1, 1, 4, 4}, }, &FloatHistogram{ - ZeroThreshold: 0.01, - ZeroCount: 3, - Count: 9, - Sum: 11, - PositiveSpans: []Span{{-2, 2}, {1, 3}}, - PositiveBuckets: []float64{1, 0, 1, 1, 1}, - NegativeSpans: []Span{{3, 2}, {3, 2}}, - NegativeBuckets: []float64{2, 0, 1, 2}, + ZeroThreshold: 0.01, + ZeroCount: 3, + Count: 9, + Sum: 11, + PositiveSpans: []Span{{-2, 2}, {1, 3}}, + PositiveBuckets: []float64{1, 0, 1, 1, 1}, + NegativeSpans: []Span{{3, 2}, {3, 2}}, + NegativeBuckets: []float64{2, 0, 1, 2}, + CounterResetHint: GaugeType, }, "", }, @@ -2415,14 +2420,15 @@ func TestFloatHistogramSub(t *testing.T) { NegativeBuckets: []float64{3, 0.5, 0.5, 2, 3, 2, 4}, }, &FloatHistogram{ - ZeroThreshold: 0.01, - ZeroCount: 6, - Count: 40, - Sum: 0.889, - PositiveSpans: []Span{{-2, 5}, {0, 3}}, - PositiveBuckets: []float64{1, 5, 4, 2, 2, 2, 0, 5}, - NegativeSpans: []Span{{3, 3}, {1, 3}}, - NegativeBuckets: []float64{1, 9, 1, 4, 9, 1}, + ZeroThreshold: 0.01, + ZeroCount: 6, + Count: 40, + Sum: 0.889, + PositiveSpans: []Span{{-2, 5}, {0, 3}}, + PositiveBuckets: []float64{1, 5, 4, 2, 2, 2, 0, 5}, + NegativeSpans: []Span{{3, 3}, {1, 3}}, + NegativeBuckets: []float64{1, 9, 1, 4, 9, 1}, + CounterResetHint: GaugeType, }, "", }, @@ -2445,12 +2451,13 @@ func TestFloatHistogramSub(t *testing.T) { CustomValues: []float64{1, 2, 3, 4}, }, &FloatHistogram{ - Schema: CustomBucketsSchema, - Count: 4, - Sum: 11, - PositiveSpans: []Span{{0, 2}, {1, 3}}, - PositiveBuckets: []float64{1, 0, 1, 1, 1}, - CustomValues: []float64{1, 2, 3, 4}, + Schema: CustomBucketsSchema, + Count: 4, + Sum: 11, + PositiveSpans: []Span{{0, 2}, {1, 3}}, + PositiveBuckets: []float64{1, 0, 1, 1, 1}, + CustomValues: []float64{1, 2, 3, 4}, + CounterResetHint: GaugeType, }, "", }, diff --git a/promql/engine_test.go b/promql/engine_test.go index 111ffc3a4b..e2e1165562 100644 --- a/promql/engine_test.go +++ b/promql/engine_test.go @@ -4049,3 +4049,85 @@ func TestSubQueryHistogramsCopy(t *testing.T) { queryable.Close() } } + +func TestHistogram_CounterResetHint(t *testing.T) { + baseT := timestamp.Time(0) + load := ` + load 2m + test_histogram {{count:0}}+{{count:1}}x4 + ` + testCases := []struct { + name string + query string + want histogram.CounterResetHint + }{ + { + name: "adding histograms", + query: `test_histogram + test_histogram`, + want: histogram.NotCounterReset, + }, + { + name: "subtraction", + query: `test_histogram - test_histogram`, + want: histogram.GaugeType, + }, + { + name: "negated addition", + query: `test_histogram + (-test_histogram)`, + want: histogram.GaugeType, + }, + { + name: "negated subtraction", + query: `test_histogram - (-test_histogram)`, + want: histogram.GaugeType, + }, + { + name: "unary negation", + query: `-test_histogram`, + want: histogram.GaugeType, + }, + { + name: "repeated unary negation", + query: `-(-test_histogram)`, + want: histogram.GaugeType, + }, + { + name: "multiplication", + query: `2 * test_histogram`, + want: histogram.NotCounterReset, + }, + { + name: "negative multiplication", + query: `-1 * test_histogram`, + want: histogram.GaugeType, + }, + { + name: "division", + query: `test_histogram / 2`, + want: histogram.NotCounterReset, + }, + { + name: "negative division", + query: `test_histogram / -1`, + want: histogram.GaugeType, + }, + } + ctx := context.Background() + queryable := promqltest.LoadedStorage(t, load) + defer queryable.Close() + engine := promqltest.NewTestEngine(t, false, 0, promqltest.DefaultMaxSamplesPerQuery) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + q, err := engine.NewInstantQuery(ctx, queryable, nil, tc.query, baseT.Add(2*time.Minute)) + require.NoError(t, err) + defer q.Close() + res := q.Exec(ctx) + require.NoError(t, res.Err) + v, err := res.Vector() + require.NoError(t, err) + require.Len(t, v, 1) + require.NotNil(t, v[0].H) + require.Equal(t, tc.want, v[0].H.CounterResetHint) + }) + } +} diff --git a/promql/parser/parse_test.go b/promql/parser/parse_test.go index 24c920fe29..80394946ab 100644 --- a/promql/parser/parse_test.go +++ b/promql/parser/parse_test.go @@ -5310,6 +5310,7 @@ func TestParseHistogramSeries(t *testing.T) { Offset: 0, Length: 3, }}, + CounterResetHint: histogram.GaugeType, }, { Schema: 1, @@ -5318,6 +5319,7 @@ func TestParseHistogramSeries(t *testing.T) { Offset: 0, Length: 3, }}, + CounterResetHint: histogram.GaugeType, }, }, },