From fb6a0d2c1b3acde23b72675ad6c510517d8a610c Mon Sep 17 00:00:00 2001 From: flamingcow Date: Tue, 4 Aug 2026 12:59:50 -0700 Subject: [PATCH] Push the new origin into the sample ring so a reset cannot underflow the totals --- main.go | 6 ++++-- main_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 main_test.go diff --git a/main.go b/main.go index b9239bb..30b0027 100644 --- a/main.go +++ b/main.go @@ -263,11 +263,13 @@ func (d *direction) snapshot() sample { } // Counters keep climbing in the workers, so resetting just moves the origin -// everything is measured from. Rates are deliberately left running, since they -// are instantaneous and would only blink to zero and back. +// everything is measured from. Rates and the rolling error window are about now +// rather than since the reset, so they keep running; the origin goes into the +// ring so the newest bucket never sits behind it. func (d *direction) reset() { d.mu.Lock() d.base = d.capture(time.Now()) + d.win.push(d.base) d.mu.Unlock() d.heldFrames = heldValue{} diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..37b1666 --- /dev/null +++ b/main_test.go @@ -0,0 +1,38 @@ +package main + +import ( + "testing" + "time" +) + +// 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), + cable: newCableStats(), + rxStats: []*rxStats{{}}, + } + + d.rxStats[0].frames.Store(100) + d.rxStats[0].bytes.Store(6400) + d.sample(time.Now()) + + d.rxStats[0].frames.Store(150) + d.rxStats[0].bytes.Store(9600) + d.reset() + + v := d.displayView(time.Now()) + if v.rxFrames != 0 { + t.Errorf("rxFrames = %d, want 0", v.rxFrames) + } + if v.rxGot != 0 { + t.Errorf("rxGot = %d, want 0", v.rxGot) + } + + // 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) + } +}