Restructure stats into delta, mask, and aggregate layers
This commit is contained in:
+53
-46
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -222,70 +223,76 @@ func TestSmearConservesFramesAndBytes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Indexed oldest first, so the error window still spans the whole ring once it
|
||||
// has wrapped.
|
||||
func TestRateWindowIndexesOldestFirst(t *testing.T) {
|
||||
w := newRateWindow(3)
|
||||
// The window is a rolling sum of one span of deltas: what leaves the ring
|
||||
// leaves the sum, so after a wrap only the last len slots are counted.
|
||||
func TestErrsRingRollingSum(t *testing.T) {
|
||||
r := newErrsRing(3)
|
||||
for i := 1; i <= 5; i++ {
|
||||
w.push(counterSet{s: sample{rxFrames: uint64(i)}})
|
||||
r.push(errs{lost: uint64(i), link: 1})
|
||||
}
|
||||
if got := w.count(); got != 3 {
|
||||
t.Fatalf("count = %d, want 3", got)
|
||||
}
|
||||
if got := w.at(0).s.rxFrames; got != 3 {
|
||||
t.Errorf("oldest = %d, want 3", got)
|
||||
}
|
||||
if got := w.at(2).s.rxFrames; got != 5 {
|
||||
t.Errorf("newest = %d, want 5", got)
|
||||
want := errs{lost: 3 + 4 + 5, link: 3}
|
||||
if r.sum != want {
|
||||
t.Errorf("sum = %+v, want %+v", r.sum, want)
|
||||
}
|
||||
}
|
||||
|
||||
// Which counter feeds which bucket is the whole taxonomy the panel reports, so
|
||||
// it is pinned here rather than left to whoever reads errsBetween next.
|
||||
func TestErrsBetweenBuckets(t *testing.T) {
|
||||
n := counterSet{
|
||||
s: sample{
|
||||
lost: 1, late: 7,
|
||||
crcErr: 2, badMagic: 3, badLen: 4, badHdr: 10,
|
||||
txErrs: 6, rxErrs: 5,
|
||||
},
|
||||
drops: 9,
|
||||
nic: 8,
|
||||
// Which delta feeds which bucket is the whole taxonomy the panel reports, so
|
||||
// it is pinned here rather than left to whoever reads classify next.
|
||||
func TestClassifyBuckets(t *testing.T) {
|
||||
cur := sample{
|
||||
lost: 1, late: 7,
|
||||
crcErr: 2, badMagic: 3, badLen: 4, badHdr: 10,
|
||||
txErrs: 6, rxErrs: 5,
|
||||
}
|
||||
got := errsBetween(counterSet{}, n)
|
||||
got := classify(cur, sample{}, 9, 8)
|
||||
want := errs{lost: 1, corrupt: 2 + 3 + 4 + 10, link: 8 + 5, internal: 9 + 7 + 6}
|
||||
if got != want {
|
||||
t.Errorf("errsBetween = %+v, want %+v", got, want)
|
||||
t.Errorf("classify = %+v, want %+v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// A reset re-bases from a fresh capture while the ring still holds buckets from
|
||||
// just before it, so the newest bucket must not be left behind the new origin.
|
||||
func TestResetDoesNotUnderflowTotals(t *testing.T) {
|
||||
d := &direction{
|
||||
win: newRateWindow(8),
|
||||
rxStats: []*rxStats{{}},
|
||||
// A masked tick's faults stop at the gate while traffic and the window clock
|
||||
// keep moving; the tick after the mask lifts aggregates again.
|
||||
func TestMaskedTickAggregatesNothing(t *testing.T) {
|
||||
var masked atomic.Bool
|
||||
d := &direction{masked: &masked, window: newErrsRing(8)}
|
||||
|
||||
masked.Store(true)
|
||||
if e := d.aggregate(10, 640, errs{lost: 5, link: 2}); e != (errs{}) {
|
||||
t.Errorf("accepted %+v from a masked tick, want nothing", e)
|
||||
}
|
||||
v := d.displayView()
|
||||
if v.since != (errs{}) || v.window != (errs{}) {
|
||||
t.Errorf("since = %+v window = %+v after a masked tick, want both empty", v.since, v.window)
|
||||
}
|
||||
if v.rxFrames != 10 || v.rxBytes != 640 {
|
||||
t.Errorf("traffic = %d frames %d bytes, want it counted through the mask", v.rxFrames, v.rxBytes)
|
||||
}
|
||||
|
||||
d.rxStats[0].frames.Store(100)
|
||||
d.rxStats[0].bytes.Store(6400)
|
||||
d.sample()
|
||||
masked.Store(false)
|
||||
d.aggregate(0, 0, errs{lost: 1})
|
||||
v = d.displayView()
|
||||
if v.since.lost != 1 || v.window.lost != 1 {
|
||||
t.Errorf("since.lost = %d window.lost = %d after the mask lifted, want 1 and 1", v.since.lost, v.window.lost)
|
||||
}
|
||||
}
|
||||
|
||||
d.rxStats[0].frames.Store(150)
|
||||
d.rxStats[0].bytes.Store(9600)
|
||||
// A reset zeroes the since-reset sums; the rolling window is about now rather
|
||||
// than since the reset, so it keeps what it held.
|
||||
func TestResetZeroesSinceAndKeepsWindow(t *testing.T) {
|
||||
var masked atomic.Bool
|
||||
d := &direction{masked: &masked, window: newErrsRing(8)}
|
||||
d.aggregate(100, 6400, errs{lost: 3})
|
||||
d.reset()
|
||||
|
||||
v := d.displayView()
|
||||
if v.rxFrames != 0 {
|
||||
t.Errorf("rxFrames = %d, want 0", v.rxFrames)
|
||||
if v.rxFrames != 0 || v.rxBytes != 0 {
|
||||
t.Errorf("traffic = %d frames %d bytes after reset, want 0 and 0", v.rxFrames, v.rxBytes)
|
||||
}
|
||||
if v.rxBytes != 0 {
|
||||
t.Errorf("rxBytes = %d, want 0", v.rxBytes)
|
||||
if v.since != (errs{}) {
|
||||
t.Errorf("since = %+v after reset, want empty", v.since)
|
||||
}
|
||||
|
||||
// The rolling window and the rates are about now rather than since the
|
||||
// reset, so the buckets from before it have to survive.
|
||||
if got := d.win.count(); got < 2 {
|
||||
t.Errorf("ring holds %d buckets after reset, want the pre-reset history kept", got)
|
||||
if v.window.lost != 3 {
|
||||
t.Errorf("window.lost = %d after reset, want the pre-reset window kept", v.window.lost)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user