in cmd/otelinmemexporter/histogram.go [60:125]
func deltaExplicitBucketsQuantile(q float64, buckets []explicitBucket) float64 {
if math.IsNaN(q) {
return math.NaN()
}
if q < 0 {
return math.Inf(-1)
}
if q > 1 {
return math.Inf(+1)
}
slices.SortFunc(buckets, func(a, b explicitBucket) int {
// We don't expect the bucket boundary to be a NaN.
return cmp.Compare(a.UpperBound, b.UpperBound)
})
if len(buckets) < 2 {
return math.NaN()
}
// The highest bound must be +Inf, and the lowest bound must be -Inf.
if !math.IsInf(buckets[len(buckets)-1].UpperBound, +1) {
return math.NaN()
}
// Check if there are any observations.
var observations uint64
for _, bucket := range buckets {
observations += bucket.Count
}
if observations == 0 {
return math.NaN()
}
// Find the bucket that the quantile falls into.
rank := q * float64(observations)
var countSoFar uint64
bucketIdx := slices.IndexFunc(buckets, func(bucket explicitBucket) bool {
countSoFar += bucket.Count
// Compare using `>=` instead of `>` since upper bound is inclusive.
return float64(countSoFar) >= rank
})
if bucketIdx == len(buckets)-1 {
return buckets[len(buckets)-2].UpperBound
}
if bucketIdx == 0 {
return buckets[0].UpperBound
}
// Interpolate to get quantile in bucket.
bucketStart := buckets[bucketIdx-1].UpperBound
bucketEnd := buckets[bucketIdx].UpperBound
bucketCount := buckets[bucketIdx].Count
// How the bucket quantile is derived:
// ==|=======|=======|=======|=======|==
// | | | | |
// | b - 2 | b - 1 | b | b + 1 |
// ==|=======|=======|=======|=======|==
// ----------------------> rank
// --------------------------> countSoFar
// |-------> bucketCount
// |---> rank - (countSoFar - bucketCount)
bucketQuantile := (rank - float64(countSoFar-bucketCount)) / float64(bucketCount)
return bucketStart + (bucketEnd-bucketStart)*bucketQuantile
}