client_golang/metrics/tallying_bucket.go

149 lines
4.0 KiB
Go
Raw Normal View History

// Copyright (c) 2012, Matt T. Proud
// All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
2012-05-20 01:59:25 +04:00
package metrics
import (
"fmt"
"github.com/prometheus/client_golang/maths"
2012-05-20 01:59:25 +04:00
"math"
"sync"
)
const (
lowerThird = 1.0 / 3.0
upperThird = 2.0 * lowerThird
2012-05-20 01:59:25 +04:00
)
// A TallyingIndexEstimator is responsible for estimating the value of index for
// a given TallyingBucket, even though a TallyingBucket does not possess a
// collection of samples. There are a few strategies listed below for how
// this value should be approximated.
2012-05-20 01:59:25 +04:00
type TallyingIndexEstimator func(minimum, maximum float64, index, observations int) float64
// Provide a filter for handling empty buckets.
2012-05-20 01:59:25 +04:00
func emptyFilter(e TallyingIndexEstimator) TallyingIndexEstimator {
return func(minimum, maximum float64, index, observations int) float64 {
if observations == 0 {
return math.NaN()
}
return e(minimum, maximum, index, observations)
}
}
// Report the smallest observed value in the bucket.
2012-05-20 01:59:25 +04:00
var Minimum TallyingIndexEstimator = emptyFilter(func(minimum, maximum float64, _, observations int) float64 {
return minimum
})
// Report the largest observed value in the bucket.
2012-05-20 01:59:25 +04:00
var Maximum TallyingIndexEstimator = emptyFilter(func(minimum, maximum float64, _, observations int) float64 {
return maximum
})
// Report the average of the extrema.
2012-05-20 01:59:25 +04:00
var Average TallyingIndexEstimator = emptyFilter(func(minimum, maximum float64, _, observations int) float64 {
return maths.Average([]float64{minimum, maximum})
})
// Report the minimum value of the index is in the lower-third of observations,
// the average if in the middle-third, and the maximum if in the largest third.
2012-05-20 01:59:25 +04:00
var Uniform TallyingIndexEstimator = emptyFilter(func(minimum, maximum float64, index, observations int) float64 {
if observations == 1 {
return minimum
}
location := float64(index) / float64(observations)
if location > upperThird {
return maximum
} else if location < lowerThird {
return minimum
}
return maths.Average([]float64{minimum, maximum})
})
// A TallyingBucket is a Bucket that tallies when an object is added to it.
// Upon insertion, an object is compared against collected extrema and noted
// as a new minimum or maximum if appropriate.
2012-05-20 01:59:25 +04:00
type TallyingBucket struct {
estimator TallyingIndexEstimator
2012-05-20 01:59:25 +04:00
largestObserved float64
mutex sync.RWMutex
observations int
smallestObserved float64
2012-05-20 01:59:25 +04:00
}
func (b *TallyingBucket) Add(value float64) {
b.mutex.Lock()
defer b.mutex.Unlock()
b.observations += 1
b.smallestObserved = math.Min(value, b.smallestObserved)
b.largestObserved = math.Max(value, b.largestObserved)
}
func (b TallyingBucket) String() string {
2012-05-20 01:59:25 +04:00
b.mutex.RLock()
defer b.mutex.RUnlock()
observations := b.observations
if observations == 0 {
return fmt.Sprintf("[TallyingBucket (Empty)]")
}
return fmt.Sprintf("[TallyingBucket (%f, %f); %d items]", b.smallestObserved, b.largestObserved, observations)
}
func (b TallyingBucket) Observations() int {
2012-05-20 01:59:25 +04:00
b.mutex.RLock()
defer b.mutex.RUnlock()
return b.observations
}
func (b TallyingBucket) ValueForIndex(index int) float64 {
2012-05-20 01:59:25 +04:00
b.mutex.RLock()
defer b.mutex.RUnlock()
return b.estimator(b.smallestObserved, b.largestObserved, index, b.observations)
}
func (b *TallyingBucket) Reset() {
b.mutex.Lock()
defer b.mutex.Unlock()
b.largestObserved = math.SmallestNonzeroFloat64
b.observations = 0
b.smallestObserved = math.MaxFloat64
}
// Produce a TallyingBucket with sane defaults.
2012-05-20 01:59:25 +04:00
func DefaultTallyingBucket() TallyingBucket {
return TallyingBucket{
estimator: Minimum,
largestObserved: math.SmallestNonzeroFloat64,
smallestObserved: math.MaxFloat64,
2012-05-20 01:59:25 +04:00
}
}
func CustomTallyingBucket(estimator TallyingIndexEstimator) TallyingBucket {
return TallyingBucket{
estimator: estimator,
largestObserved: math.SmallestNonzeroFloat64,
smallestObserved: math.MaxFloat64,
2012-05-20 01:59:25 +04:00
}
}
// This is used strictly for testing.
func tallyingBucketBuilder() Bucket {
2012-05-20 01:59:25 +04:00
b := DefaultTallyingBucket()
return &b
}