-
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. A zero in
m.zdt on a TCP session means "not that transport", not "nothing happened",
which is what m.transport is there to disambiguate.
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.
The same choice means reading them from a thread other than the session's owner is a data race, however harmless the result looks. Sample from inside a handler or a tick callback; see Threading Model.
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.
All of the above is ZDT. A TCP session populates common and tcp only, and
has no window, no RTT estimate and no retransmit counters of its own, because
the kernel owns all three.
| 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.