diff options
Diffstat (limited to 'src/go/plugin/go.d/pkg/metrics/histogram.go')
-rw-r--r-- | src/go/plugin/go.d/pkg/metrics/histogram.go | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/src/go/plugin/go.d/pkg/metrics/histogram.go b/src/go/plugin/go.d/pkg/metrics/histogram.go new file mode 100644 index 000000000..98c2302ca --- /dev/null +++ b/src/go/plugin/go.d/pkg/metrics/histogram.go @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package metrics + +import ( + "fmt" + "sort" + + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/stm" +) + +type ( + // A Histogram counts individual observations from an event or sample stream in + // configurable buckets. Similar to a summary, it also provides a sum of + // observations and an observation count. + // + // Note that Histograms, in contrast to Summaries, can be aggregated. + // However, Histograms require the user to pre-define suitable + // buckets, and they are in general less accurate. The Observe method of a + // histogram has a very low performance overhead in comparison with the Observe + // method of a summary. + // + // To create histogram instances, use NewHistogram. + Histogram interface { + Observer + } + + histogram struct { + buckets []int64 + upperBounds []float64 + sum float64 + count int64 + rangeBuckets bool + } +) + +var ( + _ stm.Value = histogram{} +) + +// DefBuckets are the default histogram buckets. The default buckets are +// tailored to broadly measure the response time (in seconds) of a network +// service. Most likely, however, you will be required to define buckets +// customized to your use case. +var DefBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10} + +// LinearBuckets creates 'count' buckets, each 'width' wide, where the lowest +// bucket has an upper bound of 'start'. The final +Inf bucket is not counted +// and not included in the returned slice. The returned slice is meant to be +// used for the Buckets field of HistogramOpts. +// +// The function panics if 'count' is zero or negative. +func LinearBuckets(start, width float64, count int) []float64 { + if count < 1 { + panic("LinearBuckets needs a positive count") + } + buckets := make([]float64, count) + for i := range buckets { + buckets[i] = start + start += width + } + return buckets +} + +// ExponentialBuckets creates 'count' buckets, where the lowest bucket has an +// upper bound of 'start' and each following bucket's upper bound is 'factor' +// times the previous bucket's upper bound. The final +Inf bucket is not counted +// and not included in the returned slice. The returned slice is meant to be +// used for the Buckets field of HistogramOpts. +// +// The function panics if 'count' is 0 or negative, if 'start' is 0 or negative, +// or if 'factor' is less than or equal 1. +func ExponentialBuckets(start, factor float64, count int) []float64 { + if count < 1 { + panic("ExponentialBuckets needs a positive count") + } + if start <= 0 { + panic("ExponentialBuckets needs a positive start value") + } + if factor <= 1 { + panic("ExponentialBuckets needs a factor greater than 1") + } + buckets := make([]float64, count) + for i := range buckets { + buckets[i] = start + start *= factor + } + return buckets +} + +// NewHistogram creates a new Histogram. +func NewHistogram(buckets []float64) Histogram { + if len(buckets) == 0 { + buckets = DefBuckets + } else { + sort.Slice(buckets, func(i, j int) bool { return buckets[i] < buckets[j] }) + } + + return &histogram{ + buckets: make([]int64, len(buckets)), + upperBounds: buckets, + count: 0, + sum: 0, + } +} + +func NewHistogramWithRangeBuckets(buckets []float64) Histogram { + if len(buckets) == 0 { + buckets = DefBuckets + } else { + sort.Slice(buckets, func(i, j int) bool { return buckets[i] < buckets[j] }) + } + + return &histogram{ + buckets: make([]int64, len(buckets)), + upperBounds: buckets, + count: 0, + sum: 0, + rangeBuckets: true, + } +} + +// WriteTo writes its values into given map. +// It adds those key-value pairs: +// +// ${key}_sum gauge, for sum of it's observed values +// ${key}_count counter, for count of it's observed values (equals to +Inf bucket) +// ${key}_bucket_1 counter, for 1st bucket count +// ${key}_bucket_2 counter, for 2nd bucket count +// ... +// ${key}_bucket_N counter, for Nth bucket count +func (h histogram) WriteTo(rv map[string]int64, key string, mul, div int) { + rv[key+"_sum"] = int64(h.sum * float64(mul) / float64(div)) + rv[key+"_count"] = h.count + var conn int64 + for i, bucket := range h.buckets { + name := fmt.Sprintf("%s_bucket_%d", key, i+1) + conn += bucket + if h.rangeBuckets { + rv[name] = bucket + } else { + rv[name] = conn + } + } + if h.rangeBuckets { + name := fmt.Sprintf("%s_bucket_inf", key) + rv[name] = h.count - conn + } +} + +// Observe observes a value +func (h *histogram) Observe(v float64) { + hotIdx := h.searchBucketIndex(v) + if hotIdx < len(h.buckets) { + h.buckets[hotIdx]++ + } + h.sum += v + h.count++ +} + +func (h *histogram) searchBucketIndex(v float64) int { + if len(h.upperBounds) < 30 { + for i, upper := range h.upperBounds { + if upper >= v { + return i + } + } + return len(h.upperBounds) + } + return sort.SearchFloat64s(h.upperBounds, v) +} |