github.com/grafana/pyroscope@v1.18.0/pkg/segmentwriter/client/distributor/placement/adaptiveplacement/ewma/ewma.go (about)

     1  package ewma
     2  
     3  import (
     4  	"math"
     5  	"time"
     6  )
     7  
     8  // Rate is an exponentially weighted moving average event rate.
     9  // The rate is always calculated per second and samples are
    10  // summed up within the second duration.
    11  type Rate struct {
    12  	lifetime   float64
    13  	last       int64
    14  	cumulative float64
    15  	ewma       float64
    16  }
    17  
    18  // New creates a new rate with a given lifetime.
    19  //
    20  // lifetime is the time required for the decaying quantity to
    21  // reduced to 1⁄e ≈ 0.367879441 times its initial value.
    22  func New(lifetime time.Duration) *Rate {
    23  	return &Rate{lifetime: max(1, lifetime.Seconds())}
    24  }
    25  
    26  // NewHalfLife creates a new rate with a given half-life.
    27  //
    28  // halflife is the time required for the decaying quantity
    29  // to fall to one half of its initial value.
    30  func NewHalfLife(halflife time.Duration) *Rate {
    31  	// https://en.wikipedia.org/wiki/Exponential_decay:
    32  	//  halflife = ln(2)/λ = tau * ln(2)
    33  	//  tau = 1/λ = halflife/ln(2)
    34  	return &Rate{lifetime: max(1, halflife.Seconds()) / math.Ln2}
    35  }
    36  
    37  func (r *Rate) Value() float64   { return r.value(time.Now().UnixNano()) }
    38  func (r *Rate) Update(v float64) { r.update(v, time.Now().UnixNano()) }
    39  
    40  func (r *Rate) ValueAt(t int64) float64     { return r.value(t) }
    41  func (r *Rate) UpdateAt(v float64, t int64) { r.update(v, t) }
    42  
    43  func (r *Rate) LastUpdate() time.Time { return time.Unix(0, r.last) }
    44  
    45  func (r *Rate) value(now int64) float64 {
    46  	delta := float64(now - r.last)
    47  	if delta < 0 {
    48  		return 0
    49  	}
    50  	delta /= 1e9
    51  	if delta >= 1 {
    52  		// Correct the result for the time passed since the last update.
    53  		// Over time, the delta increases and the decreased value (the
    54  		// instant rate cumulative/delta) dominates in both ways:
    55  		// directly and via the impact of the exponent. The value is
    56  		// asymptotically approaching zero, if no updates are made.
    57  		return ewma(r.ewma, r.cumulative/delta, delta, r.lifetime)
    58  	}
    59  	return r.ewma
    60  }
    61  
    62  // update updates the rate with a new value and timestamp in nanoseconds.
    63  // It's assumed that time never goes backwards.
    64  func (r *Rate) update(v float64, now int64) {
    65  	delta := float64(now - r.last)
    66  	if delta < 0 {
    67  		return
    68  	}
    69  	delta /= 1e9
    70  	if delta >= 1 {
    71  		r.ewma = ewma(r.ewma, r.cumulative/delta, delta, r.lifetime)
    72  		r.cumulative = 0
    73  		r.last = now
    74  	}
    75  	r.cumulative += v
    76  }
    77  
    78  func ewma(old, value, delta, lifetime float64) float64 {
    79  	alpha := 1 - math.Exp(-delta/lifetime)
    80  	return alpha*value + (1-alpha)*old
    81  }