Histograms: set annotation when adding or subtracting histograms that have `not_reset` and `reset` hints.

Signed-off-by: Julius Hinze <julius.hinze@grafana.com>
This commit is contained in:
Julius Hinze 2025-08-14 18:58:02 +02:00
parent 498f63e60b
commit 77b5c3f217
No known key found for this signature in database
9 changed files with 350 additions and 176 deletions

View File

@ -336,15 +336,18 @@ func (h *FloatHistogram) Div(scalar float64) *FloatHistogram {
// The method reconciles differences in the zero threshold and in the schema, and // The method reconciles differences in the zero threshold and in the schema, and
// changes them if needed. The other histogram will not be modified in any case. // changes them if needed. The other histogram will not be modified in any case.
// Adding is currently only supported between 2 exponential histograms, or between // Adding is currently only supported between 2 exponential histograms, or between
// 2 custom buckets histograms with the exact same custom bounds. // 2 custom buckets histograms with the exact same custom bounds. If CounterResetHint
// values conflict, the receiver's hint is set to unknown, and counterResetCollision
// is returned as true. A counter reset conflict occurs iff one of two histograms indicate
// a counter reset (CounterReset) while the other indicates no reset (NotCounterReset).
// //
// This method returns a pointer to the receiving histogram for convenience. // This method returns a pointer to the receiving histogram for convenience.
func (h *FloatHistogram) Add(other *FloatHistogram) (*FloatHistogram, error) { func (h *FloatHistogram) Add(other *FloatHistogram) (res *FloatHistogram, counterResetCollision bool, err error) {
if h.UsesCustomBuckets() != other.UsesCustomBuckets() { if h.UsesCustomBuckets() != other.UsesCustomBuckets() {
return nil, ErrHistogramsIncompatibleSchema return nil, false, ErrHistogramsIncompatibleSchema
} }
if h.UsesCustomBuckets() && !FloatBucketsMatch(h.CustomValues, other.CustomValues) { if h.UsesCustomBuckets() && !FloatBucketsMatch(h.CustomValues, other.CustomValues) {
return nil, ErrHistogramsIncompatibleBounds return nil, false, ErrHistogramsIncompatibleBounds
} }
switch { switch {
@ -368,7 +371,7 @@ func (h *FloatHistogram) Add(other *FloatHistogram) (*FloatHistogram, error) {
// They are a direct collision of CounterReset and NotCounterReset. // They are a direct collision of CounterReset and NotCounterReset.
// Conservatively set the CounterResetHint to "unknown" and issue a warning. // Conservatively set the CounterResetHint to "unknown" and issue a warning.
h.CounterResetHint = UnknownCounterReset h.CounterResetHint = UnknownCounterReset
// TODO(trevorwhitney): Actually issue the warning as soon as the plumbing for it is in place counterResetCollision = true
} }
if !h.UsesCustomBuckets() { if !h.UsesCustomBuckets() {
@ -387,7 +390,7 @@ func (h *FloatHistogram) Add(other *FloatHistogram) (*FloatHistogram, error) {
if h.UsesCustomBuckets() { if h.UsesCustomBuckets() {
h.PositiveSpans, h.PositiveBuckets = addBuckets(h.Schema, h.ZeroThreshold, false, hPositiveSpans, hPositiveBuckets, otherPositiveSpans, otherPositiveBuckets) h.PositiveSpans, h.PositiveBuckets = addBuckets(h.Schema, h.ZeroThreshold, false, hPositiveSpans, hPositiveBuckets, otherPositiveSpans, otherPositiveBuckets)
return h, nil return h, counterResetCollision, nil
} }
var ( var (
@ -411,19 +414,20 @@ func (h *FloatHistogram) Add(other *FloatHistogram) (*FloatHistogram, error) {
h.PositiveSpans, h.PositiveBuckets = addBuckets(h.Schema, h.ZeroThreshold, false, hPositiveSpans, hPositiveBuckets, otherPositiveSpans, otherPositiveBuckets) h.PositiveSpans, h.PositiveBuckets = addBuckets(h.Schema, h.ZeroThreshold, false, hPositiveSpans, hPositiveBuckets, otherPositiveSpans, otherPositiveBuckets)
h.NegativeSpans, h.NegativeBuckets = addBuckets(h.Schema, h.ZeroThreshold, false, hNegativeSpans, hNegativeBuckets, otherNegativeSpans, otherNegativeBuckets) h.NegativeSpans, h.NegativeBuckets = addBuckets(h.Schema, h.ZeroThreshold, false, hNegativeSpans, hNegativeBuckets, otherNegativeSpans, otherNegativeBuckets)
return h, nil return h, counterResetCollision, nil
} }
// Sub works like Add but subtracts the other histogram. // Sub works like Add but subtracts the other histogram.
func (h *FloatHistogram) Sub(other *FloatHistogram) (*FloatHistogram, error) { func (h *FloatHistogram) Sub(other *FloatHistogram) (res *FloatHistogram, counterResetCollision bool, err error) {
if h.UsesCustomBuckets() != other.UsesCustomBuckets() { if h.UsesCustomBuckets() != other.UsesCustomBuckets() {
return nil, ErrHistogramsIncompatibleSchema return nil, false, ErrHistogramsIncompatibleSchema
} }
if h.UsesCustomBuckets() && !FloatBucketsMatch(h.CustomValues, other.CustomValues) { if h.UsesCustomBuckets() && !FloatBucketsMatch(h.CustomValues, other.CustomValues) {
return nil, ErrHistogramsIncompatibleBounds return nil, false, ErrHistogramsIncompatibleBounds
} }
// Prevent counter resets when subtracting as this might decrease counters. counterResetCollision = hasCounterResetCollision(h, other)
h.CounterResetHint = GaugeType h.CounterResetHint = GaugeType
if !h.UsesCustomBuckets() { if !h.UsesCustomBuckets() {
@ -442,7 +446,7 @@ func (h *FloatHistogram) Sub(other *FloatHistogram) (*FloatHistogram, error) {
if h.UsesCustomBuckets() { if h.UsesCustomBuckets() {
h.PositiveSpans, h.PositiveBuckets = addBuckets(h.Schema, h.ZeroThreshold, true, hPositiveSpans, hPositiveBuckets, otherPositiveSpans, otherPositiveBuckets) h.PositiveSpans, h.PositiveBuckets = addBuckets(h.Schema, h.ZeroThreshold, true, hPositiveSpans, hPositiveBuckets, otherPositiveSpans, otherPositiveBuckets)
return h, nil return h, counterResetCollision, nil
} }
var ( var (
@ -465,7 +469,14 @@ func (h *FloatHistogram) Sub(other *FloatHistogram) (*FloatHistogram, error) {
h.PositiveSpans, h.PositiveBuckets = addBuckets(h.Schema, h.ZeroThreshold, true, hPositiveSpans, hPositiveBuckets, otherPositiveSpans, otherPositiveBuckets) 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) h.NegativeSpans, h.NegativeBuckets = addBuckets(h.Schema, h.ZeroThreshold, true, hNegativeSpans, hNegativeBuckets, otherNegativeSpans, otherNegativeBuckets)
return h, nil return h, counterResetCollision, nil
}
// hasCounterResetCollision returns true iff one of two histogram indicates
// a counter reset (CounterReset) while the other indicates no reset (NotCounterReset).
func hasCounterResetCollision(a, b *FloatHistogram) bool {
return a.CounterResetHint == CounterReset && b.CounterResetHint == NotCounterReset ||
a.CounterResetHint == NotCounterReset && b.CounterResetHint == CounterReset
} }
// Equals returns true if the given float histogram matches exactly. // Equals returns true if the given float histogram matches exactly.

View File

@ -1648,10 +1648,11 @@ func TestFloatHistogramAdd(t *testing.T) {
name string name string
in1, in2, expected *FloatHistogram in1, in2, expected *FloatHistogram
expErrMsg string expErrMsg string
expCounterResetCollision bool
}{ }{
{ {
"same bucket layout", name: "same bucket layout",
&FloatHistogram{ in1: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 11, ZeroCount: 11,
Count: 30, Count: 30,
@ -1661,7 +1662,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{3, 2}, {3, 2}}, NegativeSpans: []Span{{3, 2}, {3, 2}},
NegativeBuckets: []float64{3, 1, 5, 6}, NegativeBuckets: []float64{3, 1, 5, 6},
}, },
&FloatHistogram{ in2: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 8, ZeroCount: 8,
Count: 21, Count: 21,
@ -1671,7 +1672,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{3, 2}, {3, 2}}, NegativeSpans: []Span{{3, 2}, {3, 2}},
NegativeBuckets: []float64{1, 1, 4, 4}, NegativeBuckets: []float64{1, 1, 4, 4},
}, },
&FloatHistogram{ expected: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 19, ZeroCount: 19,
Count: 51, Count: 51,
@ -1681,11 +1682,10 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{3, 2}, {3, 2}}, NegativeSpans: []Span{{3, 2}, {3, 2}},
NegativeBuckets: []float64{4, 2, 9, 10}, NegativeBuckets: []float64{4, 2, 9, 10},
}, },
"",
}, },
{ {
"same bucket layout, defined differently", name: "same bucket layout, defined differently",
&FloatHistogram{ in1: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 11, ZeroCount: 11,
Count: 30, Count: 30,
@ -1695,7 +1695,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{3, 2}, {3, 2}}, NegativeSpans: []Span{{3, 2}, {3, 2}},
NegativeBuckets: []float64{3, 1, 5, 6}, NegativeBuckets: []float64{3, 1, 5, 6},
}, },
&FloatHistogram{ in2: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 8, ZeroCount: 8,
Count: 21, Count: 21,
@ -1705,7 +1705,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{3, 7}}, NegativeSpans: []Span{{3, 7}},
NegativeBuckets: []float64{1, 1, 0, 0, 0, 4, 4}, NegativeBuckets: []float64{1, 1, 0, 0, 0, 4, 4},
}, },
&FloatHistogram{ expected: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 19, ZeroCount: 19,
Count: 51, Count: 51,
@ -1715,11 +1715,10 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{3, 5}, {0, 2}}, NegativeSpans: []Span{{3, 5}, {0, 2}},
NegativeBuckets: []float64{4, 2, 0, 0, 0, 9, 10}, NegativeBuckets: []float64{4, 2, 0, 0, 0, 9, 10},
}, },
"",
}, },
{ {
"non-overlapping spans", name: "non-overlapping spans",
&FloatHistogram{ in1: &FloatHistogram{
ZeroThreshold: 0.001, ZeroThreshold: 0.001,
ZeroCount: 11, ZeroCount: 11,
Count: 30, Count: 30,
@ -1729,7 +1728,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{3, 2}, {3, 2}}, NegativeSpans: []Span{{3, 2}, {3, 2}},
NegativeBuckets: []float64{3, 1, 5, 6}, NegativeBuckets: []float64{3, 1, 5, 6},
}, },
&FloatHistogram{ in2: &FloatHistogram{
ZeroThreshold: 0.001, ZeroThreshold: 0.001,
ZeroCount: 8, ZeroCount: 8,
Count: 21, Count: 21,
@ -1739,7 +1738,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{-9, 2}, {3, 2}}, NegativeSpans: []Span{{-9, 2}, {3, 2}},
NegativeBuckets: []float64{1, 1, 4, 4}, NegativeBuckets: []float64{1, 1, 4, 4},
}, },
&FloatHistogram{ expected: &FloatHistogram{
ZeroThreshold: 0.001, ZeroThreshold: 0.001,
ZeroCount: 19, ZeroCount: 19,
Count: 51, Count: 51,
@ -1749,11 +1748,10 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{-9, 2}, {3, 2}, {5, 2}, {3, 2}}, NegativeSpans: []Span{{-9, 2}, {3, 2}, {5, 2}, {3, 2}},
NegativeBuckets: []float64{1, 1, 4, 4, 3, 1, 5, 6}, NegativeBuckets: []float64{1, 1, 4, 4, 3, 1, 5, 6},
}, },
"",
}, },
{ {
"non-overlapping spans inverted order", name: "non-overlapping spans inverted order",
&FloatHistogram{ in1: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 8, ZeroCount: 8,
Count: 21, Count: 21,
@ -1763,7 +1761,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{-6, 2}, {1, 2}}, NegativeSpans: []Span{{-6, 2}, {1, 2}},
NegativeBuckets: []float64{1, 1, 4, 4}, NegativeBuckets: []float64{1, 1, 4, 4},
}, },
&FloatHistogram{ in2: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 11, ZeroCount: 11,
Count: 30, Count: 30,
@ -1773,7 +1771,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{3, 2}, {3, 2}}, NegativeSpans: []Span{{3, 2}, {3, 2}},
NegativeBuckets: []float64{3, 1, 5, 6}, NegativeBuckets: []float64{3, 1, 5, 6},
}, },
&FloatHistogram{ expected: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 19, ZeroCount: 19,
Count: 51, Count: 51,
@ -1783,11 +1781,10 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{-6, 2}, {1, 2}, {4, 2}, {3, 2}}, NegativeSpans: []Span{{-6, 2}, {1, 2}, {4, 2}, {3, 2}},
NegativeBuckets: []float64{1, 1, 4, 4, 3, 1, 5, 6}, NegativeBuckets: []float64{1, 1, 4, 4, 3, 1, 5, 6},
}, },
"",
}, },
{ {
"overlapping spans", name: "overlapping spans",
&FloatHistogram{ in1: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 11, ZeroCount: 11,
Count: 30, Count: 30,
@ -1797,7 +1794,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{3, 2}, {3, 2}}, NegativeSpans: []Span{{3, 2}, {3, 2}},
NegativeBuckets: []float64{3, 1, 5, 6}, NegativeBuckets: []float64{3, 1, 5, 6},
}, },
&FloatHistogram{ in2: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 8, ZeroCount: 8,
Count: 21, Count: 21,
@ -1807,7 +1804,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{4, 2}, {1, 2}}, NegativeSpans: []Span{{4, 2}, {1, 2}},
NegativeBuckets: []float64{1, 1, 4, 4}, NegativeBuckets: []float64{1, 1, 4, 4},
}, },
&FloatHistogram{ expected: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 19, ZeroCount: 19,
Count: 51, Count: 51,
@ -1817,11 +1814,10 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{3, 3}, {1, 3}}, NegativeSpans: []Span{{3, 3}, {1, 3}},
NegativeBuckets: []float64{3, 2, 1, 4, 9, 6}, NegativeBuckets: []float64{3, 2, 1, 4, 9, 6},
}, },
"",
}, },
{ {
"overlapping spans inverted order", name: "overlapping spans inverted order",
&FloatHistogram{ in1: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 8, ZeroCount: 8,
Count: 21, Count: 21,
@ -1831,7 +1827,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{4, 2}, {1, 2}}, NegativeSpans: []Span{{4, 2}, {1, 2}},
NegativeBuckets: []float64{1, 1, 4, 4}, NegativeBuckets: []float64{1, 1, 4, 4},
}, },
&FloatHistogram{ in2: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 11, ZeroCount: 11,
Count: 30, Count: 30,
@ -1841,7 +1837,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{3, 2}, {3, 2}}, NegativeSpans: []Span{{3, 2}, {3, 2}},
NegativeBuckets: []float64{3, 1, 5, 6}, NegativeBuckets: []float64{3, 1, 5, 6},
}, },
&FloatHistogram{ expected: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 19, ZeroCount: 19,
Count: 51, Count: 51,
@ -1851,11 +1847,10 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{3, 3}, {1, 3}}, NegativeSpans: []Span{{3, 3}, {1, 3}},
NegativeBuckets: []float64{3, 2, 1, 4, 9, 6}, NegativeBuckets: []float64{3, 2, 1, 4, 9, 6},
}, },
"",
}, },
{ {
"schema change", name: "schema change",
&FloatHistogram{ in1: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 8, ZeroCount: 8,
Count: 21, Count: 21,
@ -1866,7 +1861,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{4, 2}, {1, 2}}, NegativeSpans: []Span{{4, 2}, {1, 2}},
NegativeBuckets: []float64{1, 1, 4, 4}, NegativeBuckets: []float64{1, 1, 4, 4},
}, },
&FloatHistogram{ in2: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 11, ZeroCount: 11,
Count: 30, Count: 30,
@ -1877,7 +1872,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{6, 3}, {6, 4}}, NegativeSpans: []Span{{6, 3}, {6, 4}},
NegativeBuckets: []float64{3, 0.5, 0.5, 2, 3, 2, 4}, NegativeBuckets: []float64{3, 0.5, 0.5, 2, 3, 2, 4},
}, },
&FloatHistogram{ expected: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 19, ZeroCount: 19,
Count: 51, Count: 51,
@ -1887,11 +1882,10 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{3, 3}, {1, 3}}, NegativeSpans: []Span{{3, 3}, {1, 3}},
NegativeBuckets: []float64{3, 2, 1, 4, 9, 6}, NegativeBuckets: []float64{3, 2, 1, 4, 9, 6},
}, },
"",
}, },
{ {
"larger zero bucket in first histogram", name: "larger zero bucket in first histogram",
&FloatHistogram{ in1: &FloatHistogram{
ZeroThreshold: 1, ZeroThreshold: 1,
ZeroCount: 17, ZeroCount: 17,
Count: 21, Count: 21,
@ -1901,7 +1895,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{4, 2}, {1, 2}}, NegativeSpans: []Span{{4, 2}, {1, 2}},
NegativeBuckets: []float64{1, 1, 4, 4}, NegativeBuckets: []float64{1, 1, 4, 4},
}, },
&FloatHistogram{ in2: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 11, ZeroCount: 11,
Count: 30, Count: 30,
@ -1911,7 +1905,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{3, 2}, {3, 2}}, NegativeSpans: []Span{{3, 2}, {3, 2}},
NegativeBuckets: []float64{3, 1, 5, 6}, NegativeBuckets: []float64{3, 1, 5, 6},
}, },
&FloatHistogram{ expected: &FloatHistogram{
ZeroThreshold: 1, ZeroThreshold: 1,
ZeroCount: 29, ZeroCount: 29,
Count: 51, Count: 51,
@ -1921,11 +1915,10 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{3, 3}, {1, 3}}, NegativeSpans: []Span{{3, 3}, {1, 3}},
NegativeBuckets: []float64{3, 2, 1, 4, 9, 6}, NegativeBuckets: []float64{3, 2, 1, 4, 9, 6},
}, },
"",
}, },
{ {
"larger zero bucket in second histogram", name: "larger zero bucket in second histogram",
&FloatHistogram{ in1: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 11, ZeroCount: 11,
Count: 30, Count: 30,
@ -1935,7 +1928,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{3, 2}, {3, 2}}, NegativeSpans: []Span{{3, 2}, {3, 2}},
NegativeBuckets: []float64{3, 1, 5, 6}, NegativeBuckets: []float64{3, 1, 5, 6},
}, },
&FloatHistogram{ in2: &FloatHistogram{
ZeroThreshold: 1, ZeroThreshold: 1,
ZeroCount: 17, ZeroCount: 17,
Count: 21, Count: 21,
@ -1945,7 +1938,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{4, 2}, {1, 2}}, NegativeSpans: []Span{{4, 2}, {1, 2}},
NegativeBuckets: []float64{1, 1, 4, 4}, NegativeBuckets: []float64{1, 1, 4, 4},
}, },
&FloatHistogram{ expected: &FloatHistogram{
ZeroThreshold: 1, ZeroThreshold: 1,
ZeroCount: 29, ZeroCount: 29,
Count: 51, Count: 51,
@ -1955,11 +1948,10 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{3, 3}, {1, 3}}, NegativeSpans: []Span{{3, 3}, {1, 3}},
NegativeBuckets: []float64{3, 2, 1, 4, 9, 6}, NegativeBuckets: []float64{3, 2, 1, 4, 9, 6},
}, },
"",
}, },
{ {
"larger zero threshold in first histogram ends up inside a populated bucket of second histogram", name: "larger zero threshold in first histogram ends up inside a populated bucket of second histogram",
&FloatHistogram{ in1: &FloatHistogram{
ZeroThreshold: 0.2, ZeroThreshold: 0.2,
ZeroCount: 17, ZeroCount: 17,
Count: 21, Count: 21,
@ -1969,7 +1961,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{4, 2}, {1, 2}}, NegativeSpans: []Span{{4, 2}, {1, 2}},
NegativeBuckets: []float64{1, 1, 4, 4}, NegativeBuckets: []float64{1, 1, 4, 4},
}, },
&FloatHistogram{ in2: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 11, ZeroCount: 11,
Count: 30, Count: 30,
@ -1979,7 +1971,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{3, 2}, {3, 2}}, NegativeSpans: []Span{{3, 2}, {3, 2}},
NegativeBuckets: []float64{3, 1, 5, 6}, NegativeBuckets: []float64{3, 1, 5, 6},
}, },
&FloatHistogram{ expected: &FloatHistogram{
ZeroThreshold: 0.25, ZeroThreshold: 0.25,
ZeroCount: 29, ZeroCount: 29,
Count: 51, Count: 51,
@ -1989,11 +1981,10 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{3, 3}, {1, 3}}, NegativeSpans: []Span{{3, 3}, {1, 3}},
NegativeBuckets: []float64{3, 2, 1, 4, 9, 6}, NegativeBuckets: []float64{3, 2, 1, 4, 9, 6},
}, },
"",
}, },
{ {
"larger zero threshold in second histogram ends up inside a populated bucket of first histogram", name: "larger zero threshold in second histogram ends up inside a populated bucket of first histogram",
&FloatHistogram{ in1: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 11, ZeroCount: 11,
Count: 30, Count: 30,
@ -2003,7 +1994,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{3, 2}, {3, 2}}, NegativeSpans: []Span{{3, 2}, {3, 2}},
NegativeBuckets: []float64{3, 1, 5, 6}, NegativeBuckets: []float64{3, 1, 5, 6},
}, },
&FloatHistogram{ in2: &FloatHistogram{
ZeroThreshold: 0.2, ZeroThreshold: 0.2,
ZeroCount: 17, ZeroCount: 17,
Count: 21, Count: 21,
@ -2013,7 +2004,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{4, 2}, {1, 2}}, NegativeSpans: []Span{{4, 2}, {1, 2}},
NegativeBuckets: []float64{1, 1, 4, 4}, NegativeBuckets: []float64{1, 1, 4, 4},
}, },
&FloatHistogram{ expected: &FloatHistogram{
ZeroThreshold: 0.25, ZeroThreshold: 0.25,
ZeroCount: 29, ZeroCount: 29,
Count: 51, Count: 51,
@ -2023,11 +2014,10 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{3, 3}, {1, 3}}, NegativeSpans: []Span{{3, 3}, {1, 3}},
NegativeBuckets: []float64{3, 2, 1, 4, 9, 6}, NegativeBuckets: []float64{3, 2, 1, 4, 9, 6},
}, },
"",
}, },
{ {
"schema change combined with larger zero bucket in second histogram", name: "schema change combined with larger zero bucket in second histogram",
&FloatHistogram{ in1: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 8, ZeroCount: 8,
Count: 21, Count: 21,
@ -2038,7 +2028,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{4, 2}, {1, 2}}, NegativeSpans: []Span{{4, 2}, {1, 2}},
NegativeBuckets: []float64{1, 1, 4, 4}, NegativeBuckets: []float64{1, 1, 4, 4},
}, },
&FloatHistogram{ in2: &FloatHistogram{
ZeroThreshold: 0.25, ZeroThreshold: 0.25,
ZeroCount: 12, ZeroCount: 12,
Count: 30, Count: 30,
@ -2049,7 +2039,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{6, 3}, {6, 4}}, NegativeSpans: []Span{{6, 3}, {6, 4}},
NegativeBuckets: []float64{3, 0.5, 0.5, 2, 3, 2, 4}, NegativeBuckets: []float64{3, 0.5, 0.5, 2, 3, 2, 4},
}, },
&FloatHistogram{ expected: &FloatHistogram{
ZeroThreshold: 0.25, ZeroThreshold: 0.25,
ZeroCount: 22, ZeroCount: 22,
Count: 51, Count: 51,
@ -2059,11 +2049,10 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{3, 3}, {1, 3}}, NegativeSpans: []Span{{3, 3}, {1, 3}},
NegativeBuckets: []float64{3, 2, 1, 4, 9, 6}, NegativeBuckets: []float64{3, 2, 1, 4, 9, 6},
}, },
"",
}, },
{ {
"schema change combined with larger zero bucket in first histogram", name: "schema change combined with larger zero bucket in first histogram",
&FloatHistogram{ in1: &FloatHistogram{
ZeroThreshold: 0.25, ZeroThreshold: 0.25,
ZeroCount: 8, ZeroCount: 8,
Count: 21, Count: 21,
@ -2074,7 +2063,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{4, 2}, {1, 2}}, NegativeSpans: []Span{{4, 2}, {1, 2}},
NegativeBuckets: []float64{1, 1, 4, 4}, NegativeBuckets: []float64{1, 1, 4, 4},
}, },
&FloatHistogram{ in2: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 11, ZeroCount: 11,
Count: 30, Count: 30,
@ -2085,7 +2074,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{6, 3}, {6, 4}}, NegativeSpans: []Span{{6, 3}, {6, 4}},
NegativeBuckets: []float64{3, 0.5, 0.5, 2, 3, 2, 4}, NegativeBuckets: []float64{3, 0.5, 0.5, 2, 3, 2, 4},
}, },
&FloatHistogram{ expected: &FloatHistogram{
ZeroThreshold: 0.25, ZeroThreshold: 0.25,
ZeroCount: 20, ZeroCount: 20,
Count: 51, Count: 51,
@ -2095,11 +2084,10 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{3, 3}, {1, 3}}, NegativeSpans: []Span{{3, 3}, {1, 3}},
NegativeBuckets: []float64{3, 2, 1, 4, 9, 6}, NegativeBuckets: []float64{3, 2, 1, 4, 9, 6},
}, },
"",
}, },
{ {
"same custom bucket layout", name: "same custom bucket layout",
&FloatHistogram{ in1: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 15, Count: 15,
Sum: 2.345, Sum: 2.345,
@ -2107,7 +2095,7 @@ func TestFloatHistogramAdd(t *testing.T) {
PositiveBuckets: []float64{1, 0, 3, 4, 7}, PositiveBuckets: []float64{1, 0, 3, 4, 7},
CustomValues: []float64{1, 2, 3, 4}, CustomValues: []float64{1, 2, 3, 4},
}, },
&FloatHistogram{ in2: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 11, Count: 11,
Sum: 1.234, Sum: 1.234,
@ -2115,7 +2103,7 @@ func TestFloatHistogramAdd(t *testing.T) {
PositiveBuckets: []float64{0, 0, 2, 3, 6}, PositiveBuckets: []float64{0, 0, 2, 3, 6},
CustomValues: []float64{1, 2, 3, 4}, CustomValues: []float64{1, 2, 3, 4},
}, },
&FloatHistogram{ expected: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 26, Count: 26,
Sum: 3.579, Sum: 3.579,
@ -2123,11 +2111,10 @@ func TestFloatHistogramAdd(t *testing.T) {
PositiveBuckets: []float64{1, 0, 5, 7, 13}, PositiveBuckets: []float64{1, 0, 5, 7, 13},
CustomValues: []float64{1, 2, 3, 4}, CustomValues: []float64{1, 2, 3, 4},
}, },
"",
}, },
{ {
"same custom bucket layout, defined differently", name: "same custom bucket layout, defined differently",
&FloatHistogram{ in1: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 15, Count: 15,
Sum: 2.345, Sum: 2.345,
@ -2135,7 +2122,7 @@ func TestFloatHistogramAdd(t *testing.T) {
PositiveBuckets: []float64{1, 0, 3, 4, 7}, PositiveBuckets: []float64{1, 0, 3, 4, 7},
CustomValues: []float64{1, 2, 3, 4}, CustomValues: []float64{1, 2, 3, 4},
}, },
&FloatHistogram{ in2: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 11, Count: 11,
Sum: 1.234, Sum: 1.234,
@ -2143,7 +2130,7 @@ func TestFloatHistogramAdd(t *testing.T) {
PositiveBuckets: []float64{0, 0, 2, 3, 6}, PositiveBuckets: []float64{0, 0, 2, 3, 6},
CustomValues: []float64{1, 2, 3, 4}, CustomValues: []float64{1, 2, 3, 4},
}, },
&FloatHistogram{ expected: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 26, Count: 26,
Sum: 3.579, Sum: 3.579,
@ -2151,11 +2138,10 @@ func TestFloatHistogramAdd(t *testing.T) {
PositiveBuckets: []float64{1, 0, 5, 7, 13}, PositiveBuckets: []float64{1, 0, 5, 7, 13},
CustomValues: []float64{1, 2, 3, 4}, CustomValues: []float64{1, 2, 3, 4},
}, },
"",
}, },
{ {
"non-overlapping spans with custom buckets", name: "non-overlapping spans with custom buckets",
&FloatHistogram{ in1: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 15, Count: 15,
Sum: 2.345, Sum: 2.345,
@ -2163,7 +2149,7 @@ func TestFloatHistogramAdd(t *testing.T) {
PositiveBuckets: []float64{1, 0, 3, 4, 7}, PositiveBuckets: []float64{1, 0, 3, 4, 7},
CustomValues: []float64{1, 2, 3, 4}, CustomValues: []float64{1, 2, 3, 4},
}, },
&FloatHistogram{ in2: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 20, Count: 20,
Sum: 1.234, Sum: 1.234,
@ -2171,7 +2157,7 @@ func TestFloatHistogramAdd(t *testing.T) {
PositiveBuckets: []float64{5, 4, 2, 3, 6}, PositiveBuckets: []float64{5, 4, 2, 3, 6},
CustomValues: []float64{1, 2, 3, 4}, CustomValues: []float64{1, 2, 3, 4},
}, },
&FloatHistogram{ expected: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 35, Count: 35,
Sum: 3.579, Sum: 3.579,
@ -2179,11 +2165,10 @@ func TestFloatHistogramAdd(t *testing.T) {
PositiveBuckets: []float64{1, 0, 5, 4, 3, 4, 7, 2, 3, 6}, PositiveBuckets: []float64{1, 0, 5, 4, 3, 4, 7, 2, 3, 6},
CustomValues: []float64{1, 2, 3, 4}, CustomValues: []float64{1, 2, 3, 4},
}, },
"",
}, },
{ {
"non-overlapping spans inverted order with custom buckets", name: "non-overlapping spans inverted order with custom buckets",
&FloatHistogram{ in1: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 20, Count: 20,
Sum: 1.234, Sum: 1.234,
@ -2191,7 +2176,7 @@ func TestFloatHistogramAdd(t *testing.T) {
PositiveBuckets: []float64{5, 4, 2, 3, 6}, PositiveBuckets: []float64{5, 4, 2, 3, 6},
CustomValues: []float64{1, 2, 3, 4}, CustomValues: []float64{1, 2, 3, 4},
}, },
&FloatHistogram{ in2: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 15, Count: 15,
Sum: 2.345, Sum: 2.345,
@ -2199,7 +2184,7 @@ func TestFloatHistogramAdd(t *testing.T) {
PositiveBuckets: []float64{1, 0, 3, 4, 7}, PositiveBuckets: []float64{1, 0, 3, 4, 7},
CustomValues: []float64{1, 2, 3, 4}, CustomValues: []float64{1, 2, 3, 4},
}, },
&FloatHistogram{ expected: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 35, Count: 35,
Sum: 3.579, Sum: 3.579,
@ -2207,11 +2192,10 @@ func TestFloatHistogramAdd(t *testing.T) {
PositiveBuckets: []float64{1, 0, 5, 4, 3, 4, 7, 2, 3, 6}, PositiveBuckets: []float64{1, 0, 5, 4, 3, 4, 7, 2, 3, 6},
CustomValues: []float64{1, 2, 3, 4}, CustomValues: []float64{1, 2, 3, 4},
}, },
"",
}, },
{ {
"overlapping spans with custom buckets", name: "overlapping spans with custom buckets",
&FloatHistogram{ in1: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 15, Count: 15,
Sum: 2.345, Sum: 2.345,
@ -2219,7 +2203,7 @@ func TestFloatHistogramAdd(t *testing.T) {
PositiveBuckets: []float64{1, 0, 3, 4, 7}, PositiveBuckets: []float64{1, 0, 3, 4, 7},
CustomValues: []float64{1, 2, 3, 4}, CustomValues: []float64{1, 2, 3, 4},
}, },
&FloatHistogram{ in2: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 27, Count: 27,
Sum: 1.234, Sum: 1.234,
@ -2227,7 +2211,7 @@ func TestFloatHistogramAdd(t *testing.T) {
PositiveBuckets: []float64{5, 4, 2, 3, 6, 2, 5}, PositiveBuckets: []float64{5, 4, 2, 3, 6, 2, 5},
CustomValues: []float64{1, 2, 3, 4}, CustomValues: []float64{1, 2, 3, 4},
}, },
&FloatHistogram{ expected: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 42, Count: 42,
Sum: 3.579, Sum: 3.579,
@ -2235,11 +2219,10 @@ func TestFloatHistogramAdd(t *testing.T) {
PositiveBuckets: []float64{1, 5, 4, 2, 6, 10, 9, 5}, PositiveBuckets: []float64{1, 5, 4, 2, 6, 10, 9, 5},
CustomValues: []float64{1, 2, 3, 4}, CustomValues: []float64{1, 2, 3, 4},
}, },
"",
}, },
{ {
"overlapping spans inverted order with custom buckets", name: "overlapping spans inverted order with custom buckets",
&FloatHistogram{ in1: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 27, Count: 27,
Sum: 1.234, Sum: 1.234,
@ -2247,7 +2230,7 @@ func TestFloatHistogramAdd(t *testing.T) {
PositiveBuckets: []float64{5, 4, 2, 3, 6, 2, 5}, PositiveBuckets: []float64{5, 4, 2, 3, 6, 2, 5},
CustomValues: []float64{1, 2, 3, 4}, CustomValues: []float64{1, 2, 3, 4},
}, },
&FloatHistogram{ in2: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 15, Count: 15,
Sum: 2.345, Sum: 2.345,
@ -2255,7 +2238,7 @@ func TestFloatHistogramAdd(t *testing.T) {
PositiveBuckets: []float64{1, 0, 3, 4, 7}, PositiveBuckets: []float64{1, 0, 3, 4, 7},
CustomValues: []float64{1, 2, 3, 4}, CustomValues: []float64{1, 2, 3, 4},
}, },
&FloatHistogram{ expected: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 42, Count: 42,
Sum: 3.579, Sum: 3.579,
@ -2263,11 +2246,10 @@ func TestFloatHistogramAdd(t *testing.T) {
PositiveBuckets: []float64{1, 5, 4, 2, 6, 10, 9, 5}, PositiveBuckets: []float64{1, 5, 4, 2, 6, 10, 9, 5},
CustomValues: []float64{1, 2, 3, 4}, CustomValues: []float64{1, 2, 3, 4},
}, },
"",
}, },
{ {
"different custom bucket layout", name: "different custom bucket layout",
&FloatHistogram{ in1: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 15, Count: 15,
Sum: 2.345, Sum: 2.345,
@ -2275,7 +2257,7 @@ func TestFloatHistogramAdd(t *testing.T) {
PositiveBuckets: []float64{1, 0, 3, 4, 7}, PositiveBuckets: []float64{1, 0, 3, 4, 7},
CustomValues: []float64{1, 2, 3, 4}, CustomValues: []float64{1, 2, 3, 4},
}, },
&FloatHistogram{ in2: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 11, Count: 11,
Sum: 1.234, Sum: 1.234,
@ -2283,12 +2265,11 @@ func TestFloatHistogramAdd(t *testing.T) {
PositiveBuckets: []float64{0, 0, 2, 3, 6}, PositiveBuckets: []float64{0, 0, 2, 3, 6},
CustomValues: []float64{1, 2, 3, 4, 5}, CustomValues: []float64{1, 2, 3, 4, 5},
}, },
nil, expErrMsg: "cannot apply this operation on custom buckets histograms with different custom bounds",
"cannot apply this operation on custom buckets histograms with different custom bounds",
}, },
{ {
"mix exponential and custom buckets histograms", name: "mix exponential and custom buckets histograms",
&FloatHistogram{ in1: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 8, ZeroCount: 8,
Count: 59, Count: 59,
@ -2299,7 +2280,7 @@ func TestFloatHistogramAdd(t *testing.T) {
NegativeSpans: []Span{{3, 3}, {1, 3}}, NegativeSpans: []Span{{3, 3}, {1, 3}},
NegativeBuckets: []float64{4, 10, 1, 4, 14, 7}, NegativeBuckets: []float64{4, 10, 1, 4, 14, 7},
}, },
&FloatHistogram{ in2: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 11, Count: 11,
Sum: 12, Sum: 12,
@ -2307,20 +2288,32 @@ func TestFloatHistogramAdd(t *testing.T) {
PositiveBuckets: []float64{0, 0, 2, 3, 6}, PositiveBuckets: []float64{0, 0, 2, 3, 6},
CustomValues: []float64{1, 2, 3, 4}, CustomValues: []float64{1, 2, 3, 4},
}, },
nil, expErrMsg: "cannot apply this operation on histograms with a mix of exponential and custom bucket schemas",
"cannot apply this operation on histograms with a mix of exponential and custom bucket schemas", },
{
name: "warn on counter reset hint collision",
in1: &FloatHistogram{
Schema: CustomBucketsSchema,
CounterResetHint: CounterReset,
},
in2: &FloatHistogram{
Schema: CustomBucketsSchema,
CounterResetHint: NotCounterReset,
},
expErrMsg: "",
expCounterResetCollision: true,
}, },
} }
for _, c := range cases { for _, c := range cases {
t.Run(c.name, func(t *testing.T) { t.Run(c.name, func(t *testing.T) {
testHistogramAdd(t, c.in1, c.in2, c.expected, c.expErrMsg) testHistogramAdd(t, c.in1, c.in2, c.expected, c.expErrMsg, c.expCounterResetCollision)
testHistogramAdd(t, c.in2, c.in1, c.expected, c.expErrMsg) testHistogramAdd(t, c.in2, c.in1, c.expected, c.expErrMsg, c.expCounterResetCollision)
}) })
} }
} }
func testHistogramAdd(t *testing.T, a, b, expected *FloatHistogram, expErrMsg string) { func testHistogramAdd(t *testing.T, a, b, expected *FloatHistogram, expErrMsg string, expCounterResetCollision bool) {
var ( var (
aCopy = a.Copy() aCopy = a.Copy()
bCopy = b.Copy() bCopy = b.Copy()
@ -2331,13 +2324,16 @@ func testHistogramAdd(t *testing.T, a, b, expected *FloatHistogram, expErrMsg st
expectedCopy = expected.Copy() expectedCopy = expected.Copy()
} }
res, err := aCopy.Add(bCopy) res, warn, err := aCopy.Add(bCopy)
if expErrMsg != "" { if expErrMsg != "" {
require.EqualError(t, err, expErrMsg) require.EqualError(t, err, expErrMsg)
} else { } else {
require.NoError(t, err) require.NoError(t, err)
} }
// Check that the warnings are correct.
require.Equal(t, expCounterResetCollision, warn)
if expected != nil { if expected != nil {
res.Compact(0) res.Compact(0)
expectedCopy.Compact(0) expectedCopy.Compact(0)
@ -2359,10 +2355,11 @@ func TestFloatHistogramSub(t *testing.T) {
name string name string
in1, in2, expected *FloatHistogram in1, in2, expected *FloatHistogram
expErrMsg string expErrMsg string
expCounterResetCollision bool
}{ }{
{ {
"same bucket layout", name: "same bucket layout",
&FloatHistogram{ in1: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 11, ZeroCount: 11,
Count: 30, Count: 30,
@ -2372,7 +2369,7 @@ func TestFloatHistogramSub(t *testing.T) {
NegativeSpans: []Span{{3, 2}, {3, 2}}, NegativeSpans: []Span{{3, 2}, {3, 2}},
NegativeBuckets: []float64{3, 1, 5, 6}, NegativeBuckets: []float64{3, 1, 5, 6},
}, },
&FloatHistogram{ in2: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 8, ZeroCount: 8,
Count: 21, Count: 21,
@ -2382,7 +2379,7 @@ func TestFloatHistogramSub(t *testing.T) {
NegativeSpans: []Span{{3, 2}, {3, 2}}, NegativeSpans: []Span{{3, 2}, {3, 2}},
NegativeBuckets: []float64{1, 1, 4, 4}, NegativeBuckets: []float64{1, 1, 4, 4},
}, },
&FloatHistogram{ expected: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 3, ZeroCount: 3,
Count: 9, Count: 9,
@ -2393,11 +2390,10 @@ func TestFloatHistogramSub(t *testing.T) {
NegativeBuckets: []float64{2, 0, 1, 2}, NegativeBuckets: []float64{2, 0, 1, 2},
CounterResetHint: GaugeType, CounterResetHint: GaugeType,
}, },
"",
}, },
{ {
"schema change", name: "schema change",
&FloatHistogram{ in1: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 8, ZeroCount: 8,
Count: 59, Count: 59,
@ -2408,7 +2404,7 @@ func TestFloatHistogramSub(t *testing.T) {
NegativeSpans: []Span{{3, 3}, {1, 3}}, NegativeSpans: []Span{{3, 3}, {1, 3}},
NegativeBuckets: []float64{4, 10, 1, 4, 14, 7}, NegativeBuckets: []float64{4, 10, 1, 4, 14, 7},
}, },
&FloatHistogram{ in2: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 2, ZeroCount: 2,
Count: 19, Count: 19,
@ -2419,7 +2415,7 @@ func TestFloatHistogramSub(t *testing.T) {
NegativeSpans: []Span{{6, 3}, {6, 4}}, NegativeSpans: []Span{{6, 3}, {6, 4}},
NegativeBuckets: []float64{3, 0.5, 0.5, 2, 3, 2, 4}, NegativeBuckets: []float64{3, 0.5, 0.5, 2, 3, 2, 4},
}, },
&FloatHistogram{ expected: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 6, ZeroCount: 6,
Count: 40, Count: 40,
@ -2430,11 +2426,10 @@ func TestFloatHistogramSub(t *testing.T) {
NegativeBuckets: []float64{1, 9, 1, 4, 9, 1}, NegativeBuckets: []float64{1, 9, 1, 4, 9, 1},
CounterResetHint: GaugeType, CounterResetHint: GaugeType,
}, },
"",
}, },
{ {
"same custom bucket layout", name: "same custom bucket layout",
&FloatHistogram{ in1: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 15, Count: 15,
Sum: 23, Sum: 23,
@ -2442,7 +2437,7 @@ func TestFloatHistogramSub(t *testing.T) {
PositiveBuckets: []float64{1, 0, 3, 4, 7}, PositiveBuckets: []float64{1, 0, 3, 4, 7},
CustomValues: []float64{1, 2, 3, 4}, CustomValues: []float64{1, 2, 3, 4},
}, },
&FloatHistogram{ in2: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 11, Count: 11,
Sum: 12, Sum: 12,
@ -2450,7 +2445,7 @@ func TestFloatHistogramSub(t *testing.T) {
PositiveBuckets: []float64{0, 0, 2, 3, 6}, PositiveBuckets: []float64{0, 0, 2, 3, 6},
CustomValues: []float64{1, 2, 3, 4}, CustomValues: []float64{1, 2, 3, 4},
}, },
&FloatHistogram{ expected: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 4, Count: 4,
Sum: 11, Sum: 11,
@ -2459,11 +2454,10 @@ func TestFloatHistogramSub(t *testing.T) {
CustomValues: []float64{1, 2, 3, 4}, CustomValues: []float64{1, 2, 3, 4},
CounterResetHint: GaugeType, CounterResetHint: GaugeType,
}, },
"",
}, },
{ {
"different custom bucket layout", name: "different custom bucket layout",
&FloatHistogram{ in1: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 15, Count: 15,
Sum: 23, Sum: 23,
@ -2471,7 +2465,7 @@ func TestFloatHistogramSub(t *testing.T) {
PositiveBuckets: []float64{1, 0, 3, 4, 7}, PositiveBuckets: []float64{1, 0, 3, 4, 7},
CustomValues: []float64{1, 2, 3, 4}, CustomValues: []float64{1, 2, 3, 4},
}, },
&FloatHistogram{ in2: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 11, Count: 11,
Sum: 12, Sum: 12,
@ -2479,12 +2473,11 @@ func TestFloatHistogramSub(t *testing.T) {
PositiveBuckets: []float64{0, 0, 2, 3, 6}, PositiveBuckets: []float64{0, 0, 2, 3, 6},
CustomValues: []float64{1, 2, 3, 4, 5}, CustomValues: []float64{1, 2, 3, 4, 5},
}, },
nil, expErrMsg: "cannot apply this operation on custom buckets histograms with different custom bounds",
"cannot apply this operation on custom buckets histograms with different custom bounds",
}, },
{ {
"mix exponential and custom buckets histograms", name: "mix exponential and custom buckets histograms",
&FloatHistogram{ in1: &FloatHistogram{
ZeroThreshold: 0.01, ZeroThreshold: 0.01,
ZeroCount: 8, ZeroCount: 8,
Count: 59, Count: 59,
@ -2495,7 +2488,7 @@ func TestFloatHistogramSub(t *testing.T) {
NegativeSpans: []Span{{3, 3}, {1, 3}}, NegativeSpans: []Span{{3, 3}, {1, 3}},
NegativeBuckets: []float64{4, 10, 1, 4, 14, 7}, NegativeBuckets: []float64{4, 10, 1, 4, 14, 7},
}, },
&FloatHistogram{ in2: &FloatHistogram{
Schema: CustomBucketsSchema, Schema: CustomBucketsSchema,
Count: 11, Count: 11,
Sum: 12, Sum: 12,
@ -2503,25 +2496,37 @@ func TestFloatHistogramSub(t *testing.T) {
PositiveBuckets: []float64{0, 0, 2, 3, 6}, PositiveBuckets: []float64{0, 0, 2, 3, 6},
CustomValues: []float64{1, 2, 3, 4}, CustomValues: []float64{1, 2, 3, 4},
}, },
nil, expErrMsg: "cannot apply this operation on histograms with a mix of exponential and custom bucket schemas",
"cannot apply this operation on histograms with a mix of exponential and custom bucket schemas", },
{
name: "warn on counter reset hint collision",
in1: &FloatHistogram{
Schema: CustomBucketsSchema,
CounterResetHint: CounterReset,
},
in2: &FloatHistogram{
Schema: CustomBucketsSchema,
CounterResetHint: NotCounterReset,
},
expErrMsg: "",
expCounterResetCollision: true,
}, },
} }
for _, c := range cases { for _, c := range cases {
t.Run(c.name, func(t *testing.T) { t.Run(c.name, func(t *testing.T) {
testFloatHistogramSub(t, c.in1, c.in2, c.expected, c.expErrMsg) testFloatHistogramSub(t, c.in1, c.in2, c.expected, c.expErrMsg, c.expCounterResetCollision)
var expectedNegative *FloatHistogram var expectedNegative *FloatHistogram
if c.expected != nil { if c.expected != nil {
expectedNegative = c.expected.Copy().Mul(-1) expectedNegative = c.expected.Copy().Mul(-1)
} }
testFloatHistogramSub(t, c.in2, c.in1, expectedNegative, c.expErrMsg) testFloatHistogramSub(t, c.in2, c.in1, expectedNegative, c.expErrMsg, c.expCounterResetCollision)
}) })
} }
} }
func testFloatHistogramSub(t *testing.T, a, b, expected *FloatHistogram, expErrMsg string) { func testFloatHistogramSub(t *testing.T, a, b, expected *FloatHistogram, expErrMsg string, expCounterResetCollision bool) {
var ( var (
aCopy = a.Copy() aCopy = a.Copy()
bCopy = b.Copy() bCopy = b.Copy()
@ -2532,7 +2537,7 @@ func testFloatHistogramSub(t *testing.T, a, b, expected *FloatHistogram, expErrM
expectedCopy = expected.Copy() expectedCopy = expected.Copy()
} }
res, err := aCopy.Sub(bCopy) res, warn, err := aCopy.Sub(bCopy)
if expErrMsg != "" { if expErrMsg != "" {
require.EqualError(t, err, expErrMsg) require.EqualError(t, err, expErrMsg)
} else { } else {
@ -2550,6 +2555,9 @@ func testFloatHistogramSub(t *testing.T, a, b, expected *FloatHistogram, expErrM
// Check that the argument was not mutated. // Check that the argument was not mutated.
require.Equal(t, b, bCopy) require.Equal(t, b, bCopy)
// Check that the warnings are correct.
require.Equal(t, expCounterResetCollision, warn)
} }
} }

View File

@ -2830,7 +2830,7 @@ func (ev *evaluator) VectorscalarBinop(op parser.ItemType, lhs Vector, rhs Scala
lh, rh = rh, lh lh, rh = rh, lh
} }
float, histogram, keep, err := vectorElemBinop(op, lf, rf, lh, rh, pos) float, histogram, keep, err := vectorElemBinop(op, lf, rf, lh, rh, pos)
if err != nil { if err != nil && !errors.Is(err, annotations.PromQLWarning) {
lastErr = err lastErr = err
continue continue
} }
@ -2955,17 +2955,23 @@ func vectorElemBinop(op parser.ItemType, lhs, rhs float64, hlhs, hrhs *histogram
{ {
switch op { switch op {
case parser.ADD: case parser.ADD:
res, err := hlhs.Copy().Add(hrhs) res, counterResetCollision, err := hlhs.Copy().Add(hrhs)
if err != nil { if err != nil {
return 0, nil, false, err return 0, nil, false, err
} }
return 0, res.Compact(0), true, nil if counterResetCollision {
err = annotations.NewHistogramCounterResetCollisionWarning(pos)
}
return 0, res.Compact(0), true, err
case parser.SUB: case parser.SUB:
res, err := hlhs.Copy().Sub(hrhs) res, counterResetCollision, err := hlhs.Copy().Sub(hrhs)
if err != nil { if err != nil {
return 0, nil, false, err return 0, nil, false, err
} }
return 0, res.Compact(0), true, nil if counterResetCollision {
err = annotations.NewHistogramCounterResetCollisionWarning(pos)
}
return 0, res.Compact(0), true, err
case parser.EQLC: case parser.EQLC:
// This operation expects that both histograms are compacted. // This operation expects that both histograms are compacted.
return 0, hlhs, hlhs.Equals(hrhs), nil return 0, hlhs, hlhs.Equals(hrhs), nil
@ -3078,7 +3084,7 @@ func (ev *evaluator) aggregation(e *parser.AggregateExpr, q float64, inputMatrix
if h != nil { if h != nil {
group.hasHistogram = true group.hasHistogram = true
if group.histogramValue != nil { if group.histogramValue != nil {
_, err := group.histogramValue.Add(h) _, _, err := group.histogramValue.Add(h)
if err != nil { if err != nil {
handleAggregationError(err, e, inputMatrix[si].Metric.Get(model.MetricNameLabel), &annos) handleAggregationError(err, e, inputMatrix[si].Metric.Get(model.MetricNameLabel), &annos)
group.incompatibleHistograms = true group.incompatibleHistograms = true
@ -3131,13 +3137,13 @@ func (ev *evaluator) aggregation(e *parser.AggregateExpr, q float64, inputMatrix
if group.histogramValue != nil { if group.histogramValue != nil {
left := h.Copy().Div(group.groupCount) left := h.Copy().Div(group.groupCount)
right := group.histogramValue.Copy().Div(group.groupCount) right := group.histogramValue.Copy().Div(group.groupCount)
toAdd, err := left.Sub(right) toAdd, _, err := left.Sub(right)
if err != nil { if err != nil {
handleAggregationError(err, e, inputMatrix[si].Metric.Get(model.MetricNameLabel), &annos) handleAggregationError(err, e, inputMatrix[si].Metric.Get(model.MetricNameLabel), &annos)
group.incompatibleHistograms = true group.incompatibleHistograms = true
continue continue
} }
_, err = group.histogramValue.Add(toAdd) _, _, err = group.histogramValue.Add(toAdd)
if err != nil { if err != nil {
handleAggregationError(err, e, inputMatrix[si].Metric.Get(model.MetricNameLabel), &annos) handleAggregationError(err, e, inputMatrix[si].Metric.Get(model.MetricNameLabel), &annos)
group.incompatibleHistograms = true group.incompatibleHistograms = true

View File

@ -21,7 +21,9 @@ import (
"github.com/prometheus/common/promslog" "github.com/prometheus/common/promslog"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/promql/parser" "github.com/prometheus/prometheus/promql/parser"
"github.com/prometheus/prometheus/promql/parser/posrange"
"github.com/prometheus/prometheus/util/annotations" "github.com/prometheus/prometheus/util/annotations"
) )
@ -77,3 +79,65 @@ func TestRecoverEvaluatorErrorWithWarnings(t *testing.T) {
panic(e) panic(e)
} }
func TestVectorElemBinop_Histograms(t *testing.T) {
testCases := []struct {
name string
op parser.ItemType
hlhs, hrhs *histogram.FloatHistogram
want *histogram.FloatHistogram
wantErr error
}{
{
name: "ADD with unknown counter reset hints",
op: parser.ADD,
hlhs: &histogram.FloatHistogram{},
hrhs: &histogram.FloatHistogram{},
want: &histogram.FloatHistogram{},
},
{
name: "ADD with mixed counter reset hints",
op: parser.ADD,
hlhs: &histogram.FloatHistogram{
CounterResetHint: histogram.CounterReset,
},
hrhs: &histogram.FloatHistogram{
CounterResetHint: histogram.NotCounterReset,
},
want: &histogram.FloatHistogram{},
wantErr: annotations.HistogramCounterResetCollisionWarning,
},
{
name: "SUB with unknown counter reset hints",
op: parser.SUB,
hlhs: &histogram.FloatHistogram{},
hrhs: &histogram.FloatHistogram{},
want: &histogram.FloatHistogram{
CounterResetHint: histogram.GaugeType,
},
},
{
name: "SUB with mixed counter reset hints",
op: parser.SUB,
hlhs: &histogram.FloatHistogram{
CounterResetHint: histogram.CounterReset,
},
hrhs: &histogram.FloatHistogram{
CounterResetHint: histogram.NotCounterReset,
},
want: &histogram.FloatHistogram{},
wantErr: annotations.HistogramCounterResetCollisionWarning,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, got, _, err := vectorElemBinop(tc.op, 0, 0, tc.hlhs, tc.hrhs, posrange.PositionRange{})
if tc.wantErr != nil {
require.ErrorIs(t, err, tc.wantErr)
return
}
require.NoError(t, err)
require.Equal(t, tc.want, got)
})
}
}

View File

@ -254,7 +254,7 @@ func histogramRate(points []HPoint, isCounter bool, metricName string, pos posra
} }
h := last.CopyToSchema(minSchema) h := last.CopyToSchema(minSchema)
_, err := h.Sub(prev) _, counterResetCollision, err := h.Sub(prev)
if err != nil { if err != nil {
if errors.Is(err, histogram.ErrHistogramsIncompatibleSchema) { if errors.Is(err, histogram.ErrHistogramsIncompatibleSchema) {
return nil, annotations.New().Add(annotations.NewMixedExponentialCustomHistogramsWarning(metricName, pos)) return nil, annotations.New().Add(annotations.NewMixedExponentialCustomHistogramsWarning(metricName, pos))
@ -263,12 +263,16 @@ func histogramRate(points []HPoint, isCounter bool, metricName string, pos posra
} }
} }
if counterResetCollision {
annos.Add(annotations.NewHistogramCounterResetCollisionWarning(pos))
}
if isCounter { if isCounter {
// Second iteration to deal with counter resets. // Second iteration to deal with counter resets.
for _, currPoint := range points[1:] { for _, currPoint := range points[1:] {
curr := currPoint.H curr := currPoint.H
if curr.DetectReset(prev) { if curr.DetectReset(prev) {
_, err := h.Add(prev) _, counterResetCollision, err := h.Add(prev)
if err != nil { if err != nil {
if errors.Is(err, histogram.ErrHistogramsIncompatibleSchema) { if errors.Is(err, histogram.ErrHistogramsIncompatibleSchema) {
return nil, annotations.New().Add(annotations.NewMixedExponentialCustomHistogramsWarning(metricName, pos)) return nil, annotations.New().Add(annotations.NewMixedExponentialCustomHistogramsWarning(metricName, pos))
@ -276,6 +280,9 @@ func histogramRate(points []HPoint, isCounter bool, metricName string, pos posra
return nil, annotations.New().Add(annotations.NewIncompatibleCustomBucketsHistogramsWarning(metricName, pos)) return nil, annotations.New().Add(annotations.NewIncompatibleCustomBucketsHistogramsWarning(metricName, pos))
} }
} }
if counterResetCollision {
annos.Add(annotations.NewHistogramCounterResetCollisionWarning(pos))
}
} }
prev = curr prev = curr
} }
@ -389,12 +396,15 @@ func instantValue(vals Matrix, args parser.Expressions, out Vector, isRate bool)
annos.Add(annotations.NewNativeHistogramNotGaugeWarning(metricName, args.PositionRange())) annos.Add(annotations.NewNativeHistogramNotGaugeWarning(metricName, args.PositionRange()))
} }
if !isRate || !ss[1].H.DetectReset(ss[0].H) { if !isRate || !ss[1].H.DetectReset(ss[0].H) {
_, err := resultSample.H.Sub(ss[0].H) _, counterResetCollision, err := resultSample.H.Sub(ss[0].H)
if errors.Is(err, histogram.ErrHistogramsIncompatibleSchema) { if errors.Is(err, histogram.ErrHistogramsIncompatibleSchema) {
return out, annos.Add(annotations.NewMixedExponentialCustomHistogramsWarning(metricName, args.PositionRange())) return out, annos.Add(annotations.NewMixedExponentialCustomHistogramsWarning(metricName, args.PositionRange()))
} else if errors.Is(err, histogram.ErrHistogramsIncompatibleBounds) { } else if errors.Is(err, histogram.ErrHistogramsIncompatibleBounds) {
return out, annos.Add(annotations.NewIncompatibleCustomBucketsHistogramsWarning(metricName, args.PositionRange())) return out, annos.Add(annotations.NewIncompatibleCustomBucketsHistogramsWarning(metricName, args.PositionRange()))
} }
if counterResetCollision {
annos.Add(annotations.NewHistogramCounterResetCollisionWarning(args.PositionRange()))
}
} }
resultSample.H.CounterResetHint = histogram.GaugeType resultSample.H.CounterResetHint = histogram.GaugeType
resultSample.H.Compact(0) resultSample.H.Compact(0)
@ -699,20 +709,27 @@ func funcAvgOverTime(_ []Vector, matrixVal Matrix, args parser.Expressions, enh
// the current implementation is accurate enough for practical purposes. // the current implementation is accurate enough for practical purposes.
if len(firstSeries.Floats) == 0 { if len(firstSeries.Floats) == 0 {
// The passed values only contain histograms. // The passed values only contain histograms.
var annos annotations.Annotations
vec, err := aggrHistOverTime(matrixVal, enh, func(s Series) (*histogram.FloatHistogram, error) { vec, err := aggrHistOverTime(matrixVal, enh, func(s Series) (*histogram.FloatHistogram, error) {
mean := s.Histograms[0].H.Copy() mean := s.Histograms[0].H.Copy()
for i, h := range s.Histograms[1:] { for i, h := range s.Histograms[1:] {
count := float64(i + 2) count := float64(i + 2)
left := h.H.Copy().Div(count) left := h.H.Copy().Div(count)
right := mean.Copy().Div(count) right := mean.Copy().Div(count)
toAdd, err := left.Sub(right) toAdd, counterResetCollision, err := left.Sub(right)
if err != nil { if err != nil {
return mean, err return mean, err
} }
_, err = mean.Add(toAdd) if counterResetCollision {
annos.Add(annotations.NewHistogramCounterResetCollisionWarning(args[0].PositionRange()))
}
_, counterResetCollision, err = mean.Add(toAdd)
if err != nil { if err != nil {
return mean, err return mean, err
} }
if counterResetCollision {
annos.Add(annotations.NewHistogramCounterResetCollisionWarning(args[0].PositionRange()))
}
} }
return mean, nil return mean, nil
}) })
@ -724,7 +741,7 @@ func funcAvgOverTime(_ []Vector, matrixVal Matrix, args parser.Expressions, enh
return enh.Out, annotations.New().Add(annotations.NewIncompatibleCustomBucketsHistogramsWarning(metricName, args[0].PositionRange())) return enh.Out, annotations.New().Add(annotations.NewIncompatibleCustomBucketsHistogramsWarning(metricName, args[0].PositionRange()))
} }
} }
return vec, nil return vec, annos
} }
return aggrOverTime(matrixVal, enh, func(s Series) float64 { return aggrOverTime(matrixVal, enh, func(s Series) float64 {
var ( var (
@ -901,13 +918,17 @@ func funcSumOverTime(_ []Vector, matrixVal Matrix, args parser.Expressions, enh
} }
if len(firstSeries.Floats) == 0 { if len(firstSeries.Floats) == 0 {
// The passed values only contain histograms. // The passed values only contain histograms.
var annos annotations.Annotations
vec, err := aggrHistOverTime(matrixVal, enh, func(s Series) (*histogram.FloatHistogram, error) { vec, err := aggrHistOverTime(matrixVal, enh, func(s Series) (*histogram.FloatHistogram, error) {
sum := s.Histograms[0].H.Copy() sum := s.Histograms[0].H.Copy()
for _, h := range s.Histograms[1:] { for _, h := range s.Histograms[1:] {
_, err := sum.Add(h.H) _, counterResetCollision, err := sum.Add(h.H)
if err != nil { if err != nil {
return sum, err return sum, err
} }
if counterResetCollision {
annos.Add(annotations.NewHistogramCounterResetCollisionWarning(args[0].PositionRange()))
}
} }
return sum, nil return sum, nil
}) })
@ -919,7 +940,7 @@ func funcSumOverTime(_ []Vector, matrixVal Matrix, args parser.Expressions, enh
return enh.Out, annotations.New().Add(annotations.NewIncompatibleCustomBucketsHistogramsWarning(metricName, args[0].PositionRange())) return enh.Out, annotations.New().Add(annotations.NewIncompatibleCustomBucketsHistogramsWarning(metricName, args[0].PositionRange()))
} }
} }
return vec, nil return vec, annos
} }
return aggrOverTime(matrixVal, enh, func(s Series) float64 { return aggrOverTime(matrixVal, enh, func(s Series) float64 {
var sum, c float64 var sum, c float64

View File

@ -19,6 +19,10 @@ import (
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/promql/parser/posrange"
"github.com/prometheus/prometheus/util/annotations"
) )
func TestKahanSumInc(t *testing.T) { func TestKahanSumInc(t *testing.T) {
@ -79,3 +83,51 @@ func TestKahanSumInc(t *testing.T) {
}) })
} }
} }
func newAnnotations(errs ...error) annotations.Annotations {
var annos annotations.Annotations
for _, err := range errs {
annos.Add(err)
}
return annos
}
// TODO(juliusmh): this test ensures histogramRate sets correct annotations.
// This test can be removed in favor of a user facing promqltest when
// https://github.com/prometheus/prometheus/issues/15346 is resolved.
func TestHistogramRate_Annotations(t *testing.T) {
const metricName = "test"
pos := posrange.PositionRange{}
for _, tc := range []struct {
name string
points []HPoint
wantAnnotations annotations.Annotations
}{
{
name: "empty histograms",
points: []HPoint{
{H: &histogram.FloatHistogram{}},
{H: &histogram.FloatHistogram{}},
},
wantAnnotations: newAnnotations(
annotations.NewNativeHistogramNotGaugeWarning(metricName, pos),
),
},
{
name: "counter reset hint collision",
points: []HPoint{
{H: &histogram.FloatHistogram{CounterResetHint: histogram.NotCounterReset}},
{H: &histogram.FloatHistogram{CounterResetHint: histogram.CounterReset}},
},
wantAnnotations: newAnnotations(
annotations.NewNativeHistogramNotGaugeWarning(metricName, pos),
annotations.NewHistogramCounterResetCollisionWarning(pos),
),
},
} {
t.Run(tc.name, func(t *testing.T) {
_, annos := histogramRate(tc.points, false, metricName, pos)
require.Equal(t, tc.wantAnnotations, annos)
})
}
}

View File

@ -494,13 +494,15 @@ func (p *parser) mergeMaps(left, right *map[string]interface{}) (ret *map[string
func (p *parser) histogramsIncreaseSeries(base, inc *histogram.FloatHistogram, times uint64) ([]SequenceValue, error) { func (p *parser) histogramsIncreaseSeries(base, inc *histogram.FloatHistogram, times uint64) ([]SequenceValue, error) {
return p.histogramsSeries(base, inc, times, func(a, b *histogram.FloatHistogram) (*histogram.FloatHistogram, error) { return p.histogramsSeries(base, inc, times, func(a, b *histogram.FloatHistogram) (*histogram.FloatHistogram, error) {
return a.Add(b) res, _, err := a.Add(b)
return res, err
}) })
} }
func (p *parser) histogramsDecreaseSeries(base, inc *histogram.FloatHistogram, times uint64) ([]SequenceValue, error) { func (p *parser) histogramsDecreaseSeries(base, inc *histogram.FloatHistogram, times uint64) ([]SequenceValue, error) {
return p.histogramsSeries(base, inc, times, func(a, b *histogram.FloatHistogram) (*histogram.FloatHistogram, error) { return p.histogramsSeries(base, inc, times, func(a, b *histogram.FloatHistogram) (*histogram.FloatHistogram, error) {
return a.Sub(b) res, _, err := a.Sub(b)
return res, err
}) })
} }

View File

@ -1505,7 +1505,7 @@ func TestNativeHistogramsInRecordingRules(t *testing.T) {
expHist := hists[0].ToFloat(nil) expHist := hists[0].ToFloat(nil)
for _, h := range hists[1:] { for _, h := range hists[1:] {
expHist, err = expHist.Add(h.ToFloat(nil)) expHist, _, err = expHist.Add(h.ToFloat(nil))
require.NoError(t, err) require.NoError(t, err)
} }

View File

@ -155,6 +155,7 @@ var (
NativeHistogramQuantileNaNResultInfo = fmt.Errorf("%w: input to histogram_quantile has NaN observations, result is NaN", PromQLInfo) NativeHistogramQuantileNaNResultInfo = fmt.Errorf("%w: input to histogram_quantile has NaN observations, result is NaN", PromQLInfo)
NativeHistogramQuantileNaNSkewInfo = fmt.Errorf("%w: input to histogram_quantile has NaN observations, result is skewed higher", PromQLInfo) NativeHistogramQuantileNaNSkewInfo = fmt.Errorf("%w: input to histogram_quantile has NaN observations, result is skewed higher", PromQLInfo)
NativeHistogramFractionNaNsInfo = fmt.Errorf("%w: input to histogram_fraction has NaN observations, which are excluded from all fractions", PromQLInfo) NativeHistogramFractionNaNsInfo = fmt.Errorf("%w: input to histogram_fraction has NaN observations, which are excluded from all fractions", PromQLInfo)
HistogramCounterResetCollisionWarning = fmt.Errorf("%w: conflicting histogram counter resets", PromQLWarning)
) )
type annoErr struct { type annoErr struct {
@ -356,3 +357,12 @@ func NewNativeHistogramFractionNaNsInfo(metricName string, pos posrange.Position
Err: maybeAddMetricName(NativeHistogramFractionNaNsInfo, metricName), Err: maybeAddMetricName(NativeHistogramFractionNaNsInfo, metricName),
} }
} }
// NewHistogramCounterResetCollisionWarning is used when two counter histograms are added or subtracted where one has
// a CounterReset hint and the other has NotCounterReset.
func NewHistogramCounterResetCollisionWarning(pos posrange.PositionRange) error {
return annoErr{
PositionRange: pos,
Err: fmt.Errorf("%w", HistogramCounterResetCollisionWarning),
}
}