1use rustc_hash::FxHashMap;
4
5use crate::Duration;
6use crate::FrameType;
7
8use super::PathId;
9
10#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, derive_more::Add, derive_more::AddAssign)]
15#[non_exhaustive]
16pub struct UdpStats {
17 pub datagrams: u64,
19 pub bytes: u64,
21 #[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#[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 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#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
217#[non_exhaustive]
218pub struct PathStats {
219 pub rtt: Duration,
221 pub udp_tx: UdpStats,
223 pub udp_rx: UdpStats,
225 pub frame_tx: FrameStats,
227 pub frame_rx: FrameStats,
229 pub cwnd: u64,
231 pub congestion_events: u64,
233 pub spurious_congestion_events: u64,
235 pub lost_packets: u64,
237 pub lost_bytes: u64,
239 pub sent_plpmtud_probes: u64,
243 pub lost_plpmtud_probes: u64,
247 pub black_holes_detected: u64,
249 pub current_mtu: u16,
251}
252
253#[derive(Debug, Default, Clone)]
258#[non_exhaustive]
259pub struct ConnectionStats {
260 pub udp_tx: UdpStats,
262 pub udp_rx: UdpStats,
264 pub frame_tx: FrameStats,
266 pub frame_rx: FrameStats,
268 pub lost_packets: u64,
270 pub lost_bytes: u64,
272
273 #[cfg(test)]
275 pub(crate) transmits_tx: u64,
276}
277
278impl std::ops::Add<PathStats> for ConnectionStats {
279 type Output = Self;
280
281 fn add(self, rhs: PathStats) -> Self::Output {
282 let PathStats {
285 rtt: _,
286 udp_tx,
287 udp_rx,
288 frame_tx,
289 frame_rx,
290 cwnd: _,
291 congestion_events: _,
292 spurious_congestion_events: _,
293 lost_packets,
294 lost_bytes,
295 sent_plpmtud_probes: _,
296 lost_plpmtud_probes: _,
297 black_holes_detected: _,
298 current_mtu: _,
299 } = rhs;
300 Self {
301 udp_tx: self.udp_tx + udp_tx,
302 udp_rx: self.udp_rx + udp_rx,
303 frame_tx: self.frame_tx + frame_tx,
304 frame_rx: self.frame_rx + frame_rx,
305 lost_packets: self.lost_packets + lost_packets,
306 lost_bytes: self.lost_bytes + lost_bytes,
307 #[cfg(test)]
308 transmits_tx: self.transmits_tx,
309 }
310 }
311}
312
313impl std::ops::AddAssign<PathStats> for ConnectionStats {
314 fn add_assign(&mut self, rhs: PathStats) {
315 let PathStats {
318 rtt: _,
319 udp_tx: path_udp_tx,
320 udp_rx: path_udp_rx,
321 frame_tx: path_frame_tx,
322 frame_rx: path_frame_rx,
323 cwnd: _,
324 congestion_events: _,
325 spurious_congestion_events: _,
326 lost_packets: path_lost_packets,
327 lost_bytes: path_lost_bytes,
328 sent_plpmtud_probes: _,
329 lost_plpmtud_probes: _,
330 black_holes_detected: _,
331 current_mtu: _,
332 } = rhs;
333 let Self {
334 udp_tx,
335 udp_rx,
336 frame_tx,
337 frame_rx,
338 lost_packets,
339 lost_bytes,
340 #[cfg(test)]
341 transmits_tx: _,
342 } = self;
343 *udp_tx += path_udp_tx;
344 *udp_rx += path_udp_rx;
345 *frame_tx += path_frame_tx;
346 *frame_rx += path_frame_rx;
347 *lost_packets += path_lost_packets;
348 *lost_bytes += path_lost_bytes;
349 }
350}
351
352#[derive(Debug, Default)]
359pub(super) struct PathStatsMap(FxHashMap<PathId, PathStats>);
360
361impl PathStatsMap {
362 pub(super) fn get_mut(&mut self, path_id: PathId) -> &mut PathStats {
364 self.0.entry(path_id).or_default()
365 }
366
367 pub(super) fn get(&self, path_id: PathId) -> Option<PathStats> {
368 self.0.get(&path_id).copied()
369 }
370
371 pub(super) fn iter_stats(&self) -> impl Iterator<Item = &PathStats> {
373 self.0.values()
374 }
375
376 pub(super) fn discard(&mut self, path_id: &PathId) {
380 self.0.remove(path_id);
381 }
382}