mirror of https://github.com/prometheus/prometheus
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
257 lines
7.3 KiB
257 lines
7.3 KiB
// Copyright 2024 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 convertnhcb |
|
|
|
import ( |
|
"errors" |
|
"fmt" |
|
"math" |
|
"sort" |
|
"strings" |
|
|
|
"github.com/prometheus/prometheus/model/histogram" |
|
"github.com/prometheus/prometheus/model/labels" |
|
) |
|
|
|
var ( |
|
errNegativeBucketCount = errors.New("bucket count must be non-negative") |
|
errNegativeCount = errors.New("count must be non-negative") |
|
errCountMismatch = errors.New("count mismatch") |
|
errCountNotCumulative = errors.New("count is not cumulative") |
|
) |
|
|
|
type tempHistogramBucket struct { |
|
le float64 |
|
count float64 |
|
} |
|
|
|
// TempHistogram is used to collect information about classic histogram |
|
// samples incrementally before creating a histogram.Histogram or |
|
// histogram.FloatHistogram based on the values collected. |
|
type TempHistogram struct { |
|
buckets []tempHistogramBucket |
|
count float64 |
|
sum float64 |
|
err error |
|
hasCount bool |
|
} |
|
|
|
// NewTempHistogram creates a new TempHistogram to |
|
// collect information about classic histogram samples. |
|
func NewTempHistogram() TempHistogram { |
|
return TempHistogram{ |
|
buckets: make([]tempHistogramBucket, 0, 10), |
|
} |
|
} |
|
|
|
func (h TempHistogram) Err() error { |
|
return h.err |
|
} |
|
|
|
func (h *TempHistogram) Reset() { |
|
h.buckets = h.buckets[:0] |
|
h.count = 0 |
|
h.sum = 0 |
|
h.err = nil |
|
h.hasCount = false |
|
} |
|
|
|
func (h *TempHistogram) SetBucketCount(boundary, count float64) error { |
|
if h.err != nil { |
|
return h.err |
|
} |
|
if count < 0 { |
|
h.err = fmt.Errorf("%w: le=%g, count=%g", errNegativeBucketCount, boundary, count) |
|
return h.err |
|
} |
|
// Assume that the elements are added in order. |
|
switch { |
|
case len(h.buckets) == 0: |
|
h.buckets = append(h.buckets, tempHistogramBucket{le: boundary, count: count}) |
|
case h.buckets[len(h.buckets)-1].le < boundary: |
|
// Happy case is "<". |
|
if count < h.buckets[len(h.buckets)-1].count { |
|
h.err = fmt.Errorf("%w: %g < %g", errCountNotCumulative, count, h.buckets[len(h.buckets)-1].count) |
|
return h.err |
|
} |
|
h.buckets = append(h.buckets, tempHistogramBucket{le: boundary, count: count}) |
|
case h.buckets[len(h.buckets)-1].le == boundary: |
|
// Ignore this, as it is a duplicate sample. |
|
default: |
|
// Find the correct position to insert. |
|
i := sort.Search(len(h.buckets), func(i int) bool { |
|
return h.buckets[i].le >= boundary |
|
}) |
|
if h.buckets[i].le == boundary { |
|
// Ignore this, as it is a duplicate sample. |
|
return nil |
|
} |
|
if i > 0 && count < h.buckets[i-1].count { |
|
h.err = fmt.Errorf("%w: %g < %g", errCountNotCumulative, count, h.buckets[i-1].count) |
|
return h.err |
|
} |
|
if count > h.buckets[i].count { |
|
h.err = fmt.Errorf("%w: %g > %g", errCountNotCumulative, count, h.buckets[i].count) |
|
return h.err |
|
} |
|
// Insert at the correct position unless duplicate. |
|
h.buckets = append(h.buckets, tempHistogramBucket{}) |
|
copy(h.buckets[i+1:], h.buckets[i:]) |
|
h.buckets[i] = tempHistogramBucket{le: boundary, count: count} |
|
} |
|
return nil |
|
} |
|
|
|
func (h *TempHistogram) SetCount(count float64) error { |
|
if h.err != nil { |
|
return h.err |
|
} |
|
if count < 0 { |
|
h.err = fmt.Errorf("%w: count=%g", errNegativeCount, count) |
|
return h.err |
|
} |
|
h.count = count |
|
h.hasCount = true |
|
return nil |
|
} |
|
|
|
func (h *TempHistogram) SetSum(sum float64) error { |
|
if h.err != nil { |
|
return h.err |
|
} |
|
h.sum = sum |
|
return nil |
|
} |
|
|
|
func (h TempHistogram) Convert() (*histogram.Histogram, *histogram.FloatHistogram, error) { |
|
if h.err != nil { |
|
return nil, nil, h.err |
|
} |
|
|
|
if len(h.buckets) == 0 || h.buckets[len(h.buckets)-1].le != math.Inf(1) { |
|
// No +Inf bucket. |
|
if !h.hasCount && len(h.buckets) > 0 { |
|
// No count either, so set count to the last known bucket's count. |
|
h.count = h.buckets[len(h.buckets)-1].count |
|
} |
|
// Let the last bucket be +Inf with the overall count. |
|
h.buckets = append(h.buckets, tempHistogramBucket{le: math.Inf(1), count: h.count}) |
|
} |
|
|
|
if !h.hasCount { |
|
h.count = h.buckets[len(h.buckets)-1].count |
|
h.hasCount = true |
|
} |
|
|
|
for _, b := range h.buckets { |
|
intCount := int64(math.Round(b.count)) |
|
if b.count != float64(intCount) { |
|
return h.convertToFloatHistogram() |
|
} |
|
} |
|
|
|
intCount := uint64(math.Round(h.count)) |
|
if h.count != float64(intCount) { |
|
return h.convertToFloatHistogram() |
|
} |
|
return h.convertToIntegerHistogram(intCount) |
|
} |
|
|
|
func (h TempHistogram) convertToIntegerHistogram(count uint64) (*histogram.Histogram, *histogram.FloatHistogram, error) { |
|
rh := &histogram.Histogram{ |
|
Schema: histogram.CustomBucketsSchema, |
|
Count: count, |
|
Sum: h.sum, |
|
PositiveSpans: []histogram.Span{{Length: uint32(len(h.buckets))}}, |
|
PositiveBuckets: make([]int64, len(h.buckets)), |
|
} |
|
|
|
if len(h.buckets) > 1 { |
|
rh.CustomValues = make([]float64, len(h.buckets)-1) // Not storing the last +Inf bucket. |
|
} |
|
|
|
prevCount := int64(0) |
|
prevDelta := int64(0) |
|
for i, b := range h.buckets { |
|
// delta is the actual bucket count as the input is cumulative. |
|
delta := int64(b.count) - prevCount |
|
rh.PositiveBuckets[i] = delta - prevDelta |
|
prevCount = int64(b.count) |
|
prevDelta = delta |
|
if b.le != math.Inf(1) { |
|
rh.CustomValues[i] = b.le |
|
} |
|
} |
|
|
|
if count != uint64(h.buckets[len(h.buckets)-1].count) { |
|
h.err = fmt.Errorf("%w: count=%d != le=%g count=%g", errCountMismatch, count, h.buckets[len(h.buckets)-1].le, h.buckets[len(h.buckets)-1].count) |
|
return nil, nil, h.err |
|
} |
|
|
|
return rh.Compact(2), nil, nil |
|
} |
|
|
|
func (h TempHistogram) convertToFloatHistogram() (*histogram.Histogram, *histogram.FloatHistogram, error) { |
|
rh := &histogram.FloatHistogram{ |
|
Schema: histogram.CustomBucketsSchema, |
|
Count: h.count, |
|
Sum: h.sum, |
|
PositiveSpans: []histogram.Span{{Length: uint32(len(h.buckets))}}, |
|
PositiveBuckets: make([]float64, len(h.buckets)), |
|
} |
|
|
|
if len(h.buckets) > 1 { |
|
rh.CustomValues = make([]float64, len(h.buckets)-1) // Not storing the last +Inf bucket. |
|
} |
|
|
|
prevCount := 0.0 |
|
for i, b := range h.buckets { |
|
rh.PositiveBuckets[i] = b.count - prevCount |
|
prevCount = b.count |
|
if b.le != math.Inf(1) { |
|
rh.CustomValues[i] = b.le |
|
} |
|
} |
|
|
|
if h.count != h.buckets[len(h.buckets)-1].count { |
|
h.err = fmt.Errorf("%w: count=%g != le=%g count=%g", errCountMismatch, h.count, h.buckets[len(h.buckets)-1].le, h.buckets[len(h.buckets)-1].count) |
|
return nil, nil, h.err |
|
} |
|
|
|
return nil, rh.Compact(0), nil |
|
} |
|
|
|
func GetHistogramMetricBase(m labels.Labels, suffix string) labels.Labels { |
|
mName := m.Get(labels.MetricName) |
|
return labels.NewBuilder(m). |
|
Set(labels.MetricName, strings.TrimSuffix(mName, suffix)). |
|
Del(labels.BucketLabel). |
|
Labels() |
|
} |
|
|
|
// GetHistogramMetricBaseName removes the suffixes _bucket, _sum, _count from |
|
// the metric name. We specifically do not remove the _created suffix as that |
|
// should be removed by the caller. |
|
func GetHistogramMetricBaseName(s string) string { |
|
if r, ok := strings.CutSuffix(s, "_bucket"); ok { |
|
return r |
|
} |
|
if r, ok := strings.CutSuffix(s, "_sum"); ok { |
|
return r |
|
} |
|
if r, ok := strings.CutSuffix(s, "_count"); ok { |
|
return r |
|
} |
|
return s |
|
}
|
|
|