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