noq_proto/connection/
stats.rs

1//! Connection statistics
2
3use rustc_hash::FxHashMap;
4
5use crate::Duration;
6use crate::FrameType;
7
8use super::PathId;
9
10/// Statistics about UDP datagrams transmitted or received on a connection.
11///
12/// All QUIC packets are carried by UDP datagrams. Hence, these statistics cover all traffic
13/// on a connection.
14#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, derive_more::Add, derive_more::AddAssign)]
15#[non_exhaustive]
16pub struct UdpStats {
17    /// The number of UDP datagrams observed.
18    pub datagrams: u64,
19    /// The total amount of bytes which have been transferred inside UDP datagrams.
20    pub bytes: u64,
21    /// The number of I/O operations executed.
22    ///
23    /// This can't be measured from this crate and will always be 0
24    #[deprecated(
25        since = "1.1.0",
26        note = "IO counting can't be meaningfully measured from this crate. See <https://github.com/n0-computer/noq/issues/727>"
27    )]
28    pub ios: u64,
29}
30
31impl UdpStats {
32    pub(crate) fn on_sent(&mut self, datagrams: u64, bytes: usize) {
33        self.datagrams += datagrams;
34        self.bytes += bytes as u64;
35    }
36}
37
38/// Number of frames transmitted or received of each frame type.
39#[derive(Default, Copy, Clone, PartialEq, Eq, derive_more::Add, derive_more::AddAssign)]
40#[non_exhaustive]
41#[allow(missing_docs)]
42pub struct FrameStats {
43    pub acks: u64,
44    pub path_acks: u64,
45    pub ack_frequency: u64,
46    pub crypto: u64,
47    pub connection_close: u64,
48    pub data_blocked: u64,
49    pub datagram: u64,
50    pub handshake_done: u8,
51    pub immediate_ack: u64,
52    pub max_data: u64,
53    pub max_stream_data: u64,
54    pub max_streams_bidi: u64,
55    pub max_streams_uni: u64,
56    pub new_connection_id: u64,
57    pub path_new_connection_id: u64,
58    pub new_token: u64,
59    pub path_challenge: u64,
60    pub path_response: u64,
61    pub ping: u64,
62    pub reset_stream: u64,
63    pub retire_connection_id: u64,
64    pub path_retire_connection_id: u64,
65    pub stream_data_blocked: u64,
66    pub streams_blocked_bidi: u64,
67    pub streams_blocked_uni: u64,
68    pub stop_sending: u64,
69    pub stream: u64,
70    pub observed_addr: u64,
71    pub path_abandon: u64,
72    pub path_status_available: u64,
73    pub path_status_backup: u64,
74    pub max_path_id: u64,
75    pub paths_blocked: u64,
76    pub path_cids_blocked: u64,
77    pub add_address: u64,
78    pub reach_out: u64,
79    pub remove_address: u64,
80}
81
82impl FrameStats {
83    pub(crate) fn record(&mut self, frame_type: FrameType) {
84        use FrameType::*;
85        // Increments the field. Added for readability
86        macro_rules! inc {
87            ($field_name: ident) => {{ self.$field_name = self.$field_name.saturating_add(1) }};
88        }
89        match frame_type {
90            Padding => {}
91            Ping => inc!(ping),
92            Ack | AckEcn => inc!(acks),
93            PathAck | PathAckEcn => inc!(path_acks),
94            ResetStream => inc!(reset_stream),
95            StopSending => inc!(stop_sending),
96            Crypto => inc!(crypto),
97            Datagram(_) => inc!(datagram),
98            NewToken => inc!(new_token),
99            MaxData => inc!(max_data),
100            MaxStreamData => inc!(max_stream_data),
101            MaxStreamsBidi => inc!(max_streams_bidi),
102            MaxStreamsUni => inc!(max_streams_uni),
103            DataBlocked => inc!(data_blocked),
104            Stream(_) => inc!(stream),
105            StreamDataBlocked => inc!(stream_data_blocked),
106            StreamsBlockedUni => inc!(streams_blocked_uni),
107            StreamsBlockedBidi => inc!(streams_blocked_bidi),
108            NewConnectionId => inc!(new_connection_id),
109            PathNewConnectionId => inc!(path_new_connection_id),
110            RetireConnectionId => inc!(retire_connection_id),
111            PathRetireConnectionId => inc!(path_retire_connection_id),
112            PathChallenge => inc!(path_challenge),
113            PathResponse => inc!(path_response),
114            ConnectionClose | ApplicationClose => inc!(connection_close),
115            AckFrequency => inc!(ack_frequency),
116            ImmediateAck => inc!(immediate_ack),
117            HandshakeDone => inc!(handshake_done),
118            ObservedIpv4Addr | ObservedIpv6Addr => inc!(observed_addr),
119            PathAbandon => inc!(path_abandon),
120            PathStatusAvailable => inc!(path_status_available),
121            PathStatusBackup => inc!(path_status_backup),
122            MaxPathId => inc!(max_path_id),
123            PathsBlocked => inc!(paths_blocked),
124            PathCidsBlocked => inc!(path_cids_blocked),
125            AddIpv4Address | AddIpv6Address => inc!(add_address),
126            ReachOutAtIpv4 | ReachOutAtIpv6 => inc!(reach_out),
127            RemoveAddress => inc!(remove_address),
128        };
129    }
130}
131
132impl std::fmt::Debug for FrameStats {
133    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134        let Self {
135            acks,
136            path_acks,
137            ack_frequency,
138            crypto,
139            connection_close,
140            data_blocked,
141            datagram,
142            handshake_done,
143            immediate_ack,
144            max_data,
145            max_stream_data,
146            max_streams_bidi,
147            max_streams_uni,
148            new_connection_id,
149            path_new_connection_id,
150            new_token,
151            path_challenge,
152            path_response,
153            ping,
154            reset_stream,
155            retire_connection_id,
156            path_retire_connection_id,
157            stream_data_blocked,
158            streams_blocked_bidi,
159            streams_blocked_uni,
160            stop_sending,
161            stream,
162            observed_addr,
163            path_abandon,
164            path_status_available,
165            path_status_backup,
166            max_path_id,
167            paths_blocked,
168            path_cids_blocked,
169            add_address,
170            reach_out,
171            remove_address,
172        } = self;
173        f.debug_struct("FrameStats")
174            .field("ACK", acks)
175            .field("ACK_FREQUENCY", ack_frequency)
176            .field("CONNECTION_CLOSE", connection_close)
177            .field("CRYPTO", crypto)
178            .field("DATA_BLOCKED", data_blocked)
179            .field("DATAGRAM", datagram)
180            .field("HANDSHAKE_DONE", handshake_done)
181            .field("IMMEDIATE_ACK", immediate_ack)
182            .field("MAX_DATA", max_data)
183            .field("MAX_PATH_ID", max_path_id)
184            .field("MAX_STREAM_DATA", max_stream_data)
185            .field("MAX_STREAMS_BIDI", max_streams_bidi)
186            .field("MAX_STREAMS_UNI", max_streams_uni)
187            .field("NEW_CONNECTION_ID", new_connection_id)
188            .field("NEW_TOKEN", new_token)
189            .field("PATHS_BLOCKED", paths_blocked)
190            .field("PATH_ABANDON", path_abandon)
191            .field("PATH_ACK", path_acks)
192            .field("PATH_STATUS_AVAILABLE", path_status_available)
193            .field("PATH_STATUS_BACKUP", path_status_backup)
194            .field("PATH_CHALLENGE", path_challenge)
195            .field("PATH_CIDS_BLOCKED", path_cids_blocked)
196            .field("PATH_NEW_CONNECTION_ID", path_new_connection_id)
197            .field("PATH_RESPONSE", path_response)
198            .field("PATH_RETIRE_CONNECTION_ID", path_retire_connection_id)
199            .field("PING", ping)
200            .field("RESET_STREAM", reset_stream)
201            .field("RETIRE_CONNECTION_ID", retire_connection_id)
202            .field("STREAM_DATA_BLOCKED", stream_data_blocked)
203            .field("STREAMS_BLOCKED_BIDI", streams_blocked_bidi)
204            .field("STREAMS_BLOCKED_UNI", streams_blocked_uni)
205            .field("STOP_SENDING", stop_sending)
206            .field("STREAM", stream)
207            .field("OBSERVED_ADDRESS", observed_addr)
208            .field("ADD_ADDRESS", add_address)
209            .field("REACH_OUT", reach_out)
210            .field("REMOVE_ADDRESS", remove_address)
211            .finish()
212    }
213}
214
215/// Statistics related to a transmission path.
216#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
217#[non_exhaustive]
218pub struct PathStats {
219    /// Current best estimate of this connection's latency (round-trip-time).
220    pub rtt: Duration,
221    /// Statistics about datagrams and bytes sent on this path.
222    pub udp_tx: UdpStats,
223    /// Statistics about datagrams and bytes received on this path.
224    pub udp_rx: UdpStats,
225    /// Statistics about frames transmitted on this path.
226    pub frame_tx: FrameStats,
227    /// Statistics about frames received on this path.
228    pub frame_rx: FrameStats,
229    /// Current congestion window of the connection.
230    pub cwnd: u64,
231    /// Congestion events on the connection.
232    pub congestion_events: u64,
233    /// Spurious congestion events on the connection.
234    pub spurious_congestion_events: u64,
235    /// The number of QUIC packets sent on this path.
236    ///
237    /// This counts all packets that are tracked for acknowledgement, including MTUD probes
238    /// and other probes. It does *not* count off-path packets (e.g. off-path path challenges,
239    /// off-path path responses, or NAT traversal probes) which are sent via a different code
240    /// path and are not tracked for acknowledgement.
241    ///
242    /// More specific counters such as [`Self::sent_plpmtud_probes`] allow breaking this number
243    /// down further. To get the number of non-probe packets sent, subtract
244    /// [`Self::sent_plpmtud_probes`] from this value.
245    ///
246    /// This counts individual QUIC packets, which may differ from [`UdpStats::datagrams`] when
247    /// packets are coalesced into a single UDP datagram.
248    pub sent_packets: u64,
249    /// The total number of QUIC bytes sent on this path (sum of all sent packet sizes).
250    ///
251    /// This counts only the QUIC packet payload bytes, not UDP/IP header bytes.
252    /// It does not count bytes for ACK-only (non-ack-eliciting, non-padded) packets, in an
253    /// effort to stay consistent with [`Self::lost_bytes`].
254    ///
255    /// If you're interested in the full amount of bytes transmitted, consider looking
256    /// at [`ConnectionStats::udp_tx`].
257    pub sent_bytes: u64,
258    /// The number of packets lost on this path.
259    ///
260    /// This counts all packets declared lost, including MTUD probes. More specific counters
261    /// such as [`Self::lost_plpmtud_probes`] allow breaking this number down further.
262    pub lost_packets: u64,
263    /// The number of bytes lost on this path.
264    ///
265    /// This does not count bytes for ACK-only (non-ack-eliciting, non-padded) packets.
266    pub lost_bytes: u64,
267    /// The number of PLPMTUD probe packets sent on this path.
268    ///
269    /// These are also counted by [`Self::sent_packets`] and [`Self::sent_bytes`].
270    /// They are also counted by [`UdpStats::datagrams`].
271    pub sent_plpmtud_probes: u64,
272    /// The number of PLPMTUD probe packets lost on this path.
273    ///
274    /// These are also counted by [`Self::lost_packets`] and [`Self::lost_bytes`].
275    pub lost_plpmtud_probes: u64,
276    /// The number of times a black hole was detected in the path.
277    pub black_holes_detected: u64,
278    /// Largest UDP payload size the path currently supports.
279    pub current_mtu: u16,
280}
281
282/// Connection statistics.
283///
284/// The fields here are a sum of the respective fields in the [`PathStats`] for all the
285/// paths that exist as well as all paths that previously existed.
286#[derive(Debug, Default, Clone)]
287#[non_exhaustive]
288pub struct ConnectionStats {
289    /// Statistics about UDP datagrams transmitted on the connection.
290    pub udp_tx: UdpStats,
291    /// Statistics about UDP datagrams received on the connection.
292    pub udp_rx: UdpStats,
293    /// Statistics about frames transmitted on the connection.
294    pub frame_tx: FrameStats,
295    /// Statistics about frames received on the connection.
296    pub frame_rx: FrameStats,
297    /// The number of QUIC packets sent on the connection (sum across all paths).
298    ///
299    /// See also [`PathStats::sent_packets`].
300    pub sent_packets: u64,
301    /// The total number of QUIC bytes sent on the connection (sum across all paths).
302    ///
303    /// See also [`PathStats::sent_bytes`].
304    pub sent_bytes: u64,
305    /// The number of packets lost on the connection.
306    ///
307    /// See also [`PathStats::lost_packets`].
308    pub lost_packets: u64,
309    /// The number of bytes lost on the connection.
310    ///
311    /// See also [`PathStats::lost_bytes`].
312    pub lost_bytes: u64,
313
314    /// Number of [`super::Transmit`] produced by this connection.
315    #[cfg(test)]
316    pub(crate) transmits_tx: u64,
317}
318
319impl std::ops::Add<PathStats> for ConnectionStats {
320    type Output = Self;
321
322    fn add(self, rhs: PathStats) -> Self::Output {
323        // Be aware that Connection::stats() relies on the fact this function ignores the
324        // rtt, cwnd and current_mtu fields.
325        let PathStats {
326            rtt: _,
327            udp_tx,
328            udp_rx,
329            frame_tx,
330            frame_rx,
331            cwnd: _,
332            congestion_events: _,
333            spurious_congestion_events: _,
334            sent_packets,
335            sent_bytes,
336            lost_packets,
337            lost_bytes,
338            sent_plpmtud_probes: _,
339            lost_plpmtud_probes: _,
340            black_holes_detected: _,
341            current_mtu: _,
342        } = rhs;
343        Self {
344            udp_tx: self.udp_tx + udp_tx,
345            udp_rx: self.udp_rx + udp_rx,
346            frame_tx: self.frame_tx + frame_tx,
347            frame_rx: self.frame_rx + frame_rx,
348            sent_packets: self.sent_packets + sent_packets,
349            sent_bytes: self.sent_bytes + sent_bytes,
350            lost_packets: self.lost_packets + lost_packets,
351            lost_bytes: self.lost_bytes + lost_bytes,
352            #[cfg(test)]
353            transmits_tx: self.transmits_tx,
354        }
355    }
356}
357
358impl std::ops::AddAssign<PathStats> for ConnectionStats {
359    fn add_assign(&mut self, rhs: PathStats) {
360        // Be aware that Connection::stats() relies on the fact this function ignores the
361        // rtt, cwnd and current_mtu fields.
362        let PathStats {
363            rtt: _,
364            udp_tx: path_udp_tx,
365            udp_rx: path_udp_rx,
366            frame_tx: path_frame_tx,
367            frame_rx: path_frame_rx,
368            cwnd: _,
369            congestion_events: _,
370            spurious_congestion_events: _,
371            sent_packets: path_sent_packets,
372            sent_bytes: path_sent_bytes,
373            lost_packets: path_lost_packets,
374            lost_bytes: path_lost_bytes,
375            sent_plpmtud_probes: _,
376            lost_plpmtud_probes: _,
377            black_holes_detected: _,
378            current_mtu: _,
379        } = rhs;
380        let Self {
381            udp_tx,
382            udp_rx,
383            frame_tx,
384            frame_rx,
385            sent_packets,
386            sent_bytes,
387            lost_packets,
388            lost_bytes,
389            #[cfg(test)]
390                transmits_tx: _,
391        } = self;
392        *udp_tx += path_udp_tx;
393        *udp_rx += path_udp_rx;
394        *frame_tx += path_frame_tx;
395        *frame_rx += path_frame_rx;
396        *sent_packets += path_sent_packets;
397        *sent_bytes += path_sent_bytes;
398        *lost_packets += path_lost_packets;
399        *lost_bytes += path_lost_bytes;
400    }
401}
402
403/// Helper to make [`PathStats`] infallibly available.
404///
405/// This helper also helps with borrowing issues compared to having the [`Self::get_mut`]
406/// function as a helper directly on [`Connection`].
407///
408/// [`Connection`]: super::Connection
409#[derive(Debug, Default)]
410pub(super) struct PathStatsMap(FxHashMap<PathId, PathStats>);
411
412impl PathStatsMap {
413    /// Returns the [`PathStats`] for the path.
414    pub(super) fn get_mut(&mut self, path_id: PathId) -> &mut PathStats {
415        self.0.entry(path_id).or_default()
416    }
417
418    pub(super) fn get(&self, path_id: PathId) -> Option<PathStats> {
419        self.0.get(&path_id).copied()
420    }
421
422    /// An iterator over all contained [`PathStats`].
423    pub(super) fn iter_stats(&self) -> impl Iterator<Item = &PathStats> {
424        self.0.values()
425    }
426
427    /// Removes the stats for a given path.
428    ///
429    /// Only do this once you are discarding the path.
430    pub(super) fn discard(&mut self, path_id: &PathId) {
431        self.0.remove(path_id);
432    }
433}