Including a few adjustments for normal Histogram, too, e.g. use pointer receiver to avoid the large copy on method calls. Signed-off-by: beorn7 <beorn@grafana.com>pull/9857/head
parent
8e4e8726bb
commit
6a820a646c
@ -0,0 +1,341 @@ |
||||
// Copyright 2021 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package histogram |
||||
|
||||
import ( |
||||
"fmt" |
||||
"math" |
||||
"strings" |
||||
) |
||||
|
||||
// FloatHistogram is similar to Histogram but uses float64 for all
|
||||
// counts. Additionally, bucket counts are absolute and not deltas.
|
||||
//
|
||||
// A FloatHistogram is needed by PromQL to handle operations that might result
|
||||
// in fractional counts. Since the counts in a histogram are unlikely to be too
|
||||
// large to be represented precisely by a float64, a FloatHistogram can also be
|
||||
// used to represent a histogram with integer counts and thus serves as a more
|
||||
// generalized representation.
|
||||
type FloatHistogram struct { |
||||
// Currently valid schema numbers are -4 <= n <= 8. They are all for
|
||||
// base-2 bucket schemas, where 1 is a bucket boundary in each case, and
|
||||
// then each power of two is divided into 2^n logarithmic buckets. Or
|
||||
// in other words, each bucket boundary is the previous boundary times
|
||||
// 2^(2^-n).
|
||||
Schema int32 |
||||
// Width of the zero bucket.
|
||||
ZeroThreshold float64 |
||||
// Observations falling into the zero bucket. Must be zero or positive.
|
||||
ZeroCount float64 |
||||
// Total number of observations. Must be zero or positive.
|
||||
Count float64 |
||||
// Sum of observations. This is also used as the stale marker.
|
||||
Sum float64 |
||||
// Spans for positive and negative buckets (see Span below).
|
||||
PositiveSpans, NegativeSpans []Span |
||||
// Observation counts in buckets. Each represents an absolute count and
|
||||
// must be zero or positive.
|
||||
PositiveBuckets, NegativeBuckets []float64 |
||||
} |
||||
|
||||
// Copy returns a deep copy of the Histogram.
|
||||
func (h *FloatHistogram) Copy() *FloatHistogram { |
||||
c := *h |
||||
|
||||
if h.PositiveSpans != nil { |
||||
c.PositiveSpans = make([]Span, len(h.PositiveSpans)) |
||||
copy(c.PositiveSpans, h.PositiveSpans) |
||||
} |
||||
if h.NegativeSpans != nil { |
||||
c.NegativeSpans = make([]Span, len(h.NegativeSpans)) |
||||
copy(c.NegativeSpans, h.NegativeSpans) |
||||
} |
||||
if h.PositiveBuckets != nil { |
||||
c.PositiveBuckets = make([]float64, len(h.PositiveBuckets)) |
||||
copy(c.PositiveBuckets, h.PositiveBuckets) |
||||
} |
||||
if h.NegativeBuckets != nil { |
||||
c.NegativeBuckets = make([]float64, len(h.NegativeBuckets)) |
||||
copy(c.NegativeBuckets, h.NegativeBuckets) |
||||
} |
||||
|
||||
return &c |
||||
} |
||||
|
||||
// String returns a string representation of the Histogram.
|
||||
func (h *FloatHistogram) String() string { |
||||
var sb strings.Builder |
||||
fmt.Fprintf(&sb, "{count:%g, sum:%g", h.Count, h.Sum) |
||||
|
||||
var nBuckets []FloatBucket |
||||
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 *FloatHistogram) ZeroBucket() FloatBucket { |
||||
return FloatBucket{ |
||||
Lower: -h.ZeroThreshold, |
||||
Upper: h.ZeroThreshold, |
||||
LowerInclusive: true, |
||||
UpperInclusive: true, |
||||
Count: h.ZeroCount, |
||||
} |
||||
} |
||||
|
||||
// PositiveBucketIterator returns a FloatBucketIterator to iterate over all
|
||||
// positive buckets in ascending order (starting next to the zero bucket and
|
||||
// going up).
|
||||
func (h *FloatHistogram) PositiveBucketIterator() FloatBucketIterator { |
||||
return newFloatBucketIterator(h, true) |
||||
} |
||||
|
||||
// NegativeBucketIterator returns a FloatBucketIterator to iterate over all
|
||||
// negative buckets in descending order (starting next to the zero bucket and
|
||||
// going down).
|
||||
func (h *FloatHistogram) NegativeBucketIterator() FloatBucketIterator { |
||||
return newFloatBucketIterator(h, false) |
||||
} |
||||
|
||||
// CumulativeBucketIterator returns a FloatBucketIterator to iterate over a
|
||||
// cumulative view of the buckets. This method currently only supports
|
||||
// FloatHistograms without negative buckets and panics if the FloatHistogram has
|
||||
// negative buckets. It is currently only used for testing.
|
||||
func (h *FloatHistogram) CumulativeBucketIterator() FloatBucketIterator { |
||||
if len(h.NegativeBuckets) > 0 { |
||||
panic("CumulativeBucketIterator called on FloatHistogram with negative buckets") |
||||
} |
||||
return &cumulativeFloatBucketIterator{h: h, posSpansIdx: -1} |
||||
} |
||||
|
||||
// FloatBucketIterator iterates over the buckets of a FloatHistogram, returning
|
||||
// decoded buckets.
|
||||
type FloatBucketIterator interface { |
||||
// Next advances the iterator by one.
|
||||
Next() bool |
||||
// At returns the current bucket.
|
||||
At() FloatBucket |
||||
} |
||||
|
||||
// FloatBucket 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 FloatBucket struct { |
||||
Lower, Upper float64 |
||||
LowerInclusive, UpperInclusive bool |
||||
Count float64 |
||||
Index int32 // Index within schema. To easily compare buckets that share the same schema.
|
||||
} |
||||
|
||||
// String returns a string representation of a FloatBucket, using the usual
|
||||
// mathematical notation of '['/']' for inclusive bounds and '('/')' for
|
||||
// non-inclusive bounds.
|
||||
func (b FloatBucket) 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, ":%g", b.Count) |
||||
return sb.String() |
||||
} |
||||
|
||||
type floatBucketIterator struct { |
||||
schema int32 |
||||
spans []Span |
||||
buckets []float64 |
||||
|
||||
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 float64 // Count in the current bucket.
|
||||
currIdx int32 // The actual bucket index.
|
||||
currLower, currUpper float64 // Limits of the current bucket.
|
||||
|
||||
} |
||||
|
||||
func newFloatBucketIterator(h *FloatHistogram, positive bool) *floatBucketIterator { |
||||
r := &floatBucketIterator{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 *floatBucketIterator) 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 *floatBucketIterator) At() FloatBucket { |
||||
return FloatBucket{ |
||||
Count: r.currCount, |
||||
Lower: r.currLower, |
||||
Upper: r.currUpper, |
||||
LowerInclusive: r.currLower < 0, |
||||
UpperInclusive: r.currUpper > 0, |
||||
Index: r.currIdx, |
||||
} |
||||
} |
||||
|
||||
type cumulativeFloatBucketIterator struct { |
||||
h *FloatHistogram |
||||
|
||||
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.
|
||||
|
||||
initialized bool |
||||
currIdx int32 // The actual bucket index after decoding from spans.
|
||||
currUpper float64 // The upper boundary of the current bucket.
|
||||
currCumulativeCount float64 // Current "cumulative" count for the current bucket.
|
||||
|
||||
// Between 2 spans there could be some empty buckets which
|
||||
// still needs to be counted for cumulative buckets.
|
||||
// When we hit the end of a span, we use this to iterate
|
||||
// through the empty buckets.
|
||||
emptyBucketCount int32 |
||||
} |
||||
|
||||
func (c *cumulativeFloatBucketIterator) Next() bool { |
||||
if c.posSpansIdx == -1 { |
||||
// Zero bucket.
|
||||
c.posSpansIdx++ |
||||
if c.h.ZeroCount == 0 { |
||||
return c.Next() |
||||
} |
||||
|
||||
c.currUpper = c.h.ZeroThreshold |
||||
c.currCumulativeCount = c.h.ZeroCount |
||||
return true |
||||
} |
||||
|
||||
if c.posSpansIdx >= len(c.h.PositiveSpans) { |
||||
return false |
||||
} |
||||
|
||||
if c.emptyBucketCount > 0 { |
||||
// We are traversing through empty buckets at the moment.
|
||||
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.initialized { |
||||
// Initializing.
|
||||
c.currIdx = span.Offset |
||||
c.initialized = true |
||||
} |
||||
|
||||
c.currCumulativeCount += c.h.PositiveBuckets[c.posBucketsIdx] |
||||
c.currUpper = getBound(c.currIdx, c.h.Schema) |
||||
|
||||
c.posBucketsIdx++ |
||||
c.idxInSpan++ |
||||
c.currIdx++ |
||||
if c.idxInSpan >= span.Length { |
||||
// Move to the next span. This one is done.
|
||||
c.posSpansIdx++ |
||||
c.idxInSpan = 0 |
||||
if c.posSpansIdx < len(c.h.PositiveSpans) { |
||||
c.emptyBucketCount = c.h.PositiveSpans[c.posSpansIdx].Offset |
||||
} |
||||
} |
||||
|
||||
return true |
||||
} |
||||
|
||||
func (c *cumulativeFloatBucketIterator) At() FloatBucket { |
||||
return FloatBucket{ |
||||
Upper: c.currUpper, |
||||
Lower: math.Inf(-1), |
||||
UpperInclusive: true, |
||||
LowerInclusive: true, |
||||
Count: c.currCumulativeCount, |
||||
Index: c.currIdx - 1, |
||||
} |
||||
} |
||||
Loading…
Reference in new issue