2015-09-29 23:29:49 -07:00
|
|
|
// Package bps is a simple tool for keeping track of the rate of bytes
|
|
|
|
// transmitted
|
|
|
|
package bps
|
2014-03-02 22:44:09 -08:00
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
2015-09-29 23:29:49 -07:00
|
|
|
"io/ioutil"
|
2014-03-08 00:45:55 -08:00
|
|
|
"sort"
|
2015-09-29 23:29:49 -07:00
|
|
|
"sync"
|
2014-03-02 22:44:09 -08:00
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2015-09-29 23:29:49 -07:00
|
|
|
// BPS keeps track of state for byte counts
|
|
|
|
//
|
|
|
|
// Instantiate a BPS then feed bytes either via BPS.Add, or writing to it. When
|
|
|
|
// the accumulated values are needed call BPS.Cur. When the BPS is no loner
|
|
|
|
// needed call BPS.Close
|
|
|
|
type BPS struct {
|
|
|
|
sync.RWMutex
|
2014-03-02 23:03:00 -08:00
|
|
|
|
2015-09-29 23:29:49 -07:00
|
|
|
quit chan interface{}
|
|
|
|
closed chan interface{}
|
2014-03-02 23:03:00 -08:00
|
|
|
|
2015-09-29 23:29:49 -07:00
|
|
|
snapshot []float64
|
2014-03-02 23:03:00 -08:00
|
|
|
|
2015-09-29 23:29:49 -07:00
|
|
|
dt time.Duration
|
|
|
|
dts []int64
|
2014-03-08 00:45:55 -08:00
|
|
|
|
2015-09-29 23:29:49 -07:00
|
|
|
// curBs bytes read for this dt
|
|
|
|
curBs int64
|
2014-03-08 00:45:55 -08:00
|
|
|
|
2015-09-29 23:29:49 -07:00
|
|
|
// timeBuckets contains an entry for bytes read for each dt of time up to
|
|
|
|
// the longest recoreded time slice.
|
|
|
|
timeBuckets []int64
|
2014-03-08 00:45:55 -08:00
|
|
|
|
2015-09-29 23:29:49 -07:00
|
|
|
// timeI keys into timeBuckets for the current point in time
|
2014-03-08 00:45:55 -08:00
|
|
|
timeI int
|
2015-09-29 23:29:49 -07:00
|
|
|
|
|
|
|
// max is defined in New to be the maximum number of temporal buckets
|
|
|
|
// required.
|
|
|
|
max int64
|
2014-03-02 23:03:00 -08:00
|
|
|
}
|
|
|
|
|
2015-09-29 23:29:49 -07:00
|
|
|
// New Returns a populated and ready to launch BPS. dts is
|
2014-03-08 18:07:43 -08:00
|
|
|
// a slice of multiples of dt on which to report (e.g. 1x, 10x, 60x dt). dt is
|
|
|
|
// also how often the values used to send to Rx and Tx are updated.
|
2015-09-29 23:29:49 -07:00
|
|
|
func New(dts []int, dt time.Duration) (*BPS, error) {
|
2014-03-08 00:45:55 -08:00
|
|
|
if len(dts) < 1 {
|
2014-03-02 23:03:00 -08:00
|
|
|
return nil, errors.New("must specify at least one interval lenght")
|
|
|
|
}
|
2014-03-08 00:45:55 -08:00
|
|
|
sort.Ints(dts)
|
2015-09-29 23:29:49 -07:00
|
|
|
convertedDts := []int64{}
|
|
|
|
for _, dt := range dts {
|
|
|
|
convertedDts = append(convertedDts, int64(dt))
|
|
|
|
}
|
|
|
|
max := convertedDts[len(convertedDts)-1]
|
|
|
|
r := &BPS{
|
|
|
|
dt: dt,
|
|
|
|
dts: convertedDts,
|
|
|
|
quit: make(chan interface{}),
|
|
|
|
closed: make(chan interface{}),
|
|
|
|
timeBuckets: make([]int64, max),
|
|
|
|
max: max,
|
2014-03-02 23:03:00 -08:00
|
|
|
}
|
2015-09-29 23:29:49 -07:00
|
|
|
go r.run()
|
2014-03-02 23:03:00 -08:00
|
|
|
return r, nil
|
|
|
|
}
|
|
|
|
|
2015-09-29 23:29:49 -07:00
|
|
|
func (b *BPS) runLoop() {
|
|
|
|
t := time.NewTicker(b.dt)
|
2014-03-02 23:03:00 -08:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-t.C:
|
2015-09-29 23:29:49 -07:00
|
|
|
b.Lock()
|
2014-03-08 18:07:43 -08:00
|
|
|
|
2015-09-29 23:29:49 -07:00
|
|
|
b.timeBuckets[b.timeI] = b.curBs
|
2014-03-08 18:07:43 -08:00
|
|
|
|
2015-09-29 23:29:49 -07:00
|
|
|
b.snapshot = b.averages(b.timeBuckets)
|
2014-03-08 18:07:43 -08:00
|
|
|
|
2015-09-29 23:29:49 -07:00
|
|
|
b.curBs = 0
|
|
|
|
|
|
|
|
// Here we march forward through time by going backward in our
|
|
|
|
// slice.
|
|
|
|
b.timeI = (b.timeI - 1) % int(b.max)
|
|
|
|
// because modulo does unexpected things for negative numbers.
|
|
|
|
if b.timeI < 0 {
|
|
|
|
b.timeI = b.timeI + int(b.max)
|
2014-03-08 18:07:43 -08:00
|
|
|
}
|
2015-09-29 23:29:49 -07:00
|
|
|
b.Unlock()
|
|
|
|
case <-b.quit:
|
|
|
|
return
|
2014-03-02 23:03:00 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-29 23:29:49 -07:00
|
|
|
// Run is a method of BPS that must be started in a goroutine in order
|
|
|
|
// for things to be functional.
|
|
|
|
func (b *BPS) run() {
|
|
|
|
b.runLoop()
|
|
|
|
close(b.closed)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *BPS) averages(state []int64) []float64 {
|
2014-03-08 18:07:43 -08:00
|
|
|
r := []float64{}
|
2015-09-29 23:29:49 -07:00
|
|
|
var i int64 = 0
|
|
|
|
var total int64 = 0
|
|
|
|
for _, ti := range b.dts {
|
2014-03-08 18:07:43 -08:00
|
|
|
for ; ; i++ {
|
|
|
|
if i == ti {
|
|
|
|
break
|
|
|
|
}
|
2015-09-29 23:29:49 -07:00
|
|
|
total += state[(int64(b.timeI)+i)%b.max]
|
2014-03-08 18:07:43 -08:00
|
|
|
}
|
|
|
|
r = append(r, float64(total)/float64(ti))
|
2014-03-03 22:59:50 -08:00
|
|
|
}
|
2014-03-08 18:07:43 -08:00
|
|
|
return r
|
2014-03-02 22:44:09 -08:00
|
|
|
}
|
2015-09-29 23:29:49 -07:00
|
|
|
|
|
|
|
func (b *BPS) Write(p []byte) (int, error) {
|
|
|
|
n, err := ioutil.Discard.Write(p)
|
|
|
|
b.Add(int64(n))
|
|
|
|
return n, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *BPS) Add(i int64) {
|
|
|
|
b.Lock()
|
|
|
|
b.curBs += i
|
|
|
|
b.timeBuckets[b.timeI] = b.curBs
|
|
|
|
b.snapshot = b.averages(b.timeBuckets)
|
|
|
|
b.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *BPS) Cur() []float64 {
|
|
|
|
r := make([]float64, len(b.dts))
|
|
|
|
b.Lock()
|
|
|
|
for i := range b.snapshot {
|
|
|
|
r[i] = b.snapshot[i]
|
|
|
|
}
|
|
|
|
b.Unlock()
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *BPS) Close() {
|
|
|
|
close(b.quit)
|
|
|
|
<-b.closed
|
|
|
|
}
|