mirror of
https://github.com/prometheus/prometheus.git
synced 2024-11-11 08:04:04 -08:00
9e7c3e3bcd
Since we are now getting really deep into floating point calculation, the tests had to take into account the precision loss. Since the rule tests are based on direct line matching in the output, implementing the "almost equal" semantics was pretty cumbersome, but here we are.
100 lines
3 KiB
Go
100 lines
3 KiB
Go
// Copyright 2015 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 ast
|
|
|
|
import (
|
|
"math"
|
|
"sort"
|
|
|
|
clientmodel "github.com/prometheus/client_golang/model"
|
|
)
|
|
|
|
// Helpers to calculate quantiles.
|
|
|
|
type bucket struct {
|
|
upperBound float64
|
|
count clientmodel.SampleValue
|
|
}
|
|
|
|
// buckets implements sort.Interface.
|
|
type buckets []bucket
|
|
|
|
func (b buckets) Len() int { return len(b) }
|
|
func (b buckets) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|
|
func (b buckets) Less(i, j int) bool { return b[i].upperBound < b[j].upperBound }
|
|
|
|
type metricWithBuckets struct {
|
|
metric clientmodel.COWMetric
|
|
buckets buckets
|
|
}
|
|
|
|
// quantile 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 falls into the highest
|
|
// bucket, the upper bound of the 2nd highest bucket is returned. A natural
|
|
// lower bound of 0 is assumed if the upper bound of the lowest bucket is
|
|
// greater 0. In that case, interpolation in the lowest bucket happens linearly
|
|
// between 0 and the upper bound of the lowest bucket. However, if the lowest
|
|
// bucket has an upper bound less or equal 0, this upper bound is returned if
|
|
// the quantile falls into the lowest bucket.
|
|
//
|
|
// There are a number of special cases (once we have a way to report errors
|
|
// happening during evaluations of AST functions, we should report those
|
|
// explicitly):
|
|
//
|
|
// If 'buckets' has fewer than 2 elements, NaN is returned.
|
|
//
|
|
// If the highest bucket is not +Inf, NaN is returned.
|
|
//
|
|
// If q<0, -Inf is returned.
|
|
//
|
|
// If q>1, +Inf is returned.
|
|
func quantile(q clientmodel.SampleValue, buckets buckets) float64 {
|
|
if q < 0 {
|
|
return math.Inf(-1)
|
|
}
|
|
if q > 1 {
|
|
return math.Inf(+1)
|
|
}
|
|
if len(buckets) < 2 {
|
|
return math.NaN()
|
|
}
|
|
sort.Sort(buckets)
|
|
if !math.IsInf(buckets[len(buckets)-1].upperBound, +1) {
|
|
return math.NaN()
|
|
}
|
|
|
|
rank := q * buckets[len(buckets)-1].count
|
|
b := sort.Search(len(buckets)-1, func(i int) bool { return buckets[i].count >= rank })
|
|
|
|
if b == len(buckets)-1 {
|
|
return buckets[len(buckets)-2].upperBound
|
|
}
|
|
if b == 0 && buckets[0].upperBound <= 0 {
|
|
return buckets[0].upperBound
|
|
}
|
|
var (
|
|
bucketStart float64
|
|
bucketEnd = buckets[b].upperBound
|
|
count = buckets[b].count
|
|
)
|
|
if b > 0 {
|
|
bucketStart = buckets[b-1].upperBound
|
|
count -= buckets[b-1].count
|
|
rank -= buckets[b-1].count
|
|
}
|
|
return bucketStart + (bucketEnd-bucketStart)*float64(rank/count)
|
|
}
|