diff --git a/promql/functions.go b/promql/functions.go index 23b0bbed4..40ca52773 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -490,33 +490,13 @@ func funcQuantileOverTime(ev *evaluator, args Expressions) model.Value { } el.Metric.Del(model.MetricNameLabel) - var result float64 - if q < 0 { - result = math.Inf(-1) - } else if q > 1 { - result = math.Inf(+1) - } else { - values := make([]float64, 0, len(el.Values)) - for _, v := range el.Values { - values = append(values, float64(v.Value)) - } - sort.Float64s(values) - - n := float64(len(el.Values)) - // When the quantile lies between two samples, - // we use a weighted average of the two samples. - rank := q * (n-1) - - lowerIndex := math.Max(0, math.Floor(rank)) - upperIndex := math.Min(n-1, lowerIndex+1) - - weight := rank - math.Floor(rank) - - result = values[int(lowerIndex)] * (1 - weight) + values[int(upperIndex)] * weight - } + values := make([]float64, 0, len(el.Values)) + for _, v := range el.Values { + values = append(values, float64(v.Value)) + } resultVector = append(resultVector, &sample{ Metric: el.Metric, - Value: model.SampleValue(result), + Value: model.SampleValue(quantile(q, values)), Timestamp: ev.Timestamp, }) } @@ -750,7 +730,7 @@ func funcHistogramQuantile(ev *evaluator, args Expressions) model.Value { for _, mb := range signatureToMetricWithBuckets { outVec = append(outVec, &sample{ Metric: mb.metric, - Value: model.SampleValue(quantile(q, mb.buckets)), + Value: model.SampleValue(bucketQuantile(q, mb.buckets)), Timestamp: ev.Timestamp, }) } diff --git a/promql/quantile.go b/promql/quantile.go index ca7af50d5..89a152249 100644 --- a/promql/quantile.go +++ b/promql/quantile.go @@ -48,16 +48,16 @@ type metricWithBuckets struct { 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. +// bucketQuantile 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 @@ -70,7 +70,7 @@ type metricWithBuckets struct { // If q<0, -Inf is returned. // // If q>1, +Inf is returned. -func quantile(q model.SampleValue, buckets buckets) float64 { +func bucketQuantile(q model.SampleValue, buckets buckets) float64 { if q < 0 { return math.Inf(-1) } @@ -106,3 +106,26 @@ func quantile(q model.SampleValue, buckets buckets) float64 { } return bucketStart + (bucketEnd-bucketStart)*float64(rank/count) } + +// qauntile calculates the given quantile of a slice of floats. +// The slice will be sorted. +func quantile(q float64, values []float64) float64 { + if q < 0 { + return math.Inf(-1) + } + if q > 1 { + return math.Inf(+1) + } + sort.Float64s(values) + + n := float64(len(values)) + // When the quantile lies between two samples, + // we use a weighted average of the two samples. + rank := q * (n - 1) + + lowerIndex := math.Max(0, math.Floor(rank)) + upperIndex := math.Min(n-1, lowerIndex+1) + + weight := rank - math.Floor(rank) + return values[int(lowerIndex)]*(1-weight) + values[int(upperIndex)]*weight +}