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