github.com/ethereum/go-ethereum@v1.16.1/metrics/ewma.go (about) 1 package metrics 2 3 import ( 4 "math" 5 "sync" 6 "sync/atomic" 7 "time" 8 ) 9 10 // EWMASnapshot is a read-only copy of an EWMA. 11 type EWMASnapshot float64 12 13 // Rate returns the rate of events per second at the time the snapshot was 14 // taken. 15 func (a EWMASnapshot) Rate() float64 { return float64(a) } 16 17 // NewEWMA constructs a new EWMA with the given alpha. 18 func NewEWMA(alpha float64) *EWMA { 19 return &EWMA{alpha: alpha} 20 } 21 22 // NewEWMA1 constructs a new EWMA for a one-minute moving average. 23 func NewEWMA1() *EWMA { 24 return NewEWMA(1 - math.Exp(-5.0/60.0/1)) 25 } 26 27 // NewEWMA5 constructs a new EWMA for a five-minute moving average. 28 func NewEWMA5() *EWMA { 29 return NewEWMA(1 - math.Exp(-5.0/60.0/5)) 30 } 31 32 // NewEWMA15 constructs a new EWMA for a fifteen-minute moving average. 33 func NewEWMA15() *EWMA { 34 return NewEWMA(1 - math.Exp(-5.0/60.0/15)) 35 } 36 37 // EWMA continuously calculate an exponentially-weighted moving average 38 // based on an outside source of clock ticks. 39 type EWMA struct { 40 uncounted atomic.Int64 41 alpha float64 42 rate atomic.Uint64 43 init atomic.Bool 44 mutex sync.Mutex 45 } 46 47 // Snapshot returns a read-only copy of the EWMA. 48 func (a *EWMA) Snapshot() EWMASnapshot { 49 r := math.Float64frombits(a.rate.Load()) * float64(time.Second) 50 return EWMASnapshot(r) 51 } 52 53 // tick ticks the clock to update the moving average. It assumes it is called 54 // every five seconds. 55 func (a *EWMA) tick() { 56 // Optimization to avoid mutex locking in the hot-path. 57 if a.init.Load() { 58 a.updateRate(a.fetchInstantRate()) 59 return 60 } 61 // Slow-path: this is only needed on the first tick() and preserves transactional updating 62 // of init and rate in the else block. The first conditional is needed below because 63 // a different thread could have set a.init = 1 between the time of the first atomic load and when 64 // the lock was acquired. 65 a.mutex.Lock() 66 if a.init.Load() { 67 // The fetchInstantRate() uses atomic loading, which is unnecessary in this critical section 68 // but again, this section is only invoked on the first successful tick() operation. 69 a.updateRate(a.fetchInstantRate()) 70 } else { 71 a.init.Store(true) 72 a.rate.Store(math.Float64bits(a.fetchInstantRate())) 73 } 74 a.mutex.Unlock() 75 } 76 77 func (a *EWMA) fetchInstantRate() float64 { 78 count := a.uncounted.Swap(0) 79 return float64(count) / float64(5*time.Second) 80 } 81 82 func (a *EWMA) updateRate(instantRate float64) { 83 currentRate := math.Float64frombits(a.rate.Load()) 84 currentRate += a.alpha * (instantRate - currentRate) 85 a.rate.Store(math.Float64bits(currentRate)) 86 } 87 88 // Update adds n uncounted events. 89 func (a *EWMA) Update(n int64) { 90 a.uncounted.Add(n) 91 }