bps/bandwidth.go

147 lines
3.0 KiB
Go
Raw Normal View History

// 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"
"io/ioutil"
"sort"
"sync"
2014-03-02 22:44:09 -08:00
"time"
)
// 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
quit chan interface{}
closed chan interface{}
2014-03-02 23:03:00 -08:00
snapshot []float64
2014-03-02 23:03:00 -08:00
dt time.Duration
dts []int64
// curBs bytes read for this dt
curBs int64
// timeBuckets contains an entry for bytes read for each dt of time up to
// the longest recoreded time slice.
timeBuckets []int64
// timeI keys into timeBuckets for the current point in time
timeI int
// max is defined in New to be the maximum number of temporal buckets
// required.
max int64
2014-03-02 23:03:00 -08:00
}
// New Returns a populated and ready to launch BPS. dts is
// 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.
func New(dts []int, dt time.Duration) (*BPS, error) {
if len(dts) < 1 {
2014-03-02 23:03:00 -08:00
return nil, errors.New("must specify at least one interval lenght")
}
sort.Ints(dts)
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
}
go r.run()
2014-03-02 23:03:00 -08:00
return r, nil
}
func (b *BPS) runLoop() {
t := time.NewTicker(b.dt)
2014-03-02 23:03:00 -08:00
for {
select {
case <-t.C:
b.Lock()
b.timeBuckets[b.timeI] = b.curBs
b.snapshot = b.averages(b.timeBuckets)
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)
}
b.Unlock()
case <-b.quit:
return
2014-03-02 23:03:00 -08: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 {
r := []float64{}
var i int64 = 0
var total int64 = 0
for _, ti := range b.dts {
for ; ; i++ {
if i == ti {
break
}
total += state[(int64(b.timeI)+i)%b.max]
}
r = append(r, float64(total)/float64(ti))
2014-03-03 22:59:50 -08:00
}
return r
2014-03-02 22:44:09 -08: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
}