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