github.com/theQRL/go-zond@v0.1.1/metrics/ewma.go (about)

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