package main import ( "sync" "time" ) // The panel's recent-history ring: 18 slots of 5 seconds, sampled once a // second from the same figures the console row prints. 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. 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 noiseOn int noiseN int } type history struct { mu sync.Mutex start time.Time slots [histSlots]histSlot seq int64 prevValid bool prevSince errs prevCorrected uint64 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 } // 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 } return now - prev } func (h *history) sample(now time.Time, v view, phy phyDisplay, nv noiseView, measuring bool, target float64) { h.mu.Lock() defer h.mu.Unlock() h.seq = int64(now.Sub(h.start) / histSlotSpan) s := h.slot(h.seq) 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 } 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 } } } s.noiseN++ if nv.on { s.noiseOn++ } } // The wall-time slots stay: recently is recently. Everything judged against // 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 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 } func (h *history) view() histView { h.mu.Lock() defer h.mu.Unlock() var out histView for k := 0; k < histSlots; k++ { seq := h.seq - 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 }