Always color output and fold settled values into constants

This commit is contained in:
flamingcow
2026-07-25 18:20:34 -07:00
parent 29c6ce0b56
commit 3ba68952bb
5 changed files with 29 additions and 79 deletions
+9 -37
View File
@@ -273,7 +273,7 @@ func buildDirection(label string, tx, rx endpoint, patIdx int, sizes []int, cfg
} }
for i := 0; i < cfg.txWorkers; i++ { for i := 0; i < cfg.txWorkers; i++ {
fd, err := openTxSocket(tx.idx, cfg.sndbuf) fd, err := openTxSocket(tx.idx)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s tx socket: %w", label, err) return nil, fmt.Errorf("%s tx socket: %w", label, err)
} }
@@ -281,7 +281,7 @@ func buildDirection(label string, tx, rx endpoint, patIdx int, sizes []int, cfg
d.txStats = append(d.txStats, &txStats{}) d.txStats = append(d.txStats, &txStats{})
} }
for i := 0; i < cfg.rxWorkers; i++ { for i := 0; i < cfg.rxWorkers; i++ {
fd, err := openRxSocket(rx.idx, cfg.rcvbuf, cfg.fanoutID, fm) fd, err := openRxSocket(rx.idx, cfg.fanoutID, fm)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s rx socket: %w", label, err) return nil, fmt.Errorf("%s rx socket: %w", label, err)
} }
@@ -313,7 +313,6 @@ func (d *direction) start(wg *sync.WaitGroup, doneTx, doneRx *atomic.Bool, cfg c
fd: fd, fd: fd,
batch: cfg.batch, batch: cfg.batch,
cpu: cpuAt(cfg.rxCPUs, i), cpu: cpuAt(cfg.rxCPUs, i),
verify: cfg.verify,
spec: d.spec, spec: d.spec,
stats: d.rxStats[i], stats: d.rxStats[i],
streams: d.streams, streams: d.streams,
@@ -341,9 +340,6 @@ type config struct {
txWorkers int txWorkers int
rxWorkers int rxWorkers int
batch int batch int
verify bool
sndbuf int
rcvbuf int
fanout string fanout string
fanoutID int fanoutID int
txCPUs []int txCPUs []int
@@ -360,46 +356,25 @@ func main() {
txN = flag.Int("tx", 4, "tx workers per direction") txN = flag.Int("tx", 4, "tx workers per direction")
rxN = flag.Int("rx", 4, "rx workers per direction") rxN = flag.Int("rx", 4, "rx workers per direction")
batch = flag.Int("batch", 64, "frames per sendmmsg/recvmmsg call") batch = flag.Int("batch", 64, "frames per sendmmsg/recvmmsg call")
verify = flag.Bool("verify", true, "verify payload CRC32C on receive")
interval = flag.Duration("interval", time.Second, "report interval") interval = flag.Duration("interval", time.Second, "report interval")
drain = flag.Duration("drain", 500*time.Millisecond, "keep receiving this long after tx stops, so in-flight frames are not counted as lost")
fanout = flag.String("fanout", "lb", "rx fanout mode: none, hash, lb, cpu, rollover") fanout = flag.String("fanout", "lb", "rx fanout mode: none, hash, lb, cpu, rollover")
duplex = flag.Bool("duplex", true, "run both directions simultaneously") duplex = flag.Bool("duplex", true, "run both directions simultaneously")
sndbuf = flag.Int("sndbuf", 8<<20, "SO_SNDBUFFORCE per tx socket")
rcvbuf = flag.Int("rcvbuf", 64<<20, "SO_RCVBUFFORCE per rx socket")
txCPUs = flag.String("txcpus", "", "comma-separated CPUs to pin tx workers to") txCPUs = flag.String("txcpus", "", "comma-separated CPUs to pin tx workers to")
rxCPUs = flag.String("rxcpus", "", "comma-separated CPUs to pin rx workers to") rxCPUs = flag.String("rxcpus", "", "comma-separated CPUs to pin rx workers to")
governor = flag.String("governor", "performance", "required cpufreq governor")
coalesce = flag.Uint("coalesce-usecs", 25, "required fixed rx/tx coalesce usecs, with adaptive coalescing off")
rxRing = flag.Uint("rx-ring", 8160, "required rx ring size, clamped to hardware maximum")
txRing = flag.Uint("tx-ring", 4096, "required tx ring size, clamped to hardware maximum")
color = flag.String("color", "auto", "colored output: auto, always, never")
) )
flag.Parse() flag.Parse()
if err := initColor(*color); err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
tcfg := systemConfig{
governor: *governor,
rxUsecs: uint32(*coalesce),
txUsecs: uint32(*coalesce),
rxRing: uint32(*rxRing),
txRing: uint32(*txRing),
}
if err := run(*aName, *bName, *sizesArg, *patArg, *fanout, *txCPUs, *rxCPUs, if err := run(*aName, *bName, *sizesArg, *patArg, *fanout, *txCPUs, *rxCPUs,
*duration, *interval, *drain, *txN, *rxN, *batch, *sndbuf, *rcvbuf, *verify, *duplex, tcfg); err != nil { *duration, *interval, *txN, *rxN, *batch, *duplex); err != nil {
fmt.Fprintln(os.Stderr, "error:", err) fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1) os.Exit(1)
} }
} }
const drainTime = 500 * time.Millisecond
func run(aName, bName, sizesArg, patArg, fanout, txCPUsArg, rxCPUsArg string, func run(aName, bName, sizesArg, patArg, fanout, txCPUsArg, rxCPUsArg string,
duration, interval, drain time.Duration, txN, rxN, batch, sndbuf, rcvbuf int, duration, interval time.Duration, txN, rxN, batch int, duplex bool) error {
verify, duplex bool, tcfg systemConfig) error {
if aName == "" || bName == "" { if aName == "" || bName == "" {
return fmt.Errorf("both -a and -b are required") return fmt.Errorf("both -a and -b are required")
@@ -441,7 +416,7 @@ func run(aName, bName, sizesArg, patArg, fanout, txCPUsArg, rxCPUsArg string,
var fatal []string var fatal []string
var tuneRows [][]string var tuneRows [][]string
for _, r := range configureSystem(tcfg, ifnames) { for _, r := range configureSystem(ifnames) {
tuneRows = append(tuneRows, []string{r.item, r.status(), r.detail()}) tuneRows = append(tuneRows, []string{r.item, r.status(), r.detail()})
if r.fatal { if r.fatal {
fatal = append(fatal, r.item) fatal = append(fatal, r.item)
@@ -458,9 +433,6 @@ func run(aName, bName, sizesArg, patArg, fanout, txCPUsArg, rxCPUsArg string,
txWorkers: txN, txWorkers: txN,
rxWorkers: rxN, rxWorkers: rxN,
batch: batch, batch: batch,
verify: verify,
sndbuf: sndbuf,
rcvbuf: rcvbuf,
fanout: fanout, fanout: fanout,
txCPUs: txCPUs, txCPUs: txCPUs,
rxCPUs: rxCPUs, rxCPUs: rxCPUs,
@@ -516,7 +488,7 @@ func run(aName, bName, sizesArg, patArg, fanout, txCPUsArg, rxCPUsArg string,
{"ethertype", fmt.Sprintf("0x%04x", etherType)}, {"ethertype", fmt.Sprintf("0x%04x", etherType)},
{"workers", fmt.Sprintf("%d tx, %d rx per direction", txN, rxN)}, {"workers", fmt.Sprintf("%d tx, %d rx per direction", txN, rxN)},
{"batch", fmt.Sprintf("%d frames per syscall", batch)}, {"batch", fmt.Sprintf("%d frames per syscall", batch)},
{"payload verify", fmt.Sprintf("%v", verify)}, {"payload verify", "crc32c on every frame"},
{"rx fanout", fanout}, {"rx fanout", fanout},
{"duplex", fmt.Sprintf("%v", duplex)}, {"duplex", fmt.Sprintf("%v", duplex)},
{"socket buffers", fmt.Sprintf("sndbuf %s, rcvbuf %s (granted)", {"socket buffers", fmt.Sprintf("sndbuf %s, rcvbuf %s (granted)",
@@ -589,7 +561,7 @@ loop:
doneTx.Store(true) doneTx.Store(true)
elapsed := time.Since(start).Seconds() elapsed := time.Since(start).Seconds()
time.Sleep(drain) time.Sleep(drainTime)
doneRx.Store(true) doneRx.Store(true)
wg.Wait() wg.Wait()
+1 -23
View File
@@ -2,7 +2,6 @@ package main
import ( import (
"fmt" "fmt"
"os"
"strings" "strings"
) )
@@ -17,29 +16,8 @@ const (
cGrey = "\x1b[90m" cGrey = "\x1b[90m"
) )
var useColor bool
func initColor(mode string) error {
switch mode {
case "always":
useColor = true
case "never":
useColor = false
case "auto":
if os.Getenv("NO_COLOR") != "" || os.Getenv("TERM") == "dumb" {
useColor = false
return nil
}
st, err := os.Stdout.Stat()
useColor = err == nil && st.Mode()&os.ModeCharDevice != 0
default:
return fmt.Errorf("unknown color mode %q (want auto, always, never)", mode)
}
return nil
}
func paint(s, code string) string { func paint(s, code string) string {
if !useColor || s == "" { if s == "" {
return s return s
} }
return code + s + cReset return code + s + cReset
-4
View File
@@ -28,7 +28,6 @@ type rxWorker struct {
fd int fd int
batch int batch int
cpu int cpu int
verify bool
spec *frameSpec spec *frameSpec
stats *rxStats stats *rxStats
streams []streamState streams []streamState
@@ -79,9 +78,6 @@ func (w *rxWorker) run(done *atomic.Bool) {
} }
} }
if !w.verify {
continue
}
if p.payLen > len(w.spec.ref) { if p.payLen > len(w.spec.ref) {
w.stats.badLen.Add(1) w.stats.badLen.Add(1)
continue continue
+9 -4
View File
@@ -31,7 +31,12 @@ func sockBufSize(fd, opt int) int {
return v return v
} }
func openTxSocket(ifindex, sndbuf int) (int, error) { const (
sndbufBytes = 8 << 20
rcvbufBytes = 64 << 20
)
func openTxSocket(ifindex int) (int, error) {
fd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW, 0) fd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW, 0)
if err != nil { if err != nil {
return -1, fmt.Errorf("socket: %w", err) return -1, fmt.Errorf("socket: %w", err)
@@ -44,20 +49,20 @@ func openTxSocket(ifindex, sndbuf int) (int, error) {
unix.Close(fd) unix.Close(fd)
return -1, fmt.Errorf("qdisc bypass: %w", err) return -1, fmt.Errorf("qdisc bypass: %w", err)
} }
if err := setBufForce(fd, unix.SO_SNDBUFFORCE, unix.SO_SNDBUF, sndbuf); err != nil { if err := setBufForce(fd, unix.SO_SNDBUFFORCE, unix.SO_SNDBUF, sndbufBytes); err != nil {
unix.Close(fd) unix.Close(fd)
return -1, fmt.Errorf("sndbuf: %w", err) return -1, fmt.Errorf("sndbuf: %w", err)
} }
return fd, nil return fd, nil
} }
func openRxSocket(ifindex, rcvbuf, fanoutID, fanoutMode int) (int, error) { func openRxSocket(ifindex, fanoutID, fanoutMode int) (int, error) {
proto := int(htons(etherType)) proto := int(htons(etherType))
fd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW, proto) fd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW, proto)
if err != nil { if err != nil {
return -1, fmt.Errorf("socket: %w", err) return -1, fmt.Errorf("socket: %w", err)
} }
if err := setBufForce(fd, unix.SO_RCVBUFFORCE, unix.SO_RCVBUF, rcvbuf); err != nil { if err := setBufForce(fd, unix.SO_RCVBUFFORCE, unix.SO_RCVBUF, rcvbufBytes); err != nil {
unix.Close(fd) unix.Close(fd)
return -1, fmt.Errorf("rcvbuf: %w", err) return -1, fmt.Errorf("rcvbuf: %w", err)
} }
+10 -11
View File
@@ -62,13 +62,12 @@ type ethtoolCoalesce struct {
rateSampleInterval uint32 rateSampleInterval uint32
} }
type systemConfig struct { const (
governor string wantGovernor = "performance"
rxUsecs uint32 wantCoalesceUsecs = 25
txUsecs uint32 wantRxRing = 8160
rxRing uint32 wantTxRing = 4096
txRing uint32 )
}
type checkResult struct { type checkResult struct {
item string item string
@@ -311,15 +310,15 @@ func withIoctlSocket(fn func(fd int) []checkResult) []checkResult {
return fn(fd) return fn(fd)
} }
func configureSystem(cfg systemConfig, ifnames []string) []checkResult { func configureSystem(ifnames []string) []checkResult {
return withIoctlSocket(func(fd int) []checkResult { return withIoctlSocket(func(fd int) []checkResult {
out := []checkResult{checkGovernor(cfg.governor)} out := []checkResult{checkGovernor(wantGovernor)}
for _, ifname := range ifnames { for _, ifname := range ifnames {
out = append(out, checkLinkUp(fd, ifname)) out = append(out, checkLinkUp(fd, ifname))
out = append(out, checkCoalesce(fd, ifname, cfg.rxUsecs, cfg.txUsecs)) out = append(out, checkCoalesce(fd, ifname, wantCoalesceUsecs, wantCoalesceUsecs))
carrierWait := 3 * time.Second carrierWait := 3 * time.Second
r, reset := checkRings(fd, ifname, cfg.rxRing, cfg.txRing) r, reset := checkRings(fd, ifname, wantRxRing, wantTxRing)
out = append(out, r) out = append(out, r)
if reset { if reset {
carrierWait = 10 * time.Second carrierWait = 10 * time.Second