From c2ae4c01d8fcd4157316fe27779e93a61be64a88 Mon Sep 17 00:00:00 2001 From: Sijie Yang Date: Tue, 23 Jan 2024 10:15:13 +0800 Subject: [PATCH] Update qlog events - add recovery_metrics_updated event - add recovery_packet_lost event - update quic_packet_received event - update quic_packet_sent event - add tquic_qvis.sh to convert qlog files to be compatible with qvis --- src/congestion_control/cubic.rs | 4 +- src/congestion_control/delivery_rate.rs | 9 +- src/connection/connection.rs | 64 +++++-- src/connection/recovery.rs | 214 ++++++++++++++++++++++-- src/connection/space.rs | 25 ++- src/qlog/events.rs | 8 +- tools/script/tquic_qvis.sh | 36 ++++ 7 files changed, 323 insertions(+), 37 deletions(-) create mode 100644 tools/script/tquic_qvis.sh diff --git a/src/congestion_control/cubic.rs b/src/congestion_control/cubic.rs index 7a4cbbf9..1bdbe9e4 100644 --- a/src/congestion_control/cubic.rs +++ b/src/congestion_control/cubic.rs @@ -579,7 +579,7 @@ mod tests { has_data: false, sent_size: pkt_size as usize, rate_sample_state: Default::default(), - reinjected: false, + ..SentPacket::default() }); } @@ -644,7 +644,7 @@ mod tests { has_data: false, sent_size: pkt_size as usize, rate_sample_state: Default::default(), - reinjected: false, + ..SentPacket::default() }); } diff --git a/src/congestion_control/delivery_rate.rs b/src/congestion_control/delivery_rate.rs index 277724ff..596e3fe1 100644 --- a/src/congestion_control/delivery_rate.rs +++ b/src/congestion_control/delivery_rate.rs @@ -285,7 +285,7 @@ mod tests { has_data: false, sent_size: 240, rate_sample_state: Default::default(), - reinjected: false, + ..SentPacket::default() }; rate_estimator.on_packet_sent(&mut pkt_n1, bytes_in_flight, bytes_lost); @@ -308,7 +308,7 @@ mod tests { has_data: false, sent_size: 240, rate_sample_state: Default::default(), - reinjected: false, + ..SentPacket::default() }; bytes_in_flight += pkt_n1.sent_size as u64; @@ -361,7 +361,6 @@ mod tests { let now = Instant::now(); let mut pkts_part1: Vec = Vec::new(); let mut pkts_part2: Vec = Vec::new(); - // let mut pkts_part3: Vec = Vec::new(); let bytes_lost = 0; let mut bytes_in_flight = 0; let pkt_size: u64 = 240; @@ -380,7 +379,7 @@ mod tests { has_data: false, sent_size: pkt_size as usize, rate_sample_state: Default::default(), - reinjected: false, + ..SentPacket::default() }); } @@ -397,7 +396,7 @@ mod tests { has_data: false, sent_size: pkt_size as usize, rate_sample_state: Default::default(), - reinjected: false, + ..SentPacket::default() }); } diff --git a/src/connection/connection.rs b/src/connection/connection.rs index 8690cbec..a6fedee8 100644 --- a/src/connection/connection.rs +++ b/src/connection/connection.rs @@ -583,6 +583,7 @@ impl Connection { // Process each QUIC frame in the QUIC packet let mut ack_eliciting_pkt = false; let mut probing_pkt = true; + let mut qframes = vec![]; while !payload.is_empty() { let (frame, len) = Frame::from_bytes(&mut payload, hdr.pkt_type)?; @@ -592,14 +593,23 @@ impl Connection { if !frame.probing() { probing_pkt = false; } + if self.qlog.is_some() { + qframes.push(frame.to_qlog()); + } self.recv_frame(frame, &hdr, pid, space_id, info.time)?; let _ = payload.split_to(len); } - // Write TransportPacketReceived event to qlog. + // Write events to qlog. if let Some(qlog) = &mut self.qlog { - Self::qlog_quic_packet_received(qlog, &hdr, pkt_num, read, payload_len); + // Write TransportPacketReceived event to qlog. + Self::qlog_quic_packet_received(qlog, &hdr, pkt_num, read, payload_len, qframes); + + // Write RecoveryMetricsUpdate event to qlog. + if let Ok(path) = self.paths.get_mut(pid) { + path.recovery.qlog_recovery_metrics_updated(qlog); + } } // Process acknowledged frames. @@ -715,6 +725,7 @@ impl Connection { space_id, &mut self.spaces, handshake_status, + self.qlog.as_mut(), now, )?; self.stats.lost_count += lost_pkts; @@ -1557,6 +1568,7 @@ impl Connection { )?; let sent_pkt = space::SentPacket { + pkt_type, pkt_num, time_sent: now, time_acked: None, @@ -1577,9 +1589,19 @@ impl Connection { self.paths.get(path_id)? ); - // Write TransportPacketSent event to qlog. + // Write events to qlog. if let Some(qlog) = &mut self.qlog { - Self::qlog_quic_packet_sent(qlog, &hdr, pkt_num, written, payload_len); + // Write TransportPacketSent event to qlog. + let mut qframes = Vec::with_capacity(sent_pkt.frames.len()); + for frame in &sent_pkt.frames { + qframes.push(frame.to_qlog()); + } + Self::qlog_quic_packet_sent(qlog, &hdr, pkt_num, written, payload_len, qframes); + + // Write RecoveryMetricsUpdate event to qlog. + if let Ok(path) = self.paths.get_mut(path_id) { + path.recovery.qlog_recovery_metrics_updated(qlog); + } } // Notify the packet sent event to the multipath scheduler @@ -2862,8 +2884,14 @@ impl Connection { SpaceId::Data, // TODO: update for multipath &mut self.spaces, handshake_status, + self.qlog.as_mut(), now, ); + + // Write RecoveryMetricsUpdate event to qlog. + if let Some(qlog) = &mut self.qlog { + path.recovery.qlog_recovery_metrics_updated(qlog); + } } } } @@ -3637,6 +3665,7 @@ impl Connection { pkt_num: u64, pkt_len: usize, payload_len: usize, + qlog_frames: Vec, ) { let qlog_pkt_hdr = events::PacketHeader::new_with_type( hdr.pkt_type.to_qlog(), @@ -3652,6 +3681,7 @@ impl Connection { }; let ev_data = events::EventData::QuicPacketReceived { header: qlog_pkt_hdr, + frames: Some(qlog_frames.into()), is_coalesced: None, retry_token: None, stateless_reset_token: None, @@ -3670,6 +3700,7 @@ impl Connection { pkt_num: u64, pkt_len: usize, payload_len: usize, + qlog_frames: Vec, ) { let qlog_pkt_hdr = events::PacketHeader::new_with_type( hdr.pkt_type.to_qlog(), @@ -3687,6 +3718,7 @@ impl Connection { let ev_data = events::EventData::QuicPacketSent { header: qlog_pkt_hdr, + frames: Some(qlog_frames.into()), is_coalesced: None, retry_token: None, stateless_reset_token: None, @@ -6611,29 +6643,38 @@ pub(crate) mod tests { let mut sfile = slog.reopen().unwrap(); let mut test_pair = TestPair::new_with_test_config()?; - assert_eq!(test_pair.handshake(), Ok(())); test_pair .client .set_qlog(Box::new(clog), "title".into(), "desc".into()); test_pair .server .set_qlog(Box::new(slog), "title".into(), "desc".into()); + assert_eq!(test_pair.handshake(), Ok(())); // Client create a stream and send data let data = Bytes::from_static(b"test data over quic"); - test_pair.client.stream_set_priority(0, 0, false)?; - test_pair.client.stream_write(0, data.clone(), true)?; - test_pair.client.stream_shutdown(0, Shutdown::Read, 0)?; + test_pair.client.stream_write(0, data.clone(), false)?; let packets = TestPair::conn_packets_out(&mut test_pair.client)?; + TestPair::conn_packets_in(&mut test_pair.server, packets)?; - // Server read data from the stream + // Client lost some packets + test_pair.client.stream_write(0, data.clone(), false)?; + let _ = TestPair::conn_packets_out(&mut test_pair.client)?; + test_pair.client.stream_write(0, data.clone(), false)?; + let packets = TestPair::conn_packets_out(&mut test_pair.client)?; TestPair::conn_packets_in(&mut test_pair.server, packets)?; + + // Server read data from the stream let mut buf = vec![0; data.len()]; test_pair.server.stream_read(0, &mut buf)?; - let packets = TestPair::conn_packets_out(&mut test_pair.server)?; TestPair::conn_packets_in(&mut test_pair.client, packets)?; + // Advance ticks until loss timeout + assert!(test_pair.client.timeout().is_some()); + let timeout = test_pair.client.timers.get(Timer::LossDetection); + test_pair.client.on_timeout(timeout.unwrap()); + // Check client qlog let mut clog_content = String::new(); cfile.read_to_string(&mut clog_content).unwrap(); @@ -6641,6 +6682,8 @@ pub(crate) mod tests { assert_eq!(clog_content.contains("quic:parameters_set"), true); assert_eq!(clog_content.contains("quic:stream_data_moved"), true); assert_eq!(clog_content.contains("quic:packet_sent"), true); + assert_eq!(clog_content.contains("recovery:metrics_updated"), true); + assert_eq!(clog_content.contains("recovery:packet_lost"), true); // Check server qlog let mut slog_content = String::new(); @@ -6649,6 +6692,7 @@ pub(crate) mod tests { assert_eq!(slog_content.contains("quic:parameters_set"), true); assert_eq!(slog_content.contains("quic:stream_data_moved"), true); assert_eq!(slog_content.contains("quic:packet_received"), true); + assert_eq!(slog_content.contains("recovery:metrics_updated"), true); Ok(()) } diff --git a/src/connection/recovery.rs b/src/connection/recovery.rs index 6128f486..79dca18a 100644 --- a/src/connection/recovery.rs +++ b/src/connection/recovery.rs @@ -32,6 +32,8 @@ use super::HandshakeStatus; use crate::congestion_control; use crate::congestion_control::CongestionController; use crate::frame; +use crate::qlog; +use crate::qlog::events::EventData; use crate::ranges::RangeSet; use crate::Error; use crate::RecoveryConfig; @@ -91,6 +93,10 @@ pub struct Recovery { /// Congestion controller for the corresponding path. pub congestion: Box, + /// It tracks the last metrics used for emitting qlog RecoveryMetricsUpdated + /// event. + last_metrics: RecoveryMetrics, + /// Trace id. trace_id: String, } @@ -110,6 +116,7 @@ impl Recovery { ack_eliciting_in_flight: 0, rtt: RttEstimator::new(conf.initial_rtt), congestion: congestion_control::build_congestion_controller(conf), + last_metrics: RecoveryMetrics::default(), trace_id: String::from(""), } } @@ -185,6 +192,7 @@ impl Recovery { /// Handle packet acknowledgment event. /// /// See RFC 9002 Section A.7. On Receiving an Acknowledgment. + #[allow(clippy::too_many_arguments)] pub(super) fn on_ack_received( &mut self, ranges: &RangeSet, @@ -192,6 +200,7 @@ impl Recovery { space_id: SpaceId, spaces: &mut PacketNumSpaceMap, handshake_status: HandshakeStatus, + qlog: Option<&mut qlog::QlogWriter>, now: Instant, ) -> Result<(usize, usize)> { let space = spaces.get_mut(space_id).ok_or(Error::InternalError)?; @@ -242,7 +251,7 @@ impl Recovery { } // Detect lost packets - let (lost_packets, lost_bytes) = self.detect_lost_packets(space, now); + let (lost_packets, lost_bytes) = self.detect_lost_packets(space, qlog, now); // Remove acked or lost packets from sent queue in batch. self.drain_sent_packets(space, now, self.rtt.smoothed_rtt()); @@ -374,7 +383,12 @@ impl Recovery { /// It is called every time an ACK is received or the time threshold loss /// detection timer expires. /// See RFC 9002 Section A.10. Detecting Lost Packets - fn detect_lost_packets(&mut self, space: &mut PacketNumSpace, now: Instant) -> (usize, usize) { + fn detect_lost_packets( + &mut self, + space: &mut PacketNumSpace, + mut qlog: Option<&mut qlog::QlogWriter>, + now: Instant, + ) -> (usize, usize) { space.loss_time = None; let mut lost_packets = 0; @@ -419,6 +433,9 @@ impl Recovery { } } latest_lost_packet = Some(unacked.clone()); + if let Some(qlog) = qlog.as_mut() { + self.qlog_recovery_packet_lost(qlog, unacked); + } trace!( "now={:?} {} {} ON_LOST {:?} inflight={} cwnd={}", now, @@ -532,6 +549,7 @@ impl Recovery { space_id: SpaceId, spaces: &mut PacketNumSpaceMap, handshake_status: HandshakeStatus, + qlog: Option<&mut qlog::QlogWriter>, now: Instant, ) -> (usize, usize) { let (earliest_loss_time, sid) = self.get_loss_time_and_space(space_id, spaces); @@ -543,7 +561,7 @@ impl Recovery { // Loss timer mode if earliest_loss_time.is_some() { // Time threshold loss detection. - let (lost_packets, lost_bytes) = self.detect_lost_packets(space, now); + let (lost_packets, lost_bytes) = self.detect_lost_packets(space, qlog, now); self.drain_sent_packets(space, now, self.rtt.smoothed_rtt()); self.set_loss_detection_timer(space_id, spaces, handshake_status, now); return (lost_packets, lost_bytes); @@ -783,6 +801,124 @@ impl Recovery { pub(crate) fn can_send(&self) -> bool { self.bytes_in_flight < self.congestion.congestion_window() as usize } + + /// Write a qlog RecoveryMetricsUpdated event if any recovery metric is updated. + pub(crate) fn qlog_recovery_metrics_updated(&mut self, qlog: &mut qlog::QlogWriter) { + let mut updated = false; + + let mut min_rtt = None; + if self.last_metrics.min_rtt != self.rtt.min_rtt() { + self.last_metrics.min_rtt = self.rtt.min_rtt(); + min_rtt = Some(self.last_metrics.min_rtt.as_secs_f32() * 1000.0); + updated = true; + } + + let mut smoothed_rtt = None; + if self.last_metrics.smoothed_rtt != self.rtt.smoothed_rtt() { + self.last_metrics.smoothed_rtt = self.rtt.smoothed_rtt(); + smoothed_rtt = Some(self.last_metrics.smoothed_rtt.as_secs_f32() * 1000.0); + updated = true; + } + + let mut latest_rtt = None; + if self.last_metrics.latest_rtt != self.rtt.latest_rtt() { + self.last_metrics.latest_rtt = self.rtt.latest_rtt(); + latest_rtt = Some(self.last_metrics.latest_rtt.as_secs_f32() * 1000.0); + updated = true; + } + + let mut rtt_variance = None; + if self.last_metrics.rttvar != self.rtt.rttvar() { + self.last_metrics.rttvar = self.rtt.rttvar(); + rtt_variance = Some(self.last_metrics.rttvar.as_secs_f32() * 1000.0); + updated = true; + } + + let mut congestion_window = None; + if self.last_metrics.cwnd != self.congestion.congestion_window() { + self.last_metrics.cwnd = self.congestion.congestion_window(); + congestion_window = Some(self.last_metrics.cwnd); + updated = true; + } + + let mut bytes_in_flight = None; + if self.last_metrics.bytes_in_flight != self.bytes_in_flight as u64 { + self.last_metrics.bytes_in_flight = self.bytes_in_flight as u64; + bytes_in_flight = Some(self.last_metrics.bytes_in_flight); + updated = true; + } + + let mut pacing_rate = None; + if self.last_metrics.pacing_rate != self.congestion.pacing_rate() { + self.last_metrics.pacing_rate = self.congestion.pacing_rate(); + pacing_rate = self.last_metrics.pacing_rate.map(|v| v * 8); // bps + updated = true; + } + + if !updated { + return; + } + + let ev_data = EventData::RecoveryMetricsUpdated { + min_rtt, + smoothed_rtt, + latest_rtt, + rtt_variance, + pto_count: None, + congestion_window, + bytes_in_flight, + ssthresh: None, + packets_in_flight: None, + pacing_rate, + }; + qlog.add_event_data(Instant::now(), ev_data).ok(); + } + + /// Write a qlog RecoveryPacketLost event. + pub(crate) fn qlog_recovery_packet_lost( + &mut self, + qlog: &mut qlog::QlogWriter, + pkt: &SentPacket, + ) { + let ev_data = EventData::RecoveryPacketLost { + header: Some(qlog::events::PacketHeader { + packet_type: pkt.pkt_type.to_qlog(), + packet_number: pkt.pkt_num, + ..qlog::events::PacketHeader::default() + }), + frames: None, + is_mtu_probe_packet: None, + trigger: None, + }; + qlog.add_event_data(Instant::now(), ev_data).ok(); + } +} + +/// Metrics used for emitting qlog RecoveryMetricsUpdated event. +#[derive(Default)] +struct RecoveryMetrics { + /// The minimum RTT observed on the path, ignoring ack delay + min_rtt: Duration, + + /// The smoothed RTT of the path is an exponentially weighted moving average + /// of an endpoint's RTT samples + smoothed_rtt: Duration, + + /// The most recent RTT sample. + latest_rtt: Duration, + + /// The RTT variance estimates the variation in the RTT samples using a + /// mean variation + rttvar: Duration, + + /// Congestion window in bytes. + cwnd: u64, + + /// Total number of bytes in fight. + bytes_in_flight: u64, + + /// Pacing rate in Bps + pacing_rate: Option, } #[cfg(test)] @@ -806,7 +942,7 @@ mod tests { in_flight: true, has_data: true, rate_sample_state: Default::default(), - reinjected: false, + ..SentPacket::default() } } @@ -864,7 +1000,15 @@ mod tests { let mut acked = RangeSet::default(); acked.insert(0..1); acked.insert(2..3); - recovery.on_ack_received(&acked, 0, SpaceId::Handshake, &mut spaces, status, now)?; + recovery.on_ack_received( + &acked, + 0, + SpaceId::Handshake, + &mut spaces, + status, + None, + now, + )?; assert_eq!(spaces.get(space_id).unwrap().sent.len(), 2); assert_eq!(spaces.get(space_id).unwrap().ack_eliciting_in_flight, 1); assert_eq!(recovery.ack_eliciting_in_flight, 1); @@ -872,7 +1016,7 @@ mod tests { // Advance ticks until loss timeout now = recovery.loss_detection_timer().unwrap(); let (lost_pkts, lost_bytes) = - recovery.on_loss_detection_timeout(SpaceId::Handshake, &mut spaces, status, now); + recovery.on_loss_detection_timeout(SpaceId::Handshake, &mut spaces, status, None, now); assert_eq!(lost_pkts, 1); assert_eq!(lost_bytes, 1001); assert_eq!(spaces.get(space_id).unwrap().ack_eliciting_in_flight, 0); @@ -928,16 +1072,30 @@ mod tests { acked.insert(1..4); // Detect packet loss base on reordering threshold - let (lost_pkts, lost_bytes) = - recovery.on_ack_received(&acked, 0, SpaceId::Handshake, &mut spaces, status, now)?; + let (lost_pkts, lost_bytes) = recovery.on_ack_received( + &acked, + 0, + SpaceId::Handshake, + &mut spaces, + status, + None, + now, + )?; assert_eq!(spaces.get(space_id).unwrap().sent.len(), 4); assert_eq!(lost_pkts, 1); assert_eq!(lost_bytes, 1000); // Advance ticks and fake receiving of duplicated ack now += recovery.rtt.smoothed_rtt(); - let (lost_pkts, lost_bytes) = - recovery.on_ack_received(&acked, 0, SpaceId::Handshake, &mut spaces, status, now)?; + let (lost_pkts, lost_bytes) = recovery.on_ack_received( + &acked, + 0, + SpaceId::Handshake, + &mut spaces, + status, + None, + now, + )?; assert_eq!(lost_pkts, 0); assert_eq!(lost_bytes, 0); @@ -982,8 +1140,15 @@ mod tests { now += Duration::from_millis(100); let mut acked = RangeSet::default(); acked.insert(0..1); - let (lost_pkts, lost_bytes) = - recovery.on_ack_received(&acked, 0, SpaceId::Handshake, &mut spaces, status, now)?; + let (lost_pkts, lost_bytes) = recovery.on_ack_received( + &acked, + 0, + SpaceId::Handshake, + &mut spaces, + status, + None, + now, + )?; assert_eq!(spaces.get(space_id).unwrap().sent.len(), 1); assert_eq!(lost_pkts, 0); assert_eq!(lost_bytes, 0); @@ -992,7 +1157,7 @@ mod tests { // Advance ticks until pto timeout now = recovery.loss_detection_timer().unwrap(); let (lost_pkts, lost_bytes) = - recovery.on_loss_detection_timeout(SpaceId::Handshake, &mut spaces, status, now); + recovery.on_loss_detection_timeout(SpaceId::Handshake, &mut spaces, status, None, now); assert_eq!(recovery.pto_count, 1); assert_eq!(lost_pkts, 0); assert_eq!(lost_bytes, 0); @@ -1045,7 +1210,15 @@ mod tests { now += Duration::from_millis(100); let mut acked = RangeSet::default(); acked.insert(0..2); - recovery.on_ack_received(&acked, 0, SpaceId::Handshake, &mut spaces, status, now)?; + recovery.on_ack_received( + &acked, + 0, + SpaceId::Handshake, + &mut spaces, + status, + None, + now, + )?; assert_eq!(spaces.get(SpaceId::Handshake).unwrap().sent.len(), 1); assert_eq!( spaces.get(SpaceId::Handshake).unwrap().bytes_in_flight, @@ -1146,7 +1319,7 @@ mod tests { vec![500..950], )); // Fake receiving duplicated ACK. - recovery.on_ack_received(&ack, 0, SpaceId::Data, &mut spaces, status, now)?; + recovery.on_ack_received(&ack, 0, SpaceId::Data, &mut spaces, status, None, now)?; assert!(check_acked_packets( &spaces.get(SpaceId::Data).unwrap().sent, vec![500..950], @@ -1222,8 +1395,15 @@ mod tests { acked.insert(0..2); // Detect packet loss base on reordering threshold - let (lost_pkts, lost_bytes) = - recovery.on_ack_received(&acked, 0, SpaceId::Handshake, &mut spaces, status, now)?; + let (lost_pkts, lost_bytes) = recovery.on_ack_received( + &acked, + 0, + SpaceId::Handshake, + &mut spaces, + status, + None, + now, + )?; assert_eq!(cwnd_before_ack, recovery.congestion.congestion_window()); Ok(()) diff --git a/src/connection/space.rs b/src/connection/space.rs index dd6211d6..e52900e3 100644 --- a/src/connection/space.rs +++ b/src/connection/space.rs @@ -20,6 +20,7 @@ use std::time::Instant; use rustc_hash::FxHashMap; use crate::frame; +use crate::packet; use crate::ranges::RangeSet; use crate::tls::Level; use crate::window::SeqNumWindow; @@ -303,6 +304,9 @@ pub struct RateSamplePacketState { /// Metadata of sent packet #[derive(Clone)] pub struct SentPacket { + /// The packet type of the sent packet. + pub pkt_type: packet::PacketType, + /// The packet number of the sent packet. pub pkt_num: u64, @@ -341,6 +345,25 @@ pub struct SentPacket { pub reinjected: bool, } +impl Default for SentPacket { + fn default() -> Self { + SentPacket { + pkt_type: packet::PacketType::OneRTT, + pkt_num: 0, + frames: vec![], + time_sent: Instant::now(), + time_acked: None, + time_lost: None, + ack_eliciting: false, + in_flight: false, + has_data: false, + sent_size: 0, + rate_sample_state: RateSamplePacketState::default(), + reinjected: false, + } + } +} + impl std::fmt::Debug for SentPacket { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "pn={:?}", self.pkt_num)?; @@ -444,7 +467,7 @@ mod tests { has_data: false, sent_size: 240, rate_sample_state: Default::default(), - reinjected: false, + ..SentPacket::default() }; assert_eq!( format!("{:?}", sent_pkt), diff --git a/src/qlog/events.rs b/src/qlog/events.rs index b2385018..4967847e 100644 --- a/src/qlog/events.rs +++ b/src/qlog/events.rs @@ -248,6 +248,7 @@ pub enum EventData { #[serde(rename = "quic:packet_sent")] QuicPacketSent { header: PacketHeader, + frames: Option>, is_coalesced: Option, retry_token: Option, stateless_reset_token: Option, @@ -262,6 +263,7 @@ pub enum EventData { #[serde(rename = "quic:packet_received")] QuicPacketReceived { header: PacketHeader, + frames: Option>, is_coalesced: Option, retry_token: Option, stateless_reset_token: Option, @@ -854,7 +856,7 @@ pub enum ConnectionState { Closed, } -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)] #[serde(rename_all = "snake_case")] pub enum PacketType { Initial, @@ -865,6 +867,7 @@ pub enum PacketType { OneRtt, Retry, VersionNegotiation, + #[default] Unknown, } @@ -877,7 +880,7 @@ pub enum PacketNumberSpace { } #[serde_with::skip_serializing_none] -#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)] +#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug, Default)] pub struct PacketHeader { pub packet_type: PacketType, pub packet_number: u64, @@ -1659,6 +1662,7 @@ pub mod tests { let pkt_hdr = new_test_pkt_hdr(PacketType::Initial); let event_data = EventData::QuicPacketSent { header: pkt_hdr, + frames: None, is_coalesced: None, retry_token: None, stateless_reset_token: None, diff --git a/tools/script/tquic_qvis.sh b/tools/script/tquic_qvis.sh new file mode 100644 index 00000000..7b982dc6 --- /dev/null +++ b/tools/script/tquic_qvis.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +# This tool is used to convert qlogs generated by tquic tools from JSON-SEQ +# format to JSON format, and make other changes to be compatible with qvis. +# See https://qvis.quictools.info + +# Check whether jq is installed +if ! command -v jq &> /dev/null +then + echo "Please install jq to use this script." + echo "See https://jqlang.github.io/jq/download/" + exit 1 +fi + +# Check whether a file name is provided +if [ "$#" -ne 1 ]; then + echo "Usage: $0 " + echo "Description: convert qlog from JSON-SEQ to JSON format compatible with qvis" + exit 1 +fi + +# Check whether the file exists +if [ ! -f "$1" ]; then + echo "Error: File '$1' not found." + exit 1 +fi + + +# Convert to JSON format +OUT="$1.qvis.json" +jq -s '.[1:] as $events | .[0] | .trace.events=$events | .traces=[.trace] | del(.trace) | .qlog_format="JSON"' $1 > $OUT + +# Change for backward compatibility +# Note qvis reportedly does not plan to implement qlog 0.4 and will continue +# using 0.3 until a 1.0 RC is specced. +sed -i -e 's/name": "quic:/name": "transport:/' -e 's/"qlog_version": "0.4"/"qlog_version": "0.3"/' $OUT