Ghost SNR check removed as asked: no bounds, no panic, no latch re-read — a raw 0x0000 in 1.133-136 decodes as no-margin-this-poll (the untrained resting value, which appears before 1.1 reports a drop, so no read ordering could prove a ghost); panel value columns bounded by scalePanelCount (4 chars forever) with RESET narrowed to 5 chars so counts can never reach the timeline; rows and heat cells stretch proportionally to fill the band between header and footer instead of leaving the bottom half dead
This commit is contained in:
+6
-22
@@ -32,11 +32,6 @@ const (
|
|||||||
|
|
||||||
rbPHYIDHi uint16 = 0x002B
|
rbPHYIDHi uint16 = 0x002B
|
||||||
rbPHYIDLo uint16 = 0x0BF4
|
rbPHYIDLo uint16 = 0x0BF4
|
||||||
|
|
||||||
// IEEE margins land near 7-9 dB on a healthy short cable; far outside is
|
|
||||||
// another register's data.
|
|
||||||
rbGhostLow = -10.0
|
|
||||||
rbGhostHigh = 25.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Only the registers proven safe on this PHY (docs/modules/wiitek/): single
|
// Only the registers proven safe on this PHY (docs/modules/wiitek/): single
|
||||||
@@ -170,11 +165,9 @@ func (r *rollball) identify() (ident string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// An untrained PHY honestly serves 0x0000 here, so an out-of-band value is
|
// 0x0000 is the untrained PHY's resting value in these offset-0x8000
|
||||||
// only a ghost if the link stayed up across the reads. The caller drained the
|
// registers, and the margins zero out before 1.1 reports a drop — so a zero
|
||||||
// PMA 1.1 latch when it checked the link, so one latched read now answers
|
// read is no margin this poll, never data.
|
||||||
// that: a drop since means the unplug race — discard the batch, ok false, and
|
|
||||||
// the next poll reads the link down.
|
|
||||||
func (r *rollball) snrMargins() (out [4]float64, ok bool, err error) {
|
func (r *rollball) snrMargins() (out [4]float64, ok bool, err error) {
|
||||||
r.exec(func() {
|
r.exec(func() {
|
||||||
for i := range out {
|
for i := range out {
|
||||||
@@ -182,19 +175,10 @@ func (r *rollball) snrMargins() (out [4]float64, ok bool, err error) {
|
|||||||
if v, err = r.mdioRead(1, uint16(133+i)); err != nil {
|
if v, err = r.mdioRead(1, uint16(133+i)); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
m := (float64(v) - 0x8000) / 10
|
if v == 0 {
|
||||||
if m < rbGhostLow || m > rbGhostHigh {
|
return
|
||||||
var link uint16
|
|
||||||
if link, err = r.mdioRead(1, 1); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if link&0x0004 == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
panic(fmt.Sprintf("%s: ghost SNR margin %.1f dB (1.%d=%#04x) with the link up",
|
|
||||||
r.ifname, m, 133+i, v))
|
|
||||||
}
|
}
|
||||||
out[i] = m
|
out[i] = (float64(v) - 0x8000) / 10
|
||||||
}
|
}
|
||||||
ok = true
|
ok = true
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,10 +4,31 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Panel counts fit four characters forever: whole numbers to 9999, then a
|
||||||
|
// magnitude letter with at most one decimal. The value columns are sized to
|
||||||
|
// this bound, so no count can ever grow into the timeline.
|
||||||
|
func scalePanelCount(v uint64) string {
|
||||||
|
if v < 10000 {
|
||||||
|
return strconv.FormatUint(v, 10)
|
||||||
|
}
|
||||||
|
f := float64(v)
|
||||||
|
for _, mag := range []string{"k", "M", "G", "T", "P"} {
|
||||||
|
f /= 1000
|
||||||
|
if f < 9.95 {
|
||||||
|
return fmt.Sprintf("%.1f%s", f, mag)
|
||||||
|
}
|
||||||
|
if f < 999.5 {
|
||||||
|
return fmt.Sprintf("%.0f%s", f, mag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%.0fE", f/1000)
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
uiBg = rgb{0x0d, 0x0d, 0x0d}
|
uiBg = rgb{0x0d, 0x0d, 0x0d}
|
||||||
uiInk = rgb{0xf2, 0xf2, 0xf0}
|
uiInk = rgb{0xf2, 0xf2, 0xf0}
|
||||||
@@ -41,10 +62,12 @@ const (
|
|||||||
ringD = 88
|
ringD = 88
|
||||||
ringEdge = 5
|
ringEdge = 5
|
||||||
|
|
||||||
cellH = 20
|
// Minimum row and cell heights; layout stretches both proportionally to
|
||||||
cellR = 4
|
// fill the height between the header and the footer.
|
||||||
|
cellHMin = 20
|
||||||
|
cellRMin = 4
|
||||||
cellGapX = 3
|
cellGapX = 3
|
||||||
rowH = 46
|
rowHMin = 46
|
||||||
|
|
||||||
btnW = 220
|
btnW = 220
|
||||||
btnH = 72
|
btnH = 72
|
||||||
@@ -79,6 +102,9 @@ type display struct {
|
|||||||
|
|
||||||
headerH int
|
headerH int
|
||||||
rowYs []int
|
rowYs []int
|
||||||
|
rowH int
|
||||||
|
cellH int
|
||||||
|
cellR int
|
||||||
axisY int
|
axisY int
|
||||||
labelX int
|
labelX int
|
||||||
nowX int
|
nowX int
|
||||||
@@ -137,7 +163,7 @@ func (d *display) layout(w, h int) error {
|
|||||||
}
|
}
|
||||||
d.labelX = uiMargin
|
d.labelX = uiMargin
|
||||||
d.nowW = 5 * d.textB.cellW
|
d.nowW = 5 * d.textB.cellW
|
||||||
d.resetW = 6 * d.textB.cellW
|
d.resetW = 5 * d.textB.cellW
|
||||||
d.nowX = d.labelX + labelW + uiColGap
|
d.nowX = d.labelX + labelW + uiColGap
|
||||||
d.resetX = w - uiMargin - d.resetW
|
d.resetX = w - uiMargin - d.resetW
|
||||||
|
|
||||||
@@ -153,19 +179,25 @@ func (d *display) layout(w, h int) error {
|
|||||||
|
|
||||||
d.headerH = headerTop + ringD + 24
|
d.headerH = headerTop + ringD + 24
|
||||||
colHdrH := d.small.lineH + 18
|
colHdrH := d.small.lineH + 18
|
||||||
|
d.resetBtn = rect{w - uiMargin - btnW, h - uiMargin - btnH, btnW, btnH}
|
||||||
|
|
||||||
|
// Rows stretch to spend the whole band between the column headers and the
|
||||||
|
// footer, cells riding along at their design proportion of the row.
|
||||||
|
rowsTop := d.headerH + colHdrH
|
||||||
|
d.rowH = (d.resetBtn.y - uiMargin - d.small.lineH - 8 - rowsTop) / len(panelRows)
|
||||||
|
if d.rowH < rowHMin {
|
||||||
|
return fmt.Errorf("rows would be %dpx tall, under the %dpx minimum", d.rowH, rowHMin)
|
||||||
|
}
|
||||||
|
d.cellH = d.rowH * cellHMin / rowHMin
|
||||||
|
d.cellR = max(cellRMin, d.cellH*cellRMin/cellHMin)
|
||||||
|
|
||||||
d.rowYs = make([]int, len(panelRows))
|
d.rowYs = make([]int, len(panelRows))
|
||||||
y := d.headerH + colHdrH
|
y := rowsTop
|
||||||
for i := range panelRows {
|
for i := range panelRows {
|
||||||
d.rowYs[i] = y
|
d.rowYs[i] = y
|
||||||
y += rowH
|
y += d.rowH
|
||||||
}
|
}
|
||||||
d.axisY = y + 8
|
d.axisY = y + 8
|
||||||
|
|
||||||
d.resetBtn = rect{w - uiMargin - btnW, h - uiMargin - btnH, btnW, btnH}
|
|
||||||
if d.axisY+d.small.lineH > d.resetBtn.y {
|
|
||||||
return fmt.Errorf("panel content is %dpx taller than the screen leaves",
|
|
||||||
d.axisY+d.small.lineH-d.resetBtn.y)
|
|
||||||
}
|
|
||||||
d.versionSpot = rect{w - versionSpotSide, 0, versionSpotSide, versionSpotSide}
|
d.versionSpot = rect{w - versionSpotSide, 0, versionSpotSide, versionSpotSide}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -298,7 +330,7 @@ func (d *display) hairline(y int) {
|
|||||||
// A mark or short value in the NOW/RESET columns, right-aligned to the
|
// A mark or short value in the NOW/RESET columns, right-aligned to the
|
||||||
// column's edge and vertically centred in the row.
|
// column's edge and vertically centred in the row.
|
||||||
func (d *display) colMark(right, rowY int, kind int, c rgb) {
|
func (d *display) colMark(right, rowY int, kind int, c rgb) {
|
||||||
cy := rowY + rowH/2
|
cy := rowY + d.rowH/2
|
||||||
switch kind {
|
switch kind {
|
||||||
case markCheck:
|
case markCheck:
|
||||||
d.check(right-11, cy, 20, c)
|
d.check(right-11, cy, 20, c)
|
||||||
@@ -313,12 +345,12 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (d *display) colText(f *textFace, right, rowY int, s string, c rgb) {
|
func (d *display) colText(f *textFace, right, rowY int, s string, c rgb) {
|
||||||
d.textRightAt(f, right, rowY+(rowH-f.lineH)/2, s, c)
|
d.textRightAt(f, right, rowY+(d.rowH-f.lineH)/2, s, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *display) heatCell(rowY, k int, c rgb) {
|
func (d *display) heatCell(rowY, k int, c rgb) {
|
||||||
x := d.cellsX + k*(d.cellW+cellGapX)
|
x := d.cellsX + k*(d.cellW+cellGapX)
|
||||||
d.fb.roundRect(x, rowY+(rowH-cellH)/2, d.cellW, cellH, cellR, c)
|
d.fb.roundRect(x, rowY+(d.rowH-d.cellH)/2, d.cellW, d.cellH, d.cellR, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The verdict names the newest thing wrong at any horizon: the cable's own
|
// The verdict names the newest thing wrong at any horizon: the cable's own
|
||||||
@@ -331,7 +363,7 @@ func (d *display) drawHeader(v view, phy phyDisplay, hv histView, measuring bool
|
|||||||
word, sub := "GOOD", []seg{{d.text, "no faults", uiInk2}}
|
word, sub := "GOOD", []seg{{d.text, "no faults", uiInk2}}
|
||||||
if i, age, ok := hv.newestFault(now); ok {
|
if i, age, ok := hv.newestFault(now); ok {
|
||||||
good = false
|
good = false
|
||||||
word = scaleCount(faultClasses[i].get(v.since)) + " " +
|
word = scalePanelCount(faultClasses[i].get(v.since)) + " " +
|
||||||
strings.ToUpper(faultClasses[i].label)
|
strings.ToUpper(faultClasses[i].label)
|
||||||
sub = []seg{
|
sub = []seg{
|
||||||
{d.text, "last " + faultClasses[i].noun + " ", uiInk2},
|
{d.text, "last " + faultClasses[i].noun + " ", uiInk2},
|
||||||
@@ -421,8 +453,8 @@ func (d *display) drawRows(v view, phy phyDisplay, measuring bool,
|
|||||||
resetR := d.resetX + d.resetW
|
resetR := d.resetX + d.resetW
|
||||||
for i, label := range panelRows {
|
for i, label := range panelRows {
|
||||||
y := d.rowYs[i]
|
y := d.rowYs[i]
|
||||||
d.text.draw(d.fb, d.labelX, y+(rowH-d.text.lineH)/2-d.text.capTop, label, uiInk2)
|
d.text.draw(d.fb, d.labelX, y+(d.rowH-d.text.lineH)/2-d.text.capTop, label, uiInk2)
|
||||||
d.hairline(y + rowH - 1)
|
d.hairline(y + d.rowH - 1)
|
||||||
|
|
||||||
switch label {
|
switch label {
|
||||||
case "line rate":
|
case "line rate":
|
||||||
@@ -441,13 +473,13 @@ func (d *display) drawRows(v view, phy phyDisplay, measuring bool,
|
|||||||
}
|
}
|
||||||
case "corrected":
|
case "corrected":
|
||||||
if phy.recent > 0 {
|
if phy.recent > 0 {
|
||||||
d.colText(d.textB, nowR, y, scaleCount(phy.recent), uiWarn)
|
d.colText(d.textB, nowR, y, scalePanelCount(phy.recent), uiWarn)
|
||||||
} else {
|
} else {
|
||||||
d.colMark(nowR, y, markCheck, uiGood)
|
d.colMark(nowR, y, markCheck, uiGood)
|
||||||
}
|
}
|
||||||
d.heatRow(y, hv, func(s histSlot) rgb { return okOr(s.corrected > 0, uiWarn) })
|
d.heatRow(y, hv, func(s histSlot) rgb { return okOr(s.corrected > 0, uiWarn) })
|
||||||
if phy.corrected > 0 {
|
if phy.corrected > 0 {
|
||||||
d.colText(d.textB, resetR, y, scaleCount(phy.corrected), uiWarn)
|
d.colText(d.textB, resetR, y, scalePanelCount(phy.corrected), uiWarn)
|
||||||
} else {
|
} else {
|
||||||
d.colText(d.text, resetR, y, "0", uiMuted)
|
d.colText(d.text, resetR, y, "0", uiMuted)
|
||||||
}
|
}
|
||||||
@@ -482,13 +514,13 @@ func (d *display) drawRows(v view, phy phyDisplay, measuring bool,
|
|||||||
panic("panel row " + label + " has no fault class")
|
panic("panel row " + label + " has no fault class")
|
||||||
}
|
}
|
||||||
if n := faultClasses[f].get(v.window); n > 0 {
|
if n := faultClasses[f].get(v.window); n > 0 {
|
||||||
d.colText(d.textB, nowR, y, scaleCount(n), uiCrit)
|
d.colText(d.textB, nowR, y, scalePanelCount(n), uiCrit)
|
||||||
} else {
|
} else {
|
||||||
d.colMark(nowR, y, markCheck, uiGood)
|
d.colMark(nowR, y, markCheck, uiGood)
|
||||||
}
|
}
|
||||||
d.heatRow(y, hv, func(s histSlot) rgb { return okOr(s.faults[f] > 0, uiCrit) })
|
d.heatRow(y, hv, func(s histSlot) rgb { return okOr(s.faults[f] > 0, uiCrit) })
|
||||||
if n := faultClasses[f].get(v.since); n > 0 {
|
if n := faultClasses[f].get(v.since); n > 0 {
|
||||||
d.colText(d.textB, resetR, y, scaleCount(n), uiCrit)
|
d.colText(d.textB, resetR, y, scalePanelCount(n), uiCrit)
|
||||||
} else {
|
} else {
|
||||||
d.colText(d.text, resetR, y, "0", uiMuted)
|
d.colText(d.text, resetR, y, "0", uiMuted)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -36,7 +36,7 @@ func TestPanelFitsScreen(t *testing.T) {
|
|||||||
if end := d.cellsX + d.cellsW; end > d.resetX {
|
if end := d.cellsX + d.cellsW; end > d.resetX {
|
||||||
t.Errorf("heat cells run to %d, past the reset column at %d", end, d.resetX)
|
t.Errorf("heat cells run to %d, past the reset column at %d", end, d.resetX)
|
||||||
}
|
}
|
||||||
if last := d.rowYs[len(d.rowYs)-1] + rowH; last > d.resetBtn.y {
|
if last := d.rowYs[len(d.rowYs)-1] + d.rowH; last > d.resetBtn.y {
|
||||||
t.Errorf("rows end at %d, past the footer at %d", last, d.resetBtn.y)
|
t.Errorf("rows end at %d, past the footer at %d", last, d.resetBtn.y)
|
||||||
}
|
}
|
||||||
t.Logf("cells %dpx wide, rows end at %d, footer at %d",
|
t.Logf("cells %dpx wide, rows end at %d, footer at %d",
|
||||||
|
|||||||
Reference in New Issue
Block a user