package main import ( "sync" "time" ) // 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 histSpan = histSlots * histSlotSpan // Line rate is a verdict here, not a number: the headline is smeared and // briefly gated at startup, so anything close to the target counts. rateOKFrac = 0.98 ) var faultClasses = []struct { label string noun string get func(errs) uint64 }{ {"lost", "loss", func(e errs) uint64 { return e.lost }}, {"corrupt", "corrupt", func(e errs) uint64 { return e.corrupt }}, {"link", "link fault", func(e errs) uint64 { return e.link }}, {"internal", "internal", func(e errs) uint64 { return e.internal }}, } type histSlot struct { seq int64 sampled bool measuring bool faults [4]uint64 corrected uint64 rateLow bool haveSNR bool snrMin float64 } type history struct { mu sync.Mutex start time.Time slots [histSlots]histSlot lastFault [4]time.Time sinceRateLow bool sinceHaveSNR bool sinceSNRMin float64 } func newHistory(now time.Time) *history { return &history{start: now} } func (h *history) slot(seq int64) *histSlot { s := &h.slots[seq%histSlots] if s.seq != seq || !s.sampled { *s = histSlot{seq: seq, sampled: true} } return s } // 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 } } } 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() defer h.mu.Unlock() s := h.slot(int64(now.Sub(h.start) / histSlotSpan)) if measuring { s.measuring = 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 !h.sinceHaveSNR || phy.worstMargin < h.sinceSNRMin { h.sinceHaveSNR, h.sinceSNRMin = true, phy.worstMargin } } } // The wall-time slots stay: recently is recently. Everything judged against // the reset origin clears. func (h *history) reset() { h.mu.Lock() h.lastFault = [4]time.Time{} h.sinceRateLow = false h.sinceHaveSNR = false h.sinceSNRMin = 0 h.mu.Unlock() } type histView struct { // Oldest first; a slot that predates the ring or was never sampled has // sampled false and paints as unknown. slots [histSlots]histSlot lastFault [4]time.Time sinceRateLow bool sinceHaveSNR bool sinceSNRMin float64 } // The window's newest column is the slot containing now, taken from the draw // clock rather than the last sample, so the grid rolls on the slot boundary // itself. func (h *history) view(now time.Time) histView { h.mu.Lock() defer h.mu.Unlock() newest := int64(now.Sub(h.start) / histSlotSpan) var out histView for k := 0; k < histSlots; k++ { seq := newest - int64(histSlots-1-k) if seq < 0 { continue } s := h.slots[seq%histSlots] if s.seq == seq && s.sampled { out.slots[k] = s } } out.lastFault = h.lastFault out.sinceRateLow = h.sinceRateLow out.sinceHaveSNR = h.sinceHaveSNR out.sinceSNRMin = h.sinceSNRMin return out } // The newest fault on record names the verdict; the cable's own verdict // outranks traffic faults because it is the thing under test. func (hv histView) newestFault(now time.Time) (i int, age time.Duration, ok bool) { best := -1 for c, t := range hv.lastFault { if t.IsZero() { continue } if best < 0 || t.After(hv.lastFault[best]) { best = c } } if best < 0 { return 0, 0, false } return best, now.Sub(hv.lastFault[best]), true }