Skip to main content

congestion/
fixed.rs

1//! A controller that honors a static, user-supplied budget. Matches the
2//! behavior of the existing manual `--ops-throttle` / `--iops-throttle` flags.
3
4use crate::controller::{Controller, ControllerSnapshot, Decision, Sample};
5
6/// Controller that always emits the same configured [`Decision`].
7///
8/// Useful as an explicit opt-out from adaptive control while still wanting a
9/// hard cap, and as a regression baseline when comparing adaptive algorithms
10/// under the simulator.
11#[derive(Debug, Clone, Copy)]
12pub struct FixedController {
13    decision: Decision,
14}
15
16impl FixedController {
17    pub fn new(decision: Decision) -> Self {
18        Self { decision }
19    }
20    pub fn with_concurrency(max_in_flight: u32) -> Self {
21        Self::new(Decision::with_concurrency(max_in_flight))
22    }
23    pub fn with_rate(rate_per_sec: f64) -> Self {
24        Self::new(Decision::with_rate(rate_per_sec))
25    }
26    pub fn with_concurrency_and_rate(max_in_flight: u32, rate_per_sec: f64) -> Self {
27        Self::new(Decision::with_concurrency_and_rate(
28            max_in_flight,
29            rate_per_sec,
30        ))
31    }
32}
33
34impl Controller for FixedController {
35    fn on_sample(&mut self, _sample: &Sample) {}
36    fn on_tick(&mut self, _now: std::time::Instant) -> Decision {
37        self.decision
38    }
39    fn name(&self) -> &'static str {
40        "fixed"
41    }
42    fn snapshot(&self) -> ControllerSnapshot {
43        ControllerSnapshot {
44            cwnd: self.decision.max_in_flight.unwrap_or(0),
45            ..ControllerSnapshot::default()
46        }
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53    use crate::controller::Outcome;
54
55    #[test]
56    fn emits_configured_concurrency_budget() {
57        let mut controller = FixedController::with_concurrency(16);
58        let decision = controller.on_tick(std::time::Instant::now());
59        assert_eq!(decision.max_in_flight, Some(16));
60        assert_eq!(decision.rate_per_sec, None);
61    }
62
63    #[test]
64    fn emits_configured_rate_budget() {
65        let mut controller = FixedController::with_rate(5000.0);
66        let decision = controller.on_tick(std::time::Instant::now());
67        assert_eq!(decision.max_in_flight, None);
68        assert_eq!(decision.rate_per_sec, Some(5000.0));
69    }
70
71    #[test]
72    fn samples_do_not_change_emitted_decision() {
73        let mut controller = FixedController::with_concurrency(4);
74        let start = std::time::Instant::now();
75        controller.on_sample(&Sample {
76            started_at: start,
77            completed_at: start + std::time::Duration::from_secs(60),
78            bytes: 0,
79            outcome: Outcome::Backpressure,
80        });
81        assert_eq!(controller.on_tick(start).max_in_flight, Some(4));
82    }
83}