tailscale/maths/ewma.go

73 lines
1.7 KiB
Go
Raw Normal View History

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Package maths contains additional mathematical functions or structures not
// found in the standard library.
package maths
import (
"math"
"time"
)
// EWMA is an exponentially weighted moving average supporting updates at
// irregular intervals with at most nanosecond resolution.
// The zero value will compute a half-life of 1 second.
// It is not safe for concurrent use.
// TODO(raggi): de-duplicate with tstime/rate.Value, which has a more complex
// and synchronized interface and does not provide direct access to the stable
// value.
type EWMA struct {
value float64 // current value of the average
lastTime int64 // time of last update in unix nanos
halfLife float64 // half-life in seconds
}
// NewEWMA creates a new EWMA with the specified half-life. If halfLifeSeconds
// is 0, it defaults to 1.
func NewEWMA(halfLifeSeconds float64) *EWMA {
return &EWMA{
halfLife: halfLifeSeconds,
}
}
// Update adds a new sample to the average. If t is zero or precedes the last
// update, the update is ignored.
func (e *EWMA) Update(value float64, t time.Time) {
if t.IsZero() {
return
}
hl := e.halfLife
if hl == 0 {
hl = 1
}
tn := t.UnixNano()
if e.lastTime == 0 {
e.value = value
e.lastTime = tn
return
}
dt := (time.Duration(tn-e.lastTime) * time.Nanosecond).Seconds()
if dt < 0 {
// drop out of order updates
return
}
// decay = 2^(-dt/halfLife)
decay := math.Exp2(-dt / hl)
e.value = e.value*decay + value*(1-decay)
e.lastTime = tn
}
// Get returns the current value of the average
func (e *EWMA) Get() float64 {
return e.value
}
// Reset clears the EWMA to its initial state
func (e *EWMA) Reset() {
e.value = 0
e.lastTime = 0
}