1use qlog::events::quic::QuicFrame;
30use tabled::settings::location::ByColumnName;
31use tabled::settings::object::Segment;
32use tabled::settings::Alignment;
33use tabled::settings::Disable;
34use tabled::settings::Modify;
35use tabled::settings::Style;
36use tabled::Table;
37
38use crate::config::AppConfig;
39use crate::datastore::Datastore;
40use crate::datastore::PrintStatsConfig;
41use crate::request_stub::*;
42use crate::stringify_last;
43use crate::LogFileData;
44
45pub fn request_timing_table(
46 lf: &LogFileData, config: &AppConfig,
47) -> Option<Table> {
48 let mut table = Table::new(lf.datastore.http_requests.values());
49 table.with(Modify::new(Segment::all()).with(Alignment::right()));
50
51 if config.report_omit_upload {
52 table
53 .with(Disable::column(ByColumnName::new(CLIENT_CONTENT_LENGTH)))
54 .with(Disable::column(ByColumnName::new(CLIENT_TRANSFERRED)))
55 .with(Disable::column(ByColumnName::new(UPLOAD_TIME)))
56 .with(Disable::column(ByColumnName::new(UPLOAD_RATE)));
57 }
58
59 match lf.datastore.vantage_point {
60 crate::datastore::VantagePoint::Client => {
61 table
62 .with(Disable::column(ByColumnName::new(SERVER_RX_HDR_TX_HDR)))
63 .with(Disable::column(ByColumnName::new(
64 SERVER_TX_HDR_TX_FIRST_HDR,
65 )))
66 .with(Disable::column(ByColumnName::new(
67 SERVER_TX_HDR_TX_LAST_HDR,
68 )))
69 .with(Disable::column(ByColumnName::new(
70 SERVER_TX_FIRST_DATA_TX_LAST_DATA,
71 )));
72 },
73
74 crate::datastore::VantagePoint::Server => {
75 },
77 }
78
79 if config.report_omit_priorities {
80 table
81 .with(Disable::column(ByColumnName::new(CLIENT_PRI)))
82 .with(Disable::column(ByColumnName::new(SERVER_PRI)));
83 }
84
85 if config.report_text_csv {
86 let style = Style::empty().vertical(',');
87
88 table.with(style);
89 }
90
91 Some(table)
92}
93
94pub fn print_stats(data_store: &Datastore, config: &PrintStatsConfig) {
95 if config.rx_flow_control {
96 print_rx_max_data_frames(data_store);
97 print_rx_max_stream_data_frames(data_store);
98 }
99
100 if config.tx_flow_control {
101 print_tx_max_data_frames(data_store);
102 print_tx_max_stream_data_frames(data_store);
103 }
104
105 if config.reset_streams {
106 print_tx_reset_stream_frames(data_store);
107 print_rx_reset_stream_frames(data_store);
108 }
109
110 if config.tx_stream_frames {
111 print_tx_stream_frames(data_store);
112 }
113
114 if config.stream_buffering {
115 print_local_stream_buffer_reads(data_store);
116 print_local_stream_buffer_writes(data_store);
117 print_local_stream_buffer_dropped(data_store);
118 }
119
120 if config.packet_stats {
121 print_sent_packet_stats(data_store);
122 }
123}
124
125fn print_sent_packet_stats(data_store: &Datastore) {
126 println!("### sent packets ###");
127 for (pkt_space, pkts) in &data_store.packet_sent {
128 println!("\t# packet space={:?}", pkt_space);
129
130 for (pkt_num, pkt_info) in pkts {
131 let (length, payload_length) = match &pkt_info.raw {
132 Some(raw) => (raw.length, raw.payload_length),
133
134 None => (None, None),
135 };
136
137 println!(
138 "\tpkt_num={}, acked=TODO-unknown, length={:?}, payload_length={:?}, ",
139 pkt_num, length, payload_length
140 );
141 }
142 println!();
143 }
144}
145
146fn print_rx_max_data_frames(data_store: &Datastore) {
147 println!("### received MAX_DATA frames ###");
148 if data_store
149 .received_stream_max_data_tracker
150 .per_stream
151 .is_empty()
152 {
153 println!(" None")
154 } else {
155 println!(
156 " first={:?}, last={:?}",
157 data_store.received_max_data.first().unwrap(),
158 data_store.received_max_data.last().unwrap()
159 );
160 }
161}
162
163fn print_tx_max_data_frames(data_store: &Datastore) {
164 println!("### sent MAX_DATA frames ###");
165 println!(
166 " total_count={}, first={:?}, last={:?}",
167 data_store.sent_max_data.len(),
168 data_store.sent_max_data.first().unwrap(),
169 data_store.sent_max_data.last().unwrap()
170 );
171}
172
173fn print_rx_max_stream_data_frames(data_store: &Datastore) {
174 println!("### received MAX_STREAM_DATA frames ###");
175 if data_store
176 .received_stream_max_data_tracker
177 .per_stream
178 .is_empty()
179 {
180 println!(" None")
181 } else {
182 for entry in &data_store.received_stream_max_data_tracker.per_stream {
183 println!(
184 " stream={}, total_count={}, first={:?}, last={}",
185 entry.0,
186 entry.1.len(),
187 entry.1.first(),
188 stringify_last(entry.1)
189 );
190 }
191 }
192}
193
194fn print_tx_max_stream_data_frames(data_store: &Datastore) {
195 println!("### sent MAX_STREAM_DATA frames ###");
196 if data_store
197 .sent_stream_max_data_tracker
198 .per_stream
199 .is_empty()
200 {
201 println!(" None")
202 } else {
203 for entry in &data_store.sent_stream_max_data_tracker.per_stream {
204 println!(
205 " stream={}, total_count={}, first={:?}, last={:?}",
206 entry.0,
207 entry.1.len(),
208 entry.1.first().unwrap(),
209 entry.1.last().unwrap()
210 );
211 }
212 }
213}
214
215fn print_tx_reset_stream_frames(data_store: &Datastore) {
216 println!("### sent RESET_STREAM frames ###");
217 if data_store.sent_reset_stream.is_empty() {
218 println!(" None")
219 } else {
220 for entry in &data_store.sent_reset_stream {
221 println!(
222 " stream={}, total_count={}, first={:?}, last={:?}",
223 entry.0,
224 entry.1.len(),
225 entry.1.first(),
226 stringify_last(entry.1)
227 );
228 }
229 }
230}
231
232fn print_rx_reset_stream_frames(data_store: &Datastore) {
233 println!("### received RESET_STREAM frames ###");
234 if data_store.received_reset_stream.is_empty() {
235 println!(" None")
236 } else {
237 for entry in &data_store.received_reset_stream {
238 println!(
239 " stream={}, total_count={}, first={:?}, last={:?}",
240 entry.0,
241 entry.1.len(),
242 entry.1.first(),
243 stringify_last(entry.1)
244 );
245 }
246 }
247}
248
249fn print_local_stream_buffer_reads(data_store: &Datastore) {
250 println!("### local stream buffer reads ###");
251 if data_store.stream_buffer_reads_tracker.per_stream.is_empty() {
252 println!(" None")
253 } else {
254 for entry in &data_store.stream_buffer_reads_tracker.per_stream {
255 println!(
256 " stream={}, total_count={}, first=(offset={}, length={}), last=(offset={}, length={}), total_length={}",
257 entry.0,
258 entry.1.len(),
259 entry.1.first().unwrap().1.offset,
260 entry.1.first().unwrap().1.length,
261 entry.1.last().unwrap().1.offset,
262 entry.1.last().unwrap().1.length,
263 entry.1.last().unwrap().1.offset + entry.1.last().unwrap().1.length,
264 );
265 }
266 }
267}
268
269fn print_local_stream_buffer_writes(data_store: &Datastore) {
270 println!("### local stream buffer writes ###");
271 if data_store
272 .stream_buffer_writes_tracker
273 .per_stream
274 .is_empty()
275 {
276 println!(" None")
277 } else {
278 for entry in &data_store.stream_buffer_writes_tracker.per_stream {
279 println!(
280 " stream={}, total_count={}, first=(offset={}, length={}), last=(offset={}, length={}), total_length={}",
281 entry.0,
282 entry.1.len(),
283 entry.1.first().unwrap().1.offset,
284 entry.1.first().unwrap().1.length,
285 entry.1.last().unwrap().1.offset,
286 entry.1.last().unwrap().1.length,
287 entry.1.last().unwrap().1.offset + entry.1.last().unwrap().1.length,
288 );
289 }
290 }
291}
292
293fn print_local_stream_buffer_dropped(data_store: &Datastore) {
294 println!("### local stream buffer dropped ###");
295 if data_store
296 .stream_buffer_dropped_tracker
297 .per_stream
298 .is_empty()
299 {
300 println!(" None")
301 } else {
302 for entry in &data_store.stream_buffer_dropped_tracker.per_stream {
303 println!(
304 " stream={}, total_count={}, first=(offset={}, length={}), last=(offset={}, length={}), total_length={}",
305 entry.0,
306 entry.1.len(),
307 entry.1.first().unwrap().1.offset,
308 entry.1.first().unwrap().1.length,
309 entry.1.last().unwrap().1.offset,
310 entry.1.last().unwrap().1.length,
311 entry.1.last().unwrap().1.offset + entry.1.last().unwrap().1.length,
312 );
313 }
314 }
315}
316
317fn print_tx_stream_frames(data_store: &Datastore) {
318 println!("### sent STREAM frames ###");
319 if data_store.sent_stream_frames.is_empty() {
320 println!(" None")
321 } else {
322 for entry in &data_store.sent_stream_frames {
323 let total = match entry.1.last() {
324 Some((_, QuicFrame::Stream { offset, raw, .. })) => {
325 let offset = offset.unwrap_or_default();
326 let length = raw
327 .clone()
328 .unwrap_or_default()
329 .payload_length
330 .unwrap_or_default();
331 format!("{}", offset + length)
332 },
333
334 _ => "n/a".to_string(),
335 };
336
337 println!(
338 " stream={}, total_count={}, first={:?}, last={:?}, total_length={}",
339 entry.0,
340 entry.1.len(),
341 entry.1.first(),
342 stringify_last(entry.1),
343 total
344 );
345 }
346 }
347}
348
349pub fn print_flow_control(data: &[LogFileData]) {
350 println!("================");
352 println!("flow control stuff");
353 println!("================");
354 for lf in data {
355 println!(
356 "Session={}, host={}",
357 lf.datastore.session_id.unwrap_or(-1),
358 lf.datastore
359 .host
360 .clone()
361 .unwrap_or("ERROR UNKNOWN".to_string())
362 );
363
364 println!(" Initial Client connection window, Initial Client Bidi Local Stream Window");
365 println!(
366 " {},{}",
367 lf.datastore.client_quic_tps.initial_max_data.unwrap_or(0),
368 lf.datastore
369 .client_quic_tps
370 .initial_max_stream_data_bidi_local
371 .unwrap_or(0)
372 );
373
374 for (stream_id, points) in
375 &lf.datastore.netlog_quic_client_side_window_updates
376 {
377 println!(" Stream {} flow control updates", stream_id);
378 println!(" Time, Value");
379
380 for (time, val) in points {
381 println!(" {},{}", time, val);
382 }
383 }
384 }
385}
386
387pub fn print_packet_loss(data: &[LogFileData]) {
388 println!("================");
390 println!("QUIC packet loss");
391 println!("================");
392 for lf in data {
393 let is_received_some = lf
394 .datastore
395 .netlog_ack_sent_missing_packet
396 .contains_key(&crate::PacketType::Handshake) ||
397 lf.datastore
398 .netlog_ack_sent_missing_packet
399 .contains_key(&crate::PacketType::Initial) ||
400 lf.datastore
401 .netlog_ack_sent_missing_packet
402 .contains_key(&crate::PacketType::OneRtt) ||
403 lf.datastore
404 .netlog_ack_sent_missing_packet
405 .contains_key(&crate::PacketType::ZeroRtt) ||
406 lf.datastore
407 .netlog_ack_sent_missing_packet
408 .contains_key(&crate::PacketType::Retry) ||
409 lf.datastore
410 .netlog_ack_sent_missing_packet
411 .contains_key(&crate::PacketType::VersionNegotiation);
412
413 if !lf.datastore.netlog_ack_received_missing_packet.is_empty() ||
414 is_received_some
415 {
416 println!(
417 "Session={}, host={}",
418 lf.datastore.session_id.unwrap_or(-1),
419 lf.datastore
420 .host
421 .clone()
422 .unwrap_or("ERROR UNKNOWN".to_string())
423 );
424 }
425
426 if !lf.datastore.netlog_ack_received_missing_packet.is_empty() {
427 println!(" Packets sent and lost");
428 println!(" Packet Type, Packet tx count, Packet lost count, Packet loss %, Packets lost");
429
430 for (pkt_type, pkt_nums) in
431 &lf.datastore.netlog_ack_received_missing_packet
432 {
433 if let Some(pkts) = lf.datastore.packet_received.get(pkt_type) {
434 let total_pkts_tx = pkts.len();
435 let pkt_loss = pkt_nums.len() as f64 / total_pkts_tx as f64;
436
437 if !pkt_nums.is_empty() {
438 println!(
439 " {:?}, {}, {}, {:.2}, {:?}",
440 pkt_type,
441 total_pkts_tx,
442 pkt_nums.len(),
443 pkt_loss * 100f64,
444 pkt_nums
445 );
446 }
447 }
448 }
449
450 println!();
451 }
452
453 if is_received_some {
454 println!(" Packet lost and not received");
455 println!(" Packet Type, Packet rx count, Packet lost count, Packet loss %, Packets lost");
456
457 for (pkt_type, pkt_nums) in
458 &lf.datastore.netlog_ack_sent_missing_packet
459 {
460 if let Some(pkts) = lf.datastore.packet_received.get(pkt_type) {
461 let total_pkts_tx = pkts.len();
462 let pkt_loss = pkt_nums.len() as f64 / total_pkts_tx as f64;
463
464 if !pkt_nums.is_empty() {
465 println!(
466 " {:?}, {}, {}, {:.2}, {:?}",
467 pkt_type,
468 total_pkts_tx,
469 pkt_nums.len(),
470 pkt_loss * 100f64,
471 pkt_nums
472 );
473 }
474 }
475 }
476
477 println!();
478 }
479 }
480
481 println!();
482}