Restructure stats into delta, mask, and aggregate layers

This commit is contained in:
flamingcow
2026-08-31 09:35:32 -07:00
parent ea272e489c
commit 06fa759b1a
11 changed files with 316 additions and 364 deletions
+35 -41
View File
@@ -5,11 +5,12 @@ import (
"time"
)
// The panel's recent-history ring: 18 slots of 5 seconds, sampled every drawn
// frame from the same figures the panel paints, so a slot has data within one
// frame of being born. Slots are wall-time and survive a reset — recently is
// recently, whatever the counters were re-based to — while the since-reset
// aggregates and fault ages clear.
// The panel's recent-history ring: 18 slots of 5 seconds. Fault and corrected
// deltas arrive straight from the layers below the moment they clear the mask,
// and the per-frame judgments — measure state, rate, SNR — from the draw loop,
// so a slot has data within one tick of being born. Slots are wall-time and
// survive a reset — recently is recently, whatever the aggregates were
// re-based to — while the fault ages and since-reset extremes clear.
const (
histSlots = 18
histSlotSpan = 5 * time.Second
@@ -47,10 +48,6 @@ type history struct {
start time.Time
slots [histSlots]histSlot
prevValid bool
prevSince errs
prevCorrected uint64
lastFault [4]time.Time
sinceRateLow bool
sinceHaveSNR bool
@@ -69,15 +66,27 @@ func (h *history) slot(seq int64) *histSlot {
return s
}
// Counters only move forward between samples except across a reset, where
// they re-base to zero; a backward step is that re-base, not negative faults.
func delta(now, prev uint64) uint64 {
if now < prev {
return 0
// Bucketing sits above the mask: only fault deltas that survived it arrive
// here, already flattened, so a slot is a plain sum.
func (h *history) addFaults(now time.Time, e errs) {
h.mu.Lock()
defer h.mu.Unlock()
s := h.slot(int64(now.Sub(h.start) / histSlotSpan))
for i, c := range faultClasses {
if d := c.get(e); d > 0 {
s.faults[i] += d
h.lastFault[i] = now
}
}
return now - prev
}
func (h *history) addCorrected(now time.Time, d uint64) {
h.mu.Lock()
h.slot(int64(now.Sub(h.start)/histSlotSpan)).corrected += d
h.mu.Unlock()
}
// The per-frame judgments, from the same figures the panel paints.
func (h *history) sample(now time.Time, v view, phy phyDisplay,
measuring bool, target float64) {
h.mu.Lock()
@@ -85,34 +94,20 @@ func (h *history) sample(now time.Time, v view, phy phyDisplay,
s := h.slot(int64(now.Sub(h.start) / histSlotSpan))
if h.prevValid {
for i, c := range faultClasses {
d := delta(c.get(v.since), c.get(h.prevSince))
s.faults[i] += d
if d > 0 {
h.lastFault[i] = now
}
}
s.corrected += delta(phy.corrected, h.prevCorrected)
}
h.prevValid = true
h.prevSince = v.since
h.prevCorrected = phy.corrected
if measuring {
s.measuring = true
} else {
if v.rxGbps < rateOKFrac*target {
s.rateLow = true
h.sinceRateLow = true
return
}
if v.rxGbps < rateOKFrac*target {
s.rateLow = true
h.sinceRateLow = true
}
if phy.haveSNR {
if !s.haveSNR || phy.worstMargin < s.snrMin {
s.haveSNR, s.snrMin = true, phy.worstMargin
}
if phy.haveSNR {
if !s.haveSNR || phy.worstMargin < s.snrMin {
s.haveSNR, s.snrMin = true, phy.worstMargin
}
if !h.sinceHaveSNR || phy.worstMargin < h.sinceSNRMin {
h.sinceHaveSNR, h.sinceSNRMin = true, phy.worstMargin
}
if !h.sinceHaveSNR || phy.worstMargin < h.sinceSNRMin {
h.sinceHaveSNR, h.sinceSNRMin = true, phy.worstMargin
}
}
}
@@ -121,7 +116,6 @@ func (h *history) sample(now time.Time, v view, phy phyDisplay,
// the reset origin clears.
func (h *history) reset() {
h.mu.Lock()
h.prevValid = false
h.lastFault = [4]time.Time{}
h.sinceRateLow = false
h.sinceHaveSNR = false