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  }