use std::time::Instant;
use super::BufferEstimator;
pub struct SoftwareDecayEstimator {
fullness_at_anchor: u64,
anchor_time: Instant,
}
impl SoftwareDecayEstimator {
pub fn new() -> Self {
Self {
fullness_at_anchor: 0,
anchor_time: Instant::now(),
}
}
pub fn reset(&mut self, now: Instant) {
self.fullness_at_anchor = 0;
self.anchor_time = now;
}
pub fn record_send(&mut self, now: Instant, n: u64, pps: u32) {
let current = self.read_at(now, pps);
self.fullness_at_anchor = current.saturating_add(n);
self.anchor_time = now;
}
fn read_at(&self, now: Instant, pps: u32) -> u64 {
let elapsed_secs = now
.saturating_duration_since(self.anchor_time)
.as_secs_f64();
let consumed = (elapsed_secs * pps as f64) as u64;
self.fullness_at_anchor.saturating_sub(consumed)
}
}
impl Default for SoftwareDecayEstimator {
fn default() -> Self {
Self::new()
}
}
impl BufferEstimator for SoftwareDecayEstimator {
fn estimated_fullness(&self, now: Instant, pps: u32) -> u64 {
self.read_at(now, pps)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn new_estimator_starts_empty() {
let est = SoftwareDecayEstimator::new();
assert_eq!(est.estimated_fullness(Instant::now(), 30_000), 0);
}
#[test]
fn record_send_increases_fullness() {
let mut est = SoftwareDecayEstimator::new();
let t = Instant::now();
est.record_send(t, 1000, 30_000);
assert_eq!(est.estimated_fullness(t, 30_000), 1000);
}
#[test]
fn fullness_decays_over_time() {
let mut est = SoftwareDecayEstimator::new();
let t0 = Instant::now();
est.record_send(t0, 3000, 30_000);
let t1 = t0 + Duration::from_millis(50);
assert_eq!(est.estimated_fullness(t1, 30_000), 1500);
}
#[test]
fn fullness_clamps_to_zero() {
let mut est = SoftwareDecayEstimator::new();
let t0 = Instant::now();
est.record_send(t0, 100, 30_000);
let t_far = t0 + Duration::from_secs(10);
assert_eq!(est.estimated_fullness(t_far, 30_000), 0);
}
#[test]
fn record_send_rebases_from_decayed_current() {
let mut est = SoftwareDecayEstimator::new();
let t0 = Instant::now();
est.record_send(t0, 3000, 30_000);
let t1 = t0 + Duration::from_millis(50);
est.record_send(t1, 500, 30_000);
assert_eq!(est.estimated_fullness(t1, 30_000), 2000);
}
#[test]
fn anchor_reads_avoid_fractional_truncation_drift() {
let mut est = SoftwareDecayEstimator::new();
let t0 = Instant::now();
est.record_send(t0, 10, 10);
let t1 = t0 + Duration::from_millis(50);
assert_eq!(est.estimated_fullness(t1, 10), 10);
let t2 = t0 + Duration::from_millis(100);
assert_eq!(est.estimated_fullness(t2, 10), 9);
}
#[test]
fn reset_clears_state() {
let mut est = SoftwareDecayEstimator::new();
let t0 = Instant::now();
est.record_send(t0, 1000, 30_000);
let t1 = t0 + Duration::from_millis(1);
est.reset(t1);
assert_eq!(est.estimated_fullness(t1, 30_000), 0);
}
#[test]
fn zero_pps_no_decay() {
let mut est = SoftwareDecayEstimator::new();
let t0 = Instant::now();
est.record_send(t0, 500, 0);
let later = t0 + Duration::from_secs(5);
assert_eq!(est.estimated_fullness(later, 0), 500);
}
}