1use std::ops::Add;
32use std::time::Duration;
33use std::time::Instant;
34
35use crate::recovery::gcongestion::bbr2::Params;
36use crate::recovery::gcongestion::Acked;
37use crate::recovery::gcongestion::Lost;
38use crate::recovery::RecoveryStats;
39
40use super::mode::Cycle;
41use super::mode::CyclePhase;
42use super::mode::Mode;
43use super::mode::ModeImpl;
44use super::network_model::BBRv2NetworkModel;
45use super::network_model::DEFAULT_MSS;
46use super::BBRv2CongestionEvent;
47use super::BwLoMode;
48use super::Limits;
49
50#[derive(Debug)]
51pub(super) struct ProbeBW {
52 pub(super) model: BBRv2NetworkModel,
53 pub(super) cycle: Cycle,
54}
55
56#[derive(PartialEq, PartialOrd)]
57enum AdaptUpperBoundsResult {
58 AdaptedOk,
59 AdaptedProbedTooHigh,
60 NotAdaptedInflightHighNotSet,
61 NotAdaptedInvalidSample,
62}
63
64impl ModeImpl for ProbeBW {
65 #[cfg(feature = "qlog")]
66 fn state_str(&self) -> &'static str {
67 match self.cycle.phase {
68 CyclePhase::NotStarted => unreachable!(),
69 CyclePhase::Up => "bbr_probe_bw_up",
70 CyclePhase::Down => "bbr_probe_bw_down",
71 CyclePhase::Cruise => "bbr_probe_bw_cruise",
72 CyclePhase::Refill => "bbr_probe_bw_refill",
73 }
74 }
75
76 fn enter(
77 &mut self, now: Instant,
78 _congestion_event: Option<&BBRv2CongestionEvent>, params: &Params,
79 ) {
80 self.cycle.start_time = now;
81
82 match self.cycle.phase {
83 CyclePhase::NotStarted => {
84 self.enter_probe_down(false, false, now, params)
86 },
87 CyclePhase::Cruise => self.enter_probe_cruise(now),
88 CyclePhase::Refill =>
89 self.enter_probe_refill(self.cycle.probe_up_rounds, now),
90 CyclePhase::Up | CyclePhase::Down => {},
91 }
92 }
93
94 fn on_congestion_event(
95 mut self, prior_in_flight: usize, event_time: Instant, _: &[Acked],
96 _: &[Lost], congestion_event: &mut BBRv2CongestionEvent,
97 target_bytes_inflight: usize, params: &Params,
98 _recovery_stats: &mut RecoveryStats, _cwnd: usize,
99 ) -> Mode {
100 if congestion_event.end_of_round_trip {
101 if self.cycle.start_time != event_time {
102 self.cycle.rounds_since_probe += 1;
103 }
104
105 if self.cycle.phase_start_time != event_time {
106 self.cycle.rounds_in_phase += 1;
107 }
108 }
109
110 let mut switch_to_probe_rtt = false;
111
112 match self.cycle.phase {
113 CyclePhase::NotStarted => unreachable!(),
114 CyclePhase::Up => self.update_probe_up(
115 prior_in_flight,
116 target_bytes_inflight,
117 congestion_event,
118 params,
119 ),
120 CyclePhase::Down => {
121 self.update_probe_down(
122 target_bytes_inflight,
123 congestion_event,
124 params,
125 );
126 if self.cycle.phase != CyclePhase::Down &&
127 self.model.maybe_expire_min_rtt(congestion_event, params)
128 {
129 switch_to_probe_rtt = true;
130 }
131 },
132 CyclePhase::Cruise => self.update_probe_cruise(
133 target_bytes_inflight,
134 congestion_event,
135 params,
136 ),
137 CyclePhase::Refill => self.update_probe_refill(
138 target_bytes_inflight,
139 congestion_event,
140 params,
141 ),
142 }
143
144 if !switch_to_probe_rtt {
147 self.model
148 .set_pacing_gain(self.cycle.phase.pacing_gain(params));
149 self.model.set_cwnd_gain(self.cycle.phase.cwnd_gain(params));
150 }
151
152 if switch_to_probe_rtt {
153 self.into_probe_rtt(event_time, Some(congestion_event), params)
154 } else {
155 Mode::ProbeBW(self)
156 }
157 }
158
159 fn get_cwnd_limits(&self, params: &Params) -> Limits<usize> {
160 if self.cycle.phase == CyclePhase::Cruise {
161 let limit = self
162 .model
163 .inflight_lo()
164 .min(self.model.inflight_hi_with_headroom(params));
165 return Limits::no_greater_than(limit);
166 }
167
168 if self.cycle.phase == CyclePhase::Up &&
169 params.probe_up_ignore_inflight_hi
170 {
171 return Limits::no_greater_than(self.model.inflight_lo());
173 }
174
175 Limits::no_greater_than(
176 self.model.inflight_lo().min(self.model.inflight_hi()),
177 )
178 }
179
180 fn is_probing_for_bandwidth(&self) -> bool {
181 self.cycle.phase == CyclePhase::Refill ||
182 self.cycle.phase == CyclePhase::Up
183 }
184
185 fn on_exit_quiescence(
186 mut self, now: Instant, quiescence_start_time: Instant, _params: &Params,
187 ) -> Mode {
188 self.model
189 .postpone_min_rtt_timestamp(now - quiescence_start_time);
190 Mode::ProbeBW(self)
191 }
192
193 fn leave(
194 &mut self, _now: Instant,
195 _congestion_event: Option<&BBRv2CongestionEvent>,
196 ) {
197 }
198}
199
200impl ProbeBW {
201 fn enter_probe_down(
202 &mut self, probed_too_high: bool, stopped_risky_probe: bool,
203 now: Instant, params: &Params,
204 ) {
205 let cycle = &mut self.cycle;
206 cycle.last_cycle_probed_too_high = probed_too_high;
207 cycle.last_cycle_stopped_risky_probe = stopped_risky_probe;
208
209 cycle.phase = CyclePhase::Down;
210 cycle.start_time = now;
211 cycle.phase_start_time = now;
212 cycle.rounds_in_phase = 0;
213
214 if params.bw_lo_mode != BwLoMode::Default {
215 self.model.clear_bandwidth_lo();
219 }
220
221 cycle.rounds_since_probe = 0;
223 cycle.probe_wait_time = Some(
224 params.probe_bw_probe_base_duration + Duration::from_micros(500),
225 );
226
227 cycle.probe_up_bytes = None;
228 cycle.probe_up_app_limited_since_inflight_hi_limited = false;
229 cycle.has_advanced_max_bw = false;
230 self.model.restart_round_early();
231 }
232
233 fn enter_probe_cruise(&mut self, now: Instant) {
234 if self.cycle.phase == CyclePhase::Down {
235 self.exit_probe_down();
236 }
237
238 let cycle = &mut self.cycle;
239
240 self.model.cap_inflight_lo(self.model.inflight_hi());
241 cycle.phase = CyclePhase::Cruise;
242 cycle.phase_start_time = now;
243 cycle.rounds_in_phase = 0;
244 cycle.is_sample_from_probing = false;
245 }
246
247 fn enter_probe_refill(&mut self, probe_up_rounds: usize, now: Instant) {
248 if self.cycle.phase == CyclePhase::Down {
249 self.exit_probe_down();
250 }
251
252 let cycle = &mut self.cycle;
253
254 cycle.phase = CyclePhase::Refill;
255 cycle.phase_start_time = now;
256 cycle.rounds_in_phase = 0;
257
258 cycle.is_sample_from_probing = false;
259 cycle.last_cycle_stopped_risky_probe = false;
260
261 self.model.clear_bandwidth_lo();
262 self.model.clear_inflight_lo();
263 cycle.probe_up_rounds = probe_up_rounds;
264 cycle.probe_up_acked = 0;
265 self.model.restart_round_early();
266 }
267
268 fn enter_probe_up(&mut self, now: Instant, cwnd: usize) {
269 let cycle = &mut self.cycle;
270
271 cycle.phase = CyclePhase::Up;
272 cycle.phase_start_time = now;
273 cycle.rounds_in_phase = 0;
274 cycle.is_sample_from_probing = true;
275 self.raise_inflight_high_slope(cwnd);
276 self.model.restart_round_early();
277 }
278
279 fn exit_probe_down(&mut self) {
280 if !self.cycle.has_advanced_max_bw {
281 self.model.advance_max_bandwidth_filter();
282 self.cycle.has_advanced_max_bw = true;
283 }
284 }
285
286 fn update_probe_down(
287 &mut self, target_bytes_inflight: usize,
288 congestion_event: &BBRv2CongestionEvent, params: &Params,
289 ) {
290 if self.cycle.rounds_in_phase == 1 && congestion_event.end_of_round_trip {
291 self.cycle.is_sample_from_probing = false;
292
293 if !congestion_event.last_packet_send_state.is_app_limited {
294 self.model.advance_max_bandwidth_filter();
295 self.cycle.has_advanced_max_bw = true;
296 }
297
298 if self.cycle.last_cycle_stopped_risky_probe &&
299 !self.cycle.last_cycle_probed_too_high
300 {
301 self.enter_probe_refill(0, congestion_event.event_time);
302 return;
303 }
304 }
305
306 self.maybe_adapt_upper_bounds(
307 target_bytes_inflight,
308 congestion_event,
309 params,
310 );
311
312 if self.is_time_to_probe_bandwidth(
313 target_bytes_inflight,
314 congestion_event,
315 params,
316 ) {
317 self.enter_probe_refill(0, congestion_event.event_time);
318 return;
319 }
320
321 if self.has_stayed_long_enough_in_probe_down(congestion_event, params) {
325 self.enter_probe_cruise(congestion_event.event_time);
326 return;
327 }
328
329 let inflight_with_headroom = self.model.inflight_hi_with_headroom(params);
330 let bytes_in_flight = congestion_event.bytes_in_flight;
331
332 if bytes_in_flight > inflight_with_headroom {
333 return;
335 }
336
337 let bdp = self.model.bdp0();
339
340 if bytes_in_flight < bdp {
341 self.enter_probe_cruise(congestion_event.event_time);
342 }
343 }
344
345 fn update_probe_cruise(
346 &mut self, target_bytes_inflight: usize,
347 congestion_event: &BBRv2CongestionEvent, params: &Params,
348 ) {
349 self.maybe_adapt_upper_bounds(
350 target_bytes_inflight,
351 congestion_event,
352 params,
353 );
354
355 if self.is_time_to_probe_bandwidth(
356 target_bytes_inflight,
357 congestion_event,
358 params,
359 ) {
360 self.enter_probe_refill(0, congestion_event.event_time);
361 }
362 }
363
364 fn update_probe_refill(
365 &mut self, target_bytes_inflight: usize,
366 congestion_event: &BBRv2CongestionEvent, params: &Params,
367 ) {
368 self.maybe_adapt_upper_bounds(
369 target_bytes_inflight,
370 congestion_event,
371 params,
372 );
373
374 if self.cycle.rounds_in_phase > 0 && congestion_event.end_of_round_trip {
375 self.enter_probe_up(
376 congestion_event.event_time,
377 congestion_event.prior_cwnd,
378 );
379 }
380 }
381
382 fn update_probe_up(
383 &mut self, prior_in_flight: usize, target_bytes_inflight: usize,
384 congestion_event: &BBRv2CongestionEvent, params: &Params,
385 ) {
386 if self.maybe_adapt_upper_bounds(
387 target_bytes_inflight,
388 congestion_event,
389 params,
390 ) == AdaptUpperBoundsResult::AdaptedProbedTooHigh
391 {
392 self.enter_probe_down(
393 true,
394 false,
395 congestion_event.event_time,
396 params,
397 );
398 return;
399 }
400
401 self.probe_inflight_high_upward(congestion_event, params);
402
403 let mut is_risky = false;
404 let mut is_queuing = false;
405 if self.cycle.last_cycle_probed_too_high &&
406 prior_in_flight >= self.model.inflight_hi()
407 {
408 is_risky = true;
409 } else if self.cycle.rounds_in_phase > 0 {
410 if params.max_probe_up_queue_rounds > 0 {
411 if congestion_event.end_of_round_trip {
412 self.model
413 .check_persistent_queue(params.full_bw_threshold, params);
414 if self.model.rounds_with_queueing() >=
415 params.max_probe_up_queue_rounds
416 {
417 is_queuing = true;
418 }
419 }
420 } else {
421 let mut queuing_threshold_extra_bytes =
422 self.model.queueing_threshold_extra_bytes();
423 if params.add_ack_height_to_queueing_threshold {
424 queuing_threshold_extra_bytes += self.model.max_ack_height();
425 }
426 let queuing_threshold = (params.full_bw_threshold *
427 self.model.bdp0() as f32)
428 as usize +
429 queuing_threshold_extra_bytes;
430
431 is_queuing =
432 congestion_event.bytes_in_flight >= queuing_threshold;
433 }
434 }
435
436 if is_risky || is_queuing {
437 self.enter_probe_down(
438 false,
439 is_risky,
440 congestion_event.event_time,
441 params,
442 );
443 }
444 }
445
446 fn is_time_to_probe_bandwidth(
447 &self, target_bytes_inflight: usize,
448 congestion_event: &BBRv2CongestionEvent, params: &Params,
449 ) -> bool {
450 if self.has_cycle_lasted(
451 self.cycle.probe_wait_time.unwrap(),
452 congestion_event,
453 ) {
454 return true;
455 }
456
457 if self.is_time_to_probe_for_reno_coexistence(
458 target_bytes_inflight,
459 1.0,
460 congestion_event,
461 params,
462 ) {
463 return true;
464 }
465
466 false
467 }
468
469 fn maybe_adapt_upper_bounds(
470 &mut self, target_bytes_inflight: usize,
471 congestion_event: &BBRv2CongestionEvent, params: &Params,
472 ) -> AdaptUpperBoundsResult {
473 let send_state = congestion_event.last_packet_send_state;
474
475 if !send_state.is_valid {
476 return AdaptUpperBoundsResult::NotAdaptedInvalidSample;
477 }
478
479 let mut inflight_at_send = send_state.bytes_in_flight;
481 if params.use_bytes_delivered_for_inflight_hi {
482 inflight_at_send = self.model.total_bytes_acked() -
483 congestion_event.last_packet_send_state.total_bytes_acked;
484 }
485
486 if self.cycle.is_sample_from_probing {
487 if self.model.is_inflight_too_high(
488 congestion_event,
489 params.probe_bw_full_loss_count,
490 params,
491 ) {
492 self.cycle.is_sample_from_probing = false;
493 if !send_state.is_app_limited ||
494 params.max_probe_up_queue_rounds > 0
495 {
496 let inflight_target = (target_bytes_inflight as f32 *
497 (1.0 - params.beta))
498 as usize;
499
500 let mut new_inflight_hi =
501 inflight_at_send.max(inflight_target);
502
503 if params.limit_inflight_hi_by_max_delivered {
504 new_inflight_hi = self
505 .model
506 .max_bytes_delivered_in_round()
507 .max(new_inflight_hi);
508 }
509
510 self.model.set_inflight_hi(new_inflight_hi);
511 }
512 return AdaptUpperBoundsResult::AdaptedProbedTooHigh;
513 }
514 return AdaptUpperBoundsResult::AdaptedOk;
515 }
516
517 if self.model.inflight_hi() == self.model.inflight_hi_default() {
518 return AdaptUpperBoundsResult::NotAdaptedInflightHighNotSet;
519 }
520
521 if inflight_at_send > self.model.inflight_hi() {
523 self.model.set_inflight_hi(inflight_at_send);
524 }
525
526 AdaptUpperBoundsResult::AdaptedOk
527 }
528
529 fn has_cycle_lasted(
530 &self, duration: Duration, congestion_event: &BBRv2CongestionEvent,
531 ) -> bool {
532 (congestion_event.event_time - self.cycle.start_time) > duration
533 }
534
535 fn has_phase_lasted(
536 &self, duration: Duration, congestion_event: &BBRv2CongestionEvent,
537 ) -> bool {
538 (congestion_event.event_time - self.cycle.phase_start_time) > duration
539 }
540
541 fn is_time_to_probe_for_reno_coexistence(
542 &self, target_bytes_inflight: usize, probe_wait_fraction: f64,
543 _congestion_event: &BBRv2CongestionEvent, params: &Params,
544 ) -> bool {
545 if !params.enable_reno_coexistence {
546 return false;
547 }
548
549 let mut rounds = params.probe_bw_probe_max_rounds;
550 if params.probe_bw_probe_reno_gain > 0.0 {
551 let reno_rounds = (params.probe_bw_probe_reno_gain *
552 target_bytes_inflight as f32 /
553 DEFAULT_MSS as f32) as usize;
554 rounds = reno_rounds.min(rounds);
555 }
556
557 self.cycle.rounds_since_probe >=
558 (rounds as f64 * probe_wait_fraction) as usize
559 }
560
561 fn has_stayed_long_enough_in_probe_down(
569 &self, congestion_event: &BBRv2CongestionEvent, params: &Params,
570 ) -> bool {
571 if params.disable_probe_down_early_exit {
572 return false;
573 }
574
575 self.has_phase_lasted(self.model.min_rtt(), congestion_event)
578 }
579
580 fn raise_inflight_high_slope(&mut self, cwnd: usize) {
581 let growth_this_round = 1usize << self.cycle.probe_up_rounds;
582 self.cycle.probe_up_rounds = self.cycle.probe_up_rounds.add(1).min(30);
586 let probe_up_bytes = cwnd / growth_this_round;
587 self.cycle.probe_up_bytes = Some(probe_up_bytes.max(DEFAULT_MSS));
588 }
589
590 fn probe_inflight_high_upward(
591 &mut self, congestion_event: &BBRv2CongestionEvent, params: &Params,
592 ) {
593 if params.probe_up_ignore_inflight_hi {
594 return;
597 } else {
598 if congestion_event.prior_bytes_in_flight <
600 congestion_event.prior_cwnd
601 {
602 return;
604 }
605
606 if congestion_event.prior_cwnd < self.model.inflight_hi() {
607 return;
609 }
610
611 self.cycle.probe_up_acked += congestion_event.bytes_acked;
612 }
613
614 if let Some(probe_up_bytes) = self.cycle.probe_up_bytes {
615 if self.cycle.probe_up_acked >= probe_up_bytes {
616 let delta = self.cycle.probe_up_acked / probe_up_bytes;
617 self.cycle.probe_up_acked -= delta * probe_up_bytes;
621 let new_inflight_hi =
622 self.model.inflight_hi() + delta * DEFAULT_MSS;
623 if new_inflight_hi > self.model.inflight_hi() {
624 self.model.set_inflight_hi(new_inflight_hi);
625 }
626 }
627 }
628
629 if congestion_event.end_of_round_trip {
630 self.raise_inflight_high_slope(congestion_event.prior_cwnd);
631 }
632 }
633
634 fn into_probe_rtt(
635 mut self, now: Instant, congestion_event: Option<&BBRv2CongestionEvent>,
636 params: &Params,
637 ) -> Mode {
638 self.leave(now, congestion_event);
639 let mut next_mode = Mode::probe_rtt(self.model, self.cycle);
640 next_mode.enter(now, congestion_event, params);
641 next_mode
642 }
643}
644
645#[cfg(test)]
646mod tests {
647 use rstest::rstest;
648
649 use super::*;
650 use crate::recovery::gcongestion::bbr2::SendTimeState;
651 use crate::recovery::gcongestion::bbr2::DEFAULT_PARAMS;
652
653 #[rstest]
654 fn probe_upward(#[values(100, 10_000, 65_536, 300_000)] step: usize) {
655 let test_event =
656 |probe_bw: &ProbeBW, bytes_acked: usize, end_of_round_trip: bool| {
657 BBRv2CongestionEvent {
658 event_time: probe_bw.cycle.start_time,
659 prior_cwnd: probe_bw.model.inflight_hi(),
660 prior_bytes_in_flight: probe_bw.model.inflight_hi(),
661 bytes_in_flight: 0,
662 bytes_acked,
663 bytes_lost: 0,
664 end_of_round_trip,
665 is_probing_for_bandwidth: true,
666 sample_max_bandwidth: None,
667 sample_min_rtt: None,
668 last_packet_send_state: SendTimeState::default(),
669 }
670 };
671
672 let do_probe_up = |probe_bw: &mut ProbeBW,
673 params: &Params,
674 total_bytes: usize| {
675 let mut remaining = total_bytes;
676 loop {
677 let congestion_event =
678 test_event(probe_bw, step.min(remaining), false);
679
680 probe_bw.probe_inflight_high_upward(&congestion_event, params);
681
682 if remaining > step {
683 remaining -= step;
684 } else {
685 break;
686 }
687 }
688 };
689
690 let params = &DEFAULT_PARAMS;
691 let model = BBRv2NetworkModel::new(params, Duration::from_millis(333));
692 let cycle = Cycle::default();
693 let mut probe_bw = ProbeBW { model, cycle };
694 probe_bw.model.set_inflight_hi(100_000);
695 probe_bw.raise_inflight_high_slope(100_000);
696 assert_eq!(probe_bw.cycle.probe_up_rounds, 1);
697 assert_eq!(probe_bw.cycle.probe_up_bytes, Some(100_000));
698
699 assert_eq!(probe_bw.model.inflight_hi(), 100_000);
700 do_probe_up(&mut probe_bw, params, 1_000_000);
701 assert_eq!(probe_bw.model.inflight_hi(), 113_000);
703
704 probe_bw.probe_inflight_high_upward(
706 &test_event(&probe_bw, 10_000, true),
707 params,
708 );
709 assert_eq!(probe_bw.cycle.probe_up_rounds, 2);
710 assert_eq!(probe_bw.cycle.probe_up_bytes, Some(56500));
711
712 do_probe_up(&mut probe_bw, params, 1_000_000);
713 assert_eq!(probe_bw.model.inflight_hi(), 135_100);
715
716 probe_bw.probe_inflight_high_upward(
717 &test_event(&probe_bw, 10_000, true),
718 params,
719 );
720 assert_eq!(probe_bw.cycle.probe_up_rounds, 3);
721 assert_eq!(probe_bw.cycle.probe_up_bytes, Some(33775));
722
723 do_probe_up(&mut probe_bw, params, 1_000_000);
724 assert_eq!(probe_bw.model.inflight_hi(), 174_100);
726 }
727}