Add native histograms
Signed-off-by: Shivanth <shivanth.metchem@deliveryhero.com> Apply suggestions from code review Co-authored-by: Arthur Silva Sens <arthursens2005@gmail.com> Signed-off-by: Shivanth <shivanth.metchem@deliveryhero.com> Fix references and existing tests Signed-off-by: Shivanth <shivanth.metchem@deliveryhero.com> Review comments Signed-off-by: Shivanth <shivanth.metchem@deliveryhero.com> fmt Signed-off-by: Shivanth <shivanth.metchem@deliveryhero.com> validation Signed-off-by: Shivanth <shivanth.metchem@deliveryhero.com> Count validation Signed-off-by: Shivanth <shivanth.metchem@deliveryhero.com> Validation Signed-off-by: Shivanth <shivanth.metchem@deliveryhero.com> Review comments Signed-off-by: Shivanth <shivanth.metchem@deliveryhero.com> Space formatting Signed-off-by: Shivanth <shivanth.metchem@deliveryhero.com> Review comments Signed-off-by: Shivanth <shivanth.metchem@deliveryhero.com> Rename SyncMaptoMap -> SyncMapToMap Signed-off-by: Shivanth <shivanth.metchem@deliveryhero.com> Remove exemplars from parameters for constNativeHistogram function Signed-off-by: Shivanth <shivanth.metchem@deliveryhero.com> Update prometheus/histogram.go Co-authored-by: George Krajcsovits <krajorama@users.noreply.github.com> Signed-off-by: Shivanth MP <shivanth.metchem@deliveryhero.com> Update prometheus/histogram.go Co-authored-by: George Krajcsovits <krajorama@users.noreply.github.com> Signed-off-by: Shivanth MP <shivanth.metchem@deliveryhero.com> Update prometheus/histogram.go Co-authored-by: George Krajcsovits <krajorama@users.noreply.github.com> Signed-off-by: Shivanth MP <shivanth.metchem@deliveryhero.com> Update prometheus/histogram.go Co-authored-by: Björn Rabenstein <github@rabenste.in> Signed-off-by: Shivanth MP <shivanth.metchem@deliveryhero.com> Review comments Signed-off-by: Shivanth <shivanth.metchem@deliveryhero.com> Lint fix Signed-off-by: Shivanth <shivanth.metchem@deliveryhero.com>
This commit is contained in:
parent
13851e9287
commit
ae8497940d
|
@ -14,6 +14,7 @@
|
||||||
package prometheus
|
package prometheus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
@ -28,6 +29,11 @@ import (
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
nativeHistogramSchemaMaximum = 8
|
||||||
|
nativeHistogramSchemaMinimum = -4
|
||||||
|
)
|
||||||
|
|
||||||
// nativeHistogramBounds for the frac of observed values. Only relevant for
|
// 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
|
// 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.)
|
// 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)))
|
floor := math.Floor(math.Log2(math.Log2(bucketFactor)))
|
||||||
switch {
|
switch {
|
||||||
case floor <= -8:
|
case floor <= -8:
|
||||||
return 8
|
return nativeHistogramSchemaMaximum
|
||||||
case floor >= 4:
|
case floor >= 4:
|
||||||
return -4
|
return nativeHistogramSchemaMinimum
|
||||||
default:
|
default:
|
||||||
return -int32(floor)
|
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:]...)...)...)
|
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"
|
"testing/quick"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus/internal"
|
|
||||||
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
dto "github.com/prometheus/client_model/go"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
func benchmarkHistogramObserve(w int, b *testing.B) {
|
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