tokio_quiche/http3/stats.rs
1// Copyright (C) 2025, Cloudflare, Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8// * Redistributions of source code must retain the above copyright notice,
9// this list of conditions and the following disclaimer.
10//
11// * Redistributions in binary form must reproduce the above copyright
12// notice, this list of conditions and the following disclaimer in the
13// documentation and/or other materials provided with the distribution.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27use std::sync::atomic::AtomicI64;
28use std::sync::atomic::AtomicU64;
29use std::sync::atomic::Ordering;
30use std::time::Duration;
31
32use crossbeam::atomic::AtomicCell;
33use datagram_socket::StreamClosureKind;
34
35/// Stream-level HTTP/3 audit statistics recorded by
36/// [H3Driver](crate::http3::driver::H3Driver).
37#[derive(Debug)]
38pub struct H3AuditStats {
39 /// The stream ID of this session.
40 stream_id: u64,
41 /// The number of bytes sent over the stream.
42 downstream_bytes_sent: AtomicU64,
43 /// The number of bytes received over the stream.
44 downstream_bytes_recvd: AtomicU64,
45 /// A STOP_SENDING error code received from the peer.
46 ///
47 /// -1 indicates that this error code was not received yet.
48 recvd_stop_sending_error_code: AtomicI64,
49 /// A RESET_STREAM error code received from the peer.
50 ///
51 /// -1 indicates that this error code was not received yet.
52 recvd_reset_stream_error_code: AtomicI64,
53 /// A STOP_SENDING error code sent to the peer.
54 ///
55 /// -1 indicates that this error code was not received yet.
56 sent_stop_sending_error_code: AtomicI64,
57 /// A RESET_STREAM error code sent to the peer.
58 ///
59 /// -1 indicates that this error code was not received yet.
60 sent_reset_stream_error_code: AtomicI64,
61 /// Stream FIN received from the peer.
62 recvd_stream_fin: AtomicCell<StreamClosureKind>,
63 /// Stream FIN sent to the peer.
64 sent_stream_fin: AtomicCell<StreamClosureKind>,
65 /// Cumulative time between HEADERS failed flush and complete.
66 ///
67 /// Measured as the duration between the first moment a HEADERS frame was
68 /// not flushed in full, and the moment that it was completely flushed.
69 /// Measured across all HEADERS frames sent on the stream. A value of 0
70 /// indicates there was no failed flushing.
71 headers_flush_duration: AtomicCell<Duration>,
72}
73
74impl H3AuditStats {
75 pub fn new(stream_id: u64) -> Self {
76 Self {
77 stream_id,
78 downstream_bytes_sent: AtomicU64::new(0),
79 downstream_bytes_recvd: AtomicU64::new(0),
80 recvd_stop_sending_error_code: AtomicI64::new(-1),
81 recvd_reset_stream_error_code: AtomicI64::new(-1),
82 sent_stop_sending_error_code: AtomicI64::new(-1),
83 sent_reset_stream_error_code: AtomicI64::new(-1),
84 recvd_stream_fin: AtomicCell::new(StreamClosureKind::None),
85 sent_stream_fin: AtomicCell::new(StreamClosureKind::None),
86 headers_flush_duration: AtomicCell::new(Duration::from_secs(0)),
87 }
88 }
89
90 /// The stream ID of this session.
91 #[inline]
92 pub fn stream_id(&self) -> u64 {
93 self.stream_id
94 }
95
96 /// The number of bytes sent over the stream.
97 #[inline]
98 pub fn downstream_bytes_sent(&self) -> u64 {
99 self.downstream_bytes_sent.load(Ordering::SeqCst)
100 }
101
102 /// The number of bytes received over the stream.
103 #[inline]
104 pub fn downstream_bytes_recvd(&self) -> u64 {
105 self.downstream_bytes_recvd.load(Ordering::SeqCst)
106 }
107
108 /// A STOP_SENDING error code received from the peer.
109 ///
110 /// -1 indicates that this error code was not received yet.
111 #[inline]
112 pub fn recvd_stop_sending_error_code(&self) -> i64 {
113 self.recvd_stop_sending_error_code.load(Ordering::SeqCst)
114 }
115
116 /// A RESET_STREAM error code received from the peer.
117 ///
118 /// -1 indicates that this error code was not received yet.
119 #[inline]
120 pub fn recvd_reset_stream_error_code(&self) -> i64 {
121 self.recvd_reset_stream_error_code.load(Ordering::SeqCst)
122 }
123
124 /// A STOP_SENDING error code sent to the peer.
125 ///
126 /// -1 indicates that this error code was not received yet.
127 #[inline]
128 pub fn sent_stop_sending_error_code(&self) -> i64 {
129 self.sent_stop_sending_error_code.load(Ordering::SeqCst)
130 }
131
132 /// A RESET_STREAM error code sent to the peer.
133 ///
134 /// -1 indicates that this error code was not received yet.
135 #[inline]
136 pub fn sent_reset_stream_error_code(&self) -> i64 {
137 self.sent_reset_stream_error_code.load(Ordering::SeqCst)
138 }
139
140 /// Stream FIN received from the peer.
141 #[inline]
142 pub fn recvd_stream_fin(&self) -> StreamClosureKind {
143 self.recvd_stream_fin.load()
144 }
145
146 /// Stream FIN sent to the peer.
147 #[inline]
148 pub fn sent_stream_fin(&self) -> StreamClosureKind {
149 self.sent_stream_fin.load()
150 }
151
152 /// Cumulative time between HEADERS failed flush and complete.
153 ///
154 /// Measured as the duration between the first moment a HEADERS frame was
155 /// not flushed in full, and the moment that it was completely flushed.
156 /// Measured across all HEADERS frames sent on the stream. A value of 0
157 /// indicates there was no failed flushing.
158 #[inline]
159 pub fn headers_flush_duration(&self) -> Duration {
160 self.headers_flush_duration.load()
161 }
162
163 #[inline]
164 pub fn add_downstream_bytes_sent(&self, bytes_sent: u64) {
165 self.downstream_bytes_sent
166 .fetch_add(bytes_sent, Ordering::SeqCst);
167 }
168
169 #[inline]
170 pub fn add_downstream_bytes_recvd(&self, bytes_recvd: u64) {
171 self.downstream_bytes_recvd
172 .fetch_add(bytes_recvd, Ordering::SeqCst);
173 }
174
175 #[inline]
176 pub fn set_recvd_stop_sending_error_code(
177 &self, recvd_stop_sending_error_code: i64,
178 ) {
179 self.recvd_stop_sending_error_code
180 .store(recvd_stop_sending_error_code, Ordering::SeqCst);
181 }
182
183 #[inline]
184 pub fn set_recvd_reset_stream_error_code(
185 &self, recvd_reset_stream_error_code: i64,
186 ) {
187 self.recvd_reset_stream_error_code
188 .store(recvd_reset_stream_error_code, Ordering::SeqCst);
189 }
190
191 #[inline]
192 pub fn set_sent_stop_sending_error_code(
193 &self, sent_stop_sending_error_code: i64,
194 ) {
195 self.sent_stop_sending_error_code
196 .store(sent_stop_sending_error_code, Ordering::SeqCst);
197 }
198
199 #[inline]
200 pub fn set_sent_reset_stream_error_code(
201 &self, sent_reset_stream_error_code: i64,
202 ) {
203 self.sent_reset_stream_error_code
204 .store(sent_reset_stream_error_code, Ordering::SeqCst);
205 }
206
207 #[inline]
208 pub fn set_recvd_stream_fin(&self, recvd_stream_fin: StreamClosureKind) {
209 self.recvd_stream_fin.store(recvd_stream_fin);
210 }
211
212 #[inline]
213 pub fn set_sent_stream_fin(&self, sent_stream_fin: StreamClosureKind) {
214 self.sent_stream_fin.store(sent_stream_fin);
215 }
216
217 #[inline]
218 pub fn add_header_flush_duration(&self, duration: Duration) {
219 // NB: load and store may not be atomic but we aren't accessing the
220 // object from any other thread so things should be ok.
221 let current = self.headers_flush_duration.load();
222 self.headers_flush_duration.store(current + duration);
223 }
224}