@ -51,20 +51,22 @@ var excludedLabels = []string{
labels . BucketLabel ,
}
type bucket struct {
upperBound float64
count float64
// Bucket represents a bucket of a classic histogram. It is used internally by the promql
// package, but it is nevertheless exported for potential use in other PromQL engines.
type Bucket struct {
UpperBound float64
Count float64
}
// b uckets implements sort.Interface.
type buckets [ ] b ucket
// B uckets implements sort.Interface.
type Buckets [ ] B ucket
type metricWithBuckets struct {
metric labels . Labels
buckets b uckets
buckets B uckets
}
// b ucketQuantile calculates the quantile 'q' based on the given buckets. The
// B ucketQuantile calculates the quantile 'q' based on the given buckets. The
// buckets will be sorted by upperBound by this function (i.e. no sorting
// needed before calling this function). The quantile value is interpolated
// assuming a linear distribution within a bucket. However, if the quantile
@ -95,7 +97,14 @@ type metricWithBuckets struct {
// and another bool to indicate if small differences between buckets (that
// are likely artifacts of floating point precision issues) have been
// ignored.
func bucketQuantile ( q float64 , buckets buckets ) ( float64 , bool , bool ) {
//
// Generically speaking, BucketQuantile is for calculating the
// histogram_quantile() of classic histograms. See also: HistogramQuantile
// for native histograms.
//
// BucketQuantile is exported as a useful quantile function over a set of
// given buckets. It may be used by other PromQL engine implementations.
func BucketQuantile ( q float64 , buckets Buckets ) ( float64 , bool , bool ) {
if math . IsNaN ( q ) {
return math . NaN ( ) , false , false
}
@ -105,17 +114,17 @@ func bucketQuantile(q float64, buckets buckets) (float64, bool, bool) {
if q > 1 {
return math . Inf ( + 1 ) , false , false
}
slices . SortFunc ( buckets , func ( a , b b ucket) int {
slices . SortFunc ( buckets , func ( a , b B ucket) int {
// We don't expect the bucket boundary to be a NaN.
if a . u pperBound < b . u pperBound {
if a . U pperBound < b . U pperBound {
return - 1
}
if a . u pperBound > b . u pperBound {
if a . U pperBound > b . U pperBound {
return + 1
}
return 0
} )
if ! math . IsInf ( buckets [ len ( buckets ) - 1 ] . u pperBound, + 1 ) {
if ! math . IsInf ( buckets [ len ( buckets ) - 1 ] . U pperBound, + 1 ) {
return math . NaN ( ) , false , false
}
@ -125,33 +134,33 @@ func bucketQuantile(q float64, buckets buckets) (float64, bool, bool) {
if len ( buckets ) < 2 {
return math . NaN ( ) , false , false
}
observations := buckets [ len ( buckets ) - 1 ] . c ount
observations := buckets [ len ( buckets ) - 1 ] . C ount
if observations == 0 {
return math . NaN ( ) , false , false
}
rank := q * observations
b := sort . Search ( len ( buckets ) - 1 , func ( i int ) bool { return buckets [ i ] . c ount >= rank } )
b := sort . Search ( len ( buckets ) - 1 , func ( i int ) bool { return buckets [ i ] . C ount >= rank } )
if b == len ( buckets ) - 1 {
return buckets [ len ( buckets ) - 2 ] . u pperBound, forcedMonotonic , fixedPrecision
return buckets [ len ( buckets ) - 2 ] . U pperBound, forcedMonotonic , fixedPrecision
}
if b == 0 && buckets [ 0 ] . u pperBound <= 0 {
return buckets [ 0 ] . u pperBound, forcedMonotonic , fixedPrecision
if b == 0 && buckets [ 0 ] . U pperBound <= 0 {
return buckets [ 0 ] . U pperBound, forcedMonotonic , fixedPrecision
}
var (
bucketStart float64
bucketEnd = buckets [ b ] . u pperBound
count = buckets [ b ] . c ount
bucketEnd = buckets [ b ] . U pperBound
count = buckets [ b ] . C ount
)
if b > 0 {
bucketStart = buckets [ b - 1 ] . u pperBound
count -= buckets [ b - 1 ] . c ount
rank -= buckets [ b - 1 ] . c ount
bucketStart = buckets [ b - 1 ] . U pperBound
count -= buckets [ b - 1 ] . C ount
rank -= buckets [ b - 1 ] . C ount
}
return bucketStart + ( bucketEnd - bucketStart ) * ( rank / count ) , forcedMonotonic , fixedPrecision
}
// h istogramQuantile calculates the quantile 'q' based on the given histogram.
// H istogramQuantile calculates the quantile 'q' based on the given histogram.
//
// For custom buckets, the result is interpolated linearly, i.e. it is assumed
// the observations are uniformly distributed within each bucket. (This is a
@ -186,7 +195,13 @@ func bucketQuantile(q float64, buckets buckets) (float64, bool, bool) {
// If q>1, +Inf is returned.
//
// If q is NaN, NaN is returned.
func histogramQuantile ( q float64 , h * histogram . FloatHistogram ) float64 {
//
// HistogramQuantile is for calculating the histogram_quantile() of native
// histograms. See also: BucketQuantile for classic histograms.
//
// HistogramQuantile is exported as it may be used by other PromQL engine
// implementations.
func HistogramQuantile ( q float64 , h * histogram . FloatHistogram ) float64 {
if q < 0 {
return math . Inf ( - 1 )
}
@ -297,11 +312,11 @@ func histogramQuantile(q float64, h *histogram.FloatHistogram) float64 {
return - math . Exp2 ( logUpper + ( logLower - logUpper ) * ( 1 - fraction ) )
}
// h istogramFraction calculates the fraction of observations between the
// H istogramFraction calculates the fraction of observations between the
// provided lower and upper bounds, based on the provided histogram.
//
// h istogramFraction is in a certain way the inverse of histogramQuantile. If
// histogramQuantile(0.9, h) returns 123.4, then h istogramFraction(-Inf, 123.4, h)
// H istogramFraction is in a certain way the inverse of histogramQuantile. If
// HistogramQuantile(0.9, h) returns 123.4, then H istogramFraction(-Inf, 123.4, h)
// returns 0.9.
//
// The same notes with regard to interpolation and assumptions about the zero
@ -328,7 +343,10 @@ func histogramQuantile(q float64, h *histogram.FloatHistogram) float64 {
// If lower or upper is NaN, NaN is returned.
//
// If lower >= upper and the histogram has at least 1 observation, zero is returned.
func histogramFraction ( lower , upper float64 , h * histogram . FloatHistogram ) float64 {
//
// HistogramFraction is exported as it may be used by other PromQL engine
// implementations.
func HistogramFraction ( lower , upper float64 , h * histogram . FloatHistogram ) float64 {
if h . Count == 0 || math . IsNaN ( lower ) || math . IsNaN ( upper ) {
return math . NaN ( )
}
@ -434,12 +452,12 @@ func histogramFraction(lower, upper float64, h *histogram.FloatHistogram) float6
// coalesceBuckets merges buckets with the same upper bound.
//
// The input buckets must be sorted.
func coalesceBuckets ( buckets buckets ) b uckets {
func coalesceBuckets ( buckets Buckets ) B uckets {
last := buckets [ 0 ]
i := 0
for _ , b := range buckets [ 1 : ] {
if b . u pperBound == last . u pperBound {
last . c ount += b . c ount
if b . U pperBound == last . U pperBound {
last . C ount += b . C ount
} else {
buckets [ i ] = last
last = b
@ -476,11 +494,11 @@ func coalesceBuckets(buckets buckets) buckets {
//
// We return a bool to indicate if this monotonicity was forced or not, and
// another bool to indicate if small deltas were ignored or not.
func ensureMonotonicAndIgnoreSmallDeltas ( buckets b uckets, tolerance float64 ) ( bool , bool ) {
func ensureMonotonicAndIgnoreSmallDeltas ( buckets B uckets, tolerance float64 ) ( bool , bool ) {
var forcedMonotonic , fixedPrecision bool
prev := buckets [ 0 ] . c ount
prev := buckets [ 0 ] . C ount
for i := 1 ; i < len ( buckets ) ; i ++ {
curr := buckets [ i ] . c ount // Assumed always positive.
curr := buckets [ i ] . C ount // Assumed always positive.
if curr == prev {
// No correction needed if the counts are identical between buckets.
continue
@ -489,14 +507,14 @@ func ensureMonotonicAndIgnoreSmallDeltas(buckets buckets, tolerance float64) (bo
// Silently correct numerically insignificant differences from floating
// point precision errors, regardless of direction.
// Do not update the 'prev' value as we are ignoring the difference.
buckets [ i ] . c ount = prev
buckets [ i ] . C ount = prev
fixedPrecision = true
continue
}
if curr < prev {
// Force monotonicity by removing any decreases regardless of magnitude.
// Do not update the 'prev' value as we are ignoring the decrease.
buckets [ i ] . c ount = prev
buckets [ i ] . C ount = prev
forcedMonotonic = true
continue
}