Mask measure blips end to end and show a running median cable length
This commit is contained in:
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -155,6 +156,14 @@ func (m *phyModule) poll() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Setting busy is a measure's first act, so busy still clear here proves
|
||||
// every read above finished before the measure disturbed anything; a
|
||||
// measure that began mid-read makes the samples ambiguous, and forgive()
|
||||
// re-baselines after it anyway.
|
||||
if m.busy.Load() {
|
||||
return nil
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
if m.link && !link {
|
||||
m.notes = append(m.notes,
|
||||
@@ -267,18 +276,22 @@ type cableInfo struct {
|
||||
haveMaps [2]bool
|
||||
}
|
||||
|
||||
// The median across the pairs rather than the mean: the pairs run the same
|
||||
// cable, so one pair's skewed TDR estimate is an outlier, not signal. Always
|
||||
// a value some pair actually measured — the lower middle when the count is
|
||||
// even, never an average.
|
||||
func (c cableInfo) metresValue() (int, bool) {
|
||||
sum, n := 0, 0
|
||||
var ok []int
|
||||
for i, v := range c.ecd.verdicts {
|
||||
if v == pairOK {
|
||||
sum += c.ecd.metres[i]
|
||||
n++
|
||||
ok = append(ok, c.ecd.metres[i])
|
||||
}
|
||||
}
|
||||
if n == 0 {
|
||||
if len(ok) == 0 {
|
||||
return 0, false
|
||||
}
|
||||
return (sum + n/2) / n, true
|
||||
slices.Sort(ok)
|
||||
return ok[(len(ok)-1)/2], true
|
||||
}
|
||||
|
||||
func (c cableInfo) metresString() string {
|
||||
@@ -289,26 +302,24 @@ func (c cableInfo) metresString() string {
|
||||
return fmt.Sprintf("%d", m)
|
||||
}
|
||||
|
||||
type cableRange struct {
|
||||
have bool
|
||||
min, max int
|
||||
// One value per completed healthy measure; the running median steadies as
|
||||
// they accumulate, and like every median here it is always a value that was
|
||||
// actually measured.
|
||||
type cableLengths struct {
|
||||
vals []int
|
||||
}
|
||||
|
||||
func (r *cableRange) add(m int) {
|
||||
if !r.have || m < r.min {
|
||||
r.min = m
|
||||
}
|
||||
if !r.have || m > r.max {
|
||||
r.max = m
|
||||
}
|
||||
r.have = true
|
||||
func (l *cableLengths) add(m int) {
|
||||
l.vals = append(l.vals, m)
|
||||
}
|
||||
|
||||
func (r cableRange) String() string {
|
||||
if r.min == r.max {
|
||||
return fmt.Sprintf("%d", r.min)
|
||||
func (l cableLengths) median() (int, bool) {
|
||||
if len(l.vals) == 0 {
|
||||
return 0, false
|
||||
}
|
||||
return fmt.Sprintf("%d-%d", r.min, r.max)
|
||||
s := slices.Clone(l.vals)
|
||||
slices.Sort(s)
|
||||
return s[(len(s)-1)/2], true
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -378,7 +389,7 @@ func cableSummary(cable cableInfo, measuring bool) (string, int) {
|
||||
|
||||
// The margin is the worst pair across the ends that measure SNR (the Wiitek's
|
||||
// IEEE 1.133–136), gated on the whole pair being fresh and linked.
|
||||
func phyDisplayFrom(cable cableInfo, rng cableRange, measuring bool, a, b phyModView) phyDisplay {
|
||||
func phyDisplayFrom(cable cableInfo, lens cableLengths, measuring bool, a, b phyModView) phyDisplay {
|
||||
d := phyDisplay{
|
||||
haveSNR: a.fresh && b.fresh && a.link && b.link && (a.haveSNR || b.haveSNR),
|
||||
corrected: a.blocks + a.ber + a.retrain + b.blocks + b.ber + b.retrain,
|
||||
@@ -399,8 +410,11 @@ func phyDisplayFrom(cable cableInfo, rng cableRange, measuring bool, a, b phyMod
|
||||
}
|
||||
}
|
||||
d.metres, d.metresClass = cableSummary(cable, measuring)
|
||||
if d.metresClass == clsGood && rng.have {
|
||||
d.metres = rng.String()
|
||||
// The running median outlives moments without a fresh verdict — a
|
||||
// running measure, or one that returned no data — and only an actual
|
||||
// fault verdict replaces it.
|
||||
if m, ok := lens.median(); ok && (d.metresClass == clsGood || d.metresClass == clsNone) {
|
||||
d.metres, d.metresClass = fmt.Sprintf("%d", m), clsGood
|
||||
}
|
||||
return d
|
||||
}
|
||||
@@ -444,14 +458,16 @@ func measureCable(mods []*phyModule, done *atomic.Bool) (cableInfo, error) {
|
||||
}
|
||||
// A pair the diag just called faulted is not going to relink; waiting the
|
||||
// full carrier timeout would hold the verdict hostage for 25 s on an
|
||||
// unplugged cable.
|
||||
healthy := true
|
||||
// unplugged cable. A verdict of no data is not a fault: the restart above
|
||||
// took a working link down, and returning before carrier drops the
|
||||
// suppression while it is still retraining.
|
||||
faulted := false
|
||||
for _, v := range c.ecd.verdicts {
|
||||
if v != pairOK {
|
||||
healthy = false
|
||||
if v != 0 && v != pairOK {
|
||||
faulted = true
|
||||
}
|
||||
}
|
||||
if healthy {
|
||||
if !faulted {
|
||||
waitCarrier([2]string{mods[0].dev.name(), mods[1].dev.name()}, done)
|
||||
}
|
||||
for i, m := range mods {
|
||||
@@ -475,24 +491,24 @@ type cableDiag struct {
|
||||
// hidden and reverted.
|
||||
measuring atomic.Bool
|
||||
|
||||
mu sync.Mutex
|
||||
info cableInfo
|
||||
rng cableRange
|
||||
mu sync.Mutex
|
||||
info cableInfo
|
||||
lengths cableLengths
|
||||
}
|
||||
|
||||
func newCableDiag(mods []*phyModule, info cableInfo) *cableDiag {
|
||||
return &cableDiag{mods: mods, info: info}
|
||||
}
|
||||
|
||||
func (c *cableDiag) snapshot() (cableInfo, cableRange, bool) {
|
||||
func (c *cableDiag) snapshot() (cableInfo, cableLengths, bool) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.info, c.rng, c.measuring.Load()
|
||||
return c.info, c.lengths, c.measuring.Load()
|
||||
}
|
||||
|
||||
func (c *cableDiag) resetRange() {
|
||||
func (c *cableDiag) resetLengths() {
|
||||
c.mu.Lock()
|
||||
c.rng = cableRange{}
|
||||
c.lengths = cableLengths{}
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
@@ -513,7 +529,7 @@ func (c *cableDiag) kick(done *atomic.Bool) bool {
|
||||
c.info = info
|
||||
if _, cls := cableSummary(info, false); cls == clsGood {
|
||||
if m, ok := info.metresValue(); ok {
|
||||
c.rng.add(m)
|
||||
c.lengths.add(m)
|
||||
}
|
||||
}
|
||||
c.mu.Unlock()
|
||||
|
||||
Reference in New Issue
Block a user