github.com/intfoundation/intchain@v0.0.0-20220727031208-4316ad31ca73/metrics/ewma.go (about)

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