use std::time::{Duration, Instant};
use crate::rtp_::Bitrate;
#[derive(Default)]
pub struct LinkCapacityEstimator {
capacity_estimate: Option<Bitrate>,
last_estimate_time: Option<Instant>,
}
impl LinkCapacityEstimator {
const DEFAULT_RESET_WINDOW: Duration = Duration::from_secs(60);
pub fn new() -> Self {
Self::default()
}
pub fn update_from_probe(&mut self, probe_estimate: Bitrate, now: Instant) {
if !probe_estimate.is_valid() {
return;
}
let current = self.capacity_estimate.get_or_insert(probe_estimate);
*current = (*current).max(probe_estimate);
self.last_estimate_time = Some(now);
trace!(
"Link capacity estimate updated to {} from probe",
probe_estimate
);
}
pub fn capacity_estimate(&self, now: Instant) -> Option<Bitrate> {
let estimate = self.capacity_estimate?;
let last_time = self.last_estimate_time?;
if now.saturating_duration_since(last_time) > Self::DEFAULT_RESET_WINDOW {
trace!("Link capacity estimate expired");
return None;
}
Some(estimate)
}
#[cfg(test)]
pub fn reset(&mut self) {
if self.capacity_estimate.is_some() {
trace!("Link capacity estimate reset");
}
self.capacity_estimate = None;
self.last_estimate_time = None;
}
#[cfg(test)]
pub fn has_estimate(&self) -> bool {
self.capacity_estimate.is_some() && self.last_estimate_time.is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn starts_with_no_estimate() {
let estimator = LinkCapacityEstimator::new();
let now = Instant::now();
assert_eq!(estimator.capacity_estimate(now), None);
assert!(!estimator.has_estimate());
}
#[test]
fn stores_probe_result() {
let mut estimator = LinkCapacityEstimator::new();
let now = Instant::now();
let probe_result = Bitrate::mbps(10);
estimator.update_from_probe(probe_result, now);
assert_eq!(estimator.capacity_estimate(now), Some(probe_result));
assert!(estimator.has_estimate());
}
#[test]
fn takes_maximum_of_multiple_probes() {
let mut estimator = LinkCapacityEstimator::new();
let now = Instant::now();
estimator.update_from_probe(Bitrate::mbps(10), now);
estimator.update_from_probe(Bitrate::mbps(5), now);
assert_eq!(estimator.capacity_estimate(now), Some(Bitrate::mbps(10)));
estimator.update_from_probe(Bitrate::mbps(15), now);
assert_eq!(estimator.capacity_estimate(now), Some(Bitrate::mbps(15)));
}
#[test]
fn estimate_expires_after_reset_window() {
let mut estimator = LinkCapacityEstimator::new();
let now = Instant::now();
estimator.update_from_probe(Bitrate::mbps(10), now);
assert_eq!(estimator.capacity_estimate(now), Some(Bitrate::mbps(10)));
let almost_expired = now + Duration::from_secs(59);
assert_eq!(
estimator.capacity_estimate(almost_expired),
Some(Bitrate::mbps(10))
);
let expired = now + Duration::from_secs(61);
assert_eq!(estimator.capacity_estimate(expired), None);
}
#[test]
fn reset_clears_estimate() {
let mut estimator = LinkCapacityEstimator::new();
let now = Instant::now();
estimator.update_from_probe(Bitrate::mbps(10), now);
assert!(estimator.has_estimate());
estimator.reset();
assert!(!estimator.has_estimate());
assert_eq!(estimator.capacity_estimate(now), None);
}
#[test]
fn ignores_invalid_probes() {
let mut estimator = LinkCapacityEstimator::new();
let now = Instant::now();
estimator.update_from_probe(Bitrate::mbps(10), now);
estimator.update_from_probe(Bitrate::NEG_INFINITY, now);
assert_eq!(estimator.capacity_estimate(now), Some(Bitrate::mbps(10)));
}
}