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:
+135
-37
@@ -5,15 +5,13 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// A frame lands in the bucket its receive stamp falls in, whenever the worker
|
||||
// got round to draining it.
|
||||
func TestRxObserveBucketsByStamp(t *testing.T) {
|
||||
// Batches land in the bucket of the epoch they were read in, summed, and
|
||||
// newest follows the highest epoch committed.
|
||||
func TestRxCommitBucketsByEpoch(t *testing.T) {
|
||||
var s rxStats
|
||||
run := rateRun{stats: &s}
|
||||
run.add(3*int64(time.Millisecond), 100)
|
||||
run.add(5*int64(time.Millisecond), 200)
|
||||
run.add(rateBucketNs+int64(time.Millisecond), 300)
|
||||
run.flush()
|
||||
s.commit(0, 1, 100)
|
||||
s.commit(0, 1, 200)
|
||||
s.commit(1, 1, 300)
|
||||
|
||||
if f, b := s.bucket(0); f != 2 || b != 300 {
|
||||
t.Errorf("epoch 0 = %d frames, %d bytes; want 2, 300", f, b)
|
||||
@@ -30,10 +28,8 @@ func TestRxObserveBucketsByStamp(t *testing.T) {
|
||||
// reports nothing rather than the stale counts.
|
||||
func TestRxBucketWraps(t *testing.T) {
|
||||
var s rxStats
|
||||
run := rateRun{stats: &s}
|
||||
run.add(1, 100)
|
||||
run.add(rateBuckets*rateBucketNs+1, 200)
|
||||
run.flush()
|
||||
s.commit(0, 1, 100)
|
||||
s.commit(rateBuckets, 1, 200)
|
||||
|
||||
if f, b := s.bucket(0); f != 0 || b != 0 {
|
||||
t.Errorf("evicted epoch = %d frames, %d bytes; want 0, 0", f, b)
|
||||
@@ -44,18 +40,14 @@ func TestRxBucketWraps(t *testing.T) {
|
||||
}
|
||||
|
||||
// Every stream's frontier is past the epoch being read, so the bucket is
|
||||
// complete across the board and the rate comes from the one before the lowest
|
||||
// frontier.
|
||||
// complete across the board and the rate comes from the window behind the
|
||||
// lowest frontier.
|
||||
func TestDirectionRateReadsOneBucketBack(t *testing.T) {
|
||||
d := &direction{rxStats: []*rxStats{{}, {}}}
|
||||
r0 := rateRun{stats: d.rxStats[0]}
|
||||
r1 := rateRun{stats: d.rxStats[1]}
|
||||
r0.add(rateBucketNs+1, 500)
|
||||
r1.add(rateBucketNs+2, 700)
|
||||
r0.add(2*rateBucketNs+1, 900)
|
||||
r1.add(2*rateBucketNs+2, 1100)
|
||||
r0.flush()
|
||||
r1.flush()
|
||||
d.rxStats[0].commit(1, 1, 500)
|
||||
d.rxStats[1].commit(1, 1, 700)
|
||||
d.rxStats[0].commit(2, 1, 900)
|
||||
d.rxStats[1].commit(2, 1, 1100)
|
||||
|
||||
d.readRateBucket(time.Now())
|
||||
if d.rateFrames != 2 || d.rateBytes != 1200 {
|
||||
@@ -69,11 +61,8 @@ func TestDirectionRateReadsOneBucketBack(t *testing.T) {
|
||||
// half-filled bucket summed.
|
||||
func TestDirectionRateWaitsForSlowestStream(t *testing.T) {
|
||||
d := &direction{rxStats: []*rxStats{{}, {}}}
|
||||
r0 := rateRun{stats: d.rxStats[0]}
|
||||
r1 := rateRun{stats: d.rxStats[1]}
|
||||
r0.add(rateBucketNs+1, 500)
|
||||
r0.add(2*rateBucketNs+1, 900)
|
||||
r0.flush()
|
||||
d.rxStats[0].commit(1, 1, 500)
|
||||
d.rxStats[0].commit(2, 1, 900)
|
||||
|
||||
// One stream has never delivered at all, so there is no epoch every stream
|
||||
// has reached and nothing to read.
|
||||
@@ -85,8 +74,7 @@ func TestDirectionRateWaitsForSlowestStream(t *testing.T) {
|
||||
|
||||
// The straggler is still filling the epoch the leader finished, so its
|
||||
// bucket must not be read yet.
|
||||
r1.add(rateBucketNs+2, 700)
|
||||
r1.flush()
|
||||
d.rxStats[1].commit(1, 1, 700)
|
||||
d.readRateBucket(time.Now())
|
||||
if d.rateFrames != 0 || d.rateBytes != 0 {
|
||||
t.Errorf("rate = %d frames, %d bytes; want nothing while a stream is still filling the epoch",
|
||||
@@ -95,8 +83,7 @@ func TestDirectionRateWaitsForSlowestStream(t *testing.T) {
|
||||
|
||||
// Once it moves past, the bucket is complete for both streams and is read
|
||||
// whole.
|
||||
r1.add(2*rateBucketNs+2, 1100)
|
||||
r1.flush()
|
||||
d.rxStats[1].commit(2, 1, 1100)
|
||||
d.readRateBucket(time.Now())
|
||||
if d.rateFrames != 2 || d.rateBytes != 1200 {
|
||||
t.Errorf("rate = %d frames, %d bytes; want the whole completed epoch, 2 and 1200",
|
||||
@@ -104,14 +91,12 @@ func TestDirectionRateWaitsForSlowestStream(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// A stamp only advances when a frame arrives, so an epoch that stops moving is
|
||||
// a quiet wire and must not keep reporting the last bucket.
|
||||
// An epoch only advances when a frame is read, so a frontier that stops moving
|
||||
// is a quiet wire and must not keep reporting the last bucket.
|
||||
func TestDirectionRateGoesStale(t *testing.T) {
|
||||
d := &direction{rxStats: []*rxStats{{}}}
|
||||
run := rateRun{stats: d.rxStats[0]}
|
||||
run.add(rateBucketNs+1, 500)
|
||||
run.add(2*rateBucketNs+1, 900)
|
||||
run.flush()
|
||||
d.rxStats[0].commit(1, 1, 500)
|
||||
d.rxStats[0].commit(2, 1, 900)
|
||||
|
||||
now := time.Now()
|
||||
d.readRateBucket(now)
|
||||
@@ -124,6 +109,119 @@ func TestDirectionRateGoesStale(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Each bucket entering the settled window donates once, as it arrives.
|
||||
func settleAll(frames, bytes []uint64) {
|
||||
for i := 1; i <= len(frames); i++ {
|
||||
fillBack(frames[:i], bytes[:i])
|
||||
}
|
||||
}
|
||||
|
||||
// A donated byte must never display twice: once a burst's excess has refilled
|
||||
// an earlier deficit, the burst bucket itself reaches the display slot
|
||||
// trimmed. The pre-settled-window implementation recomputed the smear from
|
||||
// the raw ring per sample and displayed the excess again — 2C here, and >20G
|
||||
// on the wire.
|
||||
func TestDirectionRateNeverRedisplaysDonatedExcess(t *testing.T) {
|
||||
d := &direction{rxStats: []*rxStats{{}}}
|
||||
c := bucketWireCap
|
||||
seq := []uint64{c, 0, 2 * c, c, c, c, c, c}
|
||||
now := time.Now()
|
||||
for i, b := range seq {
|
||||
d.rxStats[0].commit(int64(i+1), 0, b)
|
||||
d.readRateBucket(now)
|
||||
if d.rateBytes > c {
|
||||
t.Errorf("after epoch %d: displayed %d bytes, over line rate %d", i+1, d.rateBytes, c)
|
||||
}
|
||||
}
|
||||
// Jitter settled: the display holds flat line rate, not just under it.
|
||||
if d.rateBytes != c {
|
||||
t.Errorf("settled display = %d bytes, want exactly %d", d.rateBytes, c)
|
||||
}
|
||||
}
|
||||
|
||||
// A stall's deficit is refilled exactly by the burst that drains its backlog,
|
||||
// so host jitter flattens to line rate.
|
||||
func TestSmearRepairsStallBurst(t *testing.T) {
|
||||
c := bucketWireCap
|
||||
bytes := []uint64{c, 0, 0, 3 * c}
|
||||
frames := make([]uint64, 4)
|
||||
settleAll(frames, bytes)
|
||||
for i, want := range []uint64{c, c, c, c} {
|
||||
if bytes[i] != want {
|
||||
t.Errorf("bucket %d = %d bytes, want %d", i, bytes[i], want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Excess refills the nearest earlier deficit, so a genuine wire dip — a
|
||||
// deficit with no matching excess — keeps its full size in its own bucket.
|
||||
func TestSmearLeavesRealLossInPlace(t *testing.T) {
|
||||
c := bucketWireCap
|
||||
bytes := []uint64{0, c, 0, 2 * c}
|
||||
frames := make([]uint64, 4)
|
||||
settleAll(frames, bytes)
|
||||
for i, want := range []uint64{0, c, c, c} {
|
||||
if bytes[i] != want {
|
||||
t.Errorf("bucket %d = %d bytes, want %d", i, bytes[i], want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A deficit after the excess is the host not having read those frames yet, not
|
||||
// jitter to repair: nothing ever moves forward.
|
||||
func TestSmearNeverMovesForward(t *testing.T) {
|
||||
c := bucketWireCap
|
||||
bytes := []uint64{c, 2 * c, 0}
|
||||
frames := make([]uint64, 3)
|
||||
settleAll(frames, bytes)
|
||||
for i, want := range []uint64{c, 2 * c, 0} {
|
||||
if bytes[i] != want {
|
||||
t.Errorf("bucket %d = %d bytes, want %d", i, bytes[i], want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Truly below line rate has nothing to move and displays as-is.
|
||||
func TestSmearLeavesBelowRateAlone(t *testing.T) {
|
||||
c := bucketWireCap
|
||||
bytes := []uint64{c / 2, c / 4, c / 2}
|
||||
frames := []uint64{100, 50, 100}
|
||||
want := []uint64{c / 2, c / 4, c / 2}
|
||||
settleAll(frames, bytes)
|
||||
for i := range bytes {
|
||||
if bytes[i] != want[i] {
|
||||
t.Errorf("bucket %d = %d bytes, want untouched %d", i, bytes[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Frames ride along with the bytes in the donor's own proportion, and nothing
|
||||
// is invented or lost in the move.
|
||||
func TestSmearConservesFramesAndBytes(t *testing.T) {
|
||||
c := bucketWireCap
|
||||
donorFrames := uint64(1000)
|
||||
donorBytes := 2*c - donorFrames*wireOverhead
|
||||
frames := []uint64{0, donorFrames}
|
||||
bytes := []uint64{0, donorBytes}
|
||||
settleAll(frames, bytes)
|
||||
|
||||
if tf := frames[0] + frames[1]; tf != donorFrames {
|
||||
t.Errorf("total frames = %d, want %d conserved", tf, donorFrames)
|
||||
}
|
||||
if tb := bytes[0] + bytes[1]; tb != donorBytes {
|
||||
t.Errorf("total bytes = %d, want %d conserved", tb, donorBytes)
|
||||
}
|
||||
if got := bytes[0] + frames[0]*wireOverhead; got != c {
|
||||
t.Errorf("refilled bucket = %d wire bytes, want exactly %d", got, c)
|
||||
}
|
||||
if frames[0] != donorFrames/2 {
|
||||
t.Errorf("moved frames = %d, want the donor's proportion %d", frames[0], donorFrames/2)
|
||||
}
|
||||
if got := bytes[1] + frames[1]*wireOverhead; got != c {
|
||||
t.Errorf("donor left = %d wire bytes, want trimmed to %d", got, c)
|
||||
}
|
||||
}
|
||||
|
||||
// Indexed oldest first, so the error window still spans the whole ring once it
|
||||
// has wrapped.
|
||||
func TestRateWindowIndexesOldestFirst(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user