166 lines
4.2 KiB
Go
166 lines
4.2 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
uiBg = rgb{0x12, 0x14, 0x18}
|
|
uiPanel = rgb{0x1c, 0x20, 0x26}
|
|
uiFg = rgb{0xe6, 0xe8, 0xea}
|
|
uiDim = rgb{0x7a, 0x82, 0x8c}
|
|
uiCyan = rgb{0x5c, 0xc8, 0xe0}
|
|
uiGreen = rgb{0x4c, 0xc2, 0x6a}
|
|
uiRed = rgb{0xe0, 0x4b, 0x4b}
|
|
uiYellow = rgb{0xe0, 0xb0, 0x40}
|
|
)
|
|
|
|
type display struct {
|
|
fb *framebuffer
|
|
huge *textFace
|
|
big *textFace
|
|
small *textFace
|
|
config string
|
|
}
|
|
|
|
func newDisplay() (*display, error) {
|
|
fb, err := openFramebuffer("/dev/fb0")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
huge, err := loadFace(true, 46)
|
|
if err != nil {
|
|
fb.close()
|
|
return nil, err
|
|
}
|
|
big, err := loadFace(true, 24)
|
|
if err != nil {
|
|
fb.close()
|
|
return nil, err
|
|
}
|
|
small, err := loadFace(false, 18)
|
|
if err != nil {
|
|
fb.close()
|
|
return nil, err
|
|
}
|
|
return &display{fb: fb, huge: huge, big: big, small: small}, nil
|
|
}
|
|
|
|
func (d *display) close() {
|
|
d.fb.close()
|
|
}
|
|
|
|
func (d *display) right(f *textFace, xEnd, y int, s string, c rgb) {
|
|
f.draw(d.fb, xEnd-len([]rune(s))*f.cellW, y, s, c)
|
|
}
|
|
|
|
func (d *display) center(f *textFace, x0, w, y int, s string, c rgb) {
|
|
f.draw(d.fb, x0+(w-len([]rune(s))*f.cellW)/2, y, s, c)
|
|
}
|
|
|
|
func errColor(n uint64) rgb {
|
|
if n == 0 {
|
|
return uiGreen
|
|
}
|
|
return uiRed
|
|
}
|
|
|
|
func (d *display) rateColor(gb, target float64) rgb {
|
|
switch {
|
|
case gb >= target*rateGreenFrac:
|
|
return uiGreen
|
|
case gb >= target*rateYellowFrac:
|
|
return uiYellow
|
|
default:
|
|
return uiRed
|
|
}
|
|
}
|
|
|
|
func (d *display) render(dirs []*direction, views []view, elapsed time.Duration, target float64) {
|
|
fb := d.fb
|
|
fb.fill(uiBg)
|
|
|
|
d.small.draw(fb, 16, 12, "cabletest", uiCyan)
|
|
if len(dirs) > 0 {
|
|
d.small.draw(fb, 16+11*d.small.cellW, 12,
|
|
fmt.Sprintf("%s %s %s %s", dirs[0].tx.tag, dirs[0].tx.name,
|
|
dirs[0].rx.tag, dirs[0].rx.name), uiDim)
|
|
}
|
|
d.right(d.small, fb.w-16, 12, uptime(elapsed), uiDim)
|
|
|
|
var total uint64
|
|
for _, v := range views {
|
|
total += v.errors
|
|
}
|
|
|
|
// Status band, readable from across the room.
|
|
bandY, bandH := 44, 74
|
|
fb.rect(0, bandY, fb.w, bandH, uiPanel)
|
|
fb.rect(0, bandY, 6, bandH, errColor(total))
|
|
word, wc := "NO ERRORS", uiGreen
|
|
if total > 0 {
|
|
word, wc = fmt.Sprintf("%s ERRORS", commas(total)), uiRed
|
|
}
|
|
d.center(d.huge, 0, fb.w, bandY+(bandH-d.huge.cellH)/2+2, word, wc)
|
|
|
|
panelY := bandY + bandH + 12
|
|
footerH := d.small.cellH + 16
|
|
panelH := (fb.h - panelY - footerH) / max(len(dirs), 1)
|
|
for i, dir := range dirs {
|
|
d.renderDirection(dir, views[i], 12, panelY+i*panelH, fb.w-24, panelH-10, target)
|
|
}
|
|
d.footer(fb.h - footerH + 4)
|
|
fb.flush()
|
|
}
|
|
|
|
func (d *display) renderDirection(dir *direction, v view, x, y, w, h int, target float64) {
|
|
fb := d.fb
|
|
fb.rect(x, y, w, h, uiPanel)
|
|
|
|
// The font has no arrow glyph, so the direction marker is drawn.
|
|
d.big.draw(fb, x+14, y+12, dir.tx.tag, uiCyan)
|
|
d.arrow(x+14+2*d.big.cellW, y+12+d.big.cellH/2, 22, uiDim)
|
|
d.big.draw(fb, x+14+2*d.big.cellW+30, y+12, dir.rx.tag, uiCyan)
|
|
|
|
col := x + 110
|
|
d.rateBlock(col, y+10, "TX", v.txPPS, v.txGbps, target)
|
|
d.rateBlock(col+300, y+10, "RX", v.rxPPS, v.rxGbps, target)
|
|
|
|
ex := col + 620
|
|
d.errLine(ex, y+10, "lost", v.lost)
|
|
d.errLine(ex, y+10+d.small.cellH+2, "crc", v.crc)
|
|
d.errLine(ex, y+10+2*(d.small.cellH+2), "kdrop", v.kdrop)
|
|
d.errLine(ex, y+10+3*(d.small.cellH+2), "late", v.late)
|
|
|
|
totals := fmt.Sprintf("sent %s frames %s received %s frames %s",
|
|
commas(v.txFrames), humanBytes(v.txSent),
|
|
commas(v.rxFrames), humanBytes(v.rxGot))
|
|
d.small.draw(fb, x+14, y+h-d.small.cellH-8, totals, uiDim)
|
|
}
|
|
|
|
func (d *display) arrow(x, y, w int, c rgb) {
|
|
d.fb.rect(x, y-1, w-6, 3, c)
|
|
for i := 0; i < 7; i++ {
|
|
d.fb.rect(x+w-7+i, y-6+i, 1, 13-2*i, c)
|
|
}
|
|
}
|
|
|
|
func (d *display) rateBlock(x, y int, label string, pps, gb, target float64) {
|
|
d.small.draw(d.fb, x, y, label, uiDim)
|
|
d.big.draw(d.fb, x+3*d.small.cellW, y-2,
|
|
fmt.Sprintf("%6.2f Gb/s", gb), d.rateColor(gb, target))
|
|
d.small.draw(d.fb, x+3*d.small.cellW, y+d.big.cellH,
|
|
fmt.Sprintf("%s pps", commas(uint64(pps))), uiFg)
|
|
}
|
|
|
|
func (d *display) footer(y int) {
|
|
d.small.draw(d.fb, 16, y, d.config, uiDim)
|
|
d.right(d.small, d.fb.w-16, y, "space resets counts", uiDim)
|
|
}
|
|
|
|
func (d *display) errLine(x, y int, label string, n uint64) {
|
|
d.small.draw(d.fb, x, y, label, uiDim)
|
|
d.small.draw(d.fb, x+7*d.small.cellW, y, commas(n), errColor(n))
|
|
}
|