Learning objective. Define the metrics that actually explain QUIC performance (grouped as latency, throughput, loss, and stream health), how each is derived from qlog events, and which few "golden signals" to watch so a regression is caught before users feel it.
From events to metrics #
An endpoint's qlog is a firehose of low-level events; useful observability turns that stream into a
small set of metrics that map to causes. Most of the raw material comes from three event types:
the recovery layer's metrics_updated (RTT, congestion window, bytes in flight), the packet events
(packet_sent, packet_lost, acknowledgments), and the frame/stream events, aggregated into four
metric families:
Latency #
Latency is the family users feel most directly, and QUIC exposes it precisely ([RFC 9002 §5]):
min_rtt. The floor, the best RTT observed, unadjusted for ACK delay ([RFC 9002 §5.2]). It approximates the path's propagation delay (§8.4); amin_rttfar above expectation means a genuinely long or misrouted path.smoothed_rttandrttvar. The working estimate and its variation ([RFC 9002 §5.3]). The gap betweensmoothed_rttandmin_rttis queueing delay — the single most useful derived latency metric, because a large gap means buffers are filling (bufferbloat, §9.4).- Time to first byte (TTFB) and time to last byte. Application-layer timings (§8.5) that capture what the handshake and transfer cost end to end.
- Jitter. The variation of inter-arrival or per-message delay (§9.1), which sets the playout buffer for real-time traffic.
Throughput #
Throughput is bytes delivered per unit time, but the useful metric is goodput (application bytes,
excluding retransmissions and overhead) and, more importantly, what is limiting it. The diagnostic
is comparing bytes_in_flight against two ceilings ([RFC 9002 §B.2], §8.4):
- pinned at the
congestion_window→ congestion-limited: the path or congestion controller is the bottleneck (look at loss and RTT). - pinned at the flow-control limit → flow-control-limited: the window is too small for the BDP
(§8.4): raise
initial_max_dataor autotune. - pinned at neither → the application isn't offering enough data, or is serialized (§8.4).
Reporting throughput without which ceiling it hit is nearly useless; the ceiling is the diagnosis.
Loss #
Loss metrics reveal what recovery is costing ([RFC 9002 §6]):
- Loss rate. Declared-lost packets over sent packets. A steady nonzero rate is normal; a rising rate signals congestion or a degrading path.
- Spurious-retransmission rate. Packets retransmitted that were later acknowledged as originally
sent. High values mean the path reorders beyond
kPacketThreshold(3) and you are paying full-RTT penalties for nothing (§8.2). - PTO count.
pto_count, the number of PTOs fired without an ACK ([RFC 9002 §A.3]); repeated PTOs with backoff mean tail loss is dominating recovery (§8.2). - ECN-CE marks. The peer-reported
ECN-CEcount from ACK frames ([RFC 9000 §13.4]); an increase is a congestion signal QUIC treats like loss ([RFC 9002 §7.1]), but without the packet drop, a cleaner early-warning than loss itself where ECN is supported. - Persistent congestion. The severe state where all packets over a span are lost, collapsing the window ([RFC 9002 §7.6]); it should be rare and always worth investigating.
Stream health #
The multiplexed layer has its own health signals, invisible at the packet level:
- Concurrent streams. How many are open versus the
initial_max_streamslimit (§7.2); hitting the limit (STREAMS_BLOCKED) caps request concurrency. - Blocked streams. Flow-control blocking (STREAM_DATA_BLOCKED, §7.2) and QPACK head-of-line blocking (§6.3); either stalls a stream while others proceed.
- Reset rate. RESET_STREAM / STOP_SENDING frequency (§5.4); a spike can indicate client cancellations (normal for LLM streaming, §9.6) or errors.
- CONNECTION_CLOSE reasons. The error code and reason phrase on close ([RFC 9000 §19.19]), the clearest single signal of why connections are failing.
The golden signals #
You cannot watch everything, so pick a small set that catches most regressions:
| Golden signal | Derived from | Regresses when… |
|---|---|---|
queueing delay = smoothed_rtt − min_rtt |
metrics_updated | buffers fill / bufferbloat |
| goodput + limiting ceiling | packet + metrics | throughput drops, or the bottleneck moves |
| loss rate + PTO count | packet_lost | path degrades or tail loss rises |
| TTFB (p50/p99) | app timing | handshake or first-byte path regresses |
| CONNECTION_CLOSE error rate | frame events | connections start failing |
These five, tracked at p50 and a tail percentile (p99), catch the overwhelming majority of real regressions. They are exactly what the regression checklist in §11.5 automates.
Worked example: a throughput regression #
A deploy drops throughput 30%. The golden signals localize it in one pass. Goodput is down and
bytes_in_flight is now pinned at the congestion_window (congestion-limited, where before it rode the
flow-control limit). Loss rate is up and PTO count is climbing; queueing delay (smoothed_rtt − min_rtt)
is also up. That combination (congestion-limited, more loss, more queueing) points at the path being
driven harder, and a diff of the release shows the congestion controller was reconfigured to be more
aggressive. Without the ceiling and the loss/queueing signals you would only know "it's slower"; with
them you know why in minutes.
Percentiles matter more than averages for every one of these. A connection's mean RTT can look fine while its p99 — the tail that sets real-time playout buffers and user-perceived stalls — has doubled. Loss and TTFB are the same: the average hides the users having a bad time. Track p50 to see the typical case and p99 (or p999) to see the worst, and alert on the tail. An average-only dashboard will stay green through a regression that is actively hurting a tenth of your users.
editorial Emit the five golden signals from your endpoints as first-class metrics rather than leaving them buried in qlog, which is for deep dives while aggregated metrics are for alerting. The one people most often miss is the limiting ceiling for throughput: without it, a flow-control misconfiguration and a congestion problem look identical (both "slow"), and you will tune the wrong thing. And always pair a latency or loss metric with its percentile; a single averaged number is the most common way a real regression hides in plain sight on a dashboard that looks healthy.
Takeaways #
QUIC's explanatory metrics group into four families: latency (min_rtt, smoothed_rtt, queueing delay,
TTFB, jitter), throughput (goodput plus the limiting ceiling), loss (loss rate, spurious retransmits, PTO
count, ECN-CE), and stream health (concurrency, blocked, resets, close reasons), all derivable from qlog
events. A handful of golden signals, tracked at p50 and p99, catch most regressions. To read these at the
byte level when a metric is ambiguous, §11.3 decrypts a capture with a key log.