X520 live as the product NIC: rate buckets re-keyed to read time (one shared clock, one commit per drained batch) with the backward smear as a settled window — each completed bucket enters once, donates excess to the nearest earlier deficits once, locks for display when nothing later can refill it (per-sample recompute double-counted excess and read >20G on a 20G wire); timestamp machinery deleted (ts.go, SO_TIMESTAMPING, rx_filter=ALL check) after the audit confirmed the buckets were its sole consumer; testDriver ixgbe; RollBall mailbox hardened against orphaned-command completions (never sample status before a poll gap, value from the same block read, devad/reg echo required — a killed session's 7.60 read answered a PHY ID as 0x0006); full hardware pass: 20.00G flat both directions, zero errors, ECD 42m, SNR +7.4dB
This commit is contained in:
@@ -47,14 +47,24 @@ type direction struct {
|
||||
drops uint64
|
||||
base counterSet
|
||||
|
||||
// The completed receive bucket the display draws its rate from, refreshed by
|
||||
// the sampler because the buckets are keyed by the mac's clock and staleness
|
||||
// has to be judged against the wall.
|
||||
// The smeared receive bucket the display draws its rate from, refreshed by
|
||||
// the sampler because the buckets are keyed by the shared read clock and
|
||||
// staleness has to be judged against the wall.
|
||||
rateFrames uint64
|
||||
rateBytes uint64
|
||||
epoch int64
|
||||
epochAt time.Time
|
||||
|
||||
// The settled window: each completed bucket enters once, donates its
|
||||
// excess backward once, and leaves for display once no later bucket can
|
||||
// still refill it. Recomputing the smear from the raw ring every sample
|
||||
// instead would show each excess twice — once as the donation and again,
|
||||
// unspent, when its bucket reaches the display slot of a later window.
|
||||
smFrames [smearWindow]uint64
|
||||
smBytes [smearWindow]uint64
|
||||
smLen int
|
||||
smNext int64
|
||||
|
||||
nic atomic.Uint64
|
||||
poller *nicPoller
|
||||
}
|
||||
@@ -136,11 +146,13 @@ func (w *rateWindow) at(i int) counterSet {
|
||||
// frontier means a stream has stopped delivering rather than an unchanged rate.
|
||||
const rateStale = 100 * time.Millisecond
|
||||
|
||||
// A stamp is only knowable once a worker drains the frame carrying it, so each
|
||||
// stream's newest epoch is a frontier: everything the wire delivered to that
|
||||
// queue before it has been counted. Reading one bucket across the board takes
|
||||
// the one behind the lowest frontier, which every stream has delivered past.
|
||||
// The leader's frontier would claim buckets the stragglers are still filling.
|
||||
// An epoch is only reachable once a worker drains frames into it, so each
|
||||
// stream's newest epoch is a frontier: everything that queue has been read
|
||||
// through. Epochs behind the lowest frontier — the leader's would claim
|
||||
// buckets the stragglers are still filling — are closed to further commits,
|
||||
// so each is settled into the window exactly once. The display takes the
|
||||
// window's oldest bucket, the one no later bucket can still refill, so the
|
||||
// headline runs one window behind real time.
|
||||
func (d *direction) readRateBucket(now time.Time) {
|
||||
newest := int64(math.MaxInt64)
|
||||
for _, r := range d.rxStats {
|
||||
@@ -151,15 +163,35 @@ func (d *direction) readRateBucket(now time.Time) {
|
||||
if newest > d.epoch {
|
||||
d.epoch, d.epochAt = newest, now
|
||||
}
|
||||
if d.smNext == 0 && d.epoch > 1 {
|
||||
d.smNext = d.epoch - 1
|
||||
}
|
||||
for e := d.smNext; e > 0 && e < d.epoch; e++ {
|
||||
d.settle(e)
|
||||
d.smNext = e + 1
|
||||
}
|
||||
d.rateFrames, d.rateBytes = 0, 0
|
||||
if d.epoch == 0 || now.Sub(d.epochAt) > rateStale {
|
||||
if d.smLen == 0 || now.Sub(d.epochAt) > rateStale {
|
||||
return
|
||||
}
|
||||
for _, r := range d.rxStats {
|
||||
f, b := r.bucket(d.epoch - 1)
|
||||
d.rateFrames += f
|
||||
d.rateBytes += b
|
||||
d.rateFrames, d.rateBytes = d.smFrames[0], d.smBytes[0]
|
||||
}
|
||||
|
||||
func (d *direction) settle(e int64) {
|
||||
if d.smLen == smearWindow {
|
||||
copy(d.smFrames[:], d.smFrames[1:])
|
||||
copy(d.smBytes[:], d.smBytes[1:])
|
||||
d.smLen--
|
||||
}
|
||||
var f, b uint64
|
||||
for _, r := range d.rxStats {
|
||||
rf, rb := r.bucket(e)
|
||||
f += rf
|
||||
b += rb
|
||||
}
|
||||
d.smFrames[d.smLen], d.smBytes[d.smLen] = f, b
|
||||
d.smLen++
|
||||
fillBack(d.smFrames[:d.smLen], d.smBytes[:d.smLen])
|
||||
}
|
||||
|
||||
type sample struct {
|
||||
@@ -424,11 +456,6 @@ 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; 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)
|
||||
}
|
||||
d.rxFDs = append(d.rxFDs, fd)
|
||||
d.rxStats = append(d.rxStats, &rxStats{})
|
||||
}
|
||||
@@ -493,7 +520,7 @@ const (
|
||||
numStreams = 7
|
||||
batchSize = 64
|
||||
|
||||
testDriver = "ice"
|
||||
testDriver = "ixgbe"
|
||||
|
||||
// A constant rather than the negotiated speed, since this has to come up
|
||||
// with no cable in the port and nothing to negotiate.
|
||||
@@ -508,7 +535,7 @@ func main() {
|
||||
// Left empty, the test pair is found by driver name instead: as PID 1 there
|
||||
// is no udev to pin names and no command line to pass, and which port gets
|
||||
// which ethN shifts with every driver built into the kernel.
|
||||
aName := flag.String("a", "", "first interface (default: the ice pair)")
|
||||
aName := flag.String("a", "", "first interface (default: the ixgbe pair)")
|
||||
bName := flag.String("b", "", "second interface")
|
||||
flag.Parse()
|
||||
|
||||
@@ -531,7 +558,7 @@ const (
|
||||
// the sampling clock would stretch the window it reports.
|
||||
sampleInterval = 16 * time.Millisecond
|
||||
// How far back the shown errors reach. The rate is not taken from this ring
|
||||
// but from the receive buckets, which are keyed by the mac's clock.
|
||||
// but from the smeared receive buckets, which are keyed by the read clock.
|
||||
rateWindowSpan = time.Second
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user