BCM module diagnostics replace timestamp probes: bringup forces EEE off and runs ECD (re-run on every reset, re-baselining past its blip), 1 Hz SNR margin + corrected counters on panel and console; X520 bench divergences bypassed and tracked in open-questions
This commit is contained in:
@@ -37,12 +37,7 @@ type direction struct {
|
||||
streams []lossWindow
|
||||
txFDs []int
|
||||
rxFDs []int
|
||||
|
||||
probeSpec *frameSpec
|
||||
probeTxFD int
|
||||
probeRxFD int
|
||||
statFD int
|
||||
cable *cableStats
|
||||
statFD int
|
||||
|
||||
// Guards everything the sampler touches. The counters are read on their own
|
||||
// clock and drawn on another, and the two must not read them at once:
|
||||
@@ -220,16 +215,17 @@ func (d *direction) reset() {
|
||||
d.base = d.capture()
|
||||
d.win.push(d.base)
|
||||
d.mu.Unlock()
|
||||
|
||||
d.cable.reset()
|
||||
}
|
||||
|
||||
// Returns the new start time, so the uptime shown alongside the totals counts
|
||||
// from the reset rather than from launch.
|
||||
func resetAll(dirs []*direction, stats *streamTable) time.Time {
|
||||
func resetAll(dirs []*direction, mods []*phyModule, stats *streamTable) time.Time {
|
||||
for _, d := range dirs {
|
||||
d.reset()
|
||||
}
|
||||
for _, m := range mods {
|
||||
m.reset()
|
||||
}
|
||||
stats.sinceHeader = 0
|
||||
fmt.Println(stats.rule("counters reset"))
|
||||
return time.Now()
|
||||
@@ -248,15 +244,18 @@ func gbps(bytes, frames uint64, secs float64) float64 {
|
||||
var intervalCols = []colSpec{
|
||||
{group: "NOW", title: "bits/s", width: 9, right: true},
|
||||
{group: "NOW", title: "packets/s", width: 9, right: true},
|
||||
{group: "NOW", title: "snr", width: 6, right: true},
|
||||
{group: "NOW", title: "lost", width: 7, right: true},
|
||||
{group: "NOW", title: "corrupt", width: 7, right: true},
|
||||
{group: "NOW", title: "link", width: 7, right: true},
|
||||
{group: "NOW", title: "internal", width: 8, right: true},
|
||||
{group: "NOW", title: "corrected", width: 9, right: true},
|
||||
{group: "NOW", title: "noise", width: 7, right: true},
|
||||
{group: "OVERALL", title: "elapsed", width: 9, right: true},
|
||||
{group: "OVERALL", title: "packets", width: 9, right: true},
|
||||
{group: "OVERALL", title: "bytes", width: 9, right: true},
|
||||
{group: "OVERALL", title: "metres", width: 6, right: true},
|
||||
{group: "OVERALL", title: "corrected", width: 9, right: true},
|
||||
{group: "OVERALL", title: "lost", width: 9, right: true},
|
||||
{group: "OVERALL", title: "corrupt", width: 9, right: true},
|
||||
{group: "OVERALL", title: "link", width: 9, right: true},
|
||||
@@ -271,7 +270,6 @@ type view struct {
|
||||
rxFrames, rxBytes uint64
|
||||
since errs
|
||||
window errs
|
||||
cable cableView
|
||||
}
|
||||
|
||||
func errsBetween(b, n counterSet) errs {
|
||||
@@ -297,7 +295,6 @@ func (d *direction) counters(now counterSet) view {
|
||||
return view{
|
||||
rxFrames: now.s.rxFrames - d.base.s.rxFrames,
|
||||
rxBytes: now.s.rxBytes - d.base.s.rxBytes,
|
||||
cable: d.cable.view(),
|
||||
since: errsBetween(d.base, now),
|
||||
}
|
||||
}
|
||||
@@ -329,7 +326,7 @@ func (d *direction) displayView() view {
|
||||
n := d.win.count()
|
||||
if n == 0 {
|
||||
d.mu.Unlock()
|
||||
return view{cable: d.cable.view()}
|
||||
return view{}
|
||||
}
|
||||
v := d.counters(d.win.at(n - 1))
|
||||
if n >= 2 {
|
||||
@@ -344,19 +341,22 @@ func (d *direction) displayView() view {
|
||||
// The same figures the panel draws, in the same order: the last second as
|
||||
// rates and error flags with the noise cable riding at the end of them, then
|
||||
// everything since the reset.
|
||||
func totalRow(elapsed time.Duration, v view, target float64, length string, noiseMissing uint64) []string {
|
||||
func totalRow(elapsed time.Duration, v view, target float64, phy phyDisplay, noiseMissing uint64) []string {
|
||||
return []string{
|
||||
rateCell(v.rxGbps*1e9, target*1e9),
|
||||
scaleSI(v.rxPPS),
|
||||
snrCell(phy),
|
||||
flagCell(v.window.lost),
|
||||
flagCell(v.window.corrupt),
|
||||
flagCell(v.window.link),
|
||||
flagCell(v.window.internal),
|
||||
correctedFlag(phy.recent),
|
||||
flagCell(noiseMissing),
|
||||
scaleTime(elapsed),
|
||||
scaleCount(v.rxFrames),
|
||||
scaleCount(v.rxBytes),
|
||||
length,
|
||||
phy.metres,
|
||||
correctedCell(phy.corrected),
|
||||
statusCell(v.since.lost),
|
||||
statusCell(v.since.corrupt),
|
||||
statusCell(v.since.link),
|
||||
@@ -382,7 +382,6 @@ func buildDirection(label string, tx, rx endpoint) (*direction, error) {
|
||||
d := &direction{
|
||||
txStats: txs,
|
||||
streams: newLossWindows(txs),
|
||||
cable: newCableStats(),
|
||||
}
|
||||
// Held open for the life of the run: the stats ioctl is issued five times a
|
||||
// second and reopening a socket for each one is pure overhead.
|
||||
@@ -412,8 +411,8 @@ func buildDirection(label string, tx, rx endpoint) (*direction, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s rx socket for 0x%04x: %w", label, et, err)
|
||||
}
|
||||
// The mac already stamps every frame for the probe's sake, so this only
|
||||
// asks for the stamp to be delivered.
|
||||
// The mac already stamps every frame; this only asks for the stamp to be
|
||||
// delivered.
|
||||
if err := enableRxTimestamps(fd); err != nil {
|
||||
return nil, fmt.Errorf("%s rx timestamps for 0x%04x: %w", label, et, err)
|
||||
}
|
||||
@@ -421,26 +420,6 @@ func buildDirection(label string, tx, rx endpoint) (*direction, error) {
|
||||
d.rxStats = append(d.rxStats, &rxStats{})
|
||||
}
|
||||
|
||||
// Deliberately given no flow rule: a few frames a second does not need a
|
||||
// queue of its own, and the stamps are taken at the wire either way.
|
||||
d.probeSpec = newFrameSpec(rx.mac, tx.mac, probeEther, []int{probeSize})
|
||||
fd, err := openTxSocket(tx.idx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s probe tx socket: %w", label, err)
|
||||
}
|
||||
if err := enableTxTimestamps(fd); err != nil {
|
||||
return nil, fmt.Errorf("%s probe tx timestamps: %w", label, err)
|
||||
}
|
||||
d.probeTxFD = fd
|
||||
fd, err = openRxSocket(rx.idx, probeEther)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s probe rx socket: %w", label, err)
|
||||
}
|
||||
if err := enableRxTimestamps(fd); err != nil {
|
||||
return nil, fmt.Errorf("%s probe rx timestamps: %w", label, err)
|
||||
}
|
||||
d.probeRxFD = fd
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
@@ -479,22 +458,6 @@ func (d *direction) start(wg *sync.WaitGroup, done *atomic.Bool, rxReady *sync.W
|
||||
}()
|
||||
}
|
||||
|
||||
sender := &probeSender{fd: d.probeTxFD, spec: d.probeSpec, stats: d.cable}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer holdPanic()
|
||||
sender.run(done, startTx)
|
||||
}()
|
||||
|
||||
receiver := &probeReceiver{fd: d.probeRxFD, stats: d.cable, ready: rxReady}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer holdPanic()
|
||||
receiver.run(done)
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
@@ -510,8 +473,6 @@ func (d *direction) close() {
|
||||
for _, fd := range d.rxFDs {
|
||||
unix.Close(fd)
|
||||
}
|
||||
unix.Close(d.probeTxFD)
|
||||
unix.Close(d.probeRxFD)
|
||||
unix.Close(d.statFD)
|
||||
}
|
||||
|
||||
@@ -519,8 +480,6 @@ const (
|
||||
numStreams = 7
|
||||
batchSize = 64
|
||||
|
||||
probeEther uint16 = etherBase + numStreams
|
||||
|
||||
testDriver = "ice"
|
||||
|
||||
// A constant rather than the negotiated speed, since this has to come up
|
||||
@@ -538,10 +497,9 @@ func main() {
|
||||
// which ethN shifts with every driver built into the kernel.
|
||||
aName := flag.String("a", "", "first interface (default: the ice pair)")
|
||||
bName := flag.String("b", "", "second interface")
|
||||
nsPerM := flag.Float64("ns-per-m", 4.85, "mean of both directions, per metre of cable")
|
||||
flag.Parse()
|
||||
|
||||
if err := run(*aName, *bName, *nsPerM); err != nil {
|
||||
if err := run(*aName, *bName); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
// A clean return is ctrl-alt-delete, which the kernel hands PID 1 as a
|
||||
@@ -584,7 +542,7 @@ func (s *sampler) run(done *atomic.Bool, startTx <-chan struct{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func run(aName, bName string, nsPerM float64) (err error) {
|
||||
func run(aName, bName string) (err error) {
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
if os.Getpid() != 1 {
|
||||
@@ -640,6 +598,12 @@ func run(aName, bName string, nsPerM float64) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
modules, cable, moduleChecks := moduleBringup([2]string{a.name, b.name})
|
||||
if err := reportChecks("MODULES", moduleChecks); err != nil {
|
||||
return err
|
||||
}
|
||||
diag := newCableDiag(modules, cable)
|
||||
|
||||
var dirs []*direction
|
||||
for _, p := range [][2]endpoint{{a, b}, {b, a}} {
|
||||
d, err := buildDirection(p[0].name+"->"+p[1].name, p[0], p[1])
|
||||
@@ -673,7 +637,7 @@ func run(aName, bName string, nsPerM float64) (err error) {
|
||||
var rxReady sync.WaitGroup
|
||||
startTx := make(chan struct{})
|
||||
for _, d := range dirs {
|
||||
rxReady.Add(len(d.rxFDs) + 1)
|
||||
rxReady.Add(len(d.rxFDs))
|
||||
}
|
||||
for _, d := range dirs {
|
||||
d.start(&wg, &done, &rxReady, startTx)
|
||||
@@ -693,6 +657,16 @@ func run(aName, bName string, nsPerM float64) (err error) {
|
||||
defer holdPanic()
|
||||
noise.run(&done)
|
||||
}()
|
||||
// Also ungated: SNR and temperature ride the module's own management bus,
|
||||
// not the wire being measured.
|
||||
for _, m := range modules {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer holdPanic()
|
||||
m.run(&done)
|
||||
}()
|
||||
}
|
||||
// Every return from here on stops the workers before the deferred closes
|
||||
// pull their sockets out from under them: otherwise the sampler panics on a
|
||||
// closed fd and can mask the error that actually ended the run. An error
|
||||
@@ -754,41 +728,40 @@ func run(aName, bName string, nsPerM float64) (err error) {
|
||||
return fmt.Errorf("%v", p)
|
||||
case <-sig:
|
||||
return nil
|
||||
// A reset re-measures the cable first — the cable under a reset is
|
||||
// usually a new one — and the counters re-baseline when the diag's own
|
||||
// link blip is over, so it is never charged to the fresh run.
|
||||
case <-space:
|
||||
start = resetAll(dirs, stats)
|
||||
if diag.kick(&done) {
|
||||
fmt.Println(stats.rule("measuring cable"))
|
||||
}
|
||||
case <-diag.completed:
|
||||
start = resetAll(dirs, modules, stats)
|
||||
case <-disp.fb.flips:
|
||||
now := time.Now()
|
||||
px, py, down := touch.get()
|
||||
x, y := disp.fb.fromPanel(px, py)
|
||||
if disp.holdReset(x, y, down, now) {
|
||||
start = resetAll(dirs, stats)
|
||||
diag.kick(&done)
|
||||
}
|
||||
disp.showVersion = down && disp.versionSpot.contains(x, y)
|
||||
for i, d := range dirs {
|
||||
views[i] = d.displayView()
|
||||
}
|
||||
// Empty until the probe has a stamp from each direction, so the
|
||||
// panel shows nothing there rather than a placeholder.
|
||||
cable := ""
|
||||
if m, ok := cableMetres(views, nsPerM); ok {
|
||||
cable = fmt.Sprintf("%.1f", m)
|
||||
}
|
||||
if err := disp.render(totalView(views), now.Sub(start), cable,
|
||||
info, measuring := diag.snapshot()
|
||||
phy := phyDisplayFrom(info, measuring, modules[0].view(), modules[1].view())
|
||||
if err := disp.render(totalView(views), now.Sub(start), phy,
|
||||
noise.missing()); err != nil {
|
||||
return err
|
||||
}
|
||||
case now := <-tick.C:
|
||||
elapsed := now.Sub(start)
|
||||
// Length needs both directions, so every row is sampled before any of
|
||||
// them is printed.
|
||||
for i, d := range dirs {
|
||||
rows[i] = d.displayView()
|
||||
}
|
||||
length := "-"
|
||||
if m, ok := cableMetres(rows, nsPerM); ok {
|
||||
length = fmt.Sprintf("%.1f", m)
|
||||
}
|
||||
for _, line := range stats.emit(totalRow(elapsed, totalView(rows), target, length,
|
||||
info, measuring := diag.snapshot()
|
||||
phy := phyDisplayFrom(info, measuring, modules[0].view(), modules[1].view())
|
||||
for _, line := range stats.emit(totalRow(elapsed, totalView(rows), target, phy,
|
||||
noise.missing())) {
|
||||
fmt.Println(line)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user