Skip to content

Commit 2a469b6

Browse files
Plot a simulation of the congestion controller (#468)
* Move cubic congestion controller and hybrid slow start to s2n_quic_core * Add a method to the Congestion Controller trait to get the current size of the congestion window * Initial simulation plot * Add snapshot test * Remove unnecessary lifetime * Add snapshot file * add simulations workflow output * fix realpath error * Add num_traits, set packet size to MINIMUM_MTU, make threshold pub(super) * Fix clippy and miri * Add a test for a network that experiences loss at 5MB * Have 3MB test use slow start * Adjust comment * Add title to tree output and remove index.html Co-authored-by: Cameron Bytheway <[email protected]>
1 parent 2cd22fc commit 2a469b6

File tree

14 files changed

+558
-56
lines changed

14 files changed

+558
-56
lines changed

.github/config/cargo-deny.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ unmaintained = "deny"
44
notice = "deny"
55
yanked = "deny"
66
ignore = [
7-
"RUSTSEC-2019-0031", # ring currently uses 'spin' for feature detection
87
"RUSTSEC-2020-0016", # mio 0.6 uses 'net2' (removed in 0.7) but tokio is still pinned to 0.6
9-
"RUSTSEC-2020-0095", # insta (a dev dependency) uses `difference`
108
]
119

1210
[bans]
@@ -20,6 +18,7 @@ skip = [
2018
# compliance is a separate binary target so just ignore it
2119
skip-tree = [
2220
{ name = "cargo-compliance" },
21+
{ name = "criterion" }, # criterion is always going to be just a test dependency
2322
]
2423

2524
[sources]

.github/workflows/ci.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,3 +436,48 @@ jobs:
436436
name: 'perf / report (${{ matrix.script }})'
437437
status: 'success'
438438
url: "${{ steps.s3.outputs.URL }}"
439+
440+
recovery-simulations:
441+
runs-on: ubuntu-latest
442+
steps:
443+
- uses: actions/checkout@v2
444+
with:
445+
submodules: true
446+
447+
- uses: actions-rs/toolchain@v1
448+
id: toolchain
449+
with:
450+
toolchain: stable
451+
profile: minimal
452+
override: true
453+
454+
- uses: camshaft/rust-cache@v1
455+
456+
- name: Run simulations
457+
run: |
458+
./scripts/recovery-sim
459+
460+
- uses: actions/upload-artifact@v2
461+
with:
462+
name: recovery-simulations
463+
path: target/recovery-sim
464+
465+
- uses: aws-actions/configure-aws-credentials@v1
466+
with:
467+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
468+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
469+
aws-region: us-west-1
470+
471+
- name: Upload to S3
472+
id: s3
473+
run: |
474+
TARGET="${{ github.sha }}/recovery-simulations"
475+
aws s3 sync target/recovery-sim "s3://s2n-quic-ci-artifacts/$TARGET" --acl private --follow-symlinks
476+
URL="$CDN/$TARGET/index.html"
477+
echo "::set-output name=URL::$URL"
478+
479+
- uses: ouzi-dev/[email protected]
480+
with:
481+
name: 'recovery-simulations / report'
482+
status: 'success'
483+
url: "${{ steps.s3.outputs.URL }}"

quic/s2n-quic-core/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ byteorder = { version = "1.1", default-features = false }
1818
bytes = { version = "0.6", default-features = false }
1919
displaydoc = { version = "0.1", default-features = false }
2020
hex-literal = "0.3"
21+
num-traits = { version = "0.2", default-features = false, features = ["libm"] }
2122
s2n-codec = { version = "0.1", path = "../../common/s2n-codec", default-features = false }
2223
subtle = { version = "2", default-features = false }
2324
thiserror = { version = "1", optional = true }
@@ -28,6 +29,7 @@ bolero = "0.6"
2829
compliance = { path = "../../common/compliance/compliance" }
2930
criterion = "0.3"
3031
insta = "1.0"
32+
plotters = { version = "0.3", default-features = false, features = ["svg_backend", "line_series"] }
3133
s2n-codec = { version = "0.1", path = "../../common/s2n-codec", features = ["testing"] }
3234

3335
[[test]]
@@ -66,3 +68,7 @@ harness = false
6668
name = "varint"
6769
path = "tests/varint/fuzz_target.rs"
6870
harness = false
71+
72+
[[test]]
73+
name = "recovery-simulation"
74+
path = "tests/recovery/simulation.rs"

quic/s2n-quic-core/src/recovery/congestion_controller.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ impl<'a> PathInfo<'a> {
2626
}
2727

2828
pub trait CongestionController: 'static + Clone + Send + Debug {
29+
/// Returns the size of the current congestion window in bytes
30+
fn congestion_window(&self) -> u32;
31+
2932
/// Returns `true` if the congestion window does not have sufficient
3033
/// space for a packet of `max_datagram_size` considering the current
3134
/// bytes in flight
@@ -80,6 +83,10 @@ pub mod testing {
8083
pub struct Unlimited {}
8184

8285
impl CongestionController for Unlimited {
86+
fn congestion_window(&self) -> u32 {
87+
u32::max_value()
88+
}
89+
8390
fn is_congestion_limited(&self) -> bool {
8491
false
8592
}
@@ -125,6 +132,10 @@ pub mod testing {
125132
}
126133

127134
impl CongestionController for MockCongestionController {
135+
fn congestion_window(&self) -> u32 {
136+
u32::max_value()
137+
}
138+
128139
fn is_congestion_limited(&self) -> bool {
129140
false
130141
}

quic/s2n-quic-transport/src/recovery/cubic.rs renamed to quic/s2n-quic-core/src/recovery/cubic.rs

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
1-
use crate::recovery::{
2-
congestion_controller::CongestionController,
3-
cubic::{FastRetransmission::*, State::*},
4-
hybrid_slow_start::HybridSlowStart,
1+
use crate::{
2+
counter::Counter,
3+
recovery::{
4+
congestion_controller::CongestionController,
5+
cubic::{FastRetransmission::*, State::*},
6+
hybrid_slow_start::HybridSlowStart,
7+
RTTEstimator,
8+
},
9+
time::Timestamp,
510
};
611
use core::{
712
cmp::{max, min},
813
time::Duration,
914
};
10-
use s2n_quic_core::{counter::Counter, recovery::RTTEstimator, time::Timestamp};
15+
#[cfg(not(feature = "std"))]
16+
use num_traits::Float as _;
1117

1218
//= https://tools.ietf.org/id/draft-ietf-quic-recovery-32.txt#7.3
1319
//# New Path or +------------+
@@ -81,6 +87,10 @@ pub struct CubicCongestionController {
8187
type BytesInFlight = Counter<u32>;
8288

8389
impl CongestionController for CubicCongestionController {
90+
fn congestion_window(&self) -> u32 {
91+
self.congestion_window
92+
}
93+
8494
fn is_congestion_limited(&self) -> bool {
8595
let available_congestion_window =
8696
self.congestion_window.saturating_sub(*self.bytes_in_flight);
@@ -563,18 +573,12 @@ impl Cubic {
563573

564574
#[cfg(test)]
565575
mod test {
566-
use crate::recovery::{
567-
cubic::{
568-
BytesInFlight, Cubic, CubicCongestionController,
569-
FastRetransmission::{Idle, RequiresTransmission},
570-
State::{CongestionAvoidance, Recovery, SlowStart},
571-
BETA_CUBIC,
572-
},
573-
CongestionController,
574-
};
575-
use s2n_quic_core::{
576-
packet::number::PacketNumberSpace, recovery::RTTEstimator, time::Duration,
576+
use super::*;
577+
use crate::{
578+
packet::number::PacketNumberSpace,
579+
time::{Clock, NoopClock},
577580
};
581+
use core::time::Duration;
578582

579583
macro_rules! assert_delta {
580584
($x:expr, $y:expr, $d:expr) => {
@@ -739,7 +743,7 @@ mod test {
739743
fn on_packet_sent() {
740744
let mut cc = CubicCongestionController::new(1000);
741745
let mut rtt_estimator = RTTEstimator::new(Duration::from_millis(0));
742-
let now = s2n_quic_platform::time::now();
746+
let now = NoopClock.get_time();
743747

744748
cc.congestion_window = 100_000;
745749

@@ -793,7 +797,7 @@ mod test {
793797
#[test]
794798
fn on_packet_sent_application_limited() {
795799
let mut cc = CubicCongestionController::new(1000);
796-
let now = s2n_quic_platform::time::now();
800+
let now = NoopClock.get_time();
797801

798802
cc.congestion_window = 100_000;
799803
cc.bytes_in_flight = BytesInFlight::new(96_500);
@@ -833,7 +837,7 @@ mod test {
833837
#[test]
834838
fn on_packet_sent_fast_retransmission() {
835839
let mut cc = CubicCongestionController::new(1000);
836-
let now = s2n_quic_platform::time::now();
840+
let now = NoopClock.get_time();
837841

838842
cc.congestion_window = 100_000;
839843
cc.bytes_in_flight = BytesInFlight::new(99900);
@@ -853,7 +857,7 @@ mod test {
853857
#[test]
854858
fn congestion_avoidance_after_idle_period() {
855859
let mut cc = CubicCongestionController::new(1000);
856-
let now = s2n_quic_platform::time::now();
860+
let now = NoopClock.get_time();
857861
let rtt_estimator = &RTTEstimator::new(Duration::from_secs(0));
858862

859863
cc.congestion_window = 3000;
@@ -890,7 +894,7 @@ mod test {
890894
fn congestion_avoidance_after_fast_convergence() {
891895
let max_datagram_size = 1200;
892896
let mut cc = CubicCongestionController::new(max_datagram_size);
893-
let now = s2n_quic_platform::time::now();
897+
let now = NoopClock.get_time();
894898
cc.bytes_in_flight = BytesInFlight::new(100);
895899
cc.congestion_window = 80_000;
896900
cc.cubic.w_last_max = bytes_to_packets(100_000, max_datagram_size);
@@ -927,7 +931,7 @@ mod test {
927931
#[test]
928932
fn on_packet_lost() {
929933
let mut cc = CubicCongestionController::new(1000);
930-
let now = s2n_quic_platform::time::now();
934+
let now = NoopClock.get_time();
931935
cc.congestion_window = 100_000;
932936
cc.bytes_in_flight = BytesInFlight::new(100_000);
933937
cc.state = SlowStart;
@@ -957,7 +961,7 @@ mod test {
957961
#[test]
958962
fn on_packet_lost_below_minimum_window() {
959963
let mut cc = CubicCongestionController::new(1000);
960-
let now = s2n_quic_platform::time::now();
964+
let now = NoopClock.get_time();
961965
cc.congestion_window = cc.minimum_window();
962966
cc.bytes_in_flight = BytesInFlight::new(cc.minimum_window());
963967
cc.state = CongestionAvoidance(now);
@@ -970,7 +974,7 @@ mod test {
970974
#[test]
971975
fn on_packet_lost_already_in_recovery() {
972976
let mut cc = CubicCongestionController::new(1000);
973-
let now = s2n_quic_platform::time::now();
977+
let now = NoopClock.get_time();
974978
cc.congestion_window = 10000;
975979
cc.bytes_in_flight = BytesInFlight::new(1000);
976980
cc.state = Recovery(now, Idle);
@@ -990,7 +994,7 @@ mod test {
990994
#[test]
991995
fn on_packet_lost_persistent_congestion() {
992996
let mut cc = CubicCongestionController::new(1000);
993-
let now = s2n_quic_platform::time::now();
997+
let now = NoopClock.get_time();
994998
cc.congestion_window = 10000;
995999
cc.bytes_in_flight = BytesInFlight::new(1000);
9961000
cc.state = Recovery(now, Idle);
@@ -1059,7 +1063,7 @@ mod test {
10591063
#[test]
10601064
fn on_packet_ack_limited() {
10611065
let mut cc = CubicCongestionController::new(5000);
1062-
let now = s2n_quic_platform::time::now();
1066+
let now = NoopClock.get_time();
10631067
cc.congestion_window = 100_000;
10641068
cc.bytes_in_flight = BytesInFlight::new(10000);
10651069
cc.state = SlowStart;
@@ -1079,7 +1083,7 @@ mod test {
10791083
#[compliance::tests("https://tools.ietf.org/id/draft-ietf-quic-recovery-32.txt#7.3.2")]
10801084
fn on_packet_ack_recovery_to_congestion_avoidance() {
10811085
let mut cc = CubicCongestionController::new(5000);
1082-
let now = s2n_quic_platform::time::now();
1086+
let now = NoopClock.get_time();
10831087

10841088
cc.cubic.w_max = bytes_to_packets(25000, 5000);
10851089
cc.state = Recovery(now, Idle);
@@ -1102,7 +1106,7 @@ mod test {
11021106
#[compliance::tests("https://tools.ietf.org/id/draft-ietf-quic-recovery-32.txt#7.3.2")]
11031107
fn on_packet_ack_slow_start_to_congestion_avoidance() {
11041108
let mut cc = CubicCongestionController::new(5000);
1105-
let now = s2n_quic_platform::time::now();
1109+
let now = NoopClock.get_time();
11061110

11071111
cc.state = SlowStart;
11081112
cc.congestion_window = 10000;
@@ -1128,7 +1132,7 @@ mod test {
11281132
#[test]
11291133
fn on_packet_ack_recovery() {
11301134
let mut cc = CubicCongestionController::new(5000);
1131-
let now = s2n_quic_platform::time::now();
1135+
let now = NoopClock.get_time();
11321136

11331137
cc.state = Recovery(now, Idle);
11341138
cc.congestion_window = 10000;
@@ -1151,7 +1155,7 @@ mod test {
11511155
let max_datagram_size = 5000;
11521156
let mut cc = CubicCongestionController::new(max_datagram_size);
11531157
let mut cc2 = CubicCongestionController::new(max_datagram_size);
1154-
let now = s2n_quic_platform::time::now();
1158+
let now = NoopClock.get_time();
11551159

11561160
cc.state = CongestionAvoidance(now + Duration::from_millis(3300));
11571161
cc.congestion_window = 10000;

quic/s2n-quic-transport/src/recovery/hybrid_slow_start.rs renamed to quic/s2n-quic-core/src/recovery/hybrid_slow_start.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
use crate::time::Timestamp;
12
use core::time::Duration;
2-
use s2n_quic_core::time::Timestamp;
33

44
/// An implementation of the Hybrid Slow Start algorithm described in
55
/// "Hybrid Slow Start for High-Bandwidth and Long-Distance Networks"
@@ -32,7 +32,7 @@ const THRESHOLD_DIVIDEND: u32 = 8;
3232
impl HybridSlowStart {
3333
/// Constructs a new `HybridSlowStart`. `max_datagram_size` is used for determining
3434
/// the minimum slow start threshold.
35-
pub(super) fn new(max_datagram_size: u16) -> Self {
35+
pub fn new(max_datagram_size: u16) -> Self {
3636
Self {
3737
sample_count: 0,
3838
last_min_rtt: None,
@@ -51,7 +51,7 @@ impl HybridSlowStart {
5151
/// a number of samples has increased since the last
5252
/// round of samples and if so will set the slow start
5353
/// threshold.
54-
pub(super) fn on_rtt_update(
54+
pub fn on_rtt_update(
5555
&mut self,
5656
congestion_window: u32,
5757
time_sent: Timestamp,
@@ -107,7 +107,7 @@ impl HybridSlowStart {
107107
/// slow start threshold to the minimum of the Hybrid Slow Start threshold
108108
/// and the given congestion window. This will ensure we exit slow start
109109
/// early enough to avoid further congestion.
110-
pub(super) fn on_congestion_event(&mut self, ssthresh: u32) {
110+
pub fn on_congestion_event(&mut self, ssthresh: u32) {
111111
self.threshold = self.threshold.min(ssthresh).max(self.low_ssthresh());
112112
}
113113

@@ -118,7 +118,10 @@ impl HybridSlowStart {
118118

119119
#[cfg(test)]
120120
mod test {
121-
use crate::recovery::hybrid_slow_start::HybridSlowStart;
121+
use crate::{
122+
recovery::hybrid_slow_start::HybridSlowStart,
123+
time::{Clock, NoopClock},
124+
};
122125
use core::time::Duration;
123126

124127
#[test]
@@ -149,7 +152,7 @@ mod test {
149152
#[test]
150153
fn on_rtt_update_above_threshold() {
151154
let mut slow_start = HybridSlowStart::new(10);
152-
let time_zero = s2n_quic_platform::time::now();
155+
let time_zero = NoopClock.get_time();
153156
slow_start.threshold = 500;
154157

155158
assert_eq!(slow_start.sample_count, 0);
@@ -165,7 +168,7 @@ mod test {
165168

166169
assert_eq!(slow_start.sample_count, 0);
167170

168-
let time_zero = s2n_quic_platform::time::now() + Duration::from_secs(10);
171+
let time_zero = NoopClock.get_time() + Duration::from_secs(10);
169172

170173
// -- Round 1 --
171174

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
pub mod congestion_controller;
2-
mod rtt_estimator;
3-
41
pub use congestion_controller::CongestionController;
2+
pub use cubic::CubicCongestionController;
53
pub use rtt_estimator::*;
4+
5+
pub mod congestion_controller;
6+
pub mod cubic;
7+
mod hybrid_slow_start;
8+
mod rtt_estimator;

0 commit comments

Comments
 (0)