master
go 172 lines 4.56 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package oldmetrix
4
5 import (
6 "fmt"
7 "slices"
8 "sort"
9
10 "github.com/netdata/netdata/go/plugins/pkg/stm"
11 )
12
13 type (
14 // A Histogram counts individual observations from an event or sample stream in
15 // configurable buckets. Similar to a summary, it also provides a sum of
16 // observations and an observation count.
17 //
18 // Note that Histograms, in contrast to Summaries, can be aggregated.
19 // However, Histograms require the user to pre-define suitable
20 // buckets, and they are in general less accurate. The Observe method of a
21 // histogram has a very low performance overhead in comparison with the Observe
22 // method of a summary.
23 //
24 // To create histogram instances, use NewHistogram.
25 Histogram interface {
26 Observer
27 }
28
29 histogram struct {
30 buckets []int64
31 upperBounds []float64
32 sum float64
33 count int64
34 rangeBuckets bool
35 }
36 )
37
38 var (
39 _ stm.Value = histogram{}
40 )
41
42 // DefBuckets are the default histogram buckets. The default buckets are
43 // tailored to broadly measure the response time (in seconds) of a network
44 // service. Most likely, however, you will be required to define buckets
45 // customized to your use case.
46 var DefBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10}
47
48 // LinearBuckets creates 'count' buckets, each 'width' wide, where the lowest
49 // bucket has an upper bound of 'start'. The final +Inf bucket is not counted
50 // and not included in the returned slice. The returned slice is meant to be
51 // used for the Buckets field of HistogramOpts.
52 //
53 // The function panics if 'count' is zero or negative.
54 func LinearBuckets(start, width float64, count int) []float64 {
55 if count < 1 {
56 panic("LinearBuckets needs a positive count")
57 }
58 buckets := make([]float64, count)
59 for i := range buckets {
60 buckets[i] = start
61 start += width
62 }
63 return buckets
64 }
65
66 // ExponentialBuckets creates 'count' buckets, where the lowest bucket has an
67 // upper bound of 'start' and each following bucket's upper bound is 'factor'
68 // times the previous bucket's upper bound. The final +Inf bucket is not counted
69 // and not included in the returned slice. The returned slice is meant to be
70 // used for the Buckets field of HistogramOpts.
71 //
72 // The function panics if 'count' is 0 or negative, if 'start' is 0 or negative,
73 // or if 'factor' is less than or equal 1.
74 func ExponentialBuckets(start, factor float64, count int) []float64 {
75 if count < 1 {
76 panic("ExponentialBuckets needs a positive count")
77 }
78 if start <= 0 {
79 panic("ExponentialBuckets needs a positive start value")
80 }
81 if factor <= 1 {
82 panic("ExponentialBuckets needs a factor greater than 1")
83 }
84 buckets := make([]float64, count)
85 for i := range buckets {
86 buckets[i] = start
87 start *= factor
88 }
89 return buckets
90 }
91
92 // NewHistogram creates a new Histogram.
93 func NewHistogram(buckets []float64) Histogram {
94 if len(buckets) == 0 {
95 buckets = DefBuckets
96 } else {
97 slices.Sort(buckets)
98 }
99
100 return &histogram{
101 buckets: make([]int64, len(buckets)),
102 upperBounds: buckets,
103 count: 0,
104 sum: 0,
105 }
106 }
107
108 func NewHistogramWithRangeBuckets(buckets []float64) Histogram {
109 if len(buckets) == 0 {
110 buckets = DefBuckets
111 } else {
112 slices.Sort(buckets)
113 }
114
115 return &histogram{
116 buckets: make([]int64, len(buckets)),
117 upperBounds: buckets,
118 count: 0,
119 sum: 0,
120 rangeBuckets: true,
121 }
122 }
123
124 // WriteTo writes its values into given map.
125 // It adds those key-value pairs:
126 //
127 // ${key}_sum gauge, for sum of it's observed values
128 // ${key}_count counter, for count of it's observed values (equals to +Inf bucket)
129 // ${key}_bucket_1 counter, for 1st bucket count
130 // ${key}_bucket_2 counter, for 2nd bucket count
131 // ...
132 // ${key}_bucket_N counter, for Nth bucket count
133 func (h histogram) WriteTo(rv map[string]int64, key string, mul, div int) {
134 rv[key+"_sum"] = int64(h.sum * float64(mul) / float64(div))
135 rv[key+"_count"] = h.count
136 var conn int64
137 for i, bucket := range h.buckets {
138 name := fmt.Sprintf("%s_bucket_%d", key, i+1)
139 conn += bucket
140 if h.rangeBuckets {
141 rv[name] = bucket
142 } else {
143 rv[name] = conn
144 }
145 }
146 if h.rangeBuckets {
147 name := fmt.Sprintf("%s_bucket_inf", key)
148 rv[name] = h.count - conn
149 }
150 }
151
152 // Observe observes a value
153 func (h *histogram) Observe(v float64) {
154 hotIdx := h.searchBucketIndex(v)
155 if hotIdx < len(h.buckets) {
156 h.buckets[hotIdx]++
157 }
158 h.sum += v
159 h.count++
160 }
161
162 func (h *histogram) searchBucketIndex(v float64) int {
163 if len(h.upperBounds) < 30 {
164 for i, upper := range h.upperBounds {
165 if upper >= v {
166 return i
167 }
168 }
169 return len(h.upperBounds)
170 }
171 return sort.SearchFloat64s(h.upperBounds, v)
172 }