-
Notifications
You must be signed in to change notification settings - Fork 4
Metrics
Counters for a session and for a listener, grouped the way options are: what
every transport has lives in common, and each transport gets its own group.
void SampleSession(PeerSession& session) {
SessionMetrics m = session.metrics();
// any transport
uint64_t sent = m.common.messages_sent;
uint64_t refused = m.common.send_failures; // queue was full
// `transport` says which per-transport group carries real values
if (m.transport == ConnectionType::ZDT) {
uint32_t rtt_us = m.zdt.srtt_us;
uint32_t window = m.zdt.cwnd; // far below the cap means congestion control
uint64_t retransmits = m.zdt.retransmits;
} else {
uint64_t writes = m.tcp.writes;
}
}
void SampleServer(Server& server) {
ServerMetrics s = server.metrics();
uint64_t accepted = s.connections_accepted;
uint64_t rejected = s.zdt.cookies_rejected; // ZDT only
}Groups that do not apply stay zeroed rather than holding garbage, so reading the wrong one is harmless — unlike a union, which would be undefined.
metrics() takes a snapshot. The hot path only bumps plain members owned by the
thread that already owns the object: no atomics, no locks, no per-packet
callback. That is what makes them cheap enough to leave on.
The consequence is that you must sample on a timer, not per packet. Reading them in a hot loop costs more than the counting does. A second is a reasonable interval; anything under 100 ms is measuring your own sampling.
Values may be slightly stale, since the owning thread may be mid-update. For counters that does not matter. For the sampled gauges below it means an occasional reading that lags reality by a tick.
Build with -DZNET_ENABLE_METRICS=OFF to compile them out entirely, or set
collect_metrics = false per session to stop populating them.
| Counter | Meaning |
|---|---|
messages_sent / messages_received
|
Whole messages |
message_bytes_sent / message_bytes_received
|
After encode, before transport framing |
wire_bytes_sent / wire_bytes_received
|
Including transport framing |
send_failures |
SendPacket refused, e.g. the queue was full |
outbound_queued |
Current queue depth. Sampled, not accumulated |
send_failures is the one to alert on. A nonzero and rising value means the
application is producing faster than the link drains, and packets are being
refused — silently, unless you are checking SendPacket's return value.
The gap between message_bytes_sent and wire_bytes_sent is the transport's
overhead: framing, headers, retransmits.
| Counter | Meaning |
|---|---|
writes |
send() calls that succeeded |
reads |
recv() calls that returned data |
| Counter | Meaning |
|---|---|
datagrams_sent / datagrams_received
|
UDP datagrams, not messages |
retransmits |
Reliable datagrams sent again |
naks_sent / naks_received
|
Gaps reported to the peer, and by the peer |
duplicates_dropped |
Deduped by the receiver |
inbound_dropped |
Inbox was full, arrivals discarded |
reassemblies_dropped |
Incomplete, timed out, or over the cap |
And these gauges, all sampled rather than accumulated:
| Gauge | Meaning |
|---|---|
srtt_us |
Smoothed round-trip estimate |
rtt_min_us |
Windowed minimum, the baseline congestion is judged against |
rto_us |
Current retransmit timeout |
cwnd |
Congestion window, in datagrams |
in_flight |
Unacked reliable datagrams |
mtu |
What the handshake settled on |
Is the link the bottleneck, or am I? Compare cwnd against
max_datagrams_in_flight. A window sitting far below the cap means the
controller is holding it there. A window at the cap with in_flight also at the
cap means you are limited by the setting, not the network.
Is it congestion or corruption? ZDT backs off on queueing delay, so read
srtt_us against rtt_min_us: a smoothed estimate well above the minimum is a
queue building somewhere on the path. Retransmits with srtt_us near
rtt_min_us is loss without queueing — a lossy link rather than a full one.
Am I dropping on the floor? inbound_dropped means arrivals were discarded
because the inbox filled, which means the session is not being serviced fast
enough. Usually a handler doing too much work on the session's thread; see
Threading Model.
| Counter | Meaning |
|---|---|
connections_accepted |
Total ever accepted |
connections_active |
Currently held |
ZDT-only, listener scope — these exist because ZDT rejects connections before a session exists, so they cannot live on one:
| Counter | Meaning |
|---|---|
handshakes_started |
First contact from a new address |
handshakes_rejected |
Version mismatch, server full, banned |
cookies_rejected |
Failed the return-routability check |
rate_limited |
Per-source handshake cap hit |
datagrams_unroutable |
Online datagram from an unknown peer |
cookies_rejected and rate_limited rising together is the signature of a
spoofed-source flood being turned away, which is what those mechanisms are for.
Rising datagrams_unroutable on its own more often means peers whose NAT
mapping changed than an attack.