Judge loss by arrival order and a deadline, one tx ring per stream

This commit is contained in:
flamingcow
2026-08-31 14:33:09 -07:00
parent 06fa759b1a
commit 7fa48da73c
12 changed files with 220 additions and 254 deletions
+43 -3
View File
@@ -722,6 +722,45 @@ func checkRings(fd int, ifname string, rxWant, txWant uint32) checkResult {
return res
}
// With PACKET_QDISC_BYPASS the hardware ring comes from XPS, which maps the
// sending cpu to a ring, so an unpinned sender migrating across cpus spreads
// one stream over rings that drain concurrently and reorder it on the wire.
// Each stream's pinned feeder cpu maps to the stream's own ring instead. The
// rings sit past the flow-rule queues because ixgbe pairs rx and tx of one
// index on one interrupt vector, and tx cleanup does not belong on the
// vectors delivering the streams.
func checkTxQueues(ifname string, iface int) checkResult {
res := checkResult{item: ifname + " tx queues"}
queues, err := filepath.Glob("/sys/class/net/" + ifname + "/queues/tx-*/xps_cpus")
if err != nil || len(queues) == 0 {
res.err = fmt.Errorf("no tx queues found: %v", err)
return res
}
masks := make(map[string]string, numStreams)
for s := 0; s < numStreams; s++ {
masks[fmt.Sprintf("tx-%d", numStreams+s)] = fmt.Sprintf("%x", uint64(1)<<txCPU(iface, s))
}
pinned := 0
for _, q := range queues {
want := "0"
if m, ok := masks[filepath.Base(filepath.Dir(q))]; ok {
want = m
pinned++
}
if err := os.WriteFile(q, []byte(want), 0); err != nil {
res.err = fmt.Errorf("%s: %w", q, err)
return res
}
}
if pinned != numStreams {
res.err = fmt.Errorf("pinned %d of %d streams across %d tx queues", pinned, numStreams, len(queues))
return res
}
res.state = fmt.Sprintf("streams on tx-%d..%d, feeder cpus %d/%d",
numStreams, 2*numStreams-1, txCPU(iface, 0), txCPU(iface, numStreams-1))
return res
}
func withIoctlSocket(fn func(fd int) []checkResult) []checkResult {
fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
if err != nil {
@@ -737,16 +776,17 @@ func configureSystem(ifnames []string, ethertypes []uint16) []checkResult {
return withIoctlSocket(func(fd int) []checkResult {
out := []checkResult{checkGovernor(wantGovernor), checkMaxFreq(wantMaxFreqKHz),
checkIdleLatency(wantIdleLatency)}
for _, ifname := range ifnames {
for i, ifname := range ifnames {
out = append(out, checkLinkUp(fd, ifname))
out = append(out, checkMTU(fd, ifname, wantMTU))
out = append(out, clearFlowRules(fd, ifname))
// Ring changes reprogram the queues, so flow rules pointing at those
// queues have to be installed afterwards.
// Ring changes reprogram the queues, so flow rules and the xps pin,
// which point at those queues, have to be installed afterwards.
out = append(out, checkRings(fd, ifname, wantRxRing, wantTxRing))
out = append(out, checkCoalesce(fd, ifname, wantCoalesceUsecs))
out = append(out, checkFlowRules(fd, ifname, ethertypes))
out = append(out, checkTxQueues(ifname, i))
}
return out
})