Restructure stats into delta, mask, and aggregate layers
This commit is contained in:
+25
-54
@@ -5,8 +5,6 @@ import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Split by direction of travel so each counter belongs to exactly one
|
||||
@@ -79,72 +77,45 @@ func sumFields(base string, fields []string) uint64 {
|
||||
return total
|
||||
}
|
||||
|
||||
// A poll is an ioctl and a handful of sysfs attributes, each costing
|
||||
// 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 it can
|
||||
// match the sampler's cadence, so a link error lands on the panel as quickly
|
||||
// as a lost frame rather than stepping five times a second.
|
||||
const nicInterval = sampleInterval
|
||||
|
||||
type nicPoller struct {
|
||||
txBase string
|
||||
rx *statReader
|
||||
total *atomic.Uint64
|
||||
measuring *atomic.Bool
|
||||
raw uint64
|
||||
// A read is an ioctl and a handful of sysfs attributes, each costing
|
||||
// microseconds of open and read, so it rides the sampler's goroutine and
|
||||
// clock rather than the one that has a frame to draw every sixteen
|
||||
// milliseconds; a link error lands on the panel as quickly as a lost frame.
|
||||
type nicCounters struct {
|
||||
txBase string
|
||||
rx *statReader
|
||||
raw uint64
|
||||
}
|
||||
|
||||
func newNICPoller(fd int, txName, rxName string, total *atomic.Uint64, measuring *atomic.Bool) (*nicPoller, error) {
|
||||
func newNICCounters(fd int, txName, rxName string) (*nicCounters, error) {
|
||||
rx, err := newStatReader(fd, rxName, nicRxFields)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &nicPoller{
|
||||
txBase: "/sys/class/net/" + txName,
|
||||
rx: rx,
|
||||
total: total,
|
||||
measuring: measuring,
|
||||
return &nicCounters{
|
||||
txBase: "/sys/class/net/" + txName,
|
||||
rx: rx,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Carrier is a property of the netdev rather than a counter of either side, and
|
||||
// is taken from the transmitting interface only: across both directions each
|
||||
// interface transmits exactly once, so every flap is counted once.
|
||||
func (p *nicPoller) read() uint64 {
|
||||
return sumFields(p.txBase, nicTxFields) + p.rx.sum() +
|
||||
mustReadUint(p.txBase+"/carrier_down_count")
|
||||
func (c *nicCounters) read() uint64 {
|
||||
return sumFields(c.txBase, nicTxFields) + c.rx.sum() +
|
||||
mustReadUint(c.txBase+"/carrier_down_count")
|
||||
}
|
||||
|
||||
// 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. The hardware cannot pause its
|
||||
// counters for a cable measure, so its blip is suppressed here at the read:
|
||||
// the baseline tracks the raw values without accumulating until it is over.
|
||||
func (p *nicPoller) poll() {
|
||||
raw := p.read()
|
||||
if raw > p.raw && !p.measuring.Load() {
|
||||
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()
|
||||
// its statistics, so only forward motion is the delta: a raw difference would
|
||||
// charge a boot's worth of errors to the first collection and turn a reset
|
||||
// into a near-2^64 underflow.
|
||||
func (c *nicCounters) delta() uint64 {
|
||||
raw := c.read()
|
||||
var d uint64
|
||||
if raw > c.raw {
|
||||
d = raw - c.raw
|
||||
}
|
||||
c.raw = raw
|
||||
return d
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user