rtc_interceptor/gcc/
rate_control.rs1use super::overuse::Usage;
4use super::state::RateControlState;
5use std::time::{Duration, Instant};
6
7pub const DEFAULT_DECREASE_FACTOR: f64 = 0.85;
9
10pub const DEFAULT_INCREASE_FACTOR: f64 = 1.08;
12
13pub const DEFAULT_RATE_CONTROL_INTERVAL: Duration = Duration::from_millis(200);
15
16#[derive(Debug, Clone)]
26pub struct RateController {
27 state: RateControlState,
28 target: f64,
30 min: f64,
31 max: f64,
32 decrease_factor: f64,
33 increase_factor: f64,
34 interval: Duration,
35 last_decrease_rate: Option<f64>,
37 last_change: Option<Instant>,
38}
39
40impl RateController {
41 pub fn new(initial: f64, min: f64, max: f64) -> Self {
46 Self {
47 state: RateControlState::default(),
48 target: initial.clamp(min, max),
49 min,
50 max,
51 decrease_factor: DEFAULT_DECREASE_FACTOR,
52 increase_factor: DEFAULT_INCREASE_FACTOR,
53 interval: DEFAULT_RATE_CONTROL_INTERVAL,
54 last_decrease_rate: None,
55 last_change: None,
56 }
57 }
58
59 pub fn with_decrease_factor(mut self, decrease_factor: f64) -> Self {
61 self.decrease_factor = decrease_factor;
62 self
63 }
64
65 pub fn target(&self) -> f64 {
67 self.target
68 }
69
70 pub fn state(&self) -> RateControlState {
72 self.state
73 }
74
75 pub fn update(&mut self, now: Instant, usage: Usage, received: Option<f64>) -> f64 {
80 self.state = self.state.next(usage);
81
82 if let Some(last) = self.last_change
85 && now.saturating_duration_since(last) < self.interval
86 && self.state != RateControlState::Decrease
87 {
88 return self.target;
89 }
90
91 match self.state {
92 RateControlState::Hold => {}
93
94 RateControlState::Decrease => {
95 let Some(received) = received else {
97 return self.target;
98 };
99 self.target = (received * self.decrease_factor).clamp(self.min, self.max);
100 self.last_decrease_rate = Some(received);
101 self.last_change = Some(now);
102 }
103
104 RateControlState::Increase => {
105 let elapsed = self
106 .last_change
107 .map_or(self.interval, |last| now.saturating_duration_since(last));
108
109 let near_the_ceiling = self
110 .last_decrease_rate
111 .is_some_and(|ceiling| self.target > ceiling * 0.85);
112
113 self.target = if near_the_ceiling {
114 (self.target + 12_000.0).clamp(self.min, self.max)
117 } else {
118 let growth = self.increase_factor.powf(elapsed.as_secs_f64().min(1.0));
119 (self.target * growth).clamp(self.min, self.max)
120 };
121 self.last_change = Some(now);
122 }
123 }
124
125 self.target
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 const MIN: f64 = 100_000.0;
134 const MAX: f64 = 10_000_000.0;
135
136 fn controller() -> RateController {
137 RateController::new(1_000_000.0, MIN, MAX)
138 }
139
140 #[test]
143 fn overuse_backs_off_from_the_received_rate() {
144 let epoch = Instant::now();
145 let mut controller = RateController::new(2_000_000.0, MIN, MAX);
146
147 let target = controller.update(epoch, Usage::Over, Some(600_000.0));
148
149 assert!(
150 (target - 510_000.0).abs() < 1.0,
151 "expected 0.85 × 600 kb/s, got {target}"
152 );
153 }
154
155 #[test]
157 fn a_quiet_path_increases() {
158 let epoch = Instant::now();
159 let mut controller = controller();
160
161 let mut at = epoch;
162 let before = controller.target();
163 for _ in 0..10 {
164 at += DEFAULT_RATE_CONTROL_INTERVAL;
165 controller.update(at, Usage::Normal, Some(1_000_000.0));
166 }
167
168 assert!(
169 controller.target() > before,
170 "a healthy path should be probed for more: {before} → {}",
171 controller.target()
172 );
173 }
174
175 #[test]
178 fn it_climbs_carefully_near_a_known_ceiling() {
179 let epoch = Instant::now();
180 let mut controller = controller();
181
182 let mut at = epoch;
184 controller.update(at, Usage::Over, Some(1_000_000.0));
185 let after_backoff = controller.target();
186
187 let mut steps = 0;
189 while controller.target() < after_backoff * 1.5 && steps < 200 {
190 at += DEFAULT_RATE_CONTROL_INTERVAL;
191 controller.update(at, Usage::Normal, Some(1_000_000.0));
192 steps += 1;
193 }
194
195 assert!(
196 steps > 5,
197 "approaching a known ceiling should take several steps, took {steps}"
198 );
199 }
200
201 #[test]
204 fn it_holds_when_the_received_rate_is_unknown() {
205 let epoch = Instant::now();
206 let mut controller = controller();
207 let before = controller.target();
208
209 let target = controller.update(epoch, Usage::Over, None);
210
211 assert_eq!(before, target, "no rate to back off from means no change");
212 }
213
214 #[test]
217 fn the_target_stays_within_configured_bounds() {
218 let epoch = Instant::now();
219 let mut controller = RateController::new(MIN, MIN, 500_000.0);
220
221 let mut at = epoch;
222 for _ in 0..500 {
223 at += DEFAULT_RATE_CONTROL_INTERVAL;
224 controller.update(at, Usage::Normal, Some(10_000_000.0));
225 }
226 assert!(
227 controller.target() <= 500_000.0,
228 "ceiling breached: {}",
229 controller.target()
230 );
231
232 for _ in 0..50 {
233 at += DEFAULT_RATE_CONTROL_INTERVAL;
234 controller.update(at, Usage::Over, Some(1.0));
235 }
236 assert!(
237 controller.target() >= MIN,
238 "floor breached: {}",
239 controller.target()
240 );
241 }
242
243 #[test]
246 fn increases_are_paced_but_a_decrease_is_not() {
247 let epoch = Instant::now();
248 let mut controller = controller();
249
250 controller.update(epoch, Usage::Normal, Some(1_000_000.0));
251 let after_first = controller.target();
252 controller.update(
254 epoch + Duration::from_millis(1),
255 Usage::Normal,
256 Some(1_000_000.0),
257 );
258 assert_eq!(
259 after_first,
260 controller.target(),
261 "a second increase in the same interval must be ignored"
262 );
263
264 let before_backoff = controller.target();
265 controller.update(
266 epoch + Duration::from_millis(2),
267 Usage::Over,
268 Some(500_000.0),
269 );
270 assert!(
271 controller.target() < before_backoff,
272 "a decrease must not be delayed by the pacing interval"
273 );
274 }
275}