diff --git a/model/histogram/histogram.go b/model/histogram/histogram.go index 5a2ddea340..42ce8eb99d 100644 --- a/model/histogram/histogram.go +++ b/model/histogram/histogram.go @@ -14,7 +14,9 @@ package histogram import ( + "fmt" "math" + "strings" ) // Histogram encodes a sparse, high-resolution histogram. See the design @@ -65,7 +67,7 @@ type Span struct { } // Copy returns a deep copy of the Histogram. -func (h Histogram) Copy() Histogram { +func (h Histogram) Copy() *Histogram { c := h if h.PositiveSpans != nil { @@ -85,7 +87,61 @@ func (h Histogram) Copy() Histogram { copy(c.NegativeBuckets, h.NegativeBuckets) } - return c + return &c +} + +// String returns a string representation of the Histogram. +func (h Histogram) String() string { + var sb strings.Builder + fmt.Fprintf(&sb, "{count:%d, sum:%g", h.Count, h.Sum) + + var nBuckets []Bucket + for it := h.NegativeBucketIterator(); it.Next(); { + bucket := it.At() + if bucket.Count != 0 { + nBuckets = append(nBuckets, it.At()) + } + } + for i := len(nBuckets) - 1; i >= 0; i-- { + fmt.Fprintf(&sb, ", %s", nBuckets[i].String()) + } + + if h.ZeroCount != 0 { + fmt.Fprintf(&sb, ", %s", h.ZeroBucket().String()) + } + + for it := h.PositiveBucketIterator(); it.Next(); { + bucket := it.At() + if bucket.Count != 0 { + fmt.Fprintf(&sb, ", %s", bucket.String()) + } + } + + sb.WriteRune('}') + return sb.String() +} + +// ZeroBucket returns the zero bucket. +func (h Histogram) ZeroBucket() Bucket { + return Bucket{ + Lower: -h.ZeroThreshold, + Upper: h.ZeroThreshold, + LowerInclusive: true, + UpperInclusive: true, + Count: h.ZeroCount, + } +} + +// PositiveBucketIterator returns a BucketIterator to iterate over all positive +// buckets in ascending order (starting next to the zero bucket and going up). +func (h Histogram) PositiveBucketIterator() BucketIterator { + return newRegularBucketIterator(&h, true) +} + +// NegativeBucketIterator returns a BucketIterator to iterate over all negative +// buckets in descending order (starting next to the zero bucket and going down). +func (h Histogram) NegativeBucketIterator() BucketIterator { + return newRegularBucketIterator(&h, false) } // CumulativeBucketIterator returns a BucketIterator to iterate over a @@ -96,7 +152,7 @@ func (h Histogram) CumulativeBucketIterator() BucketIterator { if len(h.NegativeBuckets) > 0 { panic("CumulativeIterator called on Histogram with negative buckets") } - return &cumulativeBucketIterator{h: h, posSpansIdx: -1} + return &cumulativeBucketIterator{h: &h, posSpansIdx: -1} } // BucketIterator iterates over the buckets of a Histogram, returning decoded @@ -106,26 +162,126 @@ type BucketIterator interface { Next() bool // At returns the current bucket. At() Bucket - // Err returns the current error. It should be used only after iterator is - // exhausted, that is `Next` or `Seek` returns false. - Err() error } -// Bucket represents a bucket (currently only a cumulative one with an upper -// inclusive bound and a cumulative count). +// Bucket represents a bucket with lower and upper limit and the count of +// samples in the bucket. It also specifies if each limit is inclusive or +// not. (Mathematically, inclusive limits create a closed interval, and +// non-inclusive limits an open interval.) +// +// To represent cumulative buckets, Lower is set to -Inf, and the Count is then +// cumulative (including the counts of all buckets for smaller values). type Bucket struct { - Upper float64 - Count uint64 + Lower, Upper float64 + LowerInclusive, UpperInclusive bool + Count uint64 + Index int32 // Index within schema. To easily compare buckets that share the same schema. +} + +// String returns a string representation, using the usual mathematical notation +// of '['/']' for inclusive bounds and '('/')' for non-inclusive bounds. +func (b Bucket) String() string { + var sb strings.Builder + if b.LowerInclusive { + sb.WriteRune('[') + } else { + sb.WriteRune('(') + } + fmt.Fprintf(&sb, "%g,%g", b.Lower, b.Upper) + if b.UpperInclusive { + sb.WriteRune(']') + } else { + sb.WriteRune(')') + } + fmt.Fprintf(&sb, ":%d", b.Count) + return sb.String() +} + +type regularBucketIterator struct { + schema int32 + spans []Span + buckets []int64 + + positive bool // Whether this is for positive buckets. + + spansIdx int // Current span within spans slice. + idxInSpan uint32 // Index in the current span. 0 <= idxInSpan < span.Length. + bucketsIdx int // Current bucket within buckets slice. + + currCount int64 // Count in the current bucket. + currIdx int32 // The actual bucket index. + currLower, currUpper float64 // Limits of the current bucket. + +} + +func newRegularBucketIterator(h *Histogram, positive bool) *regularBucketIterator { + r := ®ularBucketIterator{schema: h.Schema, positive: positive} + if positive { + r.spans = h.PositiveSpans + r.buckets = h.PositiveBuckets + } else { + r.spans = h.NegativeSpans + r.buckets = h.NegativeBuckets + } + return r +} + +func (r *regularBucketIterator) Next() bool { + if r.spansIdx >= len(r.spans) { + return false + } + span := r.spans[r.spansIdx] + // Seed currIdx for the first bucket. + if r.bucketsIdx == 0 { + r.currIdx = span.Offset + } else { + r.currIdx++ + } + for r.idxInSpan >= span.Length { + // We have exhausted the current span and have to find a new + // one. We'll even handle pathologic spans of length 0. + r.idxInSpan = 0 + r.spansIdx++ + if r.spansIdx >= len(r.spans) { + return false + } + span = r.spans[r.spansIdx] + r.currIdx += span.Offset + } + + r.currCount += r.buckets[r.bucketsIdx] + if r.positive { + r.currUpper = getBound(r.currIdx, r.schema) + r.currLower = getBound(r.currIdx-1, r.schema) + } else { + r.currLower = -getBound(r.currIdx, r.schema) + r.currUpper = -getBound(r.currIdx-1, r.schema) + } + + r.idxInSpan++ + r.bucketsIdx++ + return true +} + +func (r *regularBucketIterator) At() Bucket { + return Bucket{ + Count: uint64(r.currCount), + Lower: r.currLower, + Upper: r.currUpper, + LowerInclusive: r.currLower < 0, + UpperInclusive: r.currUpper > 0, + Index: r.currIdx, + } } type cumulativeBucketIterator struct { - h Histogram + h *Histogram posSpansIdx int // Index in h.PositiveSpans we are in. -1 means 0 bucket. posBucketsIdx int // Index in h.PositiveBuckets. idxInSpan uint32 // Index in the current span. 0 <= idxInSpan < span.Length. - initialised bool + initialized bool currIdx int32 // The actual bucket index after decoding from spans. currUpper float64 // The upper boundary of the current bucket. currCount int64 // Current non-cumulative count for the current bucket. Does not apply for empty bucket. @@ -158,24 +314,24 @@ func (c *cumulativeBucketIterator) Next() bool { if c.emptyBucketCount > 0 { // We are traversing through empty buckets at the moment. - c.currUpper = getUpper(c.currIdx, c.h.Schema) + c.currUpper = getBound(c.currIdx, c.h.Schema) c.currIdx++ c.emptyBucketCount-- return true } span := c.h.PositiveSpans[c.posSpansIdx] - if c.posSpansIdx == 0 && !c.initialised { + if c.posSpansIdx == 0 && !c.initialized { // Initialising. c.currIdx = span.Offset - // The first bucket is absolute value and not a delta with Zero bucket. + // The first bucket is an absolute value and not a delta with Zero bucket. c.currCount = 0 - c.initialised = true + c.initialized = true } c.currCount += c.h.PositiveBuckets[c.posBucketsIdx] c.currCumulativeCount += uint64(c.currCount) - c.currUpper = getUpper(c.currIdx, c.h.Schema) + c.currUpper = getBound(c.currIdx, c.h.Schema) c.posBucketsIdx++ c.idxInSpan++ @@ -191,15 +347,19 @@ func (c *cumulativeBucketIterator) Next() bool { return true } + func (c *cumulativeBucketIterator) At() Bucket { return Bucket{ - Upper: c.currUpper, - Count: c.currCumulativeCount, + Upper: c.currUpper, + Lower: math.Inf(-1), + UpperInclusive: true, + LowerInclusive: true, + Count: c.currCumulativeCount, + Index: c.currIdx - 1, } } -func (c *cumulativeBucketIterator) Err() error { return nil } -func getUpper(idx, schema int32) float64 { +func getBound(idx, schema int32) float64 { if schema < 0 { return math.Ldexp(1, int(idx)<<(-schema)) } diff --git a/model/histogram/histogram_test.go b/model/histogram/histogram_test.go index 2a33fdb85b..8ef9da69bb 100644 --- a/model/histogram/histogram_test.go +++ b/model/histogram/histogram_test.go @@ -15,15 +15,72 @@ package histogram import ( "fmt" + "math" "testing" "github.com/stretchr/testify/require" ) +func TestHistogramString(t *testing.T) { + cases := []struct { + histogram Histogram + expectedString string + }{ + { + histogram: Histogram{ + Schema: 0, + }, + expectedString: "{count:0, sum:0}", + }, + { + histogram: Histogram{ + Schema: 0, + Count: 9, + Sum: -3.1415, + ZeroCount: 12, + ZeroThreshold: 0.001, + NegativeSpans: []Span{ + {Offset: 0, Length: 5}, + {Offset: 1, Length: 1}, + }, + NegativeBuckets: []int64{1, 2, -2, 1, -1, 0}, + }, + expectedString: "{count:9, sum:-3.1415, [-64,-32):1, [-16,-8):1, [-8,-4):2, [-4,-2):1, [-2,-1):3, [-1,-0.5):1, [-0.001,0.001]:12}", + }, + { + histogram: Histogram{ + Schema: 0, + Count: 19, + Sum: 2.7, + PositiveSpans: []Span{ + {Offset: 0, Length: 4}, + {Offset: 0, Length: 0}, + {Offset: 0, Length: 3}, + }, + PositiveBuckets: []int64{1, 2, -2, 1, -1, 0, 0}, + NegativeSpans: []Span{ + {Offset: 0, Length: 5}, + {Offset: 1, Length: 0}, + {Offset: 0, Length: 1}, + }, + NegativeBuckets: []int64{1, 2, -2, 1, -1, 0}, + }, + expectedString: "{count:19, sum:2.7, [-64,-32):1, [-16,-8):1, [-8,-4):2, [-4,-2):1, [-2,-1):3, [-1,-0.5):1, (0.5,1]:1, (1,2]:3, (2,4]:1, (4,8]:2, (8,16]:1, (16,32]:1, (32,64]:1}", + }, + } + + for i, c := range cases { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + actualString := c.histogram.String() + require.Equal(t, c.expectedString, actualString) + }) + } +} + func TestCumulativeBucketIterator(t *testing.T) { cases := []struct { - histogram Histogram - expectedCumulativeBuckets []Bucket + histogram Histogram + expectedBuckets []Bucket }{ { histogram: Histogram{ @@ -34,14 +91,14 @@ func TestCumulativeBucketIterator(t *testing.T) { }, PositiveBuckets: []int64{1, 1, -1, 0}, }, - expectedCumulativeBuckets: []Bucket{ - {Upper: 1, Count: 1}, - {Upper: 2, Count: 3}, + expectedBuckets: []Bucket{ + {Lower: math.Inf(-1), Upper: 1, Count: 1, LowerInclusive: true, UpperInclusive: true, Index: 0}, + {Lower: math.Inf(-1), Upper: 2, Count: 3, LowerInclusive: true, UpperInclusive: true, Index: 1}, - {Upper: 4, Count: 3}, + {Lower: math.Inf(-1), Upper: 4, Count: 3, LowerInclusive: true, UpperInclusive: true, Index: 2}, - {Upper: 8, Count: 4}, - {Upper: 16, Count: 5}, + {Lower: math.Inf(-1), Upper: 8, Count: 4, LowerInclusive: true, UpperInclusive: true, Index: 3}, + {Lower: math.Inf(-1), Upper: 16, Count: 5, LowerInclusive: true, UpperInclusive: true, Index: 4}, }, }, { @@ -53,16 +110,16 @@ func TestCumulativeBucketIterator(t *testing.T) { }, PositiveBuckets: []int64{1, 2, -2, 1, -1, 0}, }, - expectedCumulativeBuckets: []Bucket{ - {Upper: 1, Count: 1}, - {Upper: 2, Count: 4}, - {Upper: 4, Count: 5}, - {Upper: 8, Count: 7}, + expectedBuckets: []Bucket{ + {Lower: math.Inf(-1), Upper: 1, Count: 1, LowerInclusive: true, UpperInclusive: true, Index: 0}, + {Lower: math.Inf(-1), Upper: 2, Count: 4, LowerInclusive: true, UpperInclusive: true, Index: 1}, + {Lower: math.Inf(-1), Upper: 4, Count: 5, LowerInclusive: true, UpperInclusive: true, Index: 2}, + {Lower: math.Inf(-1), Upper: 8, Count: 7, LowerInclusive: true, UpperInclusive: true, Index: 3}, - {Upper: 16, Count: 8}, + {Lower: math.Inf(-1), Upper: 16, Count: 8, LowerInclusive: true, UpperInclusive: true, Index: 4}, - {Upper: 32, Count: 8}, - {Upper: 64, Count: 9}, + {Lower: math.Inf(-1), Upper: 32, Count: 8, LowerInclusive: true, UpperInclusive: true, Index: 5}, + {Lower: math.Inf(-1), Upper: 64, Count: 9, LowerInclusive: true, UpperInclusive: true, Index: 6}, }, }, { @@ -73,14 +130,14 @@ func TestCumulativeBucketIterator(t *testing.T) { }, PositiveBuckets: []int64{1, 2, -2, 1, -1, 0, 0}, }, - expectedCumulativeBuckets: []Bucket{ - {Upper: 1, Count: 1}, - {Upper: 2, Count: 4}, - {Upper: 4, Count: 5}, - {Upper: 8, Count: 7}, - {Upper: 16, Count: 8}, - {Upper: 32, Count: 9}, - {Upper: 64, Count: 10}, + expectedBuckets: []Bucket{ + {Lower: math.Inf(-1), Upper: 1, Count: 1, LowerInclusive: true, UpperInclusive: true, Index: 0}, + {Lower: math.Inf(-1), Upper: 2, Count: 4, LowerInclusive: true, UpperInclusive: true, Index: 1}, + {Lower: math.Inf(-1), Upper: 4, Count: 5, LowerInclusive: true, UpperInclusive: true, Index: 2}, + {Lower: math.Inf(-1), Upper: 8, Count: 7, LowerInclusive: true, UpperInclusive: true, Index: 3}, + {Lower: math.Inf(-1), Upper: 16, Count: 8, LowerInclusive: true, UpperInclusive: true, Index: 4}, + {Lower: math.Inf(-1), Upper: 32, Count: 9, LowerInclusive: true, UpperInclusive: true, Index: 5}, + {Lower: math.Inf(-1), Upper: 64, Count: 10, LowerInclusive: true, UpperInclusive: true, Index: 6}, }, }, { @@ -93,22 +150,22 @@ func TestCumulativeBucketIterator(t *testing.T) { }, PositiveBuckets: []int64{1, 2, -2, 1, -1, 0, 3}, }, - expectedCumulativeBuckets: []Bucket{ - {Upper: 0.6484197773255048, Count: 1}, // -5 - {Upper: 0.7071067811865475, Count: 4}, // -4 + expectedBuckets: []Bucket{ + {Lower: math.Inf(-1), Upper: 0.6484197773255048, Count: 1, LowerInclusive: true, UpperInclusive: true, Index: -5}, + {Lower: math.Inf(-1), Upper: 0.7071067811865475, Count: 4, LowerInclusive: true, UpperInclusive: true, Index: -4}, - {Upper: 0.7711054127039704, Count: 4}, // -3 - {Upper: 0.8408964152537144, Count: 4}, // -2 + {Lower: math.Inf(-1), Upper: 0.7711054127039704, Count: 4, LowerInclusive: true, UpperInclusive: true, Index: -3}, + {Lower: math.Inf(-1), Upper: 0.8408964152537144, Count: 4, LowerInclusive: true, UpperInclusive: true, Index: -2}, - {Upper: 0.9170040432046711, Count: 5}, // -1 - {Upper: 1, Count: 7}, // 1 - {Upper: 1.0905077326652577, Count: 8}, // 0 + {Lower: math.Inf(-1), Upper: 0.9170040432046711, Count: 5, LowerInclusive: true, UpperInclusive: true, Index: -1}, + {Lower: math.Inf(-1), Upper: 1, Count: 7, LowerInclusive: true, UpperInclusive: true, Index: 0}, + {Lower: math.Inf(-1), Upper: 1.0905077326652577, Count: 8, LowerInclusive: true, UpperInclusive: true, Index: 1}, - {Upper: 1.189207115002721, Count: 8}, // 1 - {Upper: 1.2968395546510096, Count: 8}, // 2 + {Lower: math.Inf(-1), Upper: 1.189207115002721, Count: 8, LowerInclusive: true, UpperInclusive: true, Index: 2}, + {Lower: math.Inf(-1), Upper: 1.2968395546510096, Count: 8, LowerInclusive: true, UpperInclusive: true, Index: 3}, - {Upper: 1.414213562373095, Count: 9}, // 3 - {Upper: 1.5422108254079407, Count: 13}, // 4 + {Lower: math.Inf(-1), Upper: 1.414213562373095, Count: 9, LowerInclusive: true, UpperInclusive: true, Index: 4}, + {Lower: math.Inf(-1), Upper: 1.5422108254079407, Count: 13, LowerInclusive: true, UpperInclusive: true, Index: 5}, }, }, { @@ -120,17 +177,17 @@ func TestCumulativeBucketIterator(t *testing.T) { }, PositiveBuckets: []int64{1, 2, -2, 1, -1, 0}, }, - expectedCumulativeBuckets: []Bucket{ - {Upper: 0.00390625, Count: 1}, // -2 - {Upper: 0.0625, Count: 4}, // -1 - {Upper: 1, Count: 5}, // 0 - {Upper: 16, Count: 7}, // 1 + expectedBuckets: []Bucket{ + {Lower: math.Inf(-1), Upper: 0.00390625, Count: 1, LowerInclusive: true, UpperInclusive: true, Index: -2}, + {Lower: math.Inf(-1), Upper: 0.0625, Count: 4, LowerInclusive: true, UpperInclusive: true, Index: -1}, + {Lower: math.Inf(-1), Upper: 1, Count: 5, LowerInclusive: true, UpperInclusive: true, Index: 0}, + {Lower: math.Inf(-1), Upper: 16, Count: 7, LowerInclusive: true, UpperInclusive: true, Index: 1}, - {Upper: 256, Count: 7}, // 2 - {Upper: 4096, Count: 7}, // 3 + {Lower: math.Inf(-1), Upper: 256, Count: 7, LowerInclusive: true, UpperInclusive: true, Index: 2}, + {Lower: math.Inf(-1), Upper: 4096, Count: 7, LowerInclusive: true, UpperInclusive: true, Index: 3}, - {Upper: 65536, Count: 8}, // 4 - {Upper: 1048576, Count: 9}, // 5 + {Lower: math.Inf(-1), Upper: 65536, Count: 8, LowerInclusive: true, UpperInclusive: true, Index: 4}, + {Lower: math.Inf(-1), Upper: 1048576, Count: 9, LowerInclusive: true, UpperInclusive: true, Index: 5}, }, }, { @@ -141,12 +198,12 @@ func TestCumulativeBucketIterator(t *testing.T) { }, PositiveBuckets: []int64{1, 2, -2, 1, -1}, }, - expectedCumulativeBuckets: []Bucket{ - {Upper: 0.0625, Count: 1}, // -2 - {Upper: 0.25, Count: 4}, // -1 - {Upper: 1, Count: 5}, // 0 - {Upper: 4, Count: 7}, // 1 - {Upper: 16, Count: 8}, // 2 + expectedBuckets: []Bucket{ + {Lower: math.Inf(-1), Upper: 0.0625, Count: 1, LowerInclusive: true, UpperInclusive: true, Index: -2}, + {Lower: math.Inf(-1), Upper: 0.25, Count: 4, LowerInclusive: true, UpperInclusive: true, Index: -1}, + {Lower: math.Inf(-1), Upper: 1, Count: 5, LowerInclusive: true, UpperInclusive: true, Index: 0}, + {Lower: math.Inf(-1), Upper: 4, Count: 7, LowerInclusive: true, UpperInclusive: true, Index: 1}, + {Lower: math.Inf(-1), Upper: 16, Count: 8, LowerInclusive: true, UpperInclusive: true, Index: 2}, }, }, } @@ -154,12 +211,177 @@ func TestCumulativeBucketIterator(t *testing.T) { for i, c := range cases { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { it := c.histogram.CumulativeBucketIterator() - actualBuckets := make([]Bucket, 0, len(c.expectedCumulativeBuckets)) + actualBuckets := make([]Bucket, 0, len(c.expectedBuckets)) for it.Next() { actualBuckets = append(actualBuckets, it.At()) } - require.NoError(t, it.Err()) - require.Equal(t, c.expectedCumulativeBuckets, actualBuckets) + require.Equal(t, c.expectedBuckets, actualBuckets) + }) + } +} + +func TestRegularBucketIterator(t *testing.T) { + cases := []struct { + histogram Histogram + expectedPositiveBuckets []Bucket + expectedNegativeBuckets []Bucket + }{ + { + histogram: Histogram{ + Schema: 0, + }, + expectedPositiveBuckets: []Bucket{}, + expectedNegativeBuckets: []Bucket{}, + }, + { + histogram: Histogram{ + Schema: 0, + PositiveSpans: []Span{ + {Offset: 0, Length: 2}, + {Offset: 1, Length: 2}, + }, + PositiveBuckets: []int64{1, 1, -1, 0}, + }, + expectedPositiveBuckets: []Bucket{ + {Lower: 0.5, Upper: 1, Count: 1, LowerInclusive: false, UpperInclusive: true, Index: 0}, + {Lower: 1, Upper: 2, Count: 2, LowerInclusive: false, UpperInclusive: true, Index: 1}, + + {Lower: 4, Upper: 8, Count: 1, LowerInclusive: false, UpperInclusive: true, Index: 3}, + {Lower: 8, Upper: 16, Count: 1, LowerInclusive: false, UpperInclusive: true, Index: 4}, + }, + expectedNegativeBuckets: []Bucket{}, + }, + { + histogram: Histogram{ + Schema: 0, + NegativeSpans: []Span{ + {Offset: 0, Length: 5}, + {Offset: 1, Length: 1}, + }, + NegativeBuckets: []int64{1, 2, -2, 1, -1, 0}, + }, + expectedPositiveBuckets: []Bucket{}, + expectedNegativeBuckets: []Bucket{ + {Lower: -1, Upper: -0.5, Count: 1, LowerInclusive: true, UpperInclusive: false, Index: 0}, + {Lower: -2, Upper: -1, Count: 3, LowerInclusive: true, UpperInclusive: false, Index: 1}, + {Lower: -4, Upper: -2, Count: 1, LowerInclusive: true, UpperInclusive: false, Index: 2}, + {Lower: -8, Upper: -4, Count: 2, LowerInclusive: true, UpperInclusive: false, Index: 3}, + {Lower: -16, Upper: -8, Count: 1, LowerInclusive: true, UpperInclusive: false, Index: 4}, + + {Lower: -64, Upper: -32, Count: 1, LowerInclusive: true, UpperInclusive: false, Index: 6}, + }, + }, + { + histogram: Histogram{ + Schema: 0, + PositiveSpans: []Span{ + {Offset: 0, Length: 4}, + {Offset: 0, Length: 0}, + {Offset: 0, Length: 3}, + }, + PositiveBuckets: []int64{1, 2, -2, 1, -1, 0, 0}, + NegativeSpans: []Span{ + {Offset: 0, Length: 5}, + {Offset: 1, Length: 0}, + {Offset: 0, Length: 1}, + }, + NegativeBuckets: []int64{1, 2, -2, 1, -1, 0}, + }, + expectedPositiveBuckets: []Bucket{ + {Lower: 0.5, Upper: 1, Count: 1, LowerInclusive: false, UpperInclusive: true, Index: 0}, + {Lower: 1, Upper: 2, Count: 3, LowerInclusive: false, UpperInclusive: true, Index: 1}, + {Lower: 2, Upper: 4, Count: 1, LowerInclusive: false, UpperInclusive: true, Index: 2}, + {Lower: 4, Upper: 8, Count: 2, LowerInclusive: false, UpperInclusive: true, Index: 3}, + {Lower: 8, Upper: 16, Count: 1, LowerInclusive: false, UpperInclusive: true, Index: 4}, + {Lower: 16, Upper: 32, Count: 1, LowerInclusive: false, UpperInclusive: true, Index: 5}, + {Lower: 32, Upper: 64, Count: 1, LowerInclusive: false, UpperInclusive: true, Index: 6}, + }, + expectedNegativeBuckets: []Bucket{ + {Lower: -1, Upper: -0.5, Count: 1, LowerInclusive: true, UpperInclusive: false, Index: 0}, + {Lower: -2, Upper: -1, Count: 3, LowerInclusive: true, UpperInclusive: false, Index: 1}, + {Lower: -4, Upper: -2, Count: 1, LowerInclusive: true, UpperInclusive: false, Index: 2}, + {Lower: -8, Upper: -4, Count: 2, LowerInclusive: true, UpperInclusive: false, Index: 3}, + {Lower: -16, Upper: -8, Count: 1, LowerInclusive: true, UpperInclusive: false, Index: 4}, + + {Lower: -64, Upper: -32, Count: 1, LowerInclusive: true, UpperInclusive: false, Index: 6}, + }, + }, + { + histogram: Histogram{ + Schema: 3, + PositiveSpans: []Span{ + {Offset: -5, Length: 2}, // -5 -4 + {Offset: 2, Length: 3}, // -1 0 1 + {Offset: 2, Length: 2}, // 4 5 + }, + PositiveBuckets: []int64{1, 2, -2, 1, -1, 0, 3}, + }, + expectedPositiveBuckets: []Bucket{ + {Lower: 0.5946035575013605, Upper: 0.6484197773255048, Count: 1, LowerInclusive: false, UpperInclusive: true, Index: -5}, + {Lower: 0.6484197773255048, Upper: 0.7071067811865475, Count: 3, LowerInclusive: false, UpperInclusive: true, Index: -4}, + + {Lower: 0.8408964152537144, Upper: 0.9170040432046711, Count: 1, LowerInclusive: false, UpperInclusive: true, Index: -1}, + {Lower: 0.9170040432046711, Upper: 1, Count: 2, LowerInclusive: false, UpperInclusive: true, Index: 0}, + {Lower: 1, Upper: 1.0905077326652577, Count: 1, LowerInclusive: false, UpperInclusive: true, Index: 1}, + + {Lower: 1.2968395546510096, Upper: 1.414213562373095, Count: 1, LowerInclusive: false, UpperInclusive: true, Index: 4}, + {Lower: 1.414213562373095, Upper: 1.5422108254079407, Count: 4, LowerInclusive: false, UpperInclusive: true, Index: 5}, + }, + expectedNegativeBuckets: []Bucket{}, + }, + { + histogram: Histogram{ + Schema: -2, + PositiveSpans: []Span{ + {Offset: -2, Length: 4}, // -2 -1 0 1 + {Offset: 2, Length: 2}, // 4 5 + }, + PositiveBuckets: []int64{1, 2, -2, 1, -1, 0}, + }, + expectedPositiveBuckets: []Bucket{ + {Lower: 0.000244140625, Upper: 0.00390625, Count: 1, LowerInclusive: false, UpperInclusive: true, Index: -2}, + {Lower: 0.00390625, Upper: 0.0625, Count: 3, LowerInclusive: false, UpperInclusive: true, Index: -1}, + {Lower: 0.0625, Upper: 1, Count: 1, LowerInclusive: false, UpperInclusive: true, Index: 0}, + {Lower: 1, Upper: 16, Count: 2, LowerInclusive: false, UpperInclusive: true, Index: 1}, + + {Lower: 4096, Upper: 65536, Count: 1, LowerInclusive: false, UpperInclusive: true, Index: 4}, + {Lower: 65536, Upper: 1048576, Count: 1, LowerInclusive: false, UpperInclusive: true, Index: 5}, + }, + expectedNegativeBuckets: []Bucket{}, + }, + { + histogram: Histogram{ + Schema: -1, + PositiveSpans: []Span{ + {Offset: -2, Length: 5}, // -2 -1 0 1 2 + }, + PositiveBuckets: []int64{1, 2, -2, 1, -1}, + }, + expectedPositiveBuckets: []Bucket{ + {Lower: 0.015625, Upper: 0.0625, Count: 1, LowerInclusive: false, UpperInclusive: true, Index: -2}, + {Lower: 0.0625, Upper: 0.25, Count: 3, LowerInclusive: false, UpperInclusive: true, Index: -1}, + {Lower: 0.25, Upper: 1, Count: 1, LowerInclusive: false, UpperInclusive: true, Index: 0}, + {Lower: 1, Upper: 4, Count: 2, LowerInclusive: false, UpperInclusive: true, Index: 1}, + {Lower: 4, Upper: 16, Count: 1, LowerInclusive: false, UpperInclusive: true, Index: 2}, + }, + expectedNegativeBuckets: []Bucket{}, + }, + } + + for i, c := range cases { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + it := c.histogram.PositiveBucketIterator() + actualPositiveBuckets := make([]Bucket, 0, len(c.expectedPositiveBuckets)) + for it.Next() { + actualPositiveBuckets = append(actualPositiveBuckets, it.At()) + } + require.Equal(t, c.expectedPositiveBuckets, actualPositiveBuckets) + it = c.histogram.NegativeBucketIterator() + actualNegativeBuckets := make([]Bucket, 0, len(c.expectedNegativeBuckets)) + for it.Next() { + actualNegativeBuckets = append(actualNegativeBuckets, it.At()) + } + require.Equal(t, c.expectedNegativeBuckets, actualNegativeBuckets) }) } } diff --git a/promql/engine_test.go b/promql/engine_test.go index 88eed7f8bb..941250a415 100644 --- a/promql/engine_test.go +++ b/promql/engine_test.go @@ -2460,4 +2460,5 @@ func TestSparseHistogramRate(t *testing.T) { require.NoError(t, err) res := qry.Exec(test.Context()) require.NoError(t, res.Err) + fmt.Println(res) } diff --git a/tsdb/compact_test.go b/tsdb/compact_test.go index 5f3d2374b6..b0df493f73 100644 --- a/tsdb/compact_test.go +++ b/tsdb/compact_test.go @@ -1533,7 +1533,6 @@ func TestSparseHistogramSpaceSavings(t *testing.T) { require.NoError(t, err) itIdx++ } - require.NoError(t, it.Err()) // _count metric. countLbls := ah.baseLabels.Copy() countLbls[0].Value = countLbls[0].Value + "_count"