use crate::device::NetworkStats;
use std::collections::VecDeque;
use std::time::Duration;
#[cfg(test)]
use std::time::SystemTime;
pub struct StatsCalculator {
history: VecDeque<NetworkStats>,
window_size: Duration,
current_speed_in: u64,
current_speed_out: u64,
avg_speed_in: u64,
avg_speed_out: u64,
min_speed_in: u64,
min_speed_out: u64,
max_speed_in: u64,
max_speed_out: u64,
graph_data_in: VecDeque<(f64, f64)>, graph_data_out: VecDeque<(f64, f64)>,
total_bytes_in: u64,
total_bytes_out: u64,
total_packets_in: u64,
total_packets_out: u64,
first_sample: bool,
}
impl StatsCalculator {
pub fn new(window_size: Duration) -> Self {
Self {
history: VecDeque::new(),
window_size,
current_speed_in: 0,
current_speed_out: 0,
avg_speed_in: 0,
avg_speed_out: 0,
min_speed_in: 0,
min_speed_out: 0,
max_speed_in: 0,
max_speed_out: 0,
graph_data_in: VecDeque::new(),
graph_data_out: VecDeque::new(),
total_bytes_in: 0,
total_bytes_out: 0,
total_packets_in: 0,
total_packets_out: 0,
first_sample: true,
}
}
pub fn add_sample(&mut self, stats: NetworkStats) {
self.total_bytes_in = stats.bytes_in;
self.total_bytes_out = stats.bytes_out;
self.total_packets_in = stats.packets_in;
self.total_packets_out = stats.packets_out;
if let Some(previous) = self.history.back() {
let time_diff = stats
.timestamp
.duration_since(previous.timestamp)
.unwrap_or_default()
.as_secs_f64();
if time_diff > 0.0 {
let bytes_in_diff = self.calculate_diff(stats.bytes_in, previous.bytes_in);
let bytes_out_diff = self.calculate_diff(stats.bytes_out, previous.bytes_out);
self.current_speed_in = (bytes_in_diff as f64 / time_diff) as u64;
self.current_speed_out = (bytes_out_diff as f64 / time_diff) as u64;
if !self.first_sample {
self.update_min_max();
}
self.add_graph_data(&stats);
}
}
self.history.push_back(stats);
self.trim_old_samples();
self.calculate_averages();
if self.first_sample {
self.first_sample = false;
}
}
fn calculate_diff(&self, current: u64, previous: u64) -> u64 {
if current >= previous {
current - previous
} else {
let diff_32 = (u32::MAX as u64) - previous + current + 1;
let diff_64 = (u64::MAX) - previous + current + 1;
if diff_32 < diff_64 / 1000 {
diff_32
} else {
diff_64
}
}
}
fn update_min_max(&mut self) {
if self.current_speed_in < self.min_speed_in || self.min_speed_in == 0 {
self.min_speed_in = self.current_speed_in;
}
if self.current_speed_in > self.max_speed_in {
self.max_speed_in = self.current_speed_in;
}
if self.current_speed_out < self.min_speed_out || self.min_speed_out == 0 {
self.min_speed_out = self.current_speed_out;
}
if self.current_speed_out > self.max_speed_out {
self.max_speed_out = self.current_speed_out;
}
}
fn add_graph_data(&mut self, _stats: &NetworkStats) {
for (time, _) in self.graph_data_in.iter_mut() {
*time += 0.5; }
for (time, _) in self.graph_data_out.iter_mut() {
*time += 0.5; }
self.graph_data_in.retain(|(time, _)| *time <= 60.0);
self.graph_data_out.retain(|(time, _)| *time <= 60.0);
self.graph_data_in
.push_back((0.0, self.current_speed_in as f64));
self.graph_data_out
.push_back((0.0, self.current_speed_out as f64));
while self.graph_data_in.len() > 120 {
self.graph_data_in.pop_front();
}
while self.graph_data_out.len() > 120 {
self.graph_data_out.pop_front();
}
}
fn trim_old_samples(&mut self) {
if let Some(latest) = self.history.back() {
let cutoff = latest.timestamp - self.window_size;
while let Some(oldest) = self.history.front() {
if oldest.timestamp < cutoff {
self.history.pop_front();
} else {
break;
}
}
}
}
fn calculate_averages(&mut self) {
if self.history.len() < 2 {
return;
}
let first = &self.history[0];
let last = &self.history[self.history.len() - 1];
let time_span = last
.timestamp
.duration_since(first.timestamp)
.unwrap_or_default()
.as_secs_f64();
if time_span > 0.0 {
let bytes_in_diff = self.calculate_diff(last.bytes_in, first.bytes_in);
let bytes_out_diff = self.calculate_diff(last.bytes_out, first.bytes_out);
self.avg_speed_in = (bytes_in_diff as f64 / time_span) as u64;
self.avg_speed_out = (bytes_out_diff as f64 / time_span) as u64;
}
}
pub fn current_speed(&self) -> (u64, u64) {
(self.current_speed_in, self.current_speed_out)
}
pub fn average_speed(&self) -> (u64, u64) {
(self.avg_speed_in, self.avg_speed_out)
}
pub fn min_speed(&self) -> (u64, u64) {
(self.min_speed_in, self.min_speed_out)
}
pub fn max_speed(&self) -> (u64, u64) {
(self.max_speed_in, self.max_speed_out)
}
pub fn total_bytes(&self) -> (u64, u64) {
(self.total_bytes_in, self.total_bytes_out)
}
pub fn total_packets(&self) -> (u64, u64) {
(self.total_packets_in, self.total_packets_out)
}
pub fn graph_data_in(&self) -> &VecDeque<(f64, f64)> {
&self.graph_data_in
}
pub fn graph_data_out(&self) -> &VecDeque<(f64, f64)> {
&self.graph_data_out
}
pub fn sample_count(&self) -> usize {
self.history.len()
}
pub fn reset(&mut self) {
self.history.clear();
self.graph_data_in.clear();
self.graph_data_out.clear();
self.current_speed_in = 0;
self.current_speed_out = 0;
self.avg_speed_in = 0;
self.avg_speed_out = 0;
self.min_speed_in = 0;
self.min_speed_out = 0;
self.max_speed_in = 0;
self.max_speed_out = 0;
self.first_sample = true;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stats_calculation() {
let mut calc = StatsCalculator::new(Duration::from_secs(60));
let stats1 = NetworkStats {
timestamp: SystemTime::now(),
bytes_in: 1000,
bytes_out: 500,
packets_in: 10,
packets_out: 5,
errors_in: 0,
errors_out: 0,
drops_in: 0,
drops_out: 0,
};
calc.add_sample(stats1);
assert_eq!(calc.current_speed(), (0, 0));
let stats2 = NetworkStats {
timestamp: SystemTime::now() + Duration::from_secs(1),
bytes_in: 2000,
bytes_out: 1000,
packets_in: 20,
packets_out: 10,
errors_in: 0,
errors_out: 0,
drops_in: 0,
drops_out: 0,
};
calc.add_sample(stats2);
let (in_speed, out_speed) = calc.current_speed();
assert!(in_speed > 0);
assert!(out_speed > 0);
}
#[test]
fn test_counter_overflow() {
let calc = StatsCalculator::new(Duration::from_secs(60));
let diff = calc.calculate_diff(100, u32::MAX as u64 - 50);
assert_eq!(diff, 151); }
}