1use qlog::events::http3::HttpHeader;
28use std::borrow::Cow;
29use std::fmt::Display;
30
31use crate::datastore::H2StreamReset;
32use crate::datastore::QuicStreamReset;
33use crate::datastore::QuicStreamStopSending;
34use crate::datastore::RequestActor;
35use crate::datastore::RequestAtClientDeltas;
36use crate::datastore::RequestAtServerDeltas;
37use netlog::http::headers_to_map;
38
39pub const CLIENT_CONTENT_LENGTH: &str = "Request Content-Length";
40pub const CLIENT_TRANSFERRED: &str = "Request Transferred";
41pub const UPLOAD_TIME: &str = "Upload duration (ms)";
42pub const UPLOAD_RATE: &str = "Upload rate (Mbps)";
43pub const SERVER_RX_HDR_TX_HDR: &str = "Server Rx Hdr, Tx Hdr";
44pub const SERVER_TX_HDR_TX_FIRST_HDR: &str = "Server Tx Hdr, Tx First Hdr";
45pub const SERVER_TX_HDR_TX_LAST_HDR: &str = "Server Tx Hdr, Tx Last Hdr";
46pub const SERVER_TX_FIRST_DATA_TX_LAST_DATA: &str =
47 "Server Tx First Data, Tx Last Data (Download time)";
48
49pub const CLIENT_PRI: &str = "Client Priority Header";
50pub const SERVER_PRI: &str = "Server Priority Header";
51
52const MAX_PATH_LENGTH: usize = 80;
53
54#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
55pub struct NaOption<T> {
56 pub inner: Option<T>,
57}
58
59impl<T> NaOption<T> {
60 pub fn new(value: Option<T>) -> Self {
61 Self { inner: value }
62 }
63}
64
65impl<T: std::fmt::Display> Display for NaOption<T> {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 match self.inner.as_ref() {
68 Some(v) => write!(f, "{}", v),
69 None => write!(f, "n/a"),
70 }
71 }
72}
73
74#[derive(Debug, Default)]
75pub struct HttpRequestStub {
76 pub request_actor: RequestActor,
77 pub stream_id: u64,
78 pub host: NaOption<String>,
79 pub method: NaOption<String>,
80 pub path: NaOption<String>,
81 pub status: NaOption<String>,
82 pub client_content_length: NaOption<String>,
83 pub server_content_length: NaOption<String>,
84 pub client_pri_hdr: NaOption<String>,
85 pub server_pri_hdr: NaOption<String>,
86 pub priority_updates: Vec<String>,
87
88 pub time_discovery: Option<f64>,
89 pub time_first_headers_rx: Option<f64>,
90 pub time_first_headers_tx: Option<f64>,
91
92 pub time_first_data_rx: Option<f64>,
93 pub time_first_data_tx: Option<f64>,
94
95 pub time_last_data_rx: Option<f64>,
96 pub time_last_data_tx: Option<f64>,
97
98 pub time_fin_rx: Option<f64>,
99
100 pub time_data_rx_set: Vec<(f64, u64)>,
102 pub time_data_tx_set: Vec<(f64, u64)>,
103
104 pub client_transferred_bytes: NaOption<u64>,
105 pub server_transferred_bytes: NaOption<u64>,
106
107 pub avg_upload_rate: NaOption<f64>,
108 pub avg_download_rate_d2d: NaOption<f64>,
109 pub avg_download_rate_h2d: NaOption<f64>,
110
111 pub at_client_deltas: Option<RequestAtClientDeltas>,
112 pub at_server_deltas: Option<RequestAtServerDeltas>,
113
114 pub quic_stream_stop_sending_sent: Option<QuicStreamStopSending>,
115 pub quic_stream_stop_sending_received: Option<QuicStreamStopSending>,
116
117 pub quic_stream_reset_sent: Option<QuicStreamReset>,
118 pub quic_stream_reset_received: Option<QuicStreamReset>,
119
120 pub h2_stream_reset_sent: Option<H2StreamReset>,
121 pub h2_stream_reset_receive: Option<H2StreamReset>,
122}
123
124struct ClientDeltaStrings {
125 pub discovery_tx_hdr: String,
126 pub tx_hdr_rx_hdr: String,
127 pub tx_hdr_rx_first_data: String,
128 pub tx_hdr_rx_last_data: String,
129 pub download_time_d2d: String,
130 pub download_time_h2d: String,
131 pub upload_time: String,
132}
133
134impl Default for ClientDeltaStrings {
135 fn default() -> Self {
136 let na = NaOption::<u8>::new(None);
137
138 Self {
139 discovery_tx_hdr: na.to_string(),
140 tx_hdr_rx_hdr: na.to_string(),
141 tx_hdr_rx_first_data: na.to_string(),
142 tx_hdr_rx_last_data: na.to_string(),
143 download_time_d2d: na.to_string(),
144 download_time_h2d: na.to_string(),
145 upload_time: na.to_string(),
146 }
147 }
148}
149
150impl HttpRequestStub {
151 fn client_deltas(&self) -> ClientDeltaStrings {
152 match self.at_client_deltas {
153 Some(d) => ClientDeltaStrings {
154 discovery_tx_hdr: d.discover_tx_hdr.to_string(),
155 tx_hdr_rx_hdr: d.tx_hdr_rx_hdr.to_string(),
156 tx_hdr_rx_first_data: d.tx_hdr_rx_first_data.to_string(),
157 tx_hdr_rx_last_data: d.tx_hdr_rx_last_data.to_string(),
158 download_time_d2d: d.rx_first_data_rx_last_data.to_string(),
159 download_time_h2d: d.tx_hdr_rx_last_data.to_string(),
160 upload_time: d.tx_first_data_tx_last_data.to_string(),
161 },
162
163 None => ClientDeltaStrings::default(),
164 }
165 }
166
167 fn server_deltas(&self) -> (String, String, String, String) {
168 match self.at_server_deltas {
169 Some(d) => (
170 d.rx_hdr_tx_hdr.to_string(),
171 d.rx_hdr_tx_first_data.to_string(),
172 d.rx_hdr_tx_last_data.to_string(),
173 d.tx_first_data_tx_last_data.to_string(),
174 ),
175
176 None => {
177 let na = NaOption::<u8>::new(None);
178 (
179 na.to_string(),
180 na.to_string(),
181 na.to_string(),
182 na.to_string(),
183 )
184 },
185 }
186 }
187}
188
189impl tabled::Tabled for HttpRequestStub {
190 const LENGTH: usize = 28;
191
192 fn fields(&self) -> Vec<Cow<'_, str>> {
193 let mut path = self.path.to_string();
195 if path.len() > MAX_PATH_LENGTH {
196 path.truncate(MAX_PATH_LENGTH);
197 path.push_str("<snip>");
198 }
199
200 let req_start = match self.request_actor {
201 RequestActor::Client => self.time_first_headers_tx,
202 RequestActor::Server => self.time_first_headers_rx,
203 };
204 let req_start = NaOption::new(req_start);
205
206 let req_end = match self.request_actor {
209 RequestActor::Client =>
210 if self.time_last_data_rx.is_some() {
211 self.time_last_data_rx
212 } else {
213 self.time_first_headers_rx
214 },
215 RequestActor::Server =>
216 if self.time_last_data_tx.is_some() {
217 self.time_first_data_tx
218 } else {
219 self.time_first_headers_tx
220 },
221 };
222
223 let req_end = NaOption::new(req_end);
224
225 let client_deltas = self.client_deltas();
226
227 let (
228 server_rx_hdr_tx_hdr,
229 server_rx_hdr_tx_first_data,
230 server_rx_hdr_tx_last_data,
231 server_tx_first_data_tx_last_data,
232 ) = self.server_deltas();
233
234 let rst_stream_sent =
235 match (&self.quic_stream_reset_sent, &self.h2_stream_reset_sent) {
236 (Some(v), None) => v.to_string(),
237 (None, Some(v)) => v.to_string(),
238 _ => NaOption::<u8>::new(None).to_string(),
239 };
240
241 let rst_stream_received = match (
242 &self.quic_stream_reset_received,
243 &self.h2_stream_reset_receive,
244 ) {
245 (Some(v), None) => v.to_string(),
246 (None, Some(v)) => v.to_string(),
247 _ => NaOption::<u8>::new(None).to_string(),
248 };
249
250 let stop_sending_sent = match &self.quic_stream_stop_sending_sent {
251 Some(v) => v.to_string(),
252 None => NaOption::<u8>::new(None).to_string(),
253 };
254
255 vec![
256 self.stream_id.to_string().into(),
257 self.method.to_string().into(),
258 self.host.to_string().into(),
259 path.into(),
260 self.status.to_string().into(),
261 self.server_content_length.to_string().into(),
262 self.server_transferred_bytes.to_string().into(),
263 NaOption::new(self.time_discovery).to_string().into(),
264 req_start.to_string().into(),
265 req_end.to_string().into(),
266 client_deltas.discovery_tx_hdr.into(),
267 client_deltas.download_time_h2d.into(),
268 self.avg_download_rate_h2d.to_string().into(),
269 client_deltas.download_time_d2d.into(),
270 self.avg_download_rate_d2d.to_string().into(),
271 client_deltas.tx_hdr_rx_hdr.into(),
272 client_deltas.tx_hdr_rx_first_data.into(),
273 client_deltas.tx_hdr_rx_last_data.into(),
274 self.client_content_length.to_string().into(),
275 self.client_transferred_bytes.to_string().into(),
276 client_deltas.upload_time.into(),
277 self.avg_upload_rate.to_string().into(),
278 server_rx_hdr_tx_hdr.into(),
279 server_rx_hdr_tx_first_data.into(),
280 server_rx_hdr_tx_last_data.into(),
281 server_tx_first_data_tx_last_data.into(),
282 self.client_pri_hdr.to_string().into(),
283 self.server_pri_hdr.to_string().into(),
284 rst_stream_sent.into(),
285 rst_stream_received.into(),
286 stop_sending_sent.into(),
287 ]
288 }
289
290 fn headers() -> Vec<Cow<'static, str>> {
291 vec![
292 "ID".into(),
293 "Method".into(),
294 "Host".into(),
295 "Path".into(),
296 "Status".into(),
297 "Response Content-Length (bytes)".into(),
298 "Response Transferred (bytes)".into(),
299 "Request Discovered Time".into(),
300 "Request Start Time".into(),
301 "Request End Time".into(),
302 "Stalled duration (ms)".into(),
303 "Download duration (h2d) (ms)".into(),
304 "Download rate (h2d) (Mbps)".into(),
305 "Download duration (d2d) (ms)".into(),
306 "Download rate (d2d) (Mbps)".into(),
307 "Client Tx Hdr, Rx Hdr".into(),
308 "Client Tx Hdr, Rx First Data".into(),
309 "Client Tx Hdr, Rx Last Data".into(),
310 CLIENT_CONTENT_LENGTH.into(),
311 CLIENT_TRANSFERRED.into(),
312 UPLOAD_TIME.into(),
313 UPLOAD_RATE.into(),
314 SERVER_RX_HDR_TX_HDR.into(),
315 SERVER_TX_HDR_TX_FIRST_HDR.into(),
316 SERVER_TX_HDR_TX_LAST_HDR.into(),
317 SERVER_TX_FIRST_DATA_TX_LAST_DATA.into(),
318 CLIENT_PRI.into(),
319 SERVER_PRI.into(),
320 "Reset Stream Sent".into(),
321 "Reset Stream Received".into(),
322 "Stop Sending Sent".into(),
323 ]
324 }
325}
326
327pub fn find_header_value(hdrs: &[HttpHeader], name: &str) -> Option<String> {
328 hdrs.iter()
329 .find(|&h| h.name.as_deref() == Some(name))
330 .and_then(|h| h.value.clone())
331}
332
333impl HttpRequestStub {
334 pub fn set_request_info_from_netlog(&mut self, hdrs: &[String]) {
335 let headers = headers_to_map(hdrs);
337
338 self.method = NaOption::new(headers.get(":method").cloned());
339 self.host = NaOption::new(headers.get(":authority").cloned());
340 self.path = NaOption::new(headers.get(":path").cloned());
341 self.client_pri_hdr = NaOption::new(headers.get("priority").cloned());
342 self.client_content_length =
343 NaOption::new(headers.get("content-length").cloned());
344 }
345
346 pub fn set_response_info_from_netlog(&mut self, hdrs: &[String]) {
347 let headers = headers_to_map(hdrs);
349
350 self.status = NaOption::new(headers.get(":status").cloned());
351 self.server_pri_hdr = NaOption::new(headers.get("priority").cloned());
352 self.server_content_length =
353 NaOption::new(headers.get("content-length").cloned());
354 }
355
356 pub fn set_request_info_from_qlog(&mut self, hdrs: &[HttpHeader]) {
357 self.method = NaOption::new(find_header_value(hdrs, ":method"));
358 self.host = NaOption::new(find_header_value(hdrs, ":authority"));
359 self.path = NaOption::new(find_header_value(hdrs, ":path"));
360 self.client_pri_hdr = NaOption::new(find_header_value(hdrs, "priority"));
361 self.client_content_length =
362 NaOption::new(find_header_value(hdrs, "content-lengt"));
363 }
364
365 pub fn set_response_info_from_qlog(&mut self, hdrs: &[HttpHeader]) {
366 self.status = NaOption::new(find_header_value(hdrs, ":status"));
367 self.server_pri_hdr = NaOption::new(find_header_value(hdrs, "priority"));
368 self.server_content_length =
369 NaOption::new(find_header_value(hdrs, "content-length"));
370 }
371
372 fn maybe_megabits_per_second(
374 start: Option<f64>, end: Option<f64>, bytes: Option<u64>,
375 ) -> NaOption<f64> {
376 match (end, start, bytes) {
377 (Some(end), Some(start), Some(bytes)) => {
378 let mut total_time = end - start;
379 if total_time == 0.0 {
382 total_time = 1.0
383 };
384
385 total_time /= 1000.0;
387
388 let megabits = (bytes * 8) as f64 / 1000000.0;
389 let avg_rate = megabits / total_time;
390
391 NaOption::new(Some(avg_rate))
392 },
393
394 _ => NaOption::<f64>::new(None),
395 }
396 }
397
398 pub fn calculate_upload_download_rate(&mut self) {
399 match self.request_actor {
400 RequestActor::Client => {
401 self.avg_upload_rate = Self::maybe_megabits_per_second(
402 self.time_first_data_tx,
403 self.time_last_data_tx,
404 self.client_transferred_bytes.inner,
405 );
406
407 self.avg_download_rate_d2d = Self::maybe_megabits_per_second(
408 self.time_first_data_rx,
409 self.time_last_data_rx,
410 self.server_transferred_bytes.inner,
411 );
412
413 self.avg_download_rate_h2d = Self::maybe_megabits_per_second(
414 self.time_first_headers_tx,
415 self.time_last_data_rx,
416 self.server_transferred_bytes.inner,
417 );
418 },
419
420 RequestActor::Server => {
421 self.avg_upload_rate = Self::maybe_megabits_per_second(
422 self.time_first_data_rx,
423 self.time_last_data_rx,
424 self.client_transferred_bytes.inner,
425 );
426
427 self.avg_download_rate_d2d = Self::maybe_megabits_per_second(
428 self.time_first_data_tx,
429 self.time_last_data_tx,
430 self.server_transferred_bytes.inner,
431 );
432 },
433 }
434 }
435
436 fn maybe_time_delta(start: Option<f64>, end: Option<f64>) -> NaOption<f64> {
437 match (start, end) {
438 (Some(start), Some(end)) => NaOption::new(Some(end - start)),
439
440 _ => NaOption::<f64>::new(None),
441 }
442 }
443
444 pub fn calculate_deltas(&mut self) {
445 match self.request_actor {
446 RequestActor::Client => {
447 let discover_tx_hdr = Self::maybe_time_delta(
448 self.time_discovery,
449 self.time_first_headers_tx,
450 );
451
452 let tx_hdr_rx_hdr = Self::maybe_time_delta(
453 self.time_first_headers_tx,
454 self.time_first_headers_rx,
455 );
456
457 let tx_hdr_rx_first_data = Self::maybe_time_delta(
458 self.time_first_headers_tx,
459 self.time_first_data_rx,
460 );
461
462 let tx_hdr_rx_last_data = Self::maybe_time_delta(
463 self.time_first_headers_tx,
464 self.time_last_data_rx,
465 );
466
467 let tx_first_data_tx_last_data = Self::maybe_time_delta(
468 self.time_first_data_tx,
469 self.time_last_data_tx,
470 );
471
472 let rx_first_data_rx_last_data = Self::maybe_time_delta(
473 self.time_first_data_rx,
474 self.time_last_data_rx,
475 );
476
477 let rx_hdr_rx_last_data = Self::maybe_time_delta(
478 self.time_first_headers_rx,
479 self.time_last_data_rx,
480 );
481
482 self.at_client_deltas = Some(RequestAtClientDeltas {
483 discover_tx_hdr,
484 tx_hdr_rx_hdr,
485 tx_hdr_rx_first_data,
486 tx_hdr_rx_last_data,
487 tx_first_data_tx_last_data,
488 rx_first_data_rx_last_data,
489 rx_hdr_rx_last_data,
490 });
491 },
492
493 RequestActor::Server => {
494 let rx_hdr_tx_hdr = match (
495 self.time_first_headers_tx,
496 self.time_first_headers_rx,
497 ) {
498 (Some(end), Some(start)) => NaOption::new(Some(end - start)),
499
500 _ => NaOption::<f64>::new(None),
501 };
502
503 let rx_hdr_tx_first_data =
504 match (self.time_first_data_tx, self.time_first_headers_rx) {
505 (Some(end), Some(start)) =>
506 NaOption::new(Some(end - start)),
507
508 _ => NaOption::<f64>::new(None),
509 };
510
511 let rx_hdr_tx_last_data =
512 match (self.time_last_data_tx, self.time_first_headers_rx) {
513 (Some(end), Some(start)) =>
514 NaOption::new(Some(end - start)),
515
516 _ => NaOption::<f64>::new(None),
517 };
518
519 let tx_first_data_tx_last_data =
520 match (self.time_last_data_tx, self.time_first_data_tx) {
521 (Some(end), Some(start)) =>
522 NaOption::new(Some(end - start)),
523
524 _ => NaOption::<f64>::new(None),
525 };
526
527 self.at_server_deltas = Some(RequestAtServerDeltas {
528 rx_hdr_tx_hdr,
529 rx_hdr_tx_first_data,
530 rx_hdr_tx_last_data,
531 tx_first_data_tx_last_data,
532 });
533 },
534 }
535 }
536}