Panel rebuilt as the heat matrix: verdict header (ring + newest fault named with its age, cable cell top right), one row per check with NOW / last-90s-at-5s-a-cell / since-reset columns, noise as a blue context lane; history.go keeps the 18-slot wall-time ring (per-class fault deltas, SNR minima, rate verdicts, measuring gray) plus since-reset aggregates and fault ages that clear on reset; noise cycle grid-locked to the shared slot clock so cells never straddle a phase and flicker, presence granted at boot until the first up-phase verdict; check/cross stroked as capsule marks (font has neither), line rate is a pure verdict everywhere, footer is a three-line stat stack beside hold-to-reset; old panel/chip/stat-grid machinery deleted; mockups/ gitignored
This commit is contained in:
+186
@@ -0,0 +1,186 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user