summaryrefslogtreecommitdiffhomepage
path: root/maths/ewma.go
diff options
context:
space:
mode:
Diffstat (limited to 'maths/ewma.go')
-rw-r--r--maths/ewma.go72
1 files changed, 72 insertions, 0 deletions
diff --git a/maths/ewma.go b/maths/ewma.go
new file mode 100644
index 000000000..0897b73e4
--- /dev/null
+++ b/maths/ewma.go
@@ -0,0 +1,72 @@
+// 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
+}