use std::time::{Duration, Instant};
#[derive(Debug, Clone, Default)]
pub struct PeriodicStats {
send_count: u64,
last_send_time: Option<Instant>,
total_interval: Duration,
interval_samples: u64,
min_interval: Option<Duration>,
max_interval: Option<Duration>,
}
impl PeriodicStats {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn record_send(&mut self, now: Instant) {
self.send_count += 1;
if let Some(last) = self.last_send_time {
let interval = now.duration_since(last);
self.total_interval += interval;
self.interval_samples += 1;
match self.min_interval {
Some(min) if interval < min => self.min_interval = Some(interval),
None => self.min_interval = Some(interval),
_ => {}
}
match self.max_interval {
Some(max) if interval > max => self.max_interval = Some(interval),
None => self.max_interval = Some(interval),
_ => {}
}
}
self.last_send_time = Some(now);
}
#[must_use]
pub fn send_count(&self) -> u64 {
self.send_count
}
#[must_use]
pub fn last_send_time(&self) -> Option<Instant> {
self.last_send_time
}
#[must_use]
pub fn average_interval(&self) -> Option<Duration> {
if self.interval_samples > 0 {
let samples = u32::try_from(self.interval_samples).ok()?;
Some(self.total_interval / samples)
} else {
None
}
}
#[must_use]
pub fn min_interval(&self) -> Option<Duration> {
self.min_interval
}
#[must_use]
pub fn max_interval(&self) -> Option<Duration> {
self.max_interval
}
#[must_use]
pub fn jitter(&self) -> Option<Duration> {
match (self.min_interval, self.max_interval) {
(Some(min), Some(max)) => max.checked_sub(min),
_ => None,
}
}
pub fn reset(&mut self) {
*self = Self::default();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_stats() {
let stats = PeriodicStats::new();
assert_eq!(stats.send_count(), 0);
assert!(stats.last_send_time().is_none());
assert!(stats.average_interval().is_none());
}
#[test]
fn test_record_single_send() {
let mut stats = PeriodicStats::new();
let now = Instant::now();
stats.record_send(now);
assert_eq!(stats.send_count(), 1);
assert!(stats.last_send_time().is_some());
assert!(stats.average_interval().is_none()); }
#[test]
fn test_record_multiple_sends() {
let mut stats = PeriodicStats::new();
let start = Instant::now();
stats.record_send(start);
stats.record_send(start + Duration::from_millis(100));
stats.record_send(start + Duration::from_millis(200));
assert_eq!(stats.send_count(), 3);
assert_eq!(stats.interval_samples, 2);
let avg = stats.average_interval().unwrap();
assert_eq!(avg, Duration::from_millis(100));
}
#[test]
fn test_min_max_interval() {
let mut stats = PeriodicStats::new();
let start = Instant::now();
stats.record_send(start);
stats.record_send(start + Duration::from_millis(90));
stats.record_send(start + Duration::from_millis(200));
assert_eq!(stats.min_interval(), Some(Duration::from_millis(90)));
assert_eq!(stats.max_interval(), Some(Duration::from_millis(110)));
}
#[test]
fn test_jitter() {
let mut stats = PeriodicStats::new();
let start = Instant::now();
stats.record_send(start);
stats.record_send(start + Duration::from_millis(90));
stats.record_send(start + Duration::from_millis(200));
let jitter = stats.jitter().unwrap();
assert_eq!(jitter, Duration::from_millis(20)); }
#[test]
fn test_reset() {
let mut stats = PeriodicStats::new();
let now = Instant::now();
stats.record_send(now);
stats.record_send(now + Duration::from_millis(100));
assert_eq!(stats.send_count(), 2);
stats.reset();
assert_eq!(stats.send_count(), 0);
assert!(stats.last_send_time().is_none());
assert!(stats.average_interval().is_none());
}
}