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  }