Show median bucket rates, slide the error window, and poll sysfs off the draw loop
This commit is contained in:
+53
@@ -4,6 +4,8 @@ import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Split by direction of travel so each counter belongs to exactly one
|
||||
@@ -58,3 +60,54 @@ func readNIC(ifname string) nicCounters {
|
||||
c.carrierDown, _ = readUint(base + "/carrier_down_count")
|
||||
return c
|
||||
}
|
||||
|
||||
// A poll is thirty-odd sysfs attributes and each one costs microseconds of open
|
||||
// and read, so it runs here rather than on the goroutine that has a frame to
|
||||
// draw every sixteen milliseconds. Off that path the interval can be short
|
||||
// enough that an unplugged cable shows up as a link error while the hand is
|
||||
// still on it.
|
||||
const nicInterval = 200 * time.Millisecond
|
||||
|
||||
type nicPoller struct {
|
||||
txName, rxName string
|
||||
total *atomic.Uint64
|
||||
raw uint64
|
||||
}
|
||||
|
||||
func (p *nicPoller) read() uint64 {
|
||||
tx := readNIC(p.txName)
|
||||
rx := readNIC(p.rxName)
|
||||
return tx.tx + rx.rx + tx.carrierDown
|
||||
}
|
||||
|
||||
// Sysfs nic counters run from boot and restart from zero whenever the driver
|
||||
// resets its statistics, so only their forward motion is accumulated. Taking
|
||||
// raw differences instead charges a boot's worth of errors to the first sample
|
||||
// and turns a reset into a near-2^64 underflow.
|
||||
func (p *nicPoller) poll() {
|
||||
raw := p.read()
|
||||
if raw > p.raw {
|
||||
p.total.Add(raw - p.raw)
|
||||
}
|
||||
p.raw = raw
|
||||
}
|
||||
|
||||
// Whatever the interfaces counted before now is not ours, so the first poll has
|
||||
// something to measure forward motion from and adds nothing.
|
||||
func (p *nicPoller) prime() {
|
||||
p.raw = p.read()
|
||||
}
|
||||
|
||||
// Held until the test starts, since polling before the baseline is primed would
|
||||
// charge everything the interfaces counted since boot to the first sample.
|
||||
func (p *nicPoller) run(done *atomic.Bool, startTx <-chan struct{}) {
|
||||
<-startTx
|
||||
|
||||
tick := time.NewTicker(nicInterval)
|
||||
defer tick.Stop()
|
||||
|
||||
for !done.Load() {
|
||||
<-tick.C
|
||||
p.poll()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user