Give each stream its own ethertype steered to its own rx queue

This commit is contained in:
flamingcow
2026-07-25 19:08:11 -07:00
parent 0af94219a7
commit 7e77d22f36
4 changed files with 232 additions and 96 deletions
+36 -70
View File
@@ -16,13 +16,6 @@ import (
"golang.org/x/sys/unix"
)
const (
fanoutHash = 0
fanoutLB = 1
fanoutCPU = 2
fanoutRollover = 3
)
const wireOverhead = 24
type endpoint struct {
@@ -44,7 +37,7 @@ type direction struct {
short string
tx endpoint
rx endpoint
spec *frameSpec
specs []*frameSpec
txStats []*txStats
rxStats []*rxStats
streams []lossWindow
@@ -128,25 +121,6 @@ func cpuAt(cpus []int, i int) int {
return cpus[i%len(cpus)]
}
func fanoutMode(name string, nsock int) (int, error) {
if nsock < 2 {
return -1, nil
}
switch name {
case "none":
return -1, nil
case "hash":
return fanoutHash, nil
case "lb":
return fanoutLB, nil
case "cpu":
return fanoutCPU, nil
case "rollover":
return fanoutRollover, nil
}
return 0, fmt.Errorf("unknown fanout mode %q", name)
}
func (d *direction) snapshot() sample {
var s sample
for _, t := range d.txStats {
@@ -244,30 +218,26 @@ func buildDirection(label string, tx, rx endpoint, patIdx int, sizes []int, cfg
short: tx.tag + "→" + rx.tag,
tx: tx,
rx: rx,
spec: newFrameSpec(patIdx, rx.mac, tx.mac, sizes),
streams: newLossWindows(cfg.txWorkers),
streams: newLossWindows(cfg.streams),
reports: make(chan string, 64),
}
d.nicTX = readNIC(tx.name)
d.nicRX = readNIC(rx.name)
fm, err := fanoutMode(cfg.fanout, cfg.rxWorkers)
if err != nil {
return nil, err
}
for i := 0; i < cfg.streams; i++ {
et := uint16(etherBase + i)
d.specs = append(d.specs, newFrameSpec(patIdx, rx.mac, tx.mac, et, sizes))
for i := 0; i < cfg.txWorkers; i++ {
fd, err := openTxSocket(tx.idx)
if err != nil {
return nil, fmt.Errorf("%s tx socket: %w", label, err)
}
d.txFDs = append(d.txFDs, fd)
d.txStats = append(d.txStats, &txStats{})
}
for i := 0; i < cfg.rxWorkers; i++ {
fd, err := openRxSocket(rx.idx, cfg.fanoutID, fm)
fd, err = openRxSocket(rx.idx, et)
if err != nil {
return nil, fmt.Errorf("%s rx socket: %w", label, err)
return nil, fmt.Errorf("%s rx socket for 0x%04x: %w", label, et, err)
}
d.rxFDs = append(d.rxFDs, fd)
d.rxStats = append(d.rxStats, &rxStats{})
@@ -280,7 +250,7 @@ func (d *direction) start(wg *sync.WaitGroup, doneTx, doneRx *atomic.Bool, cfg c
w := &txWorker{
fd: fd,
stream: uint16(i),
spec: d.spec,
spec: d.specs[i],
batch: cfg.batch,
cpu: cpuAt(cfg.txCPUs, i),
stats: d.txStats[i],
@@ -297,7 +267,7 @@ func (d *direction) start(wg *sync.WaitGroup, doneTx, doneRx *atomic.Bool, cfg c
fd: fd,
batch: cfg.batch,
cpu: cpuAt(cfg.rxCPUs, i),
spec: d.spec,
spec: d.specs[i],
stats: d.rxStats[i],
streams: d.streams,
reports: d.reports,
@@ -321,13 +291,10 @@ func (d *direction) close() {
}
type config struct {
txWorkers int
rxWorkers int
batch int
fanout string
fanoutID int
txCPUs []int
rxCPUs []int
streams int
batch int
txCPUs []int
rxCPUs []int
}
func main() {
@@ -336,18 +303,16 @@ func main() {
bName = flag.String("b", "", "second interface")
sizesArg = flag.String("sizes", "1024,1280,1514", "frame sizes in bytes, excluding FCS, cycled per packet; below ~900 the host cannot keep up and loss stops meaning anything")
patArg = flag.String("pattern", "prbs", "payload pattern")
txN = flag.Int("tx", 1, "tx workers per direction; one saturates 10G at these sizes")
rxN = flag.Int("rx", 2, "rx workers per direction; more is slower, RSS cannot spread a raw ethertype across queues")
streams = flag.Int("streams", 4, "independent streams per direction; each gets its own ethertype, steered by a flow rule to its own rx queue")
batch = flag.Int("batch", 64, "frames per sendmmsg/recvmmsg call")
fanout = flag.String("fanout", "lb", "rx fanout mode: none, hash, lb, cpu, rollover")
duplex = flag.Bool("duplex", true, "run both directions simultaneously")
txCPUs = flag.String("txcpus", "", "comma-separated CPUs to pin tx workers to")
rxCPUs = flag.String("rxcpus", "", "comma-separated CPUs to pin rx workers to")
)
flag.Parse()
if err := run(*aName, *bName, *sizesArg, *patArg, *fanout, *txCPUs, *rxCPUs,
*txN, *rxN, *batch, *duplex); err != nil {
if err := run(*aName, *bName, *sizesArg, *patArg, *txCPUs, *rxCPUs,
*streams, *batch, *duplex); err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
@@ -355,8 +320,8 @@ func main() {
const reportInterval = 500 * time.Millisecond
func run(aName, bName, sizesArg, patArg, fanout, txCPUsArg, rxCPUsArg string,
txN, rxN, batch int, duplex bool) error {
func run(aName, bName, sizesArg, patArg, txCPUsArg, rxCPUsArg string,
nStreams, batch int, duplex bool) error {
if aName == "" || bName == "" {
return fmt.Errorf("both -a and -b are required")
@@ -396,9 +361,17 @@ func run(aName, bName, sizesArg, patArg, fanout, txCPUsArg, rxCPUsArg string,
a.tag, b.tag = "A", "B"
ifnames := []string{a.name, b.name}
if nStreams < 1 {
return fmt.Errorf("need at least one stream")
}
ethertypes := make([]uint16, nStreams)
for i := range ethertypes {
ethertypes[i] = uint16(etherBase + i)
}
var fatal []string
var tuneRows [][]string
for _, r := range configureSystem(ifnames) {
for _, r := range configureSystem(ifnames, ethertypes) {
tuneRows = append(tuneRows, []string{r.item, r.status(), r.detail()})
if r.fatal {
fatal = append(fatal, r.item)
@@ -412,26 +385,20 @@ func run(aName, bName, sizesArg, patArg, fanout, txCPUsArg, rxCPUsArg string,
}
cfg := config{
txWorkers: txN,
rxWorkers: rxN,
batch: batch,
fanout: fanout,
txCPUs: txCPUs,
rxCPUs: rxCPUs,
streams: nStreams,
batch: batch,
txCPUs: txCPUs,
rxCPUs: rxCPUs,
}
var dirs []*direction
cfgA := cfg
cfgA.fanoutID = 0x4341
d0, err := buildDirection(a.name+"->"+b.name, a, b, patIdx, sizes, cfgA)
d0, err := buildDirection(a.name+"->"+b.name, a, b, patIdx, sizes, cfg)
if err != nil {
return err
}
dirs = append(dirs, d0)
if duplex {
cfgB := cfg
cfgB.fanoutID = 0x4342
d1, err := buildDirection(b.name+"->"+a.name, b, a, patIdx, sizes, cfgB)
d1, err := buildDirection(b.name+"->"+a.name, b, a, patIdx, sizes, cfg)
if err != nil {
return err
}
@@ -467,11 +434,10 @@ func run(aName, bName, sizesArg, patArg, fanout, txCPUsArg, rxCPUsArg string,
[]bool{false, false}, [][]string{
{"pattern", patterns[patIdx].name},
{"frame sizes", strings.Join(sizeStrs, " ")},
{"ethertype", fmt.Sprintf("0x%04x", etherType)},
{"workers", fmt.Sprintf("%d tx, %d rx per direction", txN, rxN)},
{"streams", fmt.Sprintf("%d per direction, ethertypes 0x%04x-0x%04x, one rx queue each",
nStreams, ethertypes[0], ethertypes[len(ethertypes)-1])},
{"batch", fmt.Sprintf("%d frames per syscall", batch)},
{"payload verify", "crc32c on every frame"},
{"rx fanout", fanout},
{"duplex", fmt.Sprintf("%v", duplex)},
{"socket buffers", fmt.Sprintf("sndbuf %s, rcvbuf %s (granted)",
humanBytes(uint64(sockBufSize(dirs[0].txFDs[0], unix.SO_SNDBUF))),