github.com/tirogen/go-ethereum@v1.10.12-0.20221226051715-250cfede41b6/metrics/runtimehistogram.go (about) 1 package metrics 2 3 import ( 4 "math" 5 "runtime/metrics" 6 "sort" 7 "sync/atomic" 8 ) 9 10 func getOrRegisterRuntimeHistogram(name string, scale float64, r Registry) *runtimeHistogram { 11 if r == nil { 12 r = DefaultRegistry 13 } 14 constructor := func() Histogram { return newRuntimeHistogram(scale) } 15 return r.GetOrRegister(name, constructor).(*runtimeHistogram) 16 } 17 18 // runtimeHistogram wraps a runtime/metrics histogram. 19 type runtimeHistogram struct { 20 v atomic.Value 21 scaleFactor float64 22 } 23 24 func newRuntimeHistogram(scale float64) *runtimeHistogram { 25 h := &runtimeHistogram{scaleFactor: scale} 26 h.update(&metrics.Float64Histogram{}) 27 return h 28 } 29 30 func (h *runtimeHistogram) update(mh *metrics.Float64Histogram) { 31 if mh == nil { 32 // The update value can be nil if the current Go version doesn't support a 33 // requested metric. It's just easier to handle nil here than putting 34 // conditionals everywhere. 35 return 36 } 37 38 s := runtimeHistogramSnapshot{ 39 Counts: make([]uint64, len(mh.Counts)), 40 Buckets: make([]float64, len(mh.Buckets)), 41 } 42 copy(s.Counts, mh.Counts) 43 copy(s.Buckets, mh.Buckets) 44 for i, b := range s.Buckets { 45 s.Buckets[i] = b * h.scaleFactor 46 } 47 h.v.Store(&s) 48 } 49 50 func (h *runtimeHistogram) load() *runtimeHistogramSnapshot { 51 return h.v.Load().(*runtimeHistogramSnapshot) 52 } 53 54 func (h *runtimeHistogram) Clear() { 55 panic("runtimeHistogram does not support Clear") 56 } 57 func (h *runtimeHistogram) Update(int64) { 58 panic("runtimeHistogram does not support Update") 59 } 60 func (h *runtimeHistogram) Sample() Sample { 61 return NilSample{} 62 } 63 64 // Snapshot returns a non-changing cop of the histogram. 65 func (h *runtimeHistogram) Snapshot() Histogram { 66 return h.load() 67 } 68 69 // Count returns the sample count. 70 func (h *runtimeHistogram) Count() int64 { 71 return h.load().Count() 72 } 73 74 // Mean returns an approximation of the mean. 75 func (h *runtimeHistogram) Mean() float64 { 76 return h.load().Mean() 77 } 78 79 // StdDev approximates the standard deviation of the histogram. 80 func (h *runtimeHistogram) StdDev() float64 { 81 return h.load().StdDev() 82 } 83 84 // Variance approximates the variance of the histogram. 85 func (h *runtimeHistogram) Variance() float64 { 86 return h.load().Variance() 87 } 88 89 // Percentile computes the p'th percentile value. 90 func (h *runtimeHistogram) Percentile(p float64) float64 { 91 return h.load().Percentile(p) 92 } 93 94 // Percentiles computes all requested percentile values. 95 func (h *runtimeHistogram) Percentiles(ps []float64) []float64 { 96 return h.load().Percentiles(ps) 97 } 98 99 // Max returns the highest sample value. 100 func (h *runtimeHistogram) Max() int64 { 101 return h.load().Max() 102 } 103 104 // Min returns the lowest sample value. 105 func (h *runtimeHistogram) Min() int64 { 106 return h.load().Min() 107 } 108 109 // Sum returns the sum of all sample values. 110 func (h *runtimeHistogram) Sum() int64 { 111 return h.load().Sum() 112 } 113 114 type runtimeHistogramSnapshot metrics.Float64Histogram 115 116 func (h *runtimeHistogramSnapshot) Clear() { 117 panic("runtimeHistogram does not support Clear") 118 } 119 func (h *runtimeHistogramSnapshot) Update(int64) { 120 panic("runtimeHistogram does not support Update") 121 } 122 func (h *runtimeHistogramSnapshot) Sample() Sample { 123 return NilSample{} 124 } 125 126 func (h *runtimeHistogramSnapshot) Snapshot() Histogram { 127 return h 128 } 129 130 // Count returns the sample count. 131 func (h *runtimeHistogramSnapshot) Count() int64 { 132 var count int64 133 for _, c := range h.Counts { 134 count += int64(c) 135 } 136 return count 137 } 138 139 // Mean returns an approximation of the mean. 140 func (h *runtimeHistogramSnapshot) Mean() float64 { 141 if len(h.Counts) == 0 { 142 return 0 143 } 144 mean, _ := h.mean() 145 return mean 146 } 147 148 // mean computes the mean and also the total sample count. 149 func (h *runtimeHistogramSnapshot) mean() (mean, totalCount float64) { 150 var sum float64 151 for i, c := range h.Counts { 152 midpoint := h.midpoint(i) 153 sum += midpoint * float64(c) 154 totalCount += float64(c) 155 } 156 return sum / totalCount, totalCount 157 } 158 159 func (h *runtimeHistogramSnapshot) midpoint(bucket int) float64 { 160 high := h.Buckets[bucket+1] 161 low := h.Buckets[bucket] 162 if math.IsInf(high, 1) { 163 // The edge of the highest bucket can be +Inf, and it's supposed to mean that this 164 // bucket contains all remaining samples > low. We can't get the middle of an 165 // infinite range, so just return the lower bound of this bucket instead. 166 return low 167 } 168 if math.IsInf(low, -1) { 169 // Similarly, we can get -Inf in the left edge of the lowest bucket, 170 // and it means the bucket contains all remaining values < high. 171 return high 172 } 173 return (low + high) / 2 174 } 175 176 // StdDev approximates the standard deviation of the histogram. 177 func (h *runtimeHistogramSnapshot) StdDev() float64 { 178 return math.Sqrt(h.Variance()) 179 } 180 181 // Variance approximates the variance of the histogram. 182 func (h *runtimeHistogramSnapshot) Variance() float64 { 183 if len(h.Counts) == 0 { 184 return 0 185 } 186 187 mean, totalCount := h.mean() 188 if totalCount <= 1 { 189 // There is no variance when there are zero or one items. 190 return 0 191 } 192 193 var sum float64 194 for i, c := range h.Counts { 195 midpoint := h.midpoint(i) 196 d := midpoint - mean 197 sum += float64(c) * (d * d) 198 } 199 return sum / (totalCount - 1) 200 } 201 202 // Percentile computes the p'th percentile value. 203 func (h *runtimeHistogramSnapshot) Percentile(p float64) float64 { 204 threshold := float64(h.Count()) * p 205 values := [1]float64{threshold} 206 h.computePercentiles(values[:]) 207 return values[0] 208 } 209 210 // Percentiles computes all requested percentile values. 211 func (h *runtimeHistogramSnapshot) Percentiles(ps []float64) []float64 { 212 // Compute threshold values. We need these to be sorted 213 // for the percentile computation, but restore the original 214 // order later, so keep the indexes as well. 215 count := float64(h.Count()) 216 thresholds := make([]float64, len(ps)) 217 indexes := make([]int, len(ps)) 218 for i, percentile := range ps { 219 thresholds[i] = count * math.Max(0, math.Min(1.0, percentile)) 220 indexes[i] = i 221 } 222 sort.Sort(floatsAscendingKeepingIndex{thresholds, indexes}) 223 224 // Now compute. The result is stored back into the thresholds slice. 225 h.computePercentiles(thresholds) 226 227 // Put the result back into the requested order. 228 sort.Sort(floatsByIndex{thresholds, indexes}) 229 return thresholds 230 } 231 232 func (h *runtimeHistogramSnapshot) computePercentiles(thresh []float64) { 233 var totalCount float64 234 for i, count := range h.Counts { 235 totalCount += float64(count) 236 237 for len(thresh) > 0 && thresh[0] < totalCount { 238 thresh[0] = h.Buckets[i] 239 thresh = thresh[1:] 240 } 241 if len(thresh) == 0 { 242 return 243 } 244 } 245 } 246 247 // Note: runtime/metrics.Float64Histogram is a collection of float64s, but the methods 248 // below need to return int64 to satisfy the interface. The histogram provided by runtime 249 // also doesn't keep track of individual samples, so results are approximated. 250 251 // Max returns the highest sample value. 252 func (h *runtimeHistogramSnapshot) Max() int64 { 253 for i := len(h.Counts) - 1; i >= 0; i-- { 254 count := h.Counts[i] 255 if count > 0 { 256 edge := h.Buckets[i+1] 257 if math.IsInf(edge, 1) { 258 edge = h.Buckets[i] 259 } 260 return int64(math.Ceil(edge)) 261 } 262 } 263 return 0 264 } 265 266 // Min returns the lowest sample value. 267 func (h *runtimeHistogramSnapshot) Min() int64 { 268 for i, count := range h.Counts { 269 if count > 0 { 270 return int64(math.Floor(h.Buckets[i])) 271 } 272 } 273 return 0 274 } 275 276 // Sum returns the sum of all sample values. 277 func (h *runtimeHistogramSnapshot) Sum() int64 { 278 var sum float64 279 for i := range h.Counts { 280 sum += h.Buckets[i] * float64(h.Counts[i]) 281 } 282 return int64(math.Ceil(sum)) 283 } 284 285 type floatsAscendingKeepingIndex struct { 286 values []float64 287 indexes []int 288 } 289 290 func (s floatsAscendingKeepingIndex) Len() int { 291 return len(s.values) 292 } 293 294 func (s floatsAscendingKeepingIndex) Less(i, j int) bool { 295 return s.values[i] < s.values[j] 296 } 297 298 func (s floatsAscendingKeepingIndex) Swap(i, j int) { 299 s.values[i], s.values[j] = s.values[j], s.values[i] 300 s.indexes[i], s.indexes[j] = s.indexes[j], s.indexes[i] 301 } 302 303 type floatsByIndex struct { 304 values []float64 305 indexes []int 306 } 307 308 func (s floatsByIndex) Len() int { 309 return len(s.values) 310 } 311 312 func (s floatsByIndex) Less(i, j int) bool { 313 return s.indexes[i] < s.indexes[j] 314 } 315 316 func (s floatsByIndex) Swap(i, j int) { 317 s.values[i], s.values[j] = s.values[j], s.values[i] 318 s.indexes[i], s.indexes[j] = s.indexes[j], s.indexes[i] 319 }