Merge pull request #1654 from shivanthzen/constNativeHistogram
Feat: Add ConstNativeHistogram
This commit is contained in:
commit
291b0b0c42
|
@ -14,6 +14,7 @@
|
|||
package prometheus
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"runtime"
|
||||
|
@ -28,6 +29,11 @@ import (
|
|||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
const (
|
||||
nativeHistogramSchemaMaximum = 8
|
||||
nativeHistogramSchemaMinimum = -4
|
||||
)
|
||||
|
||||
// nativeHistogramBounds for the frac of observed values. Only relevant for
|
||||
// schema > 0. The position in the slice is the schema. (0 is never used, just
|
||||
// here for convenience of using the schema directly as the index.)
|
||||
|
@ -1460,9 +1466,9 @@ func pickSchema(bucketFactor float64) int32 {
|
|||
floor := math.Floor(math.Log2(math.Log2(bucketFactor)))
|
||||
switch {
|
||||
case floor <= -8:
|
||||
return 8
|
||||
return nativeHistogramSchemaMaximum
|
||||
case floor >= 4:
|
||||
return -4
|
||||
return nativeHistogramSchemaMinimum
|
||||
default:
|
||||
return -int32(floor)
|
||||
}
|
||||
|
@ -1851,3 +1857,196 @@ func (n *nativeExemplars) addExemplar(e *dto.Exemplar) {
|
|||
n.exemplars = append(n.exemplars[:nIdx], append([]*dto.Exemplar{e}, append(n.exemplars[nIdx:rIdx], n.exemplars[rIdx+1:]...)...)...)
|
||||
}
|
||||
}
|
||||
|
||||
type constNativeHistogram struct {
|
||||
desc *Desc
|
||||
dto.Histogram
|
||||
labelPairs []*dto.LabelPair
|
||||
}
|
||||
|
||||
func validateCount(sum float64, count uint64, negativeBuckets, positiveBuckets map[int]int64, zeroBucket uint64) error {
|
||||
var bucketPopulationSum int64
|
||||
for _, v := range positiveBuckets {
|
||||
bucketPopulationSum += v
|
||||
}
|
||||
for _, v := range negativeBuckets {
|
||||
bucketPopulationSum += v
|
||||
}
|
||||
bucketPopulationSum += int64(zeroBucket)
|
||||
|
||||
// If the sum of observations is NaN, the number of observations must be greater or equal to the sum of all bucket counts.
|
||||
// Otherwise, the number of observations must be equal to the sum of all bucket counts .
|
||||
|
||||
if math.IsNaN(sum) && bucketPopulationSum > int64(count) ||
|
||||
!math.IsNaN(sum) && bucketPopulationSum != int64(count) {
|
||||
return errors.New("the sum of all bucket populations exceeds the count of observations")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewConstNativeHistogram returns a metric representing a Prometheus native histogram with
|
||||
// fixed values for the count, sum, and positive/negative/zero bucket counts. As those parameters
|
||||
// cannot be changed, the returned value does not implement the Histogram
|
||||
// interface (but only the Metric interface). Users of this package will not
|
||||
// have much use for it in regular operations. However, when implementing custom
|
||||
// OpenTelemetry Collectors, it is useful as a throw-away metric that is generated on the fly
|
||||
// to send it to Prometheus in the Collect method.
|
||||
//
|
||||
// zeroBucket counts all (positive and negative)
|
||||
// observations in the zero bucket (with an absolute value less or equal
|
||||
// the current threshold).
|
||||
// positiveBuckets and negativeBuckets are separate maps for negative and positive
|
||||
// observations. The map's value is an int64, counting observations in
|
||||
// that bucket. The map's key is the
|
||||
// index of the bucket according to the used
|
||||
// Schema. Index 0 is for an upper bound of 1 in positive buckets and for a lower bound of -1 in negative buckets.
|
||||
// NewConstNativeHistogram returns an error if
|
||||
// - the length of labelValues is not consistent with the variable labels in Desc or if Desc is invalid.
|
||||
// - the schema passed is not between 8 and -4
|
||||
// - the sum of counts in all buckets including the zero bucket does not equal the count if sum is not NaN (or exceeds the count if sum is NaN)
|
||||
//
|
||||
// See https://opentelemetry.io/docs/specs/otel/compatibility/prometheus_and_openmetrics/#exponential-histograms for more details about the conversion from OTel to Prometheus.
|
||||
func NewConstNativeHistogram(
|
||||
desc *Desc,
|
||||
count uint64,
|
||||
sum float64,
|
||||
positiveBuckets, negativeBuckets map[int]int64,
|
||||
zeroBucket uint64,
|
||||
schema int32,
|
||||
zeroThreshold float64,
|
||||
createdTimestamp time.Time,
|
||||
labelValues ...string,
|
||||
) (Metric, error) {
|
||||
if desc.err != nil {
|
||||
return nil, desc.err
|
||||
}
|
||||
if err := validateLabelValues(labelValues, len(desc.variableLabels.names)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if schema > nativeHistogramSchemaMaximum || schema < nativeHistogramSchemaMinimum {
|
||||
return nil, errors.New("invalid native histogram schema")
|
||||
}
|
||||
if err := validateCount(sum, count, negativeBuckets, positiveBuckets, zeroBucket); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
NegativeSpan, NegativeDelta := makeBucketsFromMap(negativeBuckets)
|
||||
PositiveSpan, PositiveDelta := makeBucketsFromMap(positiveBuckets)
|
||||
ret := &constNativeHistogram{
|
||||
desc: desc,
|
||||
Histogram: dto.Histogram{
|
||||
CreatedTimestamp: timestamppb.New(createdTimestamp),
|
||||
Schema: &schema,
|
||||
ZeroThreshold: &zeroThreshold,
|
||||
SampleCount: &count,
|
||||
SampleSum: &sum,
|
||||
|
||||
NegativeSpan: NegativeSpan,
|
||||
NegativeDelta: NegativeDelta,
|
||||
|
||||
PositiveSpan: PositiveSpan,
|
||||
PositiveDelta: PositiveDelta,
|
||||
|
||||
ZeroCount: proto.Uint64(zeroBucket),
|
||||
},
|
||||
labelPairs: MakeLabelPairs(desc, labelValues),
|
||||
}
|
||||
if *ret.ZeroThreshold == 0 && *ret.ZeroCount == 0 && len(ret.PositiveSpan) == 0 && len(ret.NegativeSpan) == 0 {
|
||||
ret.PositiveSpan = []*dto.BucketSpan{{
|
||||
Offset: proto.Int32(0),
|
||||
Length: proto.Uint32(0),
|
||||
}}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// MustNewConstNativeHistogram is a version of NewConstNativeHistogram that panics where
|
||||
// NewConstNativeHistogram would have returned an error.
|
||||
func MustNewConstNativeHistogram(
|
||||
desc *Desc,
|
||||
count uint64,
|
||||
sum float64,
|
||||
positiveBuckets, negativeBuckets map[int]int64,
|
||||
zeroBucket uint64,
|
||||
nativeHistogramSchema int32,
|
||||
nativeHistogramZeroThreshold float64,
|
||||
createdTimestamp time.Time,
|
||||
labelValues ...string,
|
||||
) Metric {
|
||||
nativehistogram, err := NewConstNativeHistogram(desc,
|
||||
count,
|
||||
sum,
|
||||
positiveBuckets,
|
||||
negativeBuckets,
|
||||
zeroBucket,
|
||||
nativeHistogramSchema,
|
||||
nativeHistogramZeroThreshold,
|
||||
createdTimestamp,
|
||||
labelValues...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return nativehistogram
|
||||
}
|
||||
|
||||
func (h *constNativeHistogram) Desc() *Desc {
|
||||
return h.desc
|
||||
}
|
||||
|
||||
func (h *constNativeHistogram) Write(out *dto.Metric) error {
|
||||
out.Histogram = &h.Histogram
|
||||
out.Label = h.labelPairs
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeBucketsFromMap(buckets map[int]int64) ([]*dto.BucketSpan, []int64) {
|
||||
if len(buckets) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var ii []int
|
||||
for k := range buckets {
|
||||
ii = append(ii, k)
|
||||
}
|
||||
sort.Ints(ii)
|
||||
|
||||
var (
|
||||
spans []*dto.BucketSpan
|
||||
deltas []int64
|
||||
prevCount int64
|
||||
nextI int
|
||||
)
|
||||
|
||||
appendDelta := func(count int64) {
|
||||
*spans[len(spans)-1].Length++
|
||||
deltas = append(deltas, count-prevCount)
|
||||
prevCount = count
|
||||
}
|
||||
|
||||
for n, i := range ii {
|
||||
count := buckets[i]
|
||||
// Multiple spans with only small gaps in between are probably
|
||||
// encoded more efficiently as one larger span with a few empty
|
||||
// buckets. Needs some research to find the sweet spot. For now,
|
||||
// we assume that gaps of one or two buckets should not create
|
||||
// a new span.
|
||||
iDelta := int32(i - nextI)
|
||||
if n == 0 || iDelta > 2 {
|
||||
// We have to create a new span, either because we are
|
||||
// at the very beginning, or because we have found a gap
|
||||
// of more than two buckets.
|
||||
spans = append(spans, &dto.BucketSpan{
|
||||
Offset: proto.Int32(iDelta),
|
||||
Length: proto.Uint32(0),
|
||||
})
|
||||
} else {
|
||||
// We have found a small gap (or no gap at all).
|
||||
// Insert empty buckets as needed.
|
||||
for j := int32(0); j < iDelta; j++ {
|
||||
appendDelta(0)
|
||||
}
|
||||
}
|
||||
appendDelta(count)
|
||||
nextI = i + 1
|
||||
}
|
||||
return spans, deltas
|
||||
}
|
||||
|
|
|
@ -25,11 +25,11 @@ import (
|
|||
"testing/quick"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus/internal"
|
||||
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus/internal"
|
||||
)
|
||||
|
||||
func benchmarkHistogramObserve(w int, b *testing.B) {
|
||||
|
@ -1543,3 +1543,544 @@ func TestFindBucket(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func syncMapToMap(syncmap *sync.Map) (m map[int]int64) {
|
||||
m = map[int]int64{}
|
||||
syncmap.Range(func(key, value any) bool {
|
||||
m[key.(int)] = *value.(*int64)
|
||||
return true
|
||||
})
|
||||
return m
|
||||
}
|
||||
|
||||
func TestConstNativeHistogram(t *testing.T) {
|
||||
now := time.Now()
|
||||
|
||||
scenarios := []struct {
|
||||
name string
|
||||
observations []float64 // With simulated interval of 1m.
|
||||
factor float64
|
||||
zeroThreshold float64
|
||||
maxBuckets uint32
|
||||
minResetDuration time.Duration
|
||||
maxZeroThreshold float64
|
||||
want *dto.Histogram
|
||||
}{
|
||||
{
|
||||
name: "no observations",
|
||||
factor: 1.1,
|
||||
want: &dto.Histogram{
|
||||
SampleCount: proto.Uint64(0),
|
||||
SampleSum: proto.Float64(0),
|
||||
Schema: proto.Int32(3),
|
||||
ZeroThreshold: proto.Float64(2.938735877055719e-39),
|
||||
ZeroCount: proto.Uint64(0),
|
||||
CreatedTimestamp: timestamppb.New(now),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no observations and zero threshold of zero resulting in no-op span",
|
||||
factor: 1.1,
|
||||
zeroThreshold: NativeHistogramZeroThresholdZero,
|
||||
want: &dto.Histogram{
|
||||
SampleCount: proto.Uint64(0),
|
||||
SampleSum: proto.Float64(0),
|
||||
Schema: proto.Int32(3),
|
||||
ZeroThreshold: proto.Float64(0),
|
||||
ZeroCount: proto.Uint64(0),
|
||||
PositiveSpan: []*dto.BucketSpan{
|
||||
{Offset: proto.Int32(0), Length: proto.Uint32(0)},
|
||||
},
|
||||
CreatedTimestamp: timestamppb.New(now),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "factor 1.1 results in schema 3",
|
||||
observations: []float64{0, 1, 2, 3},
|
||||
factor: 1.1,
|
||||
want: &dto.Histogram{
|
||||
SampleCount: proto.Uint64(4),
|
||||
SampleSum: proto.Float64(6),
|
||||
Schema: proto.Int32(3),
|
||||
ZeroThreshold: proto.Float64(2.938735877055719e-39),
|
||||
ZeroCount: proto.Uint64(1),
|
||||
PositiveSpan: []*dto.BucketSpan{
|
||||
{Offset: proto.Int32(0), Length: proto.Uint32(1)},
|
||||
{Offset: proto.Int32(7), Length: proto.Uint32(1)},
|
||||
{Offset: proto.Int32(4), Length: proto.Uint32(1)},
|
||||
},
|
||||
PositiveDelta: []int64{1, 0, 0},
|
||||
CreatedTimestamp: timestamppb.New(now),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "factor 1.2 results in schema 2",
|
||||
observations: []float64{0, 1, 1.2, 1.4, 1.8, 2},
|
||||
factor: 1.2,
|
||||
want: &dto.Histogram{
|
||||
SampleCount: proto.Uint64(6),
|
||||
SampleSum: proto.Float64(7.4),
|
||||
Schema: proto.Int32(2),
|
||||
ZeroThreshold: proto.Float64(2.938735877055719e-39),
|
||||
ZeroCount: proto.Uint64(1),
|
||||
PositiveSpan: []*dto.BucketSpan{
|
||||
{Offset: proto.Int32(0), Length: proto.Uint32(5)},
|
||||
},
|
||||
PositiveDelta: []int64{1, -1, 2, -2, 2},
|
||||
CreatedTimestamp: timestamppb.New(now),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "factor 4 results in schema -1",
|
||||
observations: []float64{
|
||||
0.0156251, 0.0625, // Bucket -2: (0.015625, 0.0625)
|
||||
0.1, 0.25, // Bucket -1: (0.0625, 0.25]
|
||||
0.5, 1, // Bucket 0: (0.25, 1]
|
||||
1.5, 2, 3, 3.5, // Bucket 1: (1, 4]
|
||||
5, 6, 7, // Bucket 2: (4, 16]
|
||||
33.33, // Bucket 3: (16, 64]
|
||||
},
|
||||
factor: 4,
|
||||
want: &dto.Histogram{
|
||||
SampleCount: proto.Uint64(14),
|
||||
SampleSum: proto.Float64(63.2581251),
|
||||
Schema: proto.Int32(-1),
|
||||
ZeroThreshold: proto.Float64(2.938735877055719e-39),
|
||||
ZeroCount: proto.Uint64(0),
|
||||
PositiveSpan: []*dto.BucketSpan{
|
||||
{Offset: proto.Int32(-2), Length: proto.Uint32(6)},
|
||||
},
|
||||
PositiveDelta: []int64{2, 0, 0, 2, -1, -2},
|
||||
CreatedTimestamp: timestamppb.New(now),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "factor 17 results in schema -2",
|
||||
observations: []float64{
|
||||
0.0156251, 0.0625, // Bucket -1: (0.015625, 0.0625]
|
||||
0.1, 0.25, 0.5, 1, // Bucket 0: (0.0625, 1]
|
||||
1.5, 2, 3, 3.5, 5, 6, 7, // Bucket 1: (1, 16]
|
||||
33.33, // Bucket 2: (16, 256]
|
||||
},
|
||||
factor: 17,
|
||||
want: &dto.Histogram{
|
||||
SampleCount: proto.Uint64(14),
|
||||
SampleSum: proto.Float64(63.2581251),
|
||||
Schema: proto.Int32(-2),
|
||||
ZeroThreshold: proto.Float64(2.938735877055719e-39),
|
||||
ZeroCount: proto.Uint64(0),
|
||||
PositiveSpan: []*dto.BucketSpan{
|
||||
{Offset: proto.Int32(-1), Length: proto.Uint32(4)},
|
||||
},
|
||||
PositiveDelta: []int64{2, 2, 3, -6},
|
||||
CreatedTimestamp: timestamppb.New(now),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "negative buckets",
|
||||
observations: []float64{0, -1, -1.2, -1.4, -1.8, -2},
|
||||
factor: 1.2,
|
||||
want: &dto.Histogram{
|
||||
SampleCount: proto.Uint64(6),
|
||||
SampleSum: proto.Float64(-7.4),
|
||||
Schema: proto.Int32(2),
|
||||
ZeroThreshold: proto.Float64(2.938735877055719e-39),
|
||||
ZeroCount: proto.Uint64(1),
|
||||
NegativeSpan: []*dto.BucketSpan{
|
||||
{Offset: proto.Int32(0), Length: proto.Uint32(5)},
|
||||
},
|
||||
NegativeDelta: []int64{1, -1, 2, -2, 2},
|
||||
CreatedTimestamp: timestamppb.New(now),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "negative and positive buckets",
|
||||
observations: []float64{0, -1, -1.2, -1.4, -1.8, -2, 1, 1.2, 1.4, 1.8, 2},
|
||||
factor: 1.2,
|
||||
want: &dto.Histogram{
|
||||
SampleCount: proto.Uint64(11),
|
||||
SampleSum: proto.Float64(0),
|
||||
Schema: proto.Int32(2),
|
||||
ZeroThreshold: proto.Float64(2.938735877055719e-39),
|
||||
ZeroCount: proto.Uint64(1),
|
||||
NegativeSpan: []*dto.BucketSpan{
|
||||
{Offset: proto.Int32(0), Length: proto.Uint32(5)},
|
||||
},
|
||||
NegativeDelta: []int64{1, -1, 2, -2, 2},
|
||||
PositiveSpan: []*dto.BucketSpan{
|
||||
{Offset: proto.Int32(0), Length: proto.Uint32(5)},
|
||||
},
|
||||
PositiveDelta: []int64{1, -1, 2, -2, 2},
|
||||
CreatedTimestamp: timestamppb.New(now),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "wide zero bucket",
|
||||
observations: []float64{0, -1, -1.2, -1.4, -1.8, -2, 1, 1.2, 1.4, 1.8, 2},
|
||||
factor: 1.2,
|
||||
zeroThreshold: 1.4,
|
||||
want: &dto.Histogram{
|
||||
SampleCount: proto.Uint64(11),
|
||||
SampleSum: proto.Float64(0),
|
||||
Schema: proto.Int32(2),
|
||||
ZeroThreshold: proto.Float64(1.4),
|
||||
ZeroCount: proto.Uint64(7),
|
||||
NegativeSpan: []*dto.BucketSpan{
|
||||
{Offset: proto.Int32(4), Length: proto.Uint32(1)},
|
||||
},
|
||||
NegativeDelta: []int64{2},
|
||||
PositiveSpan: []*dto.BucketSpan{
|
||||
{Offset: proto.Int32(4), Length: proto.Uint32(1)},
|
||||
},
|
||||
PositiveDelta: []int64{2},
|
||||
CreatedTimestamp: timestamppb.New(now),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "NaN observation",
|
||||
observations: []float64{0, 1, 1.2, 1.4, 1.8, 2, math.NaN()},
|
||||
factor: 1.2,
|
||||
want: &dto.Histogram{
|
||||
SampleCount: proto.Uint64(7),
|
||||
SampleSum: proto.Float64(math.NaN()),
|
||||
Schema: proto.Int32(2),
|
||||
ZeroThreshold: proto.Float64(2.938735877055719e-39),
|
||||
ZeroCount: proto.Uint64(1),
|
||||
PositiveSpan: []*dto.BucketSpan{
|
||||
{Offset: proto.Int32(0), Length: proto.Uint32(5)},
|
||||
},
|
||||
PositiveDelta: []int64{1, -1, 2, -2, 2},
|
||||
CreatedTimestamp: timestamppb.New(now),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "+Inf observation",
|
||||
observations: []float64{0, 1, 1.2, 1.4, 1.8, 2, math.Inf(+1)},
|
||||
factor: 1.2,
|
||||
want: &dto.Histogram{
|
||||
SampleCount: proto.Uint64(7),
|
||||
SampleSum: proto.Float64(math.Inf(+1)),
|
||||
Schema: proto.Int32(2),
|
||||
ZeroThreshold: proto.Float64(2.938735877055719e-39),
|
||||
ZeroCount: proto.Uint64(1),
|
||||
PositiveSpan: []*dto.BucketSpan{
|
||||
{Offset: proto.Int32(0), Length: proto.Uint32(5)},
|
||||
{Offset: proto.Int32(4092), Length: proto.Uint32(1)},
|
||||
},
|
||||
PositiveDelta: []int64{1, -1, 2, -2, 2, -1},
|
||||
CreatedTimestamp: timestamppb.New(now),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "-Inf observation",
|
||||
observations: []float64{0, 1, 1.2, 1.4, 1.8, 2, math.Inf(-1)},
|
||||
factor: 1.2,
|
||||
want: &dto.Histogram{
|
||||
SampleCount: proto.Uint64(7),
|
||||
SampleSum: proto.Float64(math.Inf(-1)),
|
||||
Schema: proto.Int32(2),
|
||||
ZeroThreshold: proto.Float64(2.938735877055719e-39),
|
||||
ZeroCount: proto.Uint64(1),
|
||||
NegativeSpan: []*dto.BucketSpan{
|
||||
{Offset: proto.Int32(4097), Length: proto.Uint32(1)},
|
||||
},
|
||||
NegativeDelta: []int64{1},
|
||||
PositiveSpan: []*dto.BucketSpan{
|
||||
{Offset: proto.Int32(0), Length: proto.Uint32(5)},
|
||||
},
|
||||
PositiveDelta: []int64{1, -1, 2, -2, 2},
|
||||
CreatedTimestamp: timestamppb.New(now),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "limited buckets but nothing triggered",
|
||||
observations: []float64{0, 1, 1.2, 1.4, 1.8, 2},
|
||||
factor: 1.2,
|
||||
maxBuckets: 4,
|
||||
want: &dto.Histogram{
|
||||
SampleCount: proto.Uint64(6),
|
||||
SampleSum: proto.Float64(7.4),
|
||||
Schema: proto.Int32(2),
|
||||
ZeroThreshold: proto.Float64(2.938735877055719e-39),
|
||||
ZeroCount: proto.Uint64(1),
|
||||
PositiveSpan: []*dto.BucketSpan{
|
||||
{Offset: proto.Int32(0), Length: proto.Uint32(5)},
|
||||
},
|
||||
PositiveDelta: []int64{1, -1, 2, -2, 2},
|
||||
CreatedTimestamp: timestamppb.New(now),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "buckets limited by halving resolution",
|
||||
observations: []float64{0, 1, 1.1, 1.2, 1.4, 1.8, 2, 3},
|
||||
factor: 1.2,
|
||||
maxBuckets: 4,
|
||||
want: &dto.Histogram{
|
||||
SampleCount: proto.Uint64(8),
|
||||
SampleSum: proto.Float64(11.5),
|
||||
Schema: proto.Int32(1),
|
||||
ZeroThreshold: proto.Float64(2.938735877055719e-39),
|
||||
ZeroCount: proto.Uint64(1),
|
||||
PositiveSpan: []*dto.BucketSpan{
|
||||
{Offset: proto.Int32(0), Length: proto.Uint32(5)},
|
||||
},
|
||||
PositiveDelta: []int64{1, 2, -1, -2, 1},
|
||||
CreatedTimestamp: timestamppb.New(now),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "buckets limited by widening the zero bucket",
|
||||
observations: []float64{0, 1, 1.1, 1.2, 1.4, 1.8, 2, 3},
|
||||
factor: 1.2,
|
||||
maxBuckets: 4,
|
||||
maxZeroThreshold: 1.2,
|
||||
want: &dto.Histogram{
|
||||
SampleCount: proto.Uint64(8),
|
||||
SampleSum: proto.Float64(11.5),
|
||||
Schema: proto.Int32(2),
|
||||
ZeroThreshold: proto.Float64(1),
|
||||
ZeroCount: proto.Uint64(2),
|
||||
PositiveSpan: []*dto.BucketSpan{
|
||||
{Offset: proto.Int32(1), Length: proto.Uint32(7)},
|
||||
},
|
||||
PositiveDelta: []int64{1, 1, -2, 2, -2, 0, 1},
|
||||
CreatedTimestamp: timestamppb.New(now),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "buckets limited by widening the zero bucket twice",
|
||||
observations: []float64{0, 1, 1.1, 1.2, 1.4, 1.8, 2, 3, 4},
|
||||
factor: 1.2,
|
||||
maxBuckets: 4,
|
||||
maxZeroThreshold: 1.2,
|
||||
want: &dto.Histogram{
|
||||
SampleCount: proto.Uint64(9),
|
||||
SampleSum: proto.Float64(15.5),
|
||||
Schema: proto.Int32(2),
|
||||
ZeroThreshold: proto.Float64(1.189207115002721),
|
||||
ZeroCount: proto.Uint64(3),
|
||||
PositiveSpan: []*dto.BucketSpan{
|
||||
{Offset: proto.Int32(2), Length: proto.Uint32(7)},
|
||||
},
|
||||
PositiveDelta: []int64{2, -2, 2, -2, 0, 1, 0},
|
||||
CreatedTimestamp: timestamppb.New(now),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "buckets limited by reset",
|
||||
observations: []float64{0, 1, 1.1, 1.2, 1.4, 1.8, 2, 3, 4},
|
||||
factor: 1.2,
|
||||
maxBuckets: 4,
|
||||
maxZeroThreshold: 1.2,
|
||||
minResetDuration: 5 * time.Minute,
|
||||
want: &dto.Histogram{
|
||||
SampleCount: proto.Uint64(2),
|
||||
SampleSum: proto.Float64(7),
|
||||
Schema: proto.Int32(2),
|
||||
ZeroThreshold: proto.Float64(2.938735877055719e-39),
|
||||
ZeroCount: proto.Uint64(0),
|
||||
PositiveSpan: []*dto.BucketSpan{
|
||||
{Offset: proto.Int32(7), Length: proto.Uint32(2)},
|
||||
},
|
||||
PositiveDelta: []int64{1, 0},
|
||||
CreatedTimestamp: timestamppb.New(now.Add(8 * time.Minute)), // We expect reset to happen after 8 observations.
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "limited buckets but nothing triggered, negative observations",
|
||||
observations: []float64{0, -1, -1.2, -1.4, -1.8, -2},
|
||||
factor: 1.2,
|
||||
maxBuckets: 4,
|
||||
want: &dto.Histogram{
|
||||
SampleCount: proto.Uint64(6),
|
||||
SampleSum: proto.Float64(-7.4),
|
||||
Schema: proto.Int32(2),
|
||||
ZeroThreshold: proto.Float64(2.938735877055719e-39),
|
||||
ZeroCount: proto.Uint64(1),
|
||||
NegativeSpan: []*dto.BucketSpan{
|
||||
{Offset: proto.Int32(0), Length: proto.Uint32(5)},
|
||||
},
|
||||
NegativeDelta: []int64{1, -1, 2, -2, 2},
|
||||
CreatedTimestamp: timestamppb.New(now),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "buckets limited by halving resolution, negative observations",
|
||||
observations: []float64{0, -1, -1.1, -1.2, -1.4, -1.8, -2, -3},
|
||||
factor: 1.2,
|
||||
maxBuckets: 4,
|
||||
want: &dto.Histogram{
|
||||
SampleCount: proto.Uint64(8),
|
||||
SampleSum: proto.Float64(-11.5),
|
||||
Schema: proto.Int32(1),
|
||||
ZeroThreshold: proto.Float64(2.938735877055719e-39),
|
||||
ZeroCount: proto.Uint64(1),
|
||||
NegativeSpan: []*dto.BucketSpan{
|
||||
{Offset: proto.Int32(0), Length: proto.Uint32(5)},
|
||||
},
|
||||
NegativeDelta: []int64{1, 2, -1, -2, 1},
|
||||
CreatedTimestamp: timestamppb.New(now),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "buckets limited by widening the zero bucket, negative observations",
|
||||
observations: []float64{0, -1, -1.1, -1.2, -1.4, -1.8, -2, -3},
|
||||
factor: 1.2,
|
||||
maxBuckets: 4,
|
||||
maxZeroThreshold: 1.2,
|
||||
want: &dto.Histogram{
|
||||
SampleCount: proto.Uint64(8),
|
||||
SampleSum: proto.Float64(-11.5),
|
||||
Schema: proto.Int32(2),
|
||||
ZeroThreshold: proto.Float64(1),
|
||||
ZeroCount: proto.Uint64(2),
|
||||
NegativeSpan: []*dto.BucketSpan{
|
||||
{Offset: proto.Int32(1), Length: proto.Uint32(7)},
|
||||
},
|
||||
NegativeDelta: []int64{1, 1, -2, 2, -2, 0, 1},
|
||||
CreatedTimestamp: timestamppb.New(now),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "buckets limited by widening the zero bucket twice, negative observations",
|
||||
observations: []float64{0, -1, -1.1, -1.2, -1.4, -1.8, -2, -3, -4},
|
||||
factor: 1.2,
|
||||
maxBuckets: 4,
|
||||
maxZeroThreshold: 1.2,
|
||||
want: &dto.Histogram{
|
||||
SampleCount: proto.Uint64(9),
|
||||
SampleSum: proto.Float64(-15.5),
|
||||
Schema: proto.Int32(2),
|
||||
ZeroThreshold: proto.Float64(1.189207115002721),
|
||||
ZeroCount: proto.Uint64(3),
|
||||
NegativeSpan: []*dto.BucketSpan{
|
||||
{Offset: proto.Int32(2), Length: proto.Uint32(7)},
|
||||
},
|
||||
NegativeDelta: []int64{2, -2, 2, -2, 0, 1, 0},
|
||||
CreatedTimestamp: timestamppb.New(now),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "buckets limited by reset, negative observations",
|
||||
observations: []float64{0, -1, -1.1, -1.2, -1.4, -1.8, -2, -3, -4},
|
||||
factor: 1.2,
|
||||
maxBuckets: 4,
|
||||
maxZeroThreshold: 1.2,
|
||||
minResetDuration: 5 * time.Minute,
|
||||
want: &dto.Histogram{
|
||||
SampleCount: proto.Uint64(2),
|
||||
SampleSum: proto.Float64(-7),
|
||||
Schema: proto.Int32(2),
|
||||
ZeroThreshold: proto.Float64(2.938735877055719e-39),
|
||||
ZeroCount: proto.Uint64(0),
|
||||
NegativeSpan: []*dto.BucketSpan{
|
||||
{Offset: proto.Int32(7), Length: proto.Uint32(2)},
|
||||
},
|
||||
NegativeDelta: []int64{1, 0},
|
||||
CreatedTimestamp: timestamppb.New(now.Add(8 * time.Minute)), // We expect reset to happen after 8 observations.
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "buckets limited by halving resolution, then reset",
|
||||
observations: []float64{0, 1, 1.1, 1.2, 1.4, 1.8, 2, 5, 5.1, 3, 4},
|
||||
factor: 1.2,
|
||||
maxBuckets: 4,
|
||||
minResetDuration: 9 * time.Minute,
|
||||
want: &dto.Histogram{
|
||||
SampleCount: proto.Uint64(3),
|
||||
SampleSum: proto.Float64(12.1),
|
||||
Schema: proto.Int32(2),
|
||||
ZeroThreshold: proto.Float64(2.938735877055719e-39),
|
||||
ZeroCount: proto.Uint64(0),
|
||||
PositiveSpan: []*dto.BucketSpan{
|
||||
{Offset: proto.Int32(7), Length: proto.Uint32(4)},
|
||||
},
|
||||
PositiveDelta: []int64{1, 0, -1, 1},
|
||||
CreatedTimestamp: timestamppb.New(now.Add(9 * time.Minute)), // We expect reset to happen after 8 minutes.
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "buckets limited by widening the zero bucket, then reset",
|
||||
observations: []float64{0, 1, 1.1, 1.2, 1.4, 1.8, 2, 5, 5.1, 3, 4},
|
||||
factor: 1.2,
|
||||
maxBuckets: 4,
|
||||
maxZeroThreshold: 1.2,
|
||||
minResetDuration: 9 * time.Minute,
|
||||
want: &dto.Histogram{
|
||||
SampleCount: proto.Uint64(3),
|
||||
SampleSum: proto.Float64(12.1),
|
||||
Schema: proto.Int32(2),
|
||||
ZeroThreshold: proto.Float64(2.938735877055719e-39),
|
||||
ZeroCount: proto.Uint64(0),
|
||||
PositiveSpan: []*dto.BucketSpan{
|
||||
{Offset: proto.Int32(7), Length: proto.Uint32(4)},
|
||||
},
|
||||
PositiveDelta: []int64{1, 0, -1, 1},
|
||||
CreatedTimestamp: timestamppb.New(now.Add(9 * time.Minute)), // We expect reset to happen after 8 minutes.
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
var (
|
||||
ts = now
|
||||
funcToCall func()
|
||||
whenToCall time.Duration
|
||||
)
|
||||
|
||||
his := NewHistogram(HistogramOpts{
|
||||
Name: "name",
|
||||
Help: "help",
|
||||
NativeHistogramBucketFactor: s.factor,
|
||||
NativeHistogramZeroThreshold: s.zeroThreshold,
|
||||
NativeHistogramMaxBucketNumber: s.maxBuckets,
|
||||
NativeHistogramMinResetDuration: s.minResetDuration,
|
||||
NativeHistogramMaxZeroThreshold: s.maxZeroThreshold,
|
||||
now: func() time.Time { return ts },
|
||||
afterFunc: func(d time.Duration, f func()) *time.Timer {
|
||||
funcToCall = f
|
||||
whenToCall = d
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
ts = ts.Add(time.Minute)
|
||||
for _, o := range s.observations {
|
||||
his.Observe(o)
|
||||
ts = ts.Add(time.Minute)
|
||||
whenToCall -= time.Minute
|
||||
if funcToCall != nil && whenToCall <= 0 {
|
||||
funcToCall()
|
||||
funcToCall = nil
|
||||
}
|
||||
}
|
||||
_his := his.(*histogram)
|
||||
n := atomic.LoadUint64(&_his.countAndHotIdx)
|
||||
hotIdx := n >> 63
|
||||
cold := _his.counts[hotIdx]
|
||||
consthist, err := NewConstNativeHistogram(_his.Desc(),
|
||||
cold.count,
|
||||
math.Float64frombits(cold.sumBits),
|
||||
syncMapToMap(&cold.nativeHistogramBucketsPositive),
|
||||
syncMapToMap(&cold.nativeHistogramBucketsNegative),
|
||||
cold.nativeHistogramZeroBucket,
|
||||
cold.nativeHistogramSchema,
|
||||
math.Float64frombits(cold.nativeHistogramZeroThresholdBits),
|
||||
_his.lastResetTime,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal("unexpected error writing metric", err)
|
||||
}
|
||||
m2 := &dto.Metric{}
|
||||
|
||||
if err := consthist.Write(m2); err != nil {
|
||||
t.Fatal("unexpected error writing metric", err)
|
||||
}
|
||||
got := m2.Histogram
|
||||
if !proto.Equal(s.want, got) {
|
||||
t.Errorf("want histogram %q, got %q", s.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue