diff --git a/apps/src/bin/quiche-server.rs b/apps/src/bin/quiche-server.rs index 73347f9725..0086572d70 100644 --- a/apps/src/bin/quiche-server.rs +++ b/apps/src/bin/quiche-server.rs @@ -541,8 +541,14 @@ fn main() { continue_write = false; for client in clients.values_mut() { // Reduce max_send_burst by 25% if loss is increasing more than 0.1%. - let loss_rate = - client.conn.stats().lost as f64 / client.conn.stats().sent as f64; + let stats = client.conn.stats(); + let finished = stats.acked.saturating_add(stats.lost); + let loss_rate = if finished == 0 { + 0.0 + } else { + stats.lost as f64 / finished as f64 + }; + if loss_rate > client.loss_rate + 0.001 { client.max_send_burst = client.max_send_burst / 4 * 3; // Minimum bound of 10xMSS. diff --git a/quiche/include/quiche.h b/quiche/include/quiche.h index 798559843b..8499231753 100644 --- a/quiche/include/quiche.h +++ b/quiche/include/quiche.h @@ -576,6 +576,9 @@ typedef struct { // The number of QUIC packets sent on this connection. size_t sent; + // The number of QUIC packets that were acked. + size_t acked; + // The number of QUIC packets that were lost. size_t lost; diff --git a/quiche/src/ffi.rs b/quiche/src/ffi.rs index 3306b6182c..1ed6ec9d57 100644 --- a/quiche/src/ffi.rs +++ b/quiche/src/ffi.rs @@ -1325,6 +1325,7 @@ pub extern "C" fn quiche_stream_iter_free(iter: *mut StreamIter) { pub struct Stats { recv: usize, sent: usize, + acked: usize, lost: usize, spurious_lost: usize, retrans: usize, @@ -1373,6 +1374,7 @@ pub extern "C" fn quiche_conn_stats(conn: &Connection, out: &mut Stats) { out.recv = stats.recv; out.sent = stats.sent; + out.acked = stats.acked; out.lost = stats.lost; out.spurious_lost = stats.spurious_lost; out.retrans = stats.retrans; diff --git a/quiche/src/lib.rs b/quiche/src/lib.rs index b505906f26..b7d7b6db6d 100644 --- a/quiche/src/lib.rs +++ b/quiche/src/lib.rs @@ -1374,6 +1374,9 @@ where /// Total number of sent packets. sent_count: usize, + /// Total number of acked packets. + acked_count: usize, + /// Total number of lost packets. lost_count: usize, @@ -2089,6 +2092,7 @@ impl Connection { recv_count: 0, sent_count: 0, + acked_count: 0, lost_count: 0, spurious_lost_count: 0, retrans_count: 0, @@ -7802,6 +7806,7 @@ impl Connection { Stats { recv: self.recv_count, sent: self.sent_count, + acked: self.acked_count, lost: self.lost_count, spurious_lost: self.spurious_lost_count, retrans: self.retrans_count, @@ -8301,6 +8306,7 @@ impl Connection { let OnAckReceivedOutcome { lost_packets, lost_bytes, + acked_packets, acked_bytes, spurious_losses, } = p.recovery.on_ack_received( @@ -8329,6 +8335,7 @@ impl Connection { self.lost_count += lost_packets; self.lost_bytes += lost_bytes as u64; + self.acked_count += acked_packets; self.acked_bytes += acked_bytes as u64; self.spurious_lost_count += spurious_losses; } @@ -9344,6 +9351,9 @@ pub struct Stats { /// The number of QUIC packets sent. pub sent: usize, + /// The number of QUIC packets that were acked. + pub acked: usize, + /// The number of QUIC packets that were lost. pub lost: usize, diff --git a/quiche/src/recovery/congestion/delivery_rate.rs b/quiche/src/recovery/congestion/delivery_rate.rs index 6cab772ad7..0f013cbbcd 100644 --- a/quiche/src/recovery/congestion/delivery_rate.rs +++ b/quiche/src/recovery/congestion/delivery_rate.rs @@ -507,6 +507,7 @@ mod tests { assert_eq!(ack_outcome, OnAckReceivedOutcome { lost_packets: 0, lost_bytes: 0, + acked_packets: packet_count, acked_bytes: mss * packet_count, spurious_losses: 0, }); diff --git a/quiche/src/recovery/congestion/recovery.rs b/quiche/src/recovery/congestion/recovery.rs index 736854331a..3fb8739582 100644 --- a/quiche/src/recovery/congestion/recovery.rs +++ b/quiche/src/recovery/congestion/recovery.rs @@ -102,6 +102,7 @@ struct RecoveryEpoch { } struct AckedDetectionResult { + acked_packets: usize, acked_bytes: usize, spurious_losses: usize, spurious_pkt_thresh: Option, @@ -125,6 +126,7 @@ impl RecoveryEpoch { ) -> Result { newly_acked.clear(); + let mut acked_packets = 0; let mut acked_bytes = 0; let mut spurious_losses = 0; let mut spurious_pkt_thresh = None; @@ -182,6 +184,8 @@ impl RecoveryEpoch { has_in_flight_spurious_loss = true; } } else { + acked_packets += 1; + if unacked.in_flight { self.in_flight_count -= 1; acked_bytes += unacked.size; @@ -213,6 +217,7 @@ impl RecoveryEpoch { self.drain_acked_and_lost_packets(now - rtt_stats.rtt()); Ok(AckedDetectionResult { + acked_packets, acked_bytes, spurious_losses, spurious_pkt_thresh, @@ -677,6 +682,7 @@ impl RecoveryOps for LegacyRecovery { trace_id: &str, ) -> Result { let AckedDetectionResult { + acked_packets, acked_bytes, spurious_losses, spurious_pkt_thresh, @@ -754,6 +760,7 @@ impl RecoveryOps for LegacyRecovery { Ok(OnAckReceivedOutcome { lost_packets, lost_bytes, + acked_packets, acked_bytes, spurious_losses, }) diff --git a/quiche/src/recovery/gcongestion/recovery.rs b/quiche/src/recovery/gcongestion/recovery.rs index 0372cbc0fd..81a07e6f70 100644 --- a/quiche/src/recovery/gcongestion/recovery.rs +++ b/quiche/src/recovery/gcongestion/recovery.rs @@ -119,6 +119,7 @@ struct RecoveryEpoch { } struct AckedDetectionResult { + acked_packets: usize, acked_bytes: usize, spurious_losses: usize, spurious_pkt_thresh: Option, @@ -178,6 +179,7 @@ impl RecoveryEpoch { ) -> Result { newly_acked.clear(); + let mut acked_packets = 0; let mut acked_bytes = 0; let mut spurious_losses = 0; let mut spurious_pkt_thresh = None; @@ -227,6 +229,8 @@ impl RecoveryEpoch { ack_eliciting, .. } => { + acked_packets += 1; + if in_flight { self.pkts_in_flight -= 1; acked_bytes += sent_bytes; @@ -260,6 +264,7 @@ impl RecoveryEpoch { self.drain_acked_and_lost_packets(); Ok(AckedDetectionResult { + acked_packets, acked_bytes, spurious_losses, spurious_pkt_thresh, @@ -818,6 +823,7 @@ impl RecoveryOps for GRecovery { let prior_in_flight = self.bytes_in_flight.get(); let AckedDetectionResult { + acked_packets, acked_bytes, spurious_losses, spurious_pkt_thresh, @@ -836,6 +842,7 @@ impl RecoveryOps for GRecovery { if self.newly_acked.is_empty() { return Ok(OnAckReceivedOutcome { + acked_packets, acked_bytes, spurious_losses, ..Default::default() @@ -892,6 +899,7 @@ impl RecoveryOps for GRecovery { Ok(OnAckReceivedOutcome { lost_packets, lost_bytes, + acked_packets, acked_bytes, spurious_losses, }) diff --git a/quiche/src/recovery/mod.rs b/quiche/src/recovery/mod.rs index 559fccceb4..d8c715c145 100644 --- a/quiche/src/recovery/mod.rs +++ b/quiche/src/recovery/mod.rs @@ -177,6 +177,7 @@ pub(crate) enum Recovery { pub struct OnAckReceivedOutcome { pub lost_packets: usize, pub lost_bytes: usize, + pub acked_packets: usize, pub acked_bytes: usize, pub spurious_losses: usize, } @@ -1035,6 +1036,7 @@ mod tests { OnAckReceivedOutcome { lost_packets: 0, lost_bytes: 0, + acked_packets: 2, acked_bytes: 2 * 1000, spurious_losses: 0, } @@ -1136,6 +1138,7 @@ mod tests { OnAckReceivedOutcome { lost_packets: 2, lost_bytes: 2000, + acked_packets: 2, acked_bytes: 2 * 1000, spurious_losses: 0, } @@ -1320,6 +1323,7 @@ mod tests { OnAckReceivedOutcome { lost_packets: 0, lost_bytes: 0, + acked_packets: 3, acked_bytes: 3 * 1000, spurious_losses: 0, } @@ -1412,6 +1416,7 @@ mod tests { OnAckReceivedOutcome { lost_packets: 1, lost_bytes: 1000, + acked_packets: 2, acked_bytes: 1000 * 2, spurious_losses: 0, } @@ -1442,6 +1447,7 @@ mod tests { OnAckReceivedOutcome { lost_packets: 0, lost_bytes: 0, + acked_packets: 1, acked_bytes: 1000, spurious_losses: 1, } @@ -1559,6 +1565,7 @@ mod tests { OnAckReceivedOutcome { lost_packets: 1, lost_bytes: 1000, + acked_packets: 1, acked_bytes: 1000, spurious_losses: 0, } @@ -1586,6 +1593,7 @@ mod tests { OnAckReceivedOutcome { lost_packets: 0, lost_bytes: 0, + acked_packets: 0, acked_bytes: 0, spurious_losses: 1, } @@ -1617,6 +1625,7 @@ mod tests { OnAckReceivedOutcome { lost_packets: 0, lost_bytes: 0, + acked_packets: 1, acked_bytes: 1000, spurious_losses: 0, } @@ -1646,6 +1655,7 @@ mod tests { OnAckReceivedOutcome { lost_packets: 1, lost_bytes: 1000, + acked_packets: 1, acked_bytes: 1000, spurious_losses: 0, } @@ -1734,6 +1744,7 @@ mod tests { OnAckReceivedOutcome { lost_packets: 1, lost_bytes: 1000, + acked_packets: 1, acked_bytes: 1000, spurious_losses: 0, } @@ -1762,6 +1773,7 @@ mod tests { OnAckReceivedOutcome { lost_packets: 0, lost_bytes: 0, + acked_packets: 0, acked_bytes: 0, spurious_losses: 1, } @@ -1801,6 +1813,7 @@ mod tests { OnAckReceivedOutcome { lost_packets: 1, lost_bytes: 1000, + acked_packets: 1, acked_bytes: 1000, spurious_losses: 0, } @@ -1833,6 +1846,7 @@ mod tests { OnAckReceivedOutcome { lost_packets: 0, lost_bytes: 0, + acked_packets: 0, acked_bytes: 0, spurious_losses: 1, } @@ -1934,6 +1948,7 @@ mod tests { OnAckReceivedOutcome { lost_packets: 0, lost_bytes: 0, + acked_packets: 10, acked_bytes: 12000, spurious_losses: 0, } @@ -2104,6 +2119,7 @@ mod tests { OnAckReceivedOutcome { lost_packets: 0, lost_bytes: 0, + acked_packets: 1, acked_bytes: 6000, spurious_losses: 0, } @@ -2133,6 +2149,7 @@ mod tests { OnAckReceivedOutcome { lost_packets: 0, lost_bytes: 0, + acked_packets: 1, acked_bytes: 6000, spurious_losses: 0, } @@ -2175,6 +2192,7 @@ mod tests { OnAckReceivedOutcome { lost_packets: 0, lost_bytes: 0, + acked_packets: 1, acked_bytes: 1000, spurious_losses: 0, } @@ -2283,6 +2301,7 @@ mod tests { OnAckReceivedOutcome { lost_packets: 0, lost_bytes: 0, + acked_packets: 2, acked_bytes: 2400, spurious_losses: 0, } @@ -2346,6 +2365,7 @@ mod tests { OnAckReceivedOutcome { lost_packets: 0, lost_bytes: 0, + acked_packets: 2, acked_bytes: 2 * 1000, spurious_losses: 0, } @@ -2374,6 +2394,7 @@ mod tests { OnAckReceivedOutcome { lost_packets: 0, lost_bytes: 0, + acked_packets: 2, acked_bytes: 2 * 1000, spurious_losses: 0, } @@ -2512,6 +2533,7 @@ mod tests { OnAckReceivedOutcome { lost_packets: 0, lost_bytes: 0, + acked_packets: 2, acked_bytes: 2 * 1000, spurious_losses: 0, } @@ -2601,6 +2623,7 @@ mod tests { OnAckReceivedOutcome { lost_packets: 0, lost_bytes: 0, + acked_packets: 10, acked_bytes: total_bytes_sent, spurious_losses: 0, } @@ -2680,6 +2703,7 @@ mod tests { OnAckReceivedOutcome { lost_packets: 0, lost_bytes: 0, + acked_packets: 3, acked_bytes: 3600, spurious_losses: 0, } @@ -2735,6 +2759,7 @@ mod tests { OnAckReceivedOutcome { lost_packets: 0, lost_bytes: 0, + acked_packets: 1, acked_bytes: 0, spurious_losses: 0, } diff --git a/quiche/src/tests.rs b/quiche/src/tests.rs index d58e817a61..71d478e3bc 100644 --- a/quiche/src/tests.rs +++ b/quiche/src/tests.rs @@ -2242,6 +2242,26 @@ fn stream_left_reset_bidi( assert_eq!(3, pipe.client.peer_streams_left_bidi()); } +#[rstest] +fn acked_packet_counts( + #[values("cubic", "bbr2_gcongestion")] cc_algorithm_name: &str, +) { + let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap(); + assert_eq!(pipe.handshake(), Ok(())); + + let acked_before = pipe.client.stats().acked; + + assert_eq!(pipe.client.stream_send(0, b"a", false), Ok(1)); + + // Run twice to make sure both data and the corresponding ACK are processed. + pipe.advance().unwrap(); + pipe.advance().unwrap(); + + let stats = pipe.client.stats(); + assert!(stats.acked > acked_before); + assert!(stats.acked <= stats.sent); +} + #[rstest] fn stream_reset_counts( #[values("cubic", "bbr2_gcongestion")] cc_algorithm_name: &str,