github.com/ethereum/go-ethereum@v1.16.1/metrics/sample.go (about) 1 package metrics 2 3 import ( 4 "math" 5 "math/rand" 6 "slices" 7 "sync" 8 "time" 9 ) 10 11 const rescaleThreshold = time.Hour 12 13 // Sample maintains a statistically-significant selection of values from 14 // a stream. 15 type Sample interface { 16 Snapshot() *sampleSnapshot 17 Clear() 18 Update(int64) 19 } 20 21 var ( 22 _ Sample = (*ExpDecaySample)(nil) 23 _ Sample = (*UniformSample)(nil) 24 _ Sample = (*resettingSample)(nil) 25 ) 26 27 // sampleSnapshot is a read-only copy of a Sample. 28 type sampleSnapshot struct { 29 count int64 30 values []int64 31 32 max int64 33 min int64 34 mean float64 35 sum int64 36 variance float64 37 } 38 39 // newSampleSnapshotPrecalculated creates a read-only sampleSnapShot, using 40 // precalculated sums to avoid iterating the values 41 func newSampleSnapshotPrecalculated(count int64, values []int64, min, max, sum int64) *sampleSnapshot { 42 if len(values) == 0 { 43 return &sampleSnapshot{ 44 count: count, 45 values: values, 46 } 47 } 48 return &sampleSnapshot{ 49 count: count, 50 values: values, 51 max: max, 52 min: min, 53 mean: float64(sum) / float64(len(values)), 54 sum: sum, 55 } 56 } 57 58 // newSampleSnapshot creates a read-only sampleSnapShot, and calculates some 59 // numbers. 60 func newSampleSnapshot(count int64, values []int64) *sampleSnapshot { 61 var ( 62 max int64 = math.MinInt64 63 min int64 = math.MaxInt64 64 sum int64 65 ) 66 for _, v := range values { 67 sum += v 68 if v > max { 69 max = v 70 } 71 if v < min { 72 min = v 73 } 74 } 75 return newSampleSnapshotPrecalculated(count, values, min, max, sum) 76 } 77 78 // Count returns the count of inputs at the time the snapshot was taken. 79 func (s *sampleSnapshot) Count() int64 { return s.count } 80 81 // Max returns the maximal value at the time the snapshot was taken. 82 func (s *sampleSnapshot) Max() int64 { return s.max } 83 84 // Mean returns the mean value at the time the snapshot was taken. 85 func (s *sampleSnapshot) Mean() float64 { return s.mean } 86 87 // Min returns the minimal value at the time the snapshot was taken. 88 func (s *sampleSnapshot) Min() int64 { return s.min } 89 90 // Percentile returns an arbitrary percentile of values at the time the 91 // snapshot was taken. 92 func (s *sampleSnapshot) Percentile(p float64) float64 { 93 return SamplePercentile(s.values, p) 94 } 95 96 // Percentiles returns a slice of arbitrary percentiles of values at the time 97 // the snapshot was taken. 98 func (s *sampleSnapshot) Percentiles(ps []float64) []float64 { 99 return CalculatePercentiles(s.values, ps) 100 } 101 102 // Size returns the size of the sample at the time the snapshot was taken. 103 func (s *sampleSnapshot) Size() int { return len(s.values) } 104 105 // StdDev returns the standard deviation of values at the time the snapshot was 106 // taken. 107 func (s *sampleSnapshot) StdDev() float64 { 108 if s.variance == 0.0 { 109 s.variance = SampleVariance(s.mean, s.values) 110 } 111 return math.Sqrt(s.variance) 112 } 113 114 // Sum returns the sum of values at the time the snapshot was taken. 115 func (s *sampleSnapshot) Sum() int64 { return s.sum } 116 117 // Values returns a copy of the values in the sample. 118 func (s *sampleSnapshot) Values() []int64 { 119 return slices.Clone(s.values) 120 } 121 122 // Variance returns the variance of values at the time the snapshot was taken. 123 func (s *sampleSnapshot) Variance() float64 { 124 if s.variance == 0.0 { 125 s.variance = SampleVariance(s.mean, s.values) 126 } 127 return s.variance 128 } 129 130 // ExpDecaySample is an exponentially-decaying sample using a forward-decaying 131 // priority reservoir. See Cormode et al's "Forward Decay: A Practical Time 132 // Decay Model for Streaming Systems". 133 // 134 // <http://dimacs.rutgers.edu/~graham/pubs/papers/fwddecay.pdf> 135 type ExpDecaySample struct { 136 alpha float64 137 count int64 138 mutex sync.Mutex 139 reservoirSize int 140 t0, t1 time.Time 141 values *expDecaySampleHeap 142 rand *rand.Rand 143 } 144 145 // NewExpDecaySample constructs a new exponentially-decaying sample with the 146 // given reservoir size and alpha. 147 func NewExpDecaySample(reservoirSize int, alpha float64) Sample { 148 s := &ExpDecaySample{ 149 alpha: alpha, 150 reservoirSize: reservoirSize, 151 t0: time.Now(), 152 values: newExpDecaySampleHeap(reservoirSize), 153 } 154 s.t1 = s.t0.Add(rescaleThreshold) 155 return s 156 } 157 158 // SetRand sets the random source (useful in tests) 159 func (s *ExpDecaySample) SetRand(prng *rand.Rand) Sample { 160 s.rand = prng 161 return s 162 } 163 164 // Clear clears all samples. 165 func (s *ExpDecaySample) Clear() { 166 s.mutex.Lock() 167 defer s.mutex.Unlock() 168 s.count = 0 169 s.t0 = time.Now() 170 s.t1 = s.t0.Add(rescaleThreshold) 171 s.values.Clear() 172 } 173 174 // Snapshot returns a read-only copy of the sample. 175 func (s *ExpDecaySample) Snapshot() *sampleSnapshot { 176 s.mutex.Lock() 177 defer s.mutex.Unlock() 178 var ( 179 samples = s.values.Values() 180 values = make([]int64, len(samples)) 181 max int64 = math.MinInt64 182 min int64 = math.MaxInt64 183 sum int64 184 ) 185 for i, item := range samples { 186 v := item.v 187 values[i] = v 188 sum += v 189 if v > max { 190 max = v 191 } 192 if v < min { 193 min = v 194 } 195 } 196 return newSampleSnapshotPrecalculated(s.count, values, min, max, sum) 197 } 198 199 // Update samples a new value. 200 func (s *ExpDecaySample) Update(v int64) { 201 if !metricsEnabled { 202 return 203 } 204 s.update(time.Now(), v) 205 } 206 207 // update samples a new value at a particular timestamp. This is a method all 208 // its own to facilitate testing. 209 func (s *ExpDecaySample) update(t time.Time, v int64) { 210 s.mutex.Lock() 211 defer s.mutex.Unlock() 212 s.count++ 213 if s.values.Size() == s.reservoirSize { 214 s.values.Pop() 215 } 216 var f64 float64 217 if s.rand != nil { 218 f64 = s.rand.Float64() 219 } else { 220 f64 = rand.Float64() 221 } 222 s.values.Push(expDecaySample{ 223 k: math.Exp(t.Sub(s.t0).Seconds()*s.alpha) / f64, 224 v: v, 225 }) 226 if t.After(s.t1) { 227 values := s.values.Values() 228 t0 := s.t0 229 s.values.Clear() 230 s.t0 = t 231 s.t1 = s.t0.Add(rescaleThreshold) 232 for _, v := range values { 233 v.k = v.k * math.Exp(-s.alpha*s.t0.Sub(t0).Seconds()) 234 s.values.Push(v) 235 } 236 } 237 } 238 239 // SamplePercentile returns an arbitrary percentile of the slice of int64. 240 func SamplePercentile(values []int64, p float64) float64 { 241 return CalculatePercentiles(values, []float64{p})[0] 242 } 243 244 // CalculatePercentiles returns a slice of arbitrary percentiles of the slice of 245 // int64. This method returns interpolated results, so e.g. if there are only two 246 // values, [0, 10], a 50% percentile will land between them. 247 // 248 // Note: As a side-effect, this method will also sort the slice of values. 249 // Note2: The input format for percentiles is NOT percent! To express 50%, use 0.5, not 50. 250 func CalculatePercentiles(values []int64, ps []float64) []float64 { 251 scores := make([]float64, len(ps)) 252 size := len(values) 253 if size == 0 { 254 return scores 255 } 256 slices.Sort(values) 257 for i, p := range ps { 258 pos := p * float64(size+1) 259 260 if pos < 1.0 { 261 scores[i] = float64(values[0]) 262 } else if pos >= float64(size) { 263 scores[i] = float64(values[size-1]) 264 } else { 265 lower := float64(values[int(pos)-1]) 266 upper := float64(values[int(pos)]) 267 scores[i] = lower + (pos-math.Floor(pos))*(upper-lower) 268 } 269 } 270 return scores 271 } 272 273 // SampleVariance returns the variance of the slice of int64. 274 func SampleVariance(mean float64, values []int64) float64 { 275 if len(values) == 0 { 276 return 0.0 277 } 278 var sum float64 279 for _, v := range values { 280 d := float64(v) - mean 281 sum += d * d 282 } 283 return sum / float64(len(values)) 284 } 285 286 // UniformSample implements a uniform sample using Vitter's Algorithm R. 287 // 288 // <http://www.cs.umd.edu/~samir/498/vitter.pdf> 289 type UniformSample struct { 290 count int64 291 mutex sync.Mutex 292 reservoirSize int 293 values []int64 294 rand *rand.Rand 295 } 296 297 // NewUniformSample constructs a new uniform sample with the given reservoir 298 // size. 299 func NewUniformSample(reservoirSize int) Sample { 300 return &UniformSample{ 301 reservoirSize: reservoirSize, 302 values: make([]int64, 0, reservoirSize), 303 } 304 } 305 306 // SetRand sets the random source (useful in tests) 307 func (s *UniformSample) SetRand(prng *rand.Rand) Sample { 308 s.rand = prng 309 return s 310 } 311 312 // Clear clears all samples. 313 func (s *UniformSample) Clear() { 314 s.mutex.Lock() 315 defer s.mutex.Unlock() 316 s.count = 0 317 clear(s.values) 318 } 319 320 // Snapshot returns a read-only copy of the sample. 321 func (s *UniformSample) Snapshot() *sampleSnapshot { 322 s.mutex.Lock() 323 values := slices.Clone(s.values) 324 count := s.count 325 s.mutex.Unlock() 326 return newSampleSnapshot(count, values) 327 } 328 329 // Update samples a new value. 330 func (s *UniformSample) Update(v int64) { 331 if !metricsEnabled { 332 return 333 } 334 s.mutex.Lock() 335 defer s.mutex.Unlock() 336 s.count++ 337 if len(s.values) < s.reservoirSize { 338 s.values = append(s.values, v) 339 return 340 } 341 var r int64 342 if s.rand != nil { 343 r = s.rand.Int63n(s.count) 344 } else { 345 r = rand.Int63n(s.count) 346 } 347 if r < int64(len(s.values)) { 348 s.values[int(r)] = v 349 } 350 } 351 352 // expDecaySample represents an individual sample in a heap. 353 type expDecaySample struct { 354 k float64 355 v int64 356 } 357 358 func newExpDecaySampleHeap(reservoirSize int) *expDecaySampleHeap { 359 return &expDecaySampleHeap{make([]expDecaySample, 0, reservoirSize)} 360 } 361 362 // expDecaySampleHeap is a min-heap of expDecaySamples. 363 // The internal implementation is copied from the standard library's container/heap 364 type expDecaySampleHeap struct { 365 s []expDecaySample 366 } 367 368 func (h *expDecaySampleHeap) Clear() { 369 h.s = h.s[:0] 370 } 371 372 func (h *expDecaySampleHeap) Push(s expDecaySample) { 373 n := len(h.s) 374 h.s = h.s[0 : n+1] 375 h.s[n] = s 376 h.up(n) 377 } 378 379 func (h *expDecaySampleHeap) Pop() expDecaySample { 380 n := len(h.s) - 1 381 h.s[0], h.s[n] = h.s[n], h.s[0] 382 h.down(0, n) 383 384 n = len(h.s) 385 s := h.s[n-1] 386 h.s = h.s[0 : n-1] 387 return s 388 } 389 390 func (h *expDecaySampleHeap) Size() int { 391 return len(h.s) 392 } 393 394 func (h *expDecaySampleHeap) Values() []expDecaySample { 395 return h.s 396 } 397 398 func (h *expDecaySampleHeap) up(j int) { 399 for { 400 i := (j - 1) / 2 // parent 401 if i == j || !(h.s[j].k < h.s[i].k) { 402 break 403 } 404 h.s[i], h.s[j] = h.s[j], h.s[i] 405 j = i 406 } 407 } 408 409 func (h *expDecaySampleHeap) down(i, n int) { 410 for { 411 j1 := 2*i + 1 412 if j1 >= n || j1 < 0 { // j1 < 0 after int overflow 413 break 414 } 415 j := j1 // left child 416 if j2 := j1 + 1; j2 < n && !(h.s[j1].k < h.s[j2].k) { 417 j = j2 // = 2*i + 2 // right child 418 } 419 if !(h.s[j].k < h.s[i].k) { 420 break 421 } 422 h.s[i], h.s[j] = h.s[j], h.s[i] 423 i = j 424 } 425 }