The sensor link and the capture under a speed test¶
Setup on 2026-10-20 and 21¶
The phone on the home SSID, AP A (U7 Pro Max) on 5 GHz ch 48/80, the sensor 1 m from the phone. capA on ch 48 and capB on ch 40, both in the same 80 MHz block (center 5210 MHz), so both radios hear the client's wide PPDUs. A speed test on the phone: 484 to 521 Mbit/s down, 734 to 753 up. The focus filter of the session, on the Pi 5 with kernel 6.18.50 and the MT7925U firmware of 2026-04-14; tshark 4.6.8 on the Mac.
Runs¶
| Run | Radios | Pi to Mac | The stream to | Peak per radio | Stall | Drops capA / capB |
|---|---|---|---|---|---|---|
| App session, 22:43 | USB 2 | 10 Mbit/s | the app | 11,000/s, then stalled | 20 and 16 s | 28,814 / 3,240 |
| Pi alone | USB 2 | – | /dev/null on the Pi |
13,000/s | none | 0 / 0 |
| Over SSH | USB 2 | 10 Mbit/s | /dev/null on the Mac |
13,000/s | 20.8 s | 22,960 / 12,497 |
| Over SSH, new cable | USB 2 | 1 Gbit/s | /dev/null on the Mac |
14,000/s | none | 0 / 0 |
| App session, 00:01 | USB 3 | 1 Gbit/s | the app (Debug) | 20,000/s | none | 0 / 0 |
A stall is a stretch without a stats line of linkside-cap: it blocked on its write to stdout while its ring of 16 MB filled.
The link¶
At boot eth0 came up at 100 Mbit/s three times and went down each time, then stayed at 10 Mbit/s (dmesg: macb, PHY BCM54213PE). The Mac's USB-C adapter also read 10baseT. At 10 Mbit/s, some 1.2 MB/s, the two streams did not fit: 10,000 to 13,000 frames a second each at about 126 bytes, 2.5 to 3.3 MB/s together.
- Since boot the
eth0queue (fq_codel) requeued 13,550 of 62,252 packets: the NIC's ring was full. It marked 192 of them with ECN and dropped none. - TCP retransmitted 2 segments of 35.9 MB. No pause frames, no errors, no underruns.
- After the burst the backlog took 20 s to drain at 1.2 MB/s: the stall.
A new cable brought 1000 Mbit/s on both sides, and the same speed test went through without a drop. Gigabit needs all four pairs; a link that comes up at 100 and falls back to 10 points to the cable. linkside-radio status now gives the speed of eth0 (link), and the app warns below 1000 Mbit/s.
The Pi¶
Alone, with both streams to /dev/null: the busiest core at about 25 %, the two mt76-usb threads together about 28 %, linkside-cap 0 to 3 %.
USB 2 and USB 3¶
With the same speed test the peak was 13,000 to 14,000 frames a second per radio on USB 2 and 20,000 on USB 3. Of the downlink MPDUs the Block Acks delivered, the sensor decoded 32 % as data frames on USB 2 (the session of 22:43) and 87 % on USB 3 (the session of 00:01). linkside-cap counted no drop on USB 2 outside the stall. The A9000 hands every frame on the channel over USB in full, before the filter on the Pi: a busy 80 MHz channel does not fit through USB 2, and what does not fit is lost in the radio or the driver, outside the drop counters. Two sessions, not one controlled comparison. The uplink share stayed low on both (25 % and 7 %), as at 1 m before (eht-capture.md).
USB 2 at 160 MHz, 2026-10-23¶
The controlled comparison: stick B as the client on linkside, AP A's 6 GHz ch 37/160 (rx EHT-MCS 12, tx MCS 13, 2 streams, −38 dBm), a TCP download from the iperf3 server at full rate, the app's live session on stick A at 6135/160, first on its USB 3 port, then on the USB 2 port of the same controller (xhci-hcd.1, 480M). Recordings ~/linkside-captures/2026-10-23-drops-usb3 and -usb2, outside the repo.
| USB 3 | USB 2 | |
|---|---|---|
| Download | 1.29 Gbit/s, 32 s | 1.13 Gbit/s, 30 s |
| MPDUs a second, from the client's Block Ack starts | 19,100 | 17,500 |
| The client's Block Acks a second | 344 | 152 |
| Block Ack starts more than a window past the one before | 67 | 221 |
linkside-cap drop / ifdrop |
0 / 0 | 0 / 0 |
| BA success down / up | 99.3 % / 96.9 % | 98.5 % / 93.0 % |
| The AP's TXOPs with a CTS and no Block Ack | 0.8 % | 64 % |
| Sequence gaps in the downlink data | 56,514 | 380,724 |
| Downlink data decoded | 15.5 % | 20.6 % |
The client acknowledges A-MSDUs of about 8 KB, so 19,000 MPDUs a second carry 1.29 Gbit/s; each of its Block Acks reports the whole window of 256, all set, its start 22 to 91 past the one before. On USB 2 more than half of them were lost before linkside-cap, with no drop counted: BA success fell to 93.0 % up, Fair against the Good of USB 3, and the TXOPs read as failed. Which data frames the sensor decodes follows its SNR and the AP's rate, which differed between the runs: most of what it decoded was EHT MCS 8 on USB 3 and MCS 9 and 10 on USB 2, of MCS 12 none and 224. One frame on USB 2 was no 802.11 at all (protocol version 3, on 2422 MHz during the discovery); the replay stopped at it until the decoder skipped such frames.
| Part | Debug | Release |
|---|---|---|
Decode, both tshark passes and the Swift side, one radio at a time (linkside-cli replay, 355,000 frames of the session of 00:01) |
34.9 s, about 10,200 frames/s | 14.9 s, about 23,800 frames/s |
ReorderBuffer with one radio 0.2 s or more behind the other, before the heap (235,000 frames of the burst of 22:43) |
800 to 1,800 frames/s | |
| The same after the heap | 178,000 to 330,000 frames/s |
The app's Run builds Release: a Debug build falls behind a speed test on USB 3. Live, the snapshots of the Overview come after the frames that arrived before them, so a pipeline that falls behind stops the time axis until it catches up.
A cable pulled mid-session, 2026-10-23¶
The app live on the sensor, Keep a recording on, the Ethernet cable pulled from the Pi after about two minutes of focus. The sensor host in ~/.ssh/config has ControlMaster with ServerAliveInterval 5 and ServerAliveCountMax 3, no ConnectTimeout. "Sensor not responding" showed as expected. Two minutes later no capture ssh was left (the ControlMaster had given up, its capture channels with it), but the check before the restart, ssh -o BatchMode=yes sensor date +%s.%N, had hung for 2:09: without a ConnectTimeout ssh waits on a sensor whose cable is out, and the session never ended. Since then the check has LiveSession.answerWindow (8 s), after which the session ends with "The sensor connection dropped". With that limit the same pull ended the session: "Ended early", with "The sensor connection dropped (capA)" in the strip. The time from the pull was not timed; the expectation is about 23 s, 15 s for the ControlMaster to give up (three missed server-alives 5 s apart) and 8 s for the check.