#[derive(Clone, Debug)]
pub struct ArrivalMonitor {
smoothing: f32,
interval_ms: f32,
newest_stamp: u64,
lateness_mean_ms: f32,
jitter_ms: f32,
lateness_seeded: bool,
}
impl ArrivalMonitor {
pub fn new(smoothing: f32) -> Self {
Self {
smoothing: smoothing.clamp(0.0, 1.0),
interval_ms: 0.0,
newest_stamp: 0,
lateness_mean_ms: 0.0,
jitter_ms: 0.0,
lateness_seeded: false,
}
}
pub fn observe(&mut self, stamp: u64, recv: u64) {
let lateness = recv.saturating_sub(stamp) as f32;
if self.newest_stamp > 0 && stamp > self.newest_stamp {
let gap = (stamp - self.newest_stamp) as f32;
self.interval_ms = if self.interval_ms == 0.0 {
gap
} else {
self.interval_ms + (gap - self.interval_ms) * self.smoothing
};
}
if stamp > self.newest_stamp {
self.newest_stamp = stamp;
}
if !self.lateness_seeded {
self.lateness_seeded = true;
self.lateness_mean_ms = lateness;
} else {
let deviation = (lateness - self.lateness_mean_ms).abs();
self.lateness_mean_ms += (lateness - self.lateness_mean_ms) * self.smoothing;
self.jitter_ms += (deviation - self.jitter_ms) * self.smoothing;
}
}
pub fn interval_ms(&self) -> f32 {
self.interval_ms
}
pub fn lateness_ms(&self) -> f32 {
self.lateness_mean_ms
}
pub fn jitter_ms(&self) -> f32 {
self.jitter_ms
}
pub fn needed_delay_ms(&self) -> f32 {
self.lateness_mean_ms + self.jitter_ms + self.interval_ms
}
pub fn warmed_up(&self) -> bool {
self.interval_ms > 0.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_steady_stream_measures_its_interval_and_delay() {
let mut m = ArrivalMonitor::new(0.2);
for i in 0..200u64 {
let stamp = i * 100;
m.observe(stamp, stamp + 30);
}
assert!(m.warmed_up());
assert!((m.interval_ms() - 100.0).abs() < 1.0, "interval {}", m.interval_ms());
assert!((m.lateness_ms() - 30.0).abs() < 1.0, "lateness {}", m.lateness_ms());
assert!(m.jitter_ms() < 1.0, "a steady link has no spread: {}", m.jitter_ms());
}
#[test]
fn a_constant_delay_needs_no_more_buffer_than_a_small_one() {
let mut slow = ArrivalMonitor::new(0.2);
let mut fast = ArrivalMonitor::new(0.2);
for i in 0..200u64 {
let stamp = i * 100;
slow.observe(stamp, stamp + 200);
fast.observe(stamp, stamp + 20);
}
assert!(slow.jitter_ms() < 1.0 && fast.jitter_ms() < 1.0);
let gap = slow.needed_delay_ms() - fast.needed_delay_ms();
assert!((gap - 180.0).abs() < 2.0, "gap {gap}");
}
#[test]
fn irregular_arrivals_widen_the_budget() {
let mut steady = ArrivalMonitor::new(0.2);
let mut bursty = ArrivalMonitor::new(0.2);
for i in 0..200u64 {
let stamp = i * 100;
steady.observe(stamp, stamp + 40);
let late = if i % 2 == 0 { 20 } else { 60 };
bursty.observe(stamp, stamp + late);
}
assert!(
bursty.needed_delay_ms() > steady.needed_delay_ms() + 10.0,
"spread must cost buffer: {} vs {}",
bursty.needed_delay_ms(),
steady.needed_delay_ms()
);
}
#[test]
fn a_loopback_stream_with_zero_lateness_still_measures_its_jitter() {
let mut m = ArrivalMonitor::new(0.2);
for i in 0..100u64 {
let stamp = i * 100;
let late = if i % 10 == 9 { 40 } else { 0 };
m.observe(stamp, stamp + late);
}
assert!(m.jitter_ms() > 2.0, "the occasional straggler is spread, and spread must register: {}", m.jitter_ms());
}
#[test]
fn a_reordered_stamp_is_lateness_data_but_not_an_interval() {
let mut m = ArrivalMonitor::new(0.5);
m.observe(100, 130);
m.observe(200, 230);
let interval_before = m.interval_ms();
m.observe(150, 260);
assert_eq!(m.interval_ms(), interval_before, "intervals are measured forward only");
assert!(m.lateness_ms() > 30.0, "but its lateness counted: {}", m.lateness_ms());
}
}