mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-31 00:01:40 +01:00 
			
		
		
		
	In order to improve latency tracking, we will use an exponentially weighted moving average that will smooth change over time and suppress large outlier values. Updates tailscale/corp#26649 Signed-off-by: James Tucker <james@tailscale.com>
		
			
				
	
	
		
			73 lines
		
	
	
		
			1.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			73 lines
		
	
	
		
			1.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // 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
 | |
| }
 |