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