Commit Graph

2 Commits

Author SHA1 Message Date
Roflin d8ddea13ba Measure the throughput limit: window x RTT, not the adapter
Swiss dumping a disc over the adapter measures 850 KB/s (6.97 Mbit/s, 25.8% of
the 27 Mbit/s EXI ceiling).  The capture decomposes it completely:

  18395 data frames / 9200 ACKs = 2.0 frames per ACK
  18.84 MB / 9200             = 2048 B per round trip
  22.78 s / 9200              = 2.48 ms per round trip
  2048 B / 2.48 ms            = 826 KB/s  ~= the measured 850

So the flow is window x RTT bound and nothing else.  Payload per frame is
exactly 1024 B (wire 1078 minus 54 of headers) against a 2920 B window, so two
segments fit in flight.  Zero retransmissions means the 15-page ring never
overflowed; 0.2% stall time means nothing waits on the disc; our W5100 bus
engine sits at ~17% utilisation.  The adapter is not the constraint.

Levers, and the first two are not ours: a larger lwIP TCP_SND_BUF (scales
almost linearly while window-bound), and full-MSS segments -- Swiss writes
1024 B at a time, so the same window and RTT at 1460 B would give ~1150 KB/s,
a 1.43x gain for free.  Third is a shorter round trip, which is GC-side
interrupt dispatch plus lwIP, plus EXI lock contention: libogc2's
bba_event_handler defers when EXI_Lock fails, so RX queues behind TX on the
single channel.

⚠️ This REVERSES an earlier recommendation in the TODO.  RX interrupt
coalescing (NCRB.RXINTC, RXINTT) was listed as a throughput lever; for a
window-bound flow it makes things worse, because delaying the ACK's interrupt
lengthens the round trip and throughput is bytes-per-round-trip / RTT.  The
measurement showed the guess was backwards.  Noted in item 3 as "implement for
fidelity, not for speed".

analyse-pcap.py now derives frames-per-ACK, bytes-per-round-trip and the
implied RTT itself, identifies a window-bound flow, quantifies the full-MSS
gain, and warns against coalescing -- so the next person gets the conclusion
rather than the raw numbers.  Self-test G replays this exact shape.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-10 15:26:39 +02:00
Roflin d6fb7c81b9 scripts: add a pcap analyser to find the BBA throughput limit
Swiss dumps a disc over the adapter at 740-780 KB/s, which is ~23% of the
27 Mbit/s EXI ceiling (3375 KB/s), and no published real-BBA figure exists to
compare against.  Rather than reason about where the rest goes, measure it.

./scripts/analyse-pcap.py /tmp/bba.pcap --gc-ip 10.0.0.64 reports, per
direction: throughput as a fraction of the EXI ceiling, MEAN FRAME SIZE,
retransmissions, the GC's advertised TCP window, zero-window spans, and the
largest gaps between data frames.  Each one discriminates a different
bottleneck:

  mean frame far below 1514  -> per-frame overhead (interrupts, EXI
                                transactions, socket bookkeeping) dominates
  retransmissions            -> we are dropping frames, e.g. the 15-page RX
                                ring overflowing (RBFI)
  small advertised window    -> capped at window/RTT regardless of the wire
  zero-window spans          -> the GC cannot drain: CPU/stack bound
  large gaps, little sending -> the limit is upstream of the wire entirely
                                (disc read, CPU, stack), not the adapter

Classic pcap only, stdlib only, no root.  pcapng is detected and refused with
a conversion hint rather than misparsed.

scripts/test-analyse-pcap.py synthesises captures with known properties and
checks each conclusion is reached: 1000 full frames in 2 s reports ~22% of the
ceiling, 200-byte payloads raise the overhead warning, a repeated seq counts 9
retransmissions, one 300 ms hole in 100 frames is found and attributed, a
zero-window span is called CPU/stack bound, and a pcapng header is rejected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-10 15:21:47 +02:00