Compare commits
8 Commits
937ef73a0f
...
45b69a2c78
Author | SHA1 | Date |
---|---|---|
Shivanth MP | 45b69a2c78 | |
Shivanth | ae8497940d | |
PrometheusBot | 13851e9287 | |
Ivan Goncharov | a934c35951 | |
Matthieu MOREL | bab92a7743 | |
PrometheusBot | 400ee29a10 | |
Arthur Silva Sens | 2b11a4ba39 | |
Ivan Goncharov | 78d7a94e46 |
|
@ -36,4 +36,4 @@ jobs:
|
|||
uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1
|
||||
with:
|
||||
args: --verbose
|
||||
version: v1.60.2
|
||||
version: v1.61.0
|
||||
|
|
|
@ -31,6 +31,7 @@ linters:
|
|||
- staticcheck
|
||||
- unconvert
|
||||
- unused
|
||||
- usestdlibvars
|
||||
- wastedassign
|
||||
|
||||
issues:
|
||||
|
|
|
@ -61,7 +61,7 @@ PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_
|
|||
SKIP_GOLANGCI_LINT :=
|
||||
GOLANGCI_LINT :=
|
||||
GOLANGCI_LINT_OPTS ?=
|
||||
GOLANGCI_LINT_VERSION ?= v1.60.2
|
||||
GOLANGCI_LINT_VERSION ?= v1.61.0
|
||||
# golangci-lint only supports linux, darwin and windows platforms on i386/amd64/arm64.
|
||||
# windows isn't included here because of the path separator being different.
|
||||
ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin))
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright 2014 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 prometheus
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// atomicUpdateFloat atomically updates the float64 value pointed to by bits
|
||||
// using the provided updateFunc, with an exponential backoff on contention.
|
||||
func atomicUpdateFloat(bits *uint64, updateFunc func(float64) float64) {
|
||||
const (
|
||||
// both numbers are derived from empirical observations
|
||||
// documented in this PR: https://github.com/prometheus/client_golang/pull/1661
|
||||
maxBackoff = 320 * time.Millisecond
|
||||
initialBackoff = 10 * time.Millisecond
|
||||
)
|
||||
backoff := initialBackoff
|
||||
|
||||
for {
|
||||
loadedBits := atomic.LoadUint64(bits)
|
||||
oldFloat := math.Float64frombits(loadedBits)
|
||||
newFloat := updateFunc(oldFloat)
|
||||
newBits := math.Float64bits(newFloat)
|
||||
|
||||
if atomic.CompareAndSwapUint64(bits, loadedBits, newBits) {
|
||||
break
|
||||
} else {
|
||||
// Exponential backoff with sleep and cap to avoid infinite wait
|
||||
time.Sleep(backoff)
|
||||
backoff *= 2
|
||||
if backoff > maxBackoff {
|
||||
backoff = maxBackoff
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
// Copyright 2014 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 prometheus
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var output float64
|
||||
|
||||
func TestAtomicUpdateFloat(t *testing.T) {
|
||||
var val float64 = 0.0
|
||||
bits := (*uint64)(unsafe.Pointer(&val))
|
||||
var wg sync.WaitGroup
|
||||
numGoroutines := 100000
|
||||
increment := 1.0
|
||||
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
atomicUpdateFloat(bits, func(f float64) float64 {
|
||||
return f + increment
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
expected := float64(numGoroutines) * increment
|
||||
if val != expected {
|
||||
t.Errorf("Expected %f, got %f", expected, val)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark for atomicUpdateFloat with single goroutine (no contention).
|
||||
func BenchmarkAtomicUpdateFloat_SingleGoroutine(b *testing.B) {
|
||||
var val float64 = 0.0
|
||||
bits := (*uint64)(unsafe.Pointer(&val))
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
atomicUpdateFloat(bits, func(f float64) float64 {
|
||||
return f + 1.0
|
||||
})
|
||||
}
|
||||
|
||||
output = val
|
||||
}
|
||||
|
||||
// Benchmark for old implementation with single goroutine (no contention) -> to check overhead of backoff
|
||||
func BenchmarkAtomicNoBackoff_SingleGoroutine(b *testing.B) {
|
||||
var val float64 = 0.0
|
||||
bits := (*uint64)(unsafe.Pointer(&val))
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for {
|
||||
loadedBits := atomic.LoadUint64(bits)
|
||||
newBits := math.Float64bits(math.Float64frombits(loadedBits) + 1.0)
|
||||
if atomic.CompareAndSwapUint64(bits, loadedBits, newBits) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output = val
|
||||
}
|
||||
|
||||
// Benchmark varying the number of goroutines.
|
||||
func benchmarkAtomicUpdateFloatConcurrency(b *testing.B, numGoroutines int) {
|
||||
var val float64 = 0.0
|
||||
bits := (*uint64)(unsafe.Pointer(&val))
|
||||
b.SetParallelism(numGoroutines)
|
||||
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
atomicUpdateFloat(bits, func(f float64) float64 {
|
||||
return f + 1.0
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
output = val
|
||||
}
|
||||
|
||||
func benchmarkAtomicNoBackoffFloatConcurrency(b *testing.B, numGoroutines int) {
|
||||
var val float64 = 0.0
|
||||
bits := (*uint64)(unsafe.Pointer(&val))
|
||||
b.SetParallelism(numGoroutines)
|
||||
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
for {
|
||||
loadedBits := atomic.LoadUint64(bits)
|
||||
newBits := math.Float64bits(math.Float64frombits(loadedBits) + 1.0)
|
||||
if atomic.CompareAndSwapUint64(bits, loadedBits, newBits) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
output = val
|
||||
}
|
||||
|
||||
func BenchmarkAtomicUpdateFloat_1Goroutine(b *testing.B) {
|
||||
benchmarkAtomicUpdateFloatConcurrency(b, 1)
|
||||
}
|
||||
|
||||
func BenchmarkAtomicNoBackoff_1Goroutine(b *testing.B) {
|
||||
benchmarkAtomicNoBackoffFloatConcurrency(b, 1)
|
||||
}
|
||||
|
||||
func BenchmarkAtomicUpdateFloat_2Goroutines(b *testing.B) {
|
||||
benchmarkAtomicUpdateFloatConcurrency(b, 2)
|
||||
}
|
||||
|
||||
func BenchmarkAtomicNoBackoff_2Goroutines(b *testing.B) {
|
||||
benchmarkAtomicNoBackoffFloatConcurrency(b, 2)
|
||||
}
|
||||
|
||||
func BenchmarkAtomicUpdateFloat_4Goroutines(b *testing.B) {
|
||||
benchmarkAtomicUpdateFloatConcurrency(b, 4)
|
||||
}
|
||||
|
||||
func BenchmarkAtomicNoBackoff_4Goroutines(b *testing.B) {
|
||||
benchmarkAtomicNoBackoffFloatConcurrency(b, 4)
|
||||
}
|
||||
|
||||
func BenchmarkAtomicUpdateFloat_8Goroutines(b *testing.B) {
|
||||
benchmarkAtomicUpdateFloatConcurrency(b, 8)
|
||||
}
|
||||
|
||||
func BenchmarkAtomicNoBackoff_8Goroutines(b *testing.B) {
|
||||
benchmarkAtomicNoBackoffFloatConcurrency(b, 8)
|
||||
}
|
||||
|
||||
func BenchmarkAtomicUpdateFloat_16Goroutines(b *testing.B) {
|
||||
benchmarkAtomicUpdateFloatConcurrency(b, 16)
|
||||
}
|
||||
|
||||
func BenchmarkAtomicNoBackoff_16Goroutines(b *testing.B) {
|
||||
benchmarkAtomicNoBackoffFloatConcurrency(b, 16)
|
||||
}
|
||||
|
||||
func BenchmarkAtomicUpdateFloat_32Goroutines(b *testing.B) {
|
||||
benchmarkAtomicUpdateFloatConcurrency(b, 32)
|
||||
}
|
||||
|
||||
func BenchmarkAtomicNoBackoff_32Goroutines(b *testing.B) {
|
||||
benchmarkAtomicNoBackoffFloatConcurrency(b, 32)
|
||||
}
|
|
@ -134,13 +134,9 @@ func (c *counter) Add(v float64) {
|
|||
return
|
||||
}
|
||||
|
||||
for {
|
||||
oldBits := atomic.LoadUint64(&c.valBits)
|
||||
newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
|
||||
if atomic.CompareAndSwapUint64(&c.valBits, oldBits, newBits) {
|
||||
return
|
||||
}
|
||||
}
|
||||
atomicUpdateFloat(&c.valBits, func(oldVal float64) float64 {
|
||||
return oldVal + v
|
||||
})
|
||||
}
|
||||
|
||||
func (c *counter) AddWithExemplar(v float64, e Labels) {
|
||||
|
|
|
@ -120,13 +120,9 @@ func (g *gauge) Dec() {
|
|||
}
|
||||
|
||||
func (g *gauge) Add(val float64) {
|
||||
for {
|
||||
oldBits := atomic.LoadUint64(&g.valBits)
|
||||
newBits := math.Float64bits(math.Float64frombits(oldBits) + val)
|
||||
if atomic.CompareAndSwapUint64(&g.valBits, oldBits, newBits) {
|
||||
return
|
||||
}
|
||||
}
|
||||
atomicUpdateFloat(&g.valBits, func(oldVal float64) float64 {
|
||||
return oldVal + val
|
||||
})
|
||||
}
|
||||
|
||||
func (g *gauge) Sub(val float64) {
|
||||
|
|
|
@ -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.)
|
||||
|
@ -858,15 +864,35 @@ func (h *histogram) Write(out *dto.Metric) error {
|
|||
// findBucket returns the index of the bucket for the provided value, or
|
||||
// len(h.upperBounds) for the +Inf bucket.
|
||||
func (h *histogram) findBucket(v float64) int {
|
||||
// TODO(beorn7): For small numbers of buckets (<30), a linear search is
|
||||
// slightly faster than the binary search. If we really care, we could
|
||||
// switch from one search strategy to the other depending on the number
|
||||
// of buckets.
|
||||
//
|
||||
// Microbenchmarks (BenchmarkHistogramNoLabels):
|
||||
// 11 buckets: 38.3 ns/op linear - binary 48.7 ns/op
|
||||
// 100 buckets: 78.1 ns/op linear - binary 54.9 ns/op
|
||||
// 300 buckets: 154 ns/op linear - binary 61.6 ns/op
|
||||
n := len(h.upperBounds)
|
||||
if n == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Early exit: if v is less than or equal to the first upper bound, return 0
|
||||
if v <= h.upperBounds[0] {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Early exit: if v is greater than the last upper bound, return len(h.upperBounds)
|
||||
if v > h.upperBounds[n-1] {
|
||||
return n
|
||||
}
|
||||
|
||||
// For small arrays, use simple linear search
|
||||
// "magic number" 35 is result of tests on couple different (AWS and baremetal) servers
|
||||
// see more details here: https://github.com/prometheus/client_golang/pull/1662
|
||||
if n < 35 {
|
||||
for i, bound := range h.upperBounds {
|
||||
if v <= bound {
|
||||
return i
|
||||
}
|
||||
}
|
||||
// If v is greater than all upper bounds, return len(h.upperBounds)
|
||||
return n
|
||||
}
|
||||
|
||||
// For larger arrays, use stdlib's binary search
|
||||
return sort.SearchFloat64s(h.upperBounds, v)
|
||||
}
|
||||
|
||||
|
@ -1440,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)
|
||||
}
|
||||
|
@ -1621,13 +1647,9 @@ func waitForCooldown(count uint64, counts *histogramCounts) {
|
|||
// atomicAddFloat adds the provided float atomically to another float
|
||||
// represented by the bit pattern the bits pointer is pointing to.
|
||||
func atomicAddFloat(bits *uint64, v float64) {
|
||||
for {
|
||||
loadedBits := atomic.LoadUint64(bits)
|
||||
newBits := math.Float64bits(math.Float64frombits(loadedBits) + v)
|
||||
if atomic.CompareAndSwapUint64(bits, loadedBits, newBits) {
|
||||
break
|
||||
}
|
||||
}
|
||||
atomicUpdateFloat(bits, func(oldVal float64) float64 {
|
||||
return oldVal + v
|
||||
})
|
||||
}
|
||||
|
||||
// atomicDecUint32 atomically decrements the uint32 p points to. See
|
||||
|
@ -1835,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) {
|
||||
|
@ -1455,3 +1455,632 @@ func compareNativeExemplarValues(t *testing.T, exps []*dto.Exemplar, values []fl
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
var resultFindBucket int
|
||||
|
||||
func benchmarkFindBucket(b *testing.B, l int) {
|
||||
h := &histogram{upperBounds: make([]float64, l)}
|
||||
for i := range h.upperBounds {
|
||||
h.upperBounds[i] = float64(i)
|
||||
}
|
||||
v := float64(l / 2)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
resultFindBucket = h.findBucket(v)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFindBucketShort(b *testing.B) {
|
||||
benchmarkFindBucket(b, 20)
|
||||
}
|
||||
|
||||
func BenchmarkFindBucketMid(b *testing.B) {
|
||||
benchmarkFindBucket(b, 40)
|
||||
}
|
||||
|
||||
func BenchmarkFindBucketLarge(b *testing.B) {
|
||||
benchmarkFindBucket(b, 100)
|
||||
}
|
||||
|
||||
func BenchmarkFindBucketHuge(b *testing.B) {
|
||||
benchmarkFindBucket(b, 500)
|
||||
}
|
||||
|
||||
func BenchmarkFindBucketInf(b *testing.B) {
|
||||
h := &histogram{upperBounds: make([]float64, 500)}
|
||||
for i := range h.upperBounds {
|
||||
h.upperBounds[i] = float64(i)
|
||||
}
|
||||
v := 1000.5
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
resultFindBucket = h.findBucket(v)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFindBucketLow(b *testing.B) {
|
||||
h := &histogram{upperBounds: make([]float64, 500)}
|
||||
for i := range h.upperBounds {
|
||||
h.upperBounds[i] = float64(i)
|
||||
}
|
||||
v := -1.1
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
resultFindBucket = h.findBucket(v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindBucket(t *testing.T) {
|
||||
smallHistogram := &histogram{upperBounds: []float64{1, 2, 3, 4, 5}}
|
||||
largeHistogram := &histogram{upperBounds: make([]float64, 50)}
|
||||
for i := range largeHistogram.upperBounds {
|
||||
largeHistogram.upperBounds[i] = float64(i)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
h *histogram
|
||||
v float64
|
||||
expected int
|
||||
}{
|
||||
{smallHistogram, -1, 0},
|
||||
{smallHistogram, 0.5, 0},
|
||||
{smallHistogram, 2.5, 2},
|
||||
{smallHistogram, 5.5, 5},
|
||||
{largeHistogram, -1, 0},
|
||||
{largeHistogram, 25.5, 26},
|
||||
{largeHistogram, 49.5, 50},
|
||||
{largeHistogram, 50.5, 50},
|
||||
{largeHistogram, 5000.5, 50},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
result := tt.h.findBucket(tt.v)
|
||||
if result != tt.expected {
|
||||
t.Errorf("findBucket(%v) = %d; expected %d", tt.v, result, tt.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -131,7 +131,7 @@ func TestHandlerErrorHandling(t *testing.T) {
|
|||
logger := log.New(logBuf, "", 0)
|
||||
|
||||
writer := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("GET", "/", nil)
|
||||
request, _ := http.NewRequest(http.MethodGet, "/", nil)
|
||||
request.Header.Add("Accept", "test/plain")
|
||||
|
||||
mReg := &mockTransactionGatherer{g: reg}
|
||||
|
@ -252,7 +252,7 @@ func TestInstrumentMetricHandler(t *testing.T) {
|
|||
// Do it again to test idempotency.
|
||||
InstrumentMetricHandler(reg, HandlerForTransactional(mReg, HandlerOpts{}))
|
||||
writer := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("GET", "/", nil)
|
||||
request, _ := http.NewRequest(http.MethodGet, "/", nil)
|
||||
request.Header.Add(acceptHeader, acceptTextPlain)
|
||||
|
||||
handler.ServeHTTP(writer, request)
|
||||
|
@ -311,7 +311,7 @@ func TestHandlerMaxRequestsInFlight(t *testing.T) {
|
|||
w1 := httptest.NewRecorder()
|
||||
w2 := httptest.NewRecorder()
|
||||
w3 := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("GET", "/", nil)
|
||||
request, _ := http.NewRequest(http.MethodGet, "/", nil)
|
||||
request.Header.Add(acceptHeader, acceptTextPlain)
|
||||
|
||||
c := blockingCollector{Block: make(chan struct{}), CollectStarted: make(chan struct{}, 1)}
|
||||
|
@ -348,7 +348,7 @@ func TestHandlerTimeout(t *testing.T) {
|
|||
handler := HandlerFor(reg, HandlerOpts{Timeout: time.Millisecond})
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
request, _ := http.NewRequest("GET", "/", nil)
|
||||
request, _ := http.NewRequest(http.MethodGet, "/", nil)
|
||||
request.Header.Add("Accept", "test/plain")
|
||||
|
||||
c := blockingCollector{Block: make(chan struct{}), CollectStarted: make(chan struct{}, 1)}
|
||||
|
@ -372,7 +372,7 @@ func TestInstrumentMetricHandlerWithCompression(t *testing.T) {
|
|||
handler := InstrumentMetricHandler(reg, HandlerForTransactional(mReg, HandlerOpts{DisableCompression: false}))
|
||||
compression := Zstd
|
||||
writer := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("GET", "/", nil)
|
||||
request, _ := http.NewRequest(http.MethodGet, "/", nil)
|
||||
request.Header.Add(acceptHeader, acceptTextPlain)
|
||||
request.Header.Add(acceptEncodingHeader, string(compression))
|
||||
|
||||
|
@ -533,7 +533,7 @@ func TestNegotiateEncodingWriter(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
request, _ := http.NewRequest("GET", "/", nil)
|
||||
request, _ := http.NewRequest(http.MethodGet, "/", nil)
|
||||
request.Header.Add(acceptEncodingHeader, test.acceptEncoding)
|
||||
rr := httptest.NewRecorder()
|
||||
_, encodingHeader, _, err := negotiateEncodingWriter(request, rr, test.offeredCompressions)
|
||||
|
@ -631,7 +631,7 @@ func BenchmarkCompression(b *testing.B) {
|
|||
b.Run(benchmark.name+"_"+size.name, func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
writer := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("GET", "/", nil)
|
||||
request, _ := http.NewRequest(http.MethodGet, "/", nil)
|
||||
request.Header.Add(acceptEncodingHeader, benchmark.compressionType)
|
||||
handler.ServeHTTP(writer, request)
|
||||
}
|
||||
|
|
|
@ -223,7 +223,7 @@ func TestClientMiddlewareAPI_WithRequestContext(t *testing.T) {
|
|||
}))
|
||||
defer backend.Close()
|
||||
|
||||
req, err := http.NewRequest("GET", backend.URL, nil)
|
||||
req, err := http.NewRequest(http.MethodGet, backend.URL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
@ -276,7 +276,7 @@ func TestClientMiddlewareAPIWithRequestContextTimeout(t *testing.T) {
|
|||
}))
|
||||
defer backend.Close()
|
||||
|
||||
req, err := http.NewRequest("GET", backend.URL, nil)
|
||||
req, err := http.NewRequest(http.MethodGet, backend.URL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
|
|
@ -418,7 +418,7 @@ func TestMiddlewareAPI(t *testing.T) {
|
|||
_, _ = w.Write([]byte("OK"))
|
||||
})
|
||||
|
||||
r, _ := http.NewRequest("GET", "www.example.com", nil)
|
||||
r, _ := http.NewRequest(http.MethodGet, "www.example.com", nil)
|
||||
w := httptest.NewRecorder()
|
||||
chain.ServeHTTP(w, r)
|
||||
|
||||
|
@ -432,7 +432,7 @@ func TestMiddlewareAPI_WithExemplars(t *testing.T) {
|
|||
_, _ = w.Write([]byte("OK"))
|
||||
}, WithExemplarFromContext(func(_ context.Context) prometheus.Labels { return exemplar }))
|
||||
|
||||
r, _ := http.NewRequest("GET", "www.example.com", nil)
|
||||
r, _ := http.NewRequest(http.MethodGet, "www.example.com", nil)
|
||||
w := httptest.NewRecorder()
|
||||
chain.ServeHTTP(w, r)
|
||||
|
||||
|
|
|
@ -714,7 +714,7 @@ collected metric "broken_metric" { label:<name:"foo" value:"bar" > label:<name:"
|
|||
}
|
||||
writer := httptest.NewRecorder()
|
||||
handler := promhttp.HandlerFor(gatherer, promhttp.HandlerOpts{})
|
||||
request, _ := http.NewRequest("GET", "/", nil)
|
||||
request, _ := http.NewRequest(http.MethodGet, "/", nil)
|
||||
for key, value := range scenario.headers {
|
||||
request.Header.Add(key, value)
|
||||
}
|
||||
|
|
|
@ -471,13 +471,9 @@ func (s *noObjectivesSummary) Observe(v float64) {
|
|||
n := atomic.AddUint64(&s.countAndHotIdx, 1)
|
||||
hotCounts := s.counts[n>>63]
|
||||
|
||||
for {
|
||||
oldBits := atomic.LoadUint64(&hotCounts.sumBits)
|
||||
newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
|
||||
if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
|
||||
break
|
||||
}
|
||||
}
|
||||
atomicUpdateFloat(&hotCounts.sumBits, func(oldVal float64) float64 {
|
||||
return oldVal + v
|
||||
})
|
||||
// Increment count last as we take it as a signal that the observation
|
||||
// is complete.
|
||||
atomic.AddUint64(&hotCounts.count, 1)
|
||||
|
@ -519,14 +515,13 @@ func (s *noObjectivesSummary) Write(out *dto.Metric) error {
|
|||
// Finally add all the cold counts to the new hot counts and reset the cold counts.
|
||||
atomic.AddUint64(&hotCounts.count, count)
|
||||
atomic.StoreUint64(&coldCounts.count, 0)
|
||||
for {
|
||||
oldBits := atomic.LoadUint64(&hotCounts.sumBits)
|
||||
newBits := math.Float64bits(math.Float64frombits(oldBits) + sum.GetSampleSum())
|
||||
if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
|
||||
atomic.StoreUint64(&coldCounts.sumBits, 0)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Use atomicUpdateFloat to update hotCounts.sumBits atomically.
|
||||
atomicUpdateFloat(&hotCounts.sumBits, func(oldVal float64) float64 {
|
||||
return oldVal + sum.GetSampleSum()
|
||||
})
|
||||
atomic.StoreUint64(&coldCounts.sumBits, 0)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue