quiche/recovery/gcongestion/bbr2/
mode.rs

1// Copyright (c) 2015 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// Copyright (C) 2023, Cloudflare, Inc.
6// All rights reserved.
7//
8// Redistribution and use in source and binary forms, with or without
9// modification, are permitted provided that the following conditions are
10// met:
11//
12//     * Redistributions of source code must retain the above copyright notice,
13//       this list of conditions and the following disclaimer.
14//
15//     * Redistributions in binary form must reproduce the above copyright
16//       notice, this list of conditions and the following disclaimer in the
17//       documentation and/or other materials provided with the distribution.
18//
19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
23// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31use std::fmt::Debug;
32use std::time::Duration;
33use std::time::Instant;
34
35use crate::recovery::gcongestion::bbr2::Params;
36use crate::recovery::gcongestion::Lost;
37use crate::recovery::RecoveryStats;
38
39use super::drain::Drain;
40use super::network_model::BBRv2NetworkModel;
41use super::probe_bw::ProbeBW;
42use super::probe_rtt::ProbeRTT;
43use super::startup::Startup;
44use super::Acked;
45use super::BBRv2CongestionEvent;
46use super::Limits;
47
48#[derive(Debug, Default, PartialEq)]
49pub(super) enum CyclePhase {
50    #[default]
51    NotStarted,
52    Up,
53    Down,
54    Cruise,
55    Refill,
56}
57
58impl CyclePhase {
59    pub(super) fn pacing_gain(&self, params: &Params) -> f32 {
60        match self {
61            CyclePhase::Up => params.probe_bw_probe_up_pacing_gain,
62            CyclePhase::Down => params.probe_bw_probe_down_pacing_gain,
63            _ => params.probe_bw_default_pacing_gain,
64        }
65    }
66
67    pub(super) fn cwnd_gain(&self, params: &Params) -> f32 {
68        match self {
69            CyclePhase::Up => params.probe_bw_up_cwnd_gain,
70            _ => params.probe_bw_cwnd_gain,
71        }
72    }
73}
74
75#[derive(Debug)]
76pub(super) struct Cycle {
77    pub(super) start_time: Instant,
78    pub(super) phase: CyclePhase,
79    pub(super) rounds_in_phase: usize,
80    pub(super) phase_start_time: Instant,
81    pub(super) rounds_since_probe: usize,
82    pub(super) probe_wait_time: Option<Duration>,
83    pub(super) probe_up_rounds: usize,
84    pub(super) probe_up_bytes: Option<usize>,
85    pub(super) probe_up_acked: usize,
86    pub(super) probe_up_app_limited_since_inflight_hi_limited: bool,
87    // Whether max bandwidth filter window has advanced in this cycle. It is
88    // advanced once per cycle.
89    pub(super) has_advanced_max_bw: bool,
90    pub(super) is_sample_from_probing: bool,
91
92    pub(super) last_cycle_probed_too_high: bool,
93    pub(super) last_cycle_stopped_risky_probe: bool,
94}
95
96impl Default for Cycle {
97    fn default() -> Self {
98        let now = Instant::now();
99
100        Cycle {
101            start_time: now,
102            phase_start_time: now,
103
104            phase: CyclePhase::NotStarted,
105            rounds_in_phase: 0,
106            rounds_since_probe: 0,
107            probe_wait_time: None,
108            probe_up_rounds: 0,
109            probe_up_bytes: None,
110            probe_up_acked: 0,
111            probe_up_app_limited_since_inflight_hi_limited: false,
112            has_advanced_max_bw: false,
113            is_sample_from_probing: false,
114            last_cycle_probed_too_high: false,
115            last_cycle_stopped_risky_probe: false,
116        }
117    }
118}
119
120#[enum_dispatch::enum_dispatch]
121pub(super) trait ModeImpl: Debug {
122    #[cfg(feature = "qlog")]
123    fn state_str(&self) -> &'static str;
124
125    fn enter(
126        &mut self, now: Instant, congestion_event: Option<&BBRv2CongestionEvent>,
127        params: &Params,
128    );
129
130    fn leave(
131        &mut self, now: Instant, congestion_event: Option<&BBRv2CongestionEvent>,
132    );
133
134    fn is_probing_for_bandwidth(&self) -> bool;
135
136    #[allow(clippy::too_many_arguments)]
137    fn on_congestion_event(
138        self, prior_in_flight: usize, event_time: Instant,
139        acked_packets: &[Acked], lost_packets: &[Lost],
140        congestion_event: &mut BBRv2CongestionEvent,
141        target_bytes_inflight: usize, params: &Params,
142        recovery_stats: &mut RecoveryStats, cwnd: usize,
143    ) -> Mode;
144
145    fn get_cwnd_limits(&self, params: &Params) -> Limits<usize>;
146
147    fn on_exit_quiescence(
148        self, now: Instant, quiescence_start_time: Instant, params: &Params,
149    ) -> Mode;
150}
151
152#[enum_dispatch::enum_dispatch(ModeImpl)]
153#[derive(Debug)]
154pub(super) enum Mode {
155    Startup(Startup),
156    Drain(Drain),
157    ProbeBW(ProbeBW),
158    ProbeRTT(ProbeRTT),
159    Placheolder(Placeholder),
160}
161
162impl Default for Mode {
163    fn default() -> Self {
164        Mode::Placheolder(Placeholder {})
165    }
166}
167
168impl Mode {
169    pub(super) fn startup(model: BBRv2NetworkModel) -> Self {
170        Mode::Startup(Startup { model })
171    }
172
173    pub(super) fn drain(model: BBRv2NetworkModel) -> Self {
174        Mode::Drain(Drain {
175            model,
176            cycle: Default::default(),
177        })
178    }
179
180    pub(super) fn probe_bw(model: BBRv2NetworkModel, cycle: Cycle) -> Self {
181        Mode::ProbeBW(ProbeBW { model, cycle })
182    }
183
184    pub(super) fn probe_rtt(model: BBRv2NetworkModel, cycle: Cycle) -> Self {
185        Mode::ProbeRTT(ProbeRTT::new(model, cycle))
186    }
187
188    #[allow(clippy::too_many_arguments)]
189    pub(super) fn do_on_congestion_event(
190        &mut self, prior_in_flight: usize, event_time: Instant,
191        acked_packets: &[Acked], lost_packets: &[Lost],
192        congestion_event: &mut BBRv2CongestionEvent,
193        target_bytes_inflight: usize, params: &Params,
194        recovery_stats: &mut RecoveryStats, cwnd: usize,
195    ) -> bool {
196        let mode_before = std::mem::discriminant(self);
197
198        *self = std::mem::take(self).on_congestion_event(
199            prior_in_flight,
200            event_time,
201            acked_packets,
202            lost_packets,
203            congestion_event,
204            target_bytes_inflight,
205            params,
206            recovery_stats,
207            cwnd,
208        );
209
210        let mode_after = std::mem::discriminant(self);
211
212        mode_before != mode_after
213    }
214
215    pub(super) fn do_on_exit_quiescence(
216        &mut self, now: Instant, quiescence_start_time: Instant, params: &Params,
217    ) {
218        *self = std::mem::take(self).on_exit_quiescence(
219            now,
220            quiescence_start_time,
221            params,
222        )
223    }
224
225    pub fn network_model(&self) -> &BBRv2NetworkModel {
226        match self {
227            Mode::Startup(Startup { model }) => model,
228            Mode::Drain(Drain { model, .. }) => model,
229            Mode::ProbeBW(ProbeBW { model, .. }) => model,
230            Mode::ProbeRTT(ProbeRTT { model, .. }) => model,
231            Mode::Placheolder(_) => unreachable!(),
232        }
233    }
234
235    pub fn network_model_mut(&mut self) -> &mut BBRv2NetworkModel {
236        match self {
237            Mode::Startup(Startup { model }) => model,
238            Mode::Drain(Drain { model, .. }) => model,
239            Mode::ProbeBW(ProbeBW { model, .. }) => model,
240            Mode::ProbeRTT(ProbeRTT { model, .. }) => model,
241            Mode::Placheolder(_) => unreachable!(),
242        }
243    }
244}
245
246#[derive(Debug, Default)]
247pub(super) struct Placeholder {}
248
249impl ModeImpl for Placeholder {
250    #[cfg(feature = "qlog")]
251    fn state_str(&self) -> &'static str {
252        unreachable!()
253    }
254
255    fn enter(
256        &mut self, _: Instant, _: Option<&BBRv2CongestionEvent>, _params: &Params,
257    ) {
258        unreachable!()
259    }
260
261    fn leave(&mut self, _: Instant, _: Option<&BBRv2CongestionEvent>) {
262        unreachable!()
263    }
264
265    fn is_probing_for_bandwidth(&self) -> bool {
266        unreachable!()
267    }
268
269    fn on_congestion_event(
270        self, _: usize, _: Instant, _: &[Acked], _: &[Lost],
271        _: &mut BBRv2CongestionEvent, _: usize, _params: &Params,
272        _recovery_stats: &mut RecoveryStats, _cwnd: usize,
273    ) -> Mode {
274        unreachable!()
275    }
276
277    fn get_cwnd_limits(&self, _params: &Params) -> Limits<usize> {
278        unreachable!()
279    }
280
281    fn on_exit_quiescence(
282        self, _: Instant, _: Instant, _params: &Params,
283    ) -> Mode {
284        unreachable!()
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291    use crate::recovery::gcongestion::bbr2::DEFAULT_PARAMS;
292    use crate::BbrParams;
293
294    #[test]
295    fn cycle_params() {
296        let custom_bbr_settings = BbrParams {
297            probe_bw_up_cwnd_gain: Some(2.25),
298            probe_bw_cwnd_gain: Some(2.0),
299            ..Default::default()
300        };
301        let params = &DEFAULT_PARAMS.with_overrides(&custom_bbr_settings);
302
303        assert_eq!(CyclePhase::Up.pacing_gain(params), 1.25);
304        assert_eq!(CyclePhase::Up.cwnd_gain(params), 2.25);
305
306        assert_eq!(CyclePhase::Down.pacing_gain(params), 0.9);
307        assert_eq!(CyclePhase::Down.cwnd_gain(params), 2.0);
308
309        assert_eq!(CyclePhase::NotStarted.pacing_gain(params), 1.0);
310        assert_eq!(CyclePhase::NotStarted.cwnd_gain(params), 2.0);
311
312        assert_eq!(CyclePhase::Cruise.pacing_gain(params), 1.0);
313        assert_eq!(CyclePhase::Cruise.cwnd_gain(params), 2.0);
314
315        assert_eq!(CyclePhase::Refill.pacing_gain(params), 1.0);
316        assert_eq!(CyclePhase::Refill.cwnd_gain(params), 2.0);
317    }
318}