ewma.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. package metrics
  2. import (
  3. "math"
  4. "sync"
  5. "sync/atomic"
  6. "time"
  7. )
  8. // EWMAs continuously calculate an exponentially-weighted moving average
  9. // based on an outside source of clock ticks.
  10. type EWMA interface {
  11. Rate() float64
  12. Snapshot() EWMA
  13. Tick()
  14. Update(int64)
  15. }
  16. // NewEWMA constructs a new EWMA with the given alpha.
  17. func NewEWMA(alpha float64) EWMA {
  18. return &StandardEWMA{alpha: alpha}
  19. }
  20. // NewEWMA1 constructs a new EWMA for a one-minute moving average.
  21. func NewEWMA1() EWMA {
  22. return NewEWMA(1 - math.Exp(-5.0/60.0/1))
  23. }
  24. // NewEWMA5 constructs a new EWMA for a five-minute moving average.
  25. func NewEWMA5() EWMA {
  26. return NewEWMA(1 - math.Exp(-5.0/60.0/5))
  27. }
  28. // NewEWMA15 constructs a new EWMA for a fifteen-minute moving average.
  29. func NewEWMA15() EWMA {
  30. return NewEWMA(1 - math.Exp(-5.0/60.0/15))
  31. }
  32. // EWMASnapshot is a read-only copy of another EWMA.
  33. type EWMASnapshot float64
  34. // Rate returns the rate of events per second at the time the snapshot was
  35. // taken.
  36. func (a EWMASnapshot) Rate() float64 { return float64(a) }
  37. // Snapshot returns the snapshot.
  38. func (a EWMASnapshot) Snapshot() EWMA { return a }
  39. // Tick panics.
  40. func (EWMASnapshot) Tick() {
  41. panic("Tick called on an EWMASnapshot")
  42. }
  43. // Update panics.
  44. func (EWMASnapshot) Update(int64) {
  45. panic("Update called on an EWMASnapshot")
  46. }
  47. // NilEWMA is a no-op EWMA.
  48. type NilEWMA struct{}
  49. // Rate is a no-op.
  50. func (NilEWMA) Rate() float64 { return 0.0 }
  51. // Snapshot is a no-op.
  52. func (NilEWMA) Snapshot() EWMA { return NilEWMA{} }
  53. // Tick is a no-op.
  54. func (NilEWMA) Tick() {}
  55. // Update is a no-op.
  56. func (NilEWMA) Update(n int64) {}
  57. // StandardEWMA is the standard implementation of an EWMA and tracks the number
  58. // of uncounted events and processes them on each tick. It uses the
  59. // sync/atomic package to manage uncounted events.
  60. type StandardEWMA struct {
  61. uncounted int64 // /!\ this should be the first member to ensure 64-bit alignment
  62. alpha float64
  63. rate float64
  64. init bool
  65. mutex sync.Mutex
  66. }
  67. // Rate returns the moving average rate of events per second.
  68. func (a *StandardEWMA) Rate() float64 {
  69. a.mutex.Lock()
  70. defer a.mutex.Unlock()
  71. return a.rate * float64(time.Second)
  72. }
  73. // Snapshot returns a read-only copy of the EWMA.
  74. func (a *StandardEWMA) Snapshot() EWMA {
  75. return EWMASnapshot(a.Rate())
  76. }
  77. // Tick ticks the clock to update the moving average. It assumes it is called
  78. // every five seconds.
  79. func (a *StandardEWMA) Tick() {
  80. count := atomic.LoadInt64(&a.uncounted)
  81. atomic.AddInt64(&a.uncounted, -count)
  82. instantRate := float64(count) / float64(5*time.Second)
  83. a.mutex.Lock()
  84. defer a.mutex.Unlock()
  85. if a.init {
  86. a.rate += a.alpha * (instantRate - a.rate)
  87. } else {
  88. a.init = true
  89. a.rate = instantRate
  90. }
  91. }
  92. // Update adds n uncounted events.
  93. func (a *StandardEWMA) Update(n int64) {
  94. atomic.AddInt64(&a.uncounted, n)
  95. }