1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use crate::recovery::{
bandwidth::{Bandwidth, RateSample},
bbr::{windowed_filter::WindowedMaxFilter, BETA},
};
//= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#2.9.1
//# The data rate model parameters together estimate both the sending rate required to reach the
//# full bandwidth available to the flow (BBR.max_bw), and the maximum pacing rate control parameter
//# that is consistent with the queue pressure objective (BBR.bw).
#[derive(Clone, Debug)]
pub(crate) struct Model {
//= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#2.9.1
//# The windowed maximum recent bandwidth sample - obtained using the BBR delivery rate sampling
//# algorithm [draft-cheng-iccrg-delivery-rate-estimation] - measured during the current or
//# previous bandwidth probing cycle (or during Startup, if the flow is still in that state).
max_bw_filter: WindowedMaxFilter<Bandwidth, core::num::Wrapping<u8>, core::num::Wrapping<u8>>,
//= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#2.9.1
//# The long-term maximum sending bandwidth that the algorithm estimates will produce acceptable
//# queue pressure, based on signals in the current or previous bandwidth probing cycle, as
//# measured by loss.
bw_hi: Bandwidth,
//= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#2.9.1
//# The short-term maximum sending bandwidth that the algorithm estimates is safe for matching
//# the current network path delivery rate, based on any loss signals in the current bandwidth
//# probing cycle. This is generally lower than max_bw or bw_hi (thus the name).
bw_lo: Bandwidth,
//= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#2.9.1
//# The maximum sending bandwidth that the algorithm estimates is appropriate for matching the
//# current network path delivery rate, given all available signals in the model, at any time
//# scale. It is the min() of max_bw, bw_hi, and bw_lo.
bw: Bandwidth,
//= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#2.11
//# The virtual time used by the BBR.max_bw filter window.
cycle_count: core::num::Wrapping<u8>,
}
impl Model {
/// Constructs a new `data_rate::Model`
pub fn new() -> Self {
//= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#2.11
//# The filter window length for BBR.MaxBwFilter = 2 (representing up to 2 ProbeBW cycles,
//# the current cycle and the previous full cycle)
const MAX_BW_FILTER_LEN: core::num::Wrapping<u8> = core::num::Wrapping(2);
Self {
max_bw_filter: WindowedMaxFilter::new(MAX_BW_FILTER_LEN),
bw_hi: Bandwidth::INFINITY,
bw_lo: Bandwidth::INFINITY,
bw: Bandwidth::ZERO,
cycle_count: Default::default(),
}
}
/// The windowed maximum recent bandwidth sample
pub fn max_bw(&self) -> Bandwidth {
self.max_bw_filter.value().unwrap_or(Bandwidth::ZERO)
}
/// The long-term maximum sending bandwidth that the algorithm estimates
/// will produce acceptable queue pressure
pub fn bw_hi(&self) -> Bandwidth {
self.bw_hi
}
/// The short-term maximum sending bandwidth that the algorithm estimates
/// is safe for matching the current network path delivery rate
pub fn bw_lo(&self) -> Bandwidth {
self.bw_lo
}
/// The maximum sending bandwidth that the algorithm estimates is appropriate for
/// matching the current network path delivery rate
pub fn bw(&self) -> Bandwidth {
self.bw
}
/// Increments the virtual time tracked for counting cyclical progression through ProbeBW cycles
pub fn advance_max_bw_filter(&mut self) {
//= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.5.2.5
//# BBRAdvanceMaxBwFilter():
//# BBR.cycle_count++
self.cycle_count += core::num::Wrapping(1)
}
/// Updates `max_bw` with the given `rate_sample`
pub fn update_max_bw(&mut self, rate_sample: RateSample) {
//= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.5.2.4
//# BBRUpdateMaxBw()
//# BBRUpdateRound()
//# if (rs.delivery_rate >= BBR.max_bw || !rs.is_app_limited)
//# BBR.max_bw = update_windowed_max_filter(
//# filter=BBR.MaxBwFilter,
//# value=rs.delivery_rate,
//# time=BBR.cycle_count,
//# window_length=MaxBwFilterLen)
//= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.5.2.3
//# By default, the estimator discards application-limited samples, since by definition they
//# reflect application limits. However, the estimator does use application-limited samples
//# if the measured delivery rate happens to be larger than the current BBR.max_bw estimate,
//# since this indicates the current BBR.Max_bw estimate is too low.
if rate_sample.delivery_rate() > self.max_bw() || !rate_sample.is_app_limited {
self.max_bw_filter
.update(rate_sample.delivery_rate(), self.cycle_count);
}
}
/// Updates `bw_hi` with the given `bw`
#[allow(dead_code)] // TODO: See note in probe_bw.rs about updating bw_hi
pub fn update_upper_bound(&mut self, bw: Bandwidth) {
self.bw_hi = bw
}
/// Updates `bw_lo` with the given `bw` if it exceeds the current `bw_lo` * `bbr::BETA`
pub fn update_lower_bound(&mut self, bw: Bandwidth) {
//= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.5.6.3
//# BBRInitLowerBounds():
//# if (BBR.bw_lo == Infinity)
//# BBR.bw_lo = BBR.max_bw
if self.bw_lo == Bandwidth::INFINITY {
self.bw_lo = self.max_bw()
}
//= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.5.6.3
//# BBRLossLowerBounds():
//# BBR.bw_lo = max(BBR.bw_latest,
//# BBRBeta * BBR.bw_lo)
self.bw_lo = bw.max(self.bw_lo * BETA);
}
/// Resets `bw_lo` to its initial value
pub fn reset_lower_bound(&mut self) {
//= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.5.6.3
//# BBRResetLowerBounds():
//# BBR.bw_lo = Infinity
self.bw_lo = Bandwidth::INFINITY
}
/// Bounds `bw` to min(`max_bw`, `bw_lo`, `bw_hi)
pub fn bound_bw_for_model(&mut self) {
//= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.5.6.3
//# BBRBoundBWForModel():
//# BBR.bw = min(BBR.max_bw, BBR.bw_lo, BBR.bw_hi)
self.bw = self.max_bw().min(self.bw_lo()).min(self.bw_hi())
}
#[cfg(test)]
pub fn cycle_count(&self) -> u8 {
self.cycle_count.0
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn new() {
let model = Model::new();
assert_eq!(Bandwidth::ZERO, model.max_bw());
assert_eq!(Bandwidth::ZERO, model.bw());
assert_eq!(Bandwidth::INFINITY, model.bw_hi());
assert_eq!(Bandwidth::INFINITY, model.bw_lo());
}
#[test]
fn update_max_bw() {
let mut model = Model::new();
let mut rate_sample = RateSample {
interval: Duration::from_millis(10),
delivered_bytes: 100,
is_app_limited: true,
..Default::default()
};
let bw = rate_sample.delivery_rate();
// Sample is app limited, but we don't have any data yet, so we use the sample
model.update_max_bw(rate_sample);
assert_eq!(bw, model.max_bw());
model.advance_max_bw_filter();
rate_sample.is_app_limited = false;
rate_sample.delivered_bytes = 50;
// Sample is not app limited, so the sample is used. It is less than the max
// though, so the max does not change
model.update_max_bw(rate_sample);
assert_eq!(bw, model.max_bw());
rate_sample.delivered_bytes = 75;
let bw = rate_sample.delivery_rate();
model.advance_max_bw_filter();
// The MaxBwFilter only tracks 2 values, so the previous max of 100 falls off and
// the latest bw estimate becomes the max
model.update_max_bw(rate_sample);
assert_eq!(bw, model.max_bw());
// Advance the max bw filter twice so existing values should fall off
model.advance_max_bw_filter();
model.advance_max_bw_filter();
rate_sample.is_app_limited = true;
rate_sample.delivered_bytes = 50;
// The sample is app limited so the max stays the same
model.update_max_bw(rate_sample);
assert_eq!(bw, model.max_bw());
}
#[test]
fn update_lower_bound() {
let mut model = Model::new();
let bw = Bandwidth::new(50, Duration::from_millis(10));
let rate_sample = RateSample {
interval: Duration::from_millis(10),
delivered_bytes: 100,
..Default::default()
};
model.update_max_bw(rate_sample);
model.update_lower_bound(bw);
let max_bw = model.max_bw();
// We didn't have a valid bw_lo value yet, and the given bw is lower than max_bw * BETA,
// so bw_lo is set to max_bw * BETA
assert_eq!(max_bw * BETA, model.bw_lo());
let lower_bw = Bandwidth::new(50, Duration::from_millis(10));
model.update_upper_bound(lower_bw);
// The new sample is lower than bw_lo, so don't update bw_lo
assert_eq!(max_bw * BETA, model.bw_lo());
let higher_bw = Bandwidth::new(150, Duration::from_millis(10));
model.update_lower_bound(higher_bw);
// The new sample is higher than bw_lo, so update bw_lo
assert_eq!(higher_bw, model.bw_lo());
// Resetting the lower bound sets bw_lo to Bandwidth::MAX
model.reset_lower_bound();
assert_eq!(Bandwidth::INFINITY, model.bw_lo());
}
#[test]
fn bound_bw_for_model() {
let mut model = Model::new();
let mut rate_sample = RateSample {
interval: Duration::from_millis(10),
delivered_bytes: 100,
..Default::default()
};
let high_bw = rate_sample.delivery_rate();
let med_bw = Bandwidth::new(75, Duration::from_millis(10));
let low_bw = Bandwidth::new(50, Duration::from_millis(10));
model.update_max_bw(rate_sample);
model.update_upper_bound(low_bw);
model.update_lower_bound(med_bw);
// bw is not updated yet
assert_eq!(Bandwidth::ZERO, model.bw());
model.bound_bw_for_model();
assert_eq!(low_bw, model.bw());
assert_eq!(
model.max_bw().min(model.bw_lo()).min(model.bw_hi()),
model.bw()
);
// bw_hi is no longer the min
model.update_upper_bound(high_bw);
model.bound_bw_for_model();
assert_eq!(med_bw, model.bw());
assert_eq!(
model.max_bw().min(model.bw_lo()).min(model.bw_hi()),
model.bw()
);
rate_sample.delivered_bytes = 10;
let low_bw = rate_sample.delivery_rate();
// bw_lo is no longer the min
model.advance_max_bw_filter();
model.advance_max_bw_filter();
model.update_max_bw(rate_sample);
model.bound_bw_for_model();
assert_eq!(low_bw, model.bw());
assert_eq!(
model.max_bw().min(model.bw_lo()).min(model.bw_hi()),
model.bw()
);
}
}