Measure your connection properly with a tool already on your Mac
networkQuality reports throughput and, more usefully, how badly latency degrades while the link is busy. Three runs on this connection took about three minutes and found a problem no speed test reports.
macOS has shipped a network measurement tool since Monterey and almost nobody uses it. It reports throughput like any speed test, and it also reports how much latency degrades while the connection is saturated, which is the figure that actually predicts whether video calls hold up.
networkQuality -s
That is the whole command. It takes under a minute.
Use -s, always
Without -s, the tool runs the uplink and downlink phases concurrently. That is faster and the two phases compete for the same link, so each measurement is distorted by the other.
-s runs them sequentially. It roughly doubles the duration and the numbers mean something afterwards.
Reading the output
==== SUMMARY ====
Uplink capacity: 58.736 Mbps
Downlink capacity: 41.681 Mbps
Uplink Responsiveness: Low (590.758 milliseconds | 101 RPM)
Downlink Responsiveness: Low (405.672 milliseconds | 147 RPM)
Idle Latency: 30.841 milliseconds | 1945 RPM
The two capacity lines are the familiar speed-test numbers.
The three latency lines are the part worth having. Idle latency is the round-trip time when nothing else is happening. The two responsiveness figures are the round-trip time while that direction is saturated. RPM is round-trips per minute, so higher is better; Apple uses a rate because the underlying value changes by an order of magnitude and rates are easier to compare than milliseconds.
On this connection the gap is large: 1945 RPM idle against 101 RPM while uploading.
How many runs you actually need
I ran it three times back to back. The two categories of result behaved completely differently.
| Run | Down | Up | Idle latency | Uplink loaded | Downlink loaded |
|---|---|---|---|---|---|
| 1 | 41.681 | 58.736 | 30.841 ms | 590.758 ms | 405.672 ms |
| 2 | 40.812 | 51.553 | 30.489 ms | 668.164 ms | 535.196 ms |
| 3 | 39.617 | 51.843 | 30.457 ms | 662.071 ms | 322.213 ms |
Idle latency moved by 0.384 ms across all three runs. That is a stable measurement and one run is enough.
Downlink responsiveness ranged from 322 ms to 535 ms, a spread of 213 ms, which is more than half its own mean. One run tells you almost nothing about it.
The practical rule: one run for idle latency and rough throughput, at least three for anything you intend to say about responsiveness. The instability is not noise in the tool, it is the property being measured. Congested queues drain unpredictably.
Running it repeatedly without watching
for i in 1 2 3; do
networkQuality -s 2>&1 | grep -E "capacity|Responsiveness|Idle Latency"
echo "---"
done
For a log you can compare over time, the tool emits JSON:
networkQuality -s -c >> ~/netquality.jsonl
One JSON object per run, appended. Run it hourly from a scheduled job and you can see whether your connection degrades at particular times of day, which is a far more useful diagnostic than a single reading during an argument with an ISP.
What a bad result means
A large gap between idle and loaded latency is bufferbloat: equipment somewhere between you and the far end has a queue larger than it needs, so a bulk transfer fills it and every other packet waits behind that backlog.
The test cannot tell you which device is responsible. It measures the end-to-end result. Narrowing it down means testing from different points, and the usual suspect is the router or modem in your own home rather than anything upstream, particularly on the uplink where customer equipment does the queueing.
If your router offers Smart Queue Management, fq_codel or CAKE, turning it on is the single change most likely to move this number. Those algorithms cap queue depth deliberately, trading a small amount of peak throughput for a large improvement in loaded latency.
Checking your Wi-Fi’s contribution
Wi-Fi adds latency and variance of its own, so a poor result may be the last hop rather than the line. If you can, run the test once wired and once wireless:
# Confirm which interface is active
route get default | grep interface
A large difference between the two runs points at the wireless link. A similar result in both points past it, at your router or beyond.
Why this beats a browser test
A browser speed test runs inside a tab, competing with the browser’s own network activity, and it reports throughput plus a single unloaded ping. networkQuality runs outside the browser, saturates the link deliberately, and measures latency during that saturation. It is measuring the condition your connection is actually in when it matters, which the ping-when-idle figure never describes.
Reading the JSON output
For anything you intend to compare over time, the -c flag emits structured output rather than
the human summary:
networkQuality -s -c
The fields worth extracting are dl_throughput and ul_throughput in bits per second,
dl_responsiveness and ul_responsiveness in RPM, and base_rtt in milliseconds, which is the
idle latency figure.
networkQuality -s -c 2>/dev/null | python3 -c '
import json,sys
d = json.load(sys.stdin)
print(f"down {d[\"dl_throughput\"]/1e6:8.2f} Mbps rpm {d.get(\"dl_responsiveness\",0):6}")
print(f"up {d[\"ul_throughput\"]/1e6:8.2f} Mbps rpm {d.get(\"ul_responsiveness\",0):6}")
print(f"idle {d.get(\"base_rtt\",0):8.2f} ms")
'
Appending one JSON object per run to a file gives you something you can plot or diff later, which is considerably more persuasive in a conversation with an internet provider than a screenshot of a speed test taken during an argument.
What the numbers predict
A responsiveness figure is abstract until it is attached to something recognisable. On a connection measuring 640 ms under upload load, these follow directly.
A video call degrades whenever anything else uploads, because the call’s outbound packets queue behind the bulk transfer. The bandwidth figures suggest there is ample headroom, and there is; the problem is queueing delay, not capacity.
A web page needing six sequential round trips to render takes six times the loaded latency, so close to four seconds of pure waiting on a connection that benchmarks at 40 Mbps. This is why browsing can feel slow on a connection that tests fine.
An interactive terminal session over SSH becomes unusable during a file transfer, for exactly the same reason and more visibly, because each keystroke is its own round trip.
None of those would be predicted by the throughput number alone, which is the argument for measuring responsiveness at all.
Testing at different times
The single most useful thing to do with this tool is not one careful measurement but many casual ones. Congestion is time-dependent, both in your own home and in your provider’s network, and a result taken at 3am describes a network nobody is using.
# Add to crontab: hourly, appended
0 * * * * /usr/bin/networkQuality -s -c >> $HOME/netquality.jsonl 2>/dev/null
A week of that answers questions a single run cannot: whether the line degrades in the evening, whether responsiveness collapses at a particular hour, and whether the throughput you pay for is present at the times you actually use it.
One caveat about the test itself
networkQuality saturates your connection deliberately, which means that while it runs, everyone
else on the network has a bad time. On a shared connection it is worth saying so before running a
sequence of tests, and it is worth not scheduling an hourly cron job on a link other people
depend on during working hours.
It also consumes real data. Each run transfers enough to measure throughput, which on a metered or capped connection is a cost worth accounting for before setting up automated sampling. On an unmetered domestic line it is irrelevant; on a mobile hotspot it is not.
Takeaways
- networkQuality ships with macOS from Monterey onward, needs no install, no account and no browser.
- Idle latency was stable across three runs at 30.60 ms with a spread of 0.384 ms, so one run is enough for that figure.
- Loaded responsiveness varied by up to 213 ms between runs, so a single measurement of it is close to meaningless.
- Use -s to run the directions sequentially, otherwise the uplink and downlink phases contaminate each other.
Questions
- Does this replace a browser speed test?
- It includes one. You get uplink and downlink capacity plus the responsiveness figures, from one command, without a browser tab competing for the bandwidth being measured.
- What is a good RPM figure?
- Apple labels results as Low, Medium or High. Broadly, above 1000 RPM under load is good and under 200 is poor. This connection managed 1960 idle and 93 under upload load.
- Should I test over Wi-Fi or Ethernet?
- Both, if you can. Wi-Fi adds its own latency and variance, so a wired run tells you about the line while a wireless run tells you about what you actually experience.
Sources
What we read. Distinct from what we measured, which is in the article itself.