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