use std::collections::VecDeque;
use std::time::Instant;
const HISTORY_SLOTS: usize = 40;
#[derive(Debug, Clone)]
pub struct FlowHistory {
pub sent: VecDeque<u64>,
pub recv: VecDeque<u64>,
pub total_sent: u64,
pub total_recv: u64,
pub peak_sent: f64,
pub peak_recv: f64,
pub last_seen: Instant,
pub process_name: Option<String>,
pub pid: Option<u32>,
}
impl Default for FlowHistory {
fn default() -> Self {
Self::new()
}
}
impl FlowHistory {
pub fn new() -> Self {
let now = Instant::now();
let mut sent = VecDeque::with_capacity(HISTORY_SLOTS);
let mut recv = VecDeque::with_capacity(HISTORY_SLOTS);
sent.push_back(0);
recv.push_back(0);
FlowHistory {
sent,
recv,
total_sent: 0,
total_recv: 0,
peak_sent: 0.0,
peak_recv: 0.0,
last_seen: now,
process_name: None,
pid: None,
}
}
pub fn add_sent(&mut self, bytes: u64) {
self.total_sent += bytes;
self.last_seen = Instant::now();
if let Some(slot) = self.sent.back_mut() {
*slot += bytes;
}
}
pub fn add_recv(&mut self, bytes: u64) {
self.total_recv += bytes;
self.last_seen = Instant::now();
if let Some(slot) = self.recv.back_mut() {
*slot += bytes;
}
}
pub fn rotate(&mut self) {
if let Some(&last) = self.sent.back() {
let rate = last as f64;
if rate > self.peak_sent {
self.peak_sent = rate;
}
}
if let Some(&last) = self.recv.back() {
let rate = last as f64;
if rate > self.peak_recv {
self.peak_recv = rate;
}
}
self.sent.push_back(0);
self.recv.push_back(0);
if self.sent.len() > HISTORY_SLOTS {
self.sent.pop_front();
}
if self.recv.len() > HISTORY_SLOTS {
self.recv.pop_front();
}
}
fn window_total(slots: &VecDeque<u64>, n: usize) -> f64 {
let len = slots.len();
if len == 0 {
return 0.0;
}
let take = n.min(len);
let sum: u64 = slots.iter().rev().take(take).sum();
sum as f64
}
pub fn avg_sent_2s(&self) -> f64 {
Self::window_total(&self.sent, 2)
}
pub fn avg_sent_10s(&self) -> f64 {
Self::window_total(&self.sent, 10)
}
pub fn avg_sent_40s(&self) -> f64 {
Self::window_total(&self.sent, 40)
}
pub fn avg_recv_2s(&self) -> f64 {
Self::window_total(&self.recv, 2)
}
pub fn avg_recv_10s(&self) -> f64 {
Self::window_total(&self.recv, 10)
}
pub fn avg_recv_40s(&self) -> f64 {
Self::window_total(&self.recv, 40)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_history_is_empty() {
let h = FlowHistory::new();
assert_eq!(h.total_sent, 0);
assert_eq!(h.total_recv, 0);
assert_eq!(h.peak_sent, 0.0);
assert_eq!(h.peak_recv, 0.0);
assert_eq!(h.sent.len(), 1);
assert_eq!(h.recv.len(), 1);
}
#[test]
fn add_sent_accumulates() {
let mut h = FlowHistory::new();
h.add_sent(100);
h.add_sent(200);
assert_eq!(h.total_sent, 300);
assert_eq!(*h.sent.back().unwrap(), 300);
}
#[test]
fn add_recv_accumulates() {
let mut h = FlowHistory::new();
h.add_recv(500);
assert_eq!(h.total_recv, 500);
assert_eq!(*h.recv.back().unwrap(), 500);
}
#[test]
fn rotate_pushes_new_slot() {
let mut h = FlowHistory::new();
h.add_sent(1000);
h.rotate();
assert_eq!(h.sent.len(), 2);
assert_eq!(*h.sent.back().unwrap(), 0); assert_eq!(h.peak_sent, 1000.0);
}
#[test]
fn rotate_evicts_after_40_slots() {
let mut h = FlowHistory::new();
for _ in 0..50 {
h.add_sent(1);
h.rotate();
}
assert!(h.sent.len() <= 40);
}
#[test]
fn window_averages() {
let mut h = FlowHistory::new();
h.add_sent(100);
h.rotate();
h.add_sent(200);
assert_eq!(h.avg_sent_2s(), 300.0);
assert_eq!(h.avg_sent_10s(), 300.0);
}
#[test]
fn peak_tracking() {
let mut h = FlowHistory::new();
h.add_sent(500);
h.rotate();
h.add_sent(1000);
h.rotate();
h.add_sent(200);
h.rotate();
assert_eq!(h.peak_sent, 1000.0);
}
#[test]
fn default_trait() {
let h = FlowHistory::default();
assert_eq!(h.total_sent, 0);
assert_eq!(h.total_recv, 0);
}
#[test]
fn last_seen_updates_on_sent() {
let h1 = FlowHistory::new();
let before = h1.last_seen;
std::thread::sleep(std::time::Duration::from_millis(1));
let mut h2 = FlowHistory::new();
h2.add_sent(100);
assert!(h2.last_seen >= before);
}
#[test]
fn last_seen_updates_on_recv() {
let mut h = FlowHistory::new();
let before = h.last_seen;
std::thread::sleep(std::time::Duration::from_millis(1));
h.add_recv(100);
assert!(h.last_seen >= before);
}
#[test]
fn process_fields_none_by_default() {
let h = FlowHistory::new();
assert!(h.process_name.is_none());
assert!(h.pid.is_none());
}
#[test]
fn recv_window_averages() {
let mut h = FlowHistory::new();
h.add_recv(100);
h.rotate();
h.add_recv(200);
assert_eq!(h.avg_recv_2s(), 300.0);
assert_eq!(h.avg_recv_10s(), 300.0);
assert_eq!(h.avg_recv_40s(), 300.0);
}
#[test]
fn recv_peak_tracking() {
let mut h = FlowHistory::new();
h.add_recv(500);
h.rotate();
h.add_recv(1000);
h.rotate();
assert_eq!(h.peak_recv, 1000.0);
}
#[test]
fn window_avg_single_slot() {
let mut h = FlowHistory::new();
h.add_sent(42);
assert_eq!(h.avg_sent_2s(), 42.0);
assert_eq!(h.avg_sent_10s(), 42.0);
assert_eq!(h.avg_sent_40s(), 42.0);
}
#[test]
fn window_avg_many_slots() {
let mut h = FlowHistory::new();
for i in 0..20 {
h.add_sent(i * 10);
h.rotate();
}
let s2 = h.avg_sent_2s();
let s10 = h.avg_sent_10s();
let s40 = h.avg_sent_40s();
assert!(s2 <= s10);
assert!(s10 <= s40);
}
#[test]
fn rotate_evicts_recv_after_40_slots() {
let mut h = FlowHistory::new();
for _ in 0..50 {
h.add_recv(1);
h.rotate();
}
assert!(h.recv.len() <= 40);
}
}