Skip to main content

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    /// Can be less than `datagrams` when GSO, GRO, and/or batched system calls are in use.
24    pub ios: u64,
25}
26
27impl UdpStats {
28    pub(crate) fn on_sent(&mut self, datagrams: u64, bytes: usize) {
29        self.datagrams += datagrams;
30        self.bytes += bytes as u64;
31        self.ios += 1;
32    }
33}
34
35/// Number of frames transmitted or received of each frame type.
36#[derive(Default, Copy, Clone, PartialEq, Eq, derive_more::Add, derive_more::AddAssign)]
37#[non_exhaustive]
38#[allow(missing_docs)]
39pub struct FrameStats {
40    pub acks: u64,
41    pub path_acks: u64,
42    pub ack_frequency: u64,
43    pub crypto: u64,
44    pub connection_close: u64,
45    pub data_blocked: u64,
46    pub datagram: u64,
47    pub handshake_done: u8,
48    pub immediate_ack: u64,
49    pub max_data: u64,
50    pub max_stream_data: u64,
51    pub max_streams_bidi: u64,
52    pub max_streams_uni: u64,
53    pub new_connection_id: u64,
54    pub path_new_connection_id: u64,
55    pub new_token: u64,
56    pub path_challenge: u64,
57    pub path_response: u64,
58    pub ping: u64,
59    pub reset_stream: u64,
60    pub retire_connection_id: u64,
61    pub path_retire_connection_id: u64,
62    pub stream_data_blocked: u64,
63    pub streams_blocked_bidi: u64,
64    pub streams_blocked_uni: u64,
65    pub stop_sending: u64,
66    pub stream: u64,
67    pub observed_addr: u64,
68    pub path_abandon: u64,
69    pub path_status_available: u64,
70    pub path_status_backup: u64,
71    pub max_path_id: u64,
72    pub paths_blocked: u64,
73    pub path_cids_blocked: u64,
74    pub add_address: u64,
75    pub reach_out: u64,
76    pub remove_address: u64,
77}
78
79impl FrameStats {
80    pub(crate) fn record(&mut self, frame_type: FrameType) {
81        use FrameType::*;
82        // Increments the field. Added for readability
83        macro_rules! inc {
84            ($field_name: ident) => {{ self.$field_name = self.$field_name.saturating_add(1) }};
85        }
86        match frame_type {
87            Padding => {}
88            Ping => inc!(ping),
89            Ack | AckEcn => inc!(acks),
90            PathAck | PathAckEcn => inc!(path_acks),
91            ResetStream => inc!(reset_stream),
92            StopSending => inc!(stop_sending),
93            Crypto => inc!(crypto),
94            Datagram(_) => inc!(datagram),
95            NewToken => inc!(new_token),
96            MaxData => inc!(max_data),
97            MaxStreamData => inc!(max_stream_data),
98            MaxStreamsBidi => inc!(max_streams_bidi),
99            MaxStreamsUni => inc!(max_streams_uni),
100            DataBlocked => inc!(data_blocked),
101            Stream(_) => inc!(stream),
102            StreamDataBlocked => inc!(stream_data_blocked),
103            StreamsBlockedUni => inc!(streams_blocked_uni),
104            StreamsBlockedBidi => inc!(streams_blocked_bidi),
105            NewConnectionId => inc!(new_connection_id),
106            PathNewConnectionId => inc!(path_new_connection_id),
107            RetireConnectionId => inc!(retire_connection_id),
108            PathRetireConnectionId => inc!(path_retire_connection_id),
109            PathChallenge => inc!(path_challenge),
110            PathResponse => inc!(path_response),
111            ConnectionClose | ApplicationClose => inc!(connection_close),
112            AckFrequency => inc!(ack_frequency),
113            ImmediateAck => inc!(immediate_ack),
114            HandshakeDone => inc!(handshake_done),
115            ObservedIpv4Addr | ObservedIpv6Addr => inc!(observed_addr),
116            PathAbandon => inc!(path_abandon),
117            PathStatusAvailable => inc!(path_status_available),
118            PathStatusBackup => inc!(path_status_backup),
119            MaxPathId => inc!(max_path_id),
120            PathsBlocked => inc!(paths_blocked),
121            PathCidsBlocked => inc!(path_cids_blocked),
122            AddIpv4Address | AddIpv6Address => inc!(add_address),
123            ReachOutAtIpv4 | ReachOutAtIpv6 => inc!(reach_out),
124            RemoveAddress => inc!(remove_address),
125        };
126    }
127}
128
129impl std::fmt::Debug for FrameStats {
130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131        let Self {
132            acks,
133            path_acks,
134            ack_frequency,
135            crypto,
136            connection_close,
137            data_blocked,
138            datagram,
139            handshake_done,
140            immediate_ack,
141            max_data,
142            max_stream_data,
143            max_streams_bidi,
144            max_streams_uni,
145            new_connection_id,
146            path_new_connection_id,
147            new_token,
148            path_challenge,
149            path_response,
150            ping,
151            reset_stream,
152            retire_connection_id,
153            path_retire_connection_id,
154            stream_data_blocked,
155            streams_blocked_bidi,
156            streams_blocked_uni,
157            stop_sending,
158            stream,
159            observed_addr,
160            path_abandon,
161            path_status_available,
162            path_status_backup,
163            max_path_id,
164            paths_blocked,
165            path_cids_blocked,
166            add_address,
167            reach_out,
168            remove_address,
169        } = self;
170        f.debug_struct("FrameStats")
171            .field("ACK", acks)
172            .field("ACK_FREQUENCY", ack_frequency)
173            .field("CONNECTION_CLOSE", connection_close)
174            .field("CRYPTO", crypto)
175            .field("DATA_BLOCKED", data_blocked)
176            .field("DATAGRAM", datagram)
177            .field("HANDSHAKE_DONE", handshake_done)
178            .field("IMMEDIATE_ACK", immediate_ack)
179            .field("MAX_DATA", max_data)
180            .field("MAX_PATH_ID", max_path_id)
181            .field("MAX_STREAM_DATA", max_stream_data)
182            .field("MAX_STREAMS_BIDI", max_streams_bidi)
183            .field("MAX_STREAMS_UNI", max_streams_uni)
184            .field("NEW_CONNECTION_ID", new_connection_id)
185            .field("NEW_TOKEN", new_token)
186            .field("PATHS_BLOCKED", paths_blocked)
187            .field("PATH_ABANDON", path_abandon)
188            .field("PATH_ACK", path_acks)
189            .field("PATH_STATUS_AVAILABLE", path_status_available)
190            .field("PATH_STATUS_BACKUP", path_status_backup)
191            .field("PATH_CHALLENGE", path_challenge)
192            .field("PATH_CIDS_BLOCKED", path_cids_blocked)
193            .field("PATH_NEW_CONNECTION_ID", path_new_connection_id)
194            .field("PATH_RESPONSE", path_response)
195            .field("PATH_RETIRE_CONNECTION_ID", path_retire_connection_id)
196            .field("PING", ping)
197            .field("RESET_STREAM", reset_stream)
198            .field("RETIRE_CONNECTION_ID", retire_connection_id)
199            .field("STREAM_DATA_BLOCKED", stream_data_blocked)
200            .field("STREAMS_BLOCKED_BIDI", streams_blocked_bidi)
201            .field("STREAMS_BLOCKED_UNI", streams_blocked_uni)
202            .field("STOP_SENDING", stop_sending)
203            .field("STREAM", stream)
204            .field("OBSERVED_ADDRESS", observed_addr)
205            .field("ADD_ADDRESS", add_address)
206            .field("REACH_OUT", reach_out)
207            .field("REMOVE_ADDRESS", remove_address)
208            .finish()
209    }
210}
211
212/// Statistics related to a transmission path.
213#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
214#[non_exhaustive]
215pub struct PathStats {
216    /// Current best estimate of this connection's latency (round-trip-time).
217    pub rtt: Duration,
218    /// Statistics about datagrams and bytes sent on this path.
219    pub udp_tx: UdpStats,
220    /// Statistics about datagrams and bytes received on this path.
221    pub udp_rx: UdpStats,
222    /// Statistics about frames transmitted on this path.
223    pub frame_tx: FrameStats,
224    /// Statistics about frames received on this path.
225    pub frame_rx: FrameStats,
226    /// Current congestion window of the connection.
227    pub cwnd: u64,
228    /// Congestion events on the connection.
229    pub congestion_events: u64,
230    /// The number of packets lost on this path.
231    pub lost_packets: u64,
232    /// The number of bytes lost on this path.
233    pub lost_bytes: u64,
234    /// The number of PLPMTUD probe packets sent on this path.
235    ///
236    /// These are also counted by [`UdpStats::datagrams`].
237    pub sent_plpmtud_probes: u64,
238    /// The number of PLPMTUD probe packets lost on this path.
239    ///
240    /// These are not included in [`Self::lost_packets`] and [`Self::lost_bytes`].
241    pub lost_plpmtud_probes: u64,
242    /// The number of times a black hole was detected in the path.
243    pub black_holes_detected: u64,
244    /// Largest UDP payload size the path currently supports.
245    pub current_mtu: u16,
246}
247
248/// Connection statistics.
249///
250/// The fields here are a sum of the respective fields in the [`PathStats`] for all the
251/// paths that exist as well as all paths that previously existed.
252#[derive(Debug, Default, Clone)]
253#[non_exhaustive]
254pub struct ConnectionStats {
255    /// Statistics about UDP datagrams transmitted on the connection.
256    pub udp_tx: UdpStats,
257    /// Statistics about UDP datagrams received on the connection.
258    pub udp_rx: UdpStats,
259    /// Statistics about frames transmitted on the connection.
260    pub frame_tx: FrameStats,
261    /// Statistics about frames received on the connection.
262    pub frame_rx: FrameStats,
263    /// The number of packets lost on the connection.
264    pub lost_packets: u64,
265    /// The number of bytes lost on the connection.
266    pub lost_bytes: u64,
267}
268
269impl std::ops::Add<PathStats> for ConnectionStats {
270    type Output = Self;
271
272    fn add(self, rhs: PathStats) -> Self::Output {
273        // Be aware that Connection::stats() relies on the fact this function ignores the
274        // rtt, cwnd and current_mtu fields.
275        let PathStats {
276            rtt: _,
277            udp_tx,
278            udp_rx,
279            frame_tx,
280            frame_rx,
281            cwnd: _,
282            congestion_events: _,
283            lost_packets,
284            lost_bytes,
285            sent_plpmtud_probes: _,
286            lost_plpmtud_probes: _,
287            black_holes_detected: _,
288            current_mtu: _,
289        } = rhs;
290        Self {
291            udp_tx: self.udp_tx + udp_tx,
292            udp_rx: self.udp_rx + udp_rx,
293            frame_tx: self.frame_tx + frame_tx,
294            frame_rx: self.frame_rx + frame_rx,
295            lost_packets: self.lost_packets + lost_packets,
296            lost_bytes: self.lost_bytes + lost_bytes,
297        }
298    }
299}
300
301impl std::ops::AddAssign<PathStats> for ConnectionStats {
302    fn add_assign(&mut self, rhs: PathStats) {
303        // Be aware that Connection::stats() relies on the fact this function ignores the
304        // rtt, cwnd and current_mtu fields.
305        let PathStats {
306            rtt: _,
307            udp_tx: path_udp_tx,
308            udp_rx: path_udp_rx,
309            frame_tx: path_frame_tx,
310            frame_rx: path_frame_rx,
311            cwnd: _,
312            congestion_events: _,
313            lost_packets: path_lost_packets,
314            lost_bytes: path_lost_bytes,
315            sent_plpmtud_probes: _,
316            lost_plpmtud_probes: _,
317            black_holes_detected: _,
318            current_mtu: _,
319        } = rhs;
320        let Self {
321            udp_tx,
322            udp_rx,
323            frame_tx,
324            frame_rx,
325            lost_packets,
326            lost_bytes,
327        } = self;
328        *udp_tx += path_udp_tx;
329        *udp_rx += path_udp_rx;
330        *frame_tx += path_frame_tx;
331        *frame_rx += path_frame_rx;
332        *lost_packets += path_lost_packets;
333        *lost_bytes += path_lost_bytes;
334    }
335}
336
337/// Helper to make [`PathStats`] infallibly available.
338///
339/// This helper also helps with borrowing issues compared to having the [`Self::for_path`]
340/// function as a helper directly on [`Connection`].
341///
342/// [`Connection`]: super::Connection
343#[derive(Debug, Default)]
344pub(super) struct PathStatsMap(FxHashMap<PathId, PathStats>);
345
346impl PathStatsMap {
347    /// Returns the [`PathStats`] for the path.
348    pub(super) fn for_path(&mut self, path_id: PathId) -> &mut PathStats {
349        self.0.entry(path_id).or_default()
350    }
351
352    /// An iterator over all contained [`PathStats`].
353    pub(super) fn iter_stats(&self) -> impl Iterator<Item = &PathStats> {
354        self.0.values()
355    }
356
357    /// Removes the stats for a given path.
358    ///
359    /// Only do this once you are discarding the path.
360    pub(super) fn discard(&mut self, path_id: &PathId) -> PathStats {
361        self.0.remove(path_id).unwrap_or_default()
362    }
363}