use std::collections::VecDeque;
use std::time::{Duration, Instant};
#[derive(Debug)]
pub struct BandwidthEstimator {
samples: VecDeque<BandwidthSample>,
window_duration: Duration,
total_bytes: u64,
}
#[derive(Debug, Clone)]
struct BandwidthSample {
timestamp: Instant,
bytes: u64,
}
impl BandwidthEstimator {
#[must_use]
pub fn new(window_duration: Duration) -> Self {
Self {
samples: VecDeque::new(),
window_duration,
total_bytes: 0,
}
}
pub fn record(&mut self, bytes: u64) {
let now = Instant::now();
while let Some(sample) = self.samples.front() {
if now.duration_since(sample.timestamp) > self.window_duration {
if let Some(old) = self.samples.pop_front() {
self.total_bytes = self.total_bytes.saturating_sub(old.bytes);
}
} else {
break;
}
}
self.samples.push_back(BandwidthSample {
timestamp: now,
bytes,
});
self.total_bytes += bytes;
}
#[must_use]
pub fn estimate(&self) -> f64 {
if self.samples.is_empty() {
return 0.0;
}
let oldest = match self.samples.front() {
Some(s) => s.timestamp,
None => return 0.0,
};
let duration = Instant::now().duration_since(oldest).as_secs_f64();
if duration > 0.0 {
self.total_bytes as f64 / duration
} else {
0.0
}
}
#[must_use]
pub fn mbps(&self) -> f64 {
(self.estimate() * 8.0) / 1_000_000.0
}
pub fn reset(&mut self) {
self.samples.clear();
self.total_bytes = 0;
}
}
#[derive(Debug, Clone, Default)]
pub struct QualityMetrics {
pub rtt: u32,
pub rtt_var: u32,
pub loss_rate: f64,
pub bandwidth: f64,
pub send_buffer_util: f64,
pub recv_buffer_util: f64,
pub retransmit_count: u64,
pub jitter: u32,
}
impl QualityMetrics {
#[must_use]
pub const fn new() -> Self {
Self {
rtt: 0,
rtt_var: 0,
loss_rate: 0.0,
bandwidth: 0.0,
send_buffer_util: 0.0,
recv_buffer_util: 0.0,
retransmit_count: 0,
jitter: 0,
}
}
#[must_use]
pub const fn is_good(&self) -> bool {
self.loss_rate < 0.01 && self.rtt < 100_000
}
#[must_use]
pub const fn is_degraded(&self) -> bool {
self.loss_rate > 0.05 || self.rtt > 500_000
}
#[must_use]
pub fn quality_score(&self) -> f64 {
let mut score = 100.0;
score -= self.loss_rate * 1000.0;
let rtt_ms = self.rtt as f64 / 1000.0;
if rtt_ms > 50.0 {
score -= (rtt_ms - 50.0) * 0.5;
}
let jitter_ms = self.jitter as f64 / 1000.0;
if jitter_ms > 10.0 {
score -= (jitter_ms - 10.0) * 0.3;
}
score.clamp(0.0, 100.0)
}
}
#[derive(Debug)]
pub struct JitterCalculator {
last_arrival: Option<Instant>,
last_timestamp: u32,
jitter: f64,
}
impl JitterCalculator {
#[must_use]
pub const fn new() -> Self {
Self {
last_arrival: None,
last_timestamp: 0,
jitter: 0.0,
}
}
pub fn update(&mut self, arrival: Instant, timestamp: u32) {
if let Some(last_arrival) = self.last_arrival {
let arrival_delta = arrival.duration_since(last_arrival).as_micros() as i64;
let timestamp_delta = timestamp.wrapping_sub(self.last_timestamp) as i64;
let delta = arrival_delta - timestamp_delta;
let abs_delta = delta.unsigned_abs() as f64;
self.jitter += (abs_delta - self.jitter) / 16.0;
}
self.last_arrival = Some(arrival);
self.last_timestamp = timestamp;
}
#[must_use]
pub const fn jitter(&self) -> u32 {
self.jitter as u32
}
pub fn reset(&mut self) {
self.last_arrival = None;
self.last_timestamp = 0;
self.jitter = 0.0;
}
}
impl Default for JitterCalculator {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct LossRateEstimator {
total_expected: u64,
total_lost: u64,
recent_samples: VecDeque<(u64, u64)>, window_size: usize,
}
impl LossRateEstimator {
#[must_use]
pub fn new(window_size: usize) -> Self {
Self {
total_expected: 0,
total_lost: 0,
recent_samples: VecDeque::with_capacity(window_size),
window_size,
}
}
pub fn record(&mut self, expected: u64, lost: u64) {
self.total_expected += expected;
self.total_lost += lost;
self.recent_samples.push_back((expected, lost));
if self.recent_samples.len() > self.window_size {
self.recent_samples.pop_front();
}
}
#[must_use]
pub fn overall_loss_rate(&self) -> f64 {
if self.total_expected == 0 {
0.0
} else {
self.total_lost as f64 / self.total_expected as f64
}
}
#[must_use]
pub fn recent_loss_rate(&self) -> f64 {
let (total_exp, total_lost): (u64, u64) = self
.recent_samples
.iter()
.fold((0, 0), |(exp, lost), &(e, l)| (exp + e, lost + l));
if total_exp == 0 {
0.0
} else {
total_lost as f64 / total_exp as f64
}
}
pub fn reset(&mut self) {
self.total_expected = 0;
self.total_lost = 0;
self.recent_samples.clear();
}
}
#[derive(Debug)]
pub struct ConnectionMonitor {
bandwidth: BandwidthEstimator,
jitter: JitterCalculator,
loss_rate: LossRateEstimator,
last_update: Instant,
}
impl ConnectionMonitor {
#[must_use]
pub fn new() -> Self {
Self {
bandwidth: BandwidthEstimator::new(Duration::from_secs(5)),
jitter: JitterCalculator::new(),
loss_rate: LossRateEstimator::new(100),
last_update: Instant::now(),
}
}
pub fn record_send(&mut self, bytes: u64) {
self.bandwidth.record(bytes);
self.last_update = Instant::now();
}
pub fn record_receive(&mut self, bytes: u64, timestamp: u32) {
self.bandwidth.record(bytes);
self.jitter.update(Instant::now(), timestamp);
self.last_update = Instant::now();
}
pub fn record_loss(&mut self, expected: u64, lost: u64) {
self.loss_rate.record(expected, lost);
}
#[must_use]
pub fn metrics(&self, rtt: u32, rtt_var: u32, retransmit_count: u64) -> QualityMetrics {
QualityMetrics {
rtt,
rtt_var,
loss_rate: self.loss_rate.recent_loss_rate(),
bandwidth: self.bandwidth.estimate(),
send_buffer_util: 0.0, recv_buffer_util: 0.0,
retransmit_count,
jitter: self.jitter.jitter(),
}
}
#[must_use]
pub fn bandwidth_mbps(&self) -> f64 {
self.bandwidth.mbps()
}
pub fn reset(&mut self) {
self.bandwidth.reset();
self.jitter.reset();
self.loss_rate.reset();
self.last_update = Instant::now();
}
#[must_use]
pub fn time_since_update(&self) -> Duration {
self.last_update.elapsed()
}
}
impl Default for ConnectionMonitor {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bandwidth_estimator() {
let mut est = BandwidthEstimator::new(Duration::from_secs(1));
est.record(1000);
assert!(est.estimate() > 0.0);
}
#[test]
fn test_quality_metrics_score() {
let metrics = QualityMetrics {
rtt: 50_000,
loss_rate: 0.0,
jitter: 5_000,
..Default::default()
};
let score = metrics.quality_score();
assert!(score > 90.0);
assert!(metrics.is_good());
}
#[test]
fn test_quality_metrics_degraded() {
let metrics = QualityMetrics {
rtt: 600_000,
loss_rate: 0.1,
..Default::default()
};
assert!(metrics.is_degraded());
assert!(!metrics.is_good());
}
#[test]
fn test_jitter_calculator() {
let mut calc = JitterCalculator::new();
let now = Instant::now();
calc.update(now, 0);
calc.update(now + Duration::from_millis(20), 20_000);
assert_eq!(calc.jitter(), 0);
}
#[test]
fn test_loss_rate_estimator() {
let mut est = LossRateEstimator::new(10);
est.record(100, 5);
est.record(100, 3);
let rate = est.overall_loss_rate();
assert!((rate - 0.04).abs() < 0.01);
}
#[test]
fn test_connection_monitor() {
let mut monitor = ConnectionMonitor::new();
monitor.record_send(1000);
monitor.record_receive(500, 1000);
let metrics = monitor.metrics(50_000, 10_000, 0);
assert_eq!(metrics.rtt, 50_000);
}
}