github.com/cavaliergopher/grab/v3@v3.0.1/pkg/bps/sma.go (about) 1 package bps 2 3 import ( 4 "sync" 5 "time" 6 ) 7 8 // NewSMA returns a gauge that uses a Simple Moving Average with the given 9 // number of samples to measure the bytes per second of a byte stream. 10 // 11 // BPS is computed using the timestamp of the most recent and oldest sample in 12 // the sample buffer. When a new sample is added, the oldest sample is dropped 13 // if the sample count exceeds maxSamples. 14 // 15 // The gauge does not account for any latency in arrival time of new samples or 16 // the desired window size. Any variance in the arrival of samples will result 17 // in a BPS measurement that is correct for the submitted samples, but over a 18 // varying time window. 19 // 20 // maxSamples should be equal to 1 + (window size / sampling interval) where 21 // window size is the number of seconds over which the moving average is 22 // smoothed and sampling interval is the number of seconds between each sample. 23 // 24 // For example, if you want a five second window, sampling once per second, 25 // maxSamples should be 1 + 5/1 = 6. 26 func NewSMA(maxSamples int) Gauge { 27 if maxSamples < 2 { 28 panic("sample count must be greater than 1") 29 } 30 return &sma{ 31 maxSamples: uint64(maxSamples), 32 samples: make([]int64, maxSamples), 33 timestamps: make([]time.Time, maxSamples), 34 } 35 } 36 37 type sma struct { 38 mu sync.Mutex 39 index uint64 40 maxSamples uint64 41 sampleCount uint64 42 samples []int64 43 timestamps []time.Time 44 } 45 46 func (c *sma) Sample(t time.Time, n int64) { 47 c.mu.Lock() 48 defer c.mu.Unlock() 49 50 c.timestamps[c.index] = t 51 c.samples[c.index] = n 52 c.index = (c.index + 1) % c.maxSamples 53 54 // prevent integer overflow in sampleCount. Values greater or equal to 55 // maxSamples have the same semantic meaning. 56 c.sampleCount++ 57 if c.sampleCount > c.maxSamples { 58 c.sampleCount = c.maxSamples 59 } 60 } 61 62 func (c *sma) BPS() float64 { 63 c.mu.Lock() 64 defer c.mu.Unlock() 65 66 // we need two samples to start 67 if c.sampleCount < 2 { 68 return 0 69 } 70 71 // First sample is always the oldest until ring buffer first overflows 72 oldest := c.index 73 if c.sampleCount < c.maxSamples { 74 oldest = 0 75 } 76 77 newest := (c.index + c.maxSamples - 1) % c.maxSamples 78 seconds := c.timestamps[newest].Sub(c.timestamps[oldest]).Seconds() 79 bytes := float64(c.samples[newest] - c.samples[oldest]) 80 return bytes / seconds 81 }