use serde::Serialize;
use std::sync::Mutex;
use std::time::{Duration, Instant};
use sysinfo::Networks;
#[derive(Debug, Clone, Serialize)]
pub struct NetworkStats {
pub upload_mbps: f64,
pub download_mbps: f64,
pub upload_active: bool,
pub download_active: bool,
pub available: bool,
}
struct MonitorState {
networks: Networks,
selected_interface: Option<String>,
last_update: Instant,
last_stats: (u64, u64),
last_rates: (f64, f64),
last_active: (Instant, Instant),
last_rate_update: (Instant, Instant),
last_rate_stats: (u64, u64),
}
pub struct NetworkMonitor {
state: Mutex<MonitorState>,
}
impl NetworkMonitor {
const REFRESH_INTERVAL: Duration = Duration::from_millis(100);
const RATE_INTERVAL: Duration = Duration::from_millis(400);
const ACTIVE_HOLD: Duration = Duration::from_millis(180);
pub fn new() -> Self {
let mut networks = Networks::new_with_refreshed_list();
networks.refresh(true);
let (rx, tx, _) = sum_traffic(&networks, None);
let now = Instant::now();
let inactive = now
.checked_sub(Self::ACTIVE_HOLD)
.and_then(|t| t.checked_sub(Self::ACTIVE_HOLD))
.unwrap_or(now);
Self {
state: Mutex::new(MonitorState {
networks,
selected_interface: None,
last_update: now,
last_stats: (rx, tx),
last_rates: (0.0, 0.0),
last_active: (inactive, inactive),
last_rate_update: (now, now),
last_rate_stats: (rx, tx),
}),
}
}
fn lock(&self) -> std::sync::MutexGuard<'_, MonitorState> {
self.state.lock().unwrap_or_else(|e| e.into_inner())
}
pub fn set_interface(&self, interface: Option<String>) {
let mut s = self.lock();
s.selected_interface = interface;
s.networks.refresh(true);
let (rx, tx, _) = sum_traffic(&s.networks, s.selected_interface.as_deref());
let now = Instant::now();
let inactive = now
.checked_sub(Self::ACTIVE_HOLD)
.and_then(|t| t.checked_sub(Self::ACTIVE_HOLD))
.unwrap_or(now);
s.last_update = now;
s.last_stats = (rx, tx);
s.last_rates = (0.0, 0.0);
s.last_active = (inactive, inactive);
s.last_rate_update = (now, now);
s.last_rate_stats = (rx, tx);
}
pub fn selected_interface(&self) -> Option<String> {
self.lock().selected_interface.clone()
}
pub fn available_interfaces(&self) -> Vec<String> {
let mut s = self.lock();
s.networks.refresh(true);
let mut names: Vec<String> = s.networks.keys().map(|n| n.to_string()).collect();
names.sort_unstable();
names
}
pub fn get_stats(&self) -> NetworkStats {
let mut s = self.lock();
let now = Instant::now();
let elapsed = now.duration_since(s.last_update);
if elapsed < Self::REFRESH_INTERVAL {
let (upload_mbps, download_mbps) = s.last_rates;
return NetworkStats {
upload_mbps,
download_mbps,
upload_active: now.duration_since(s.last_active.0) < Self::ACTIVE_HOLD,
download_active: now.duration_since(s.last_active.1) < Self::ACTIVE_HOLD,
available: true,
};
}
s.networks.refresh(true);
let (current_rx, current_tx, available) =
sum_traffic(&s.networks, s.selected_interface.as_deref());
if !available {
return NetworkStats {
upload_mbps: 0.0,
download_mbps: 0.0,
upload_active: false,
download_active: false,
available: false,
};
}
let (seen_rx, seen_tx) = s.last_stats;
if current_tx.saturating_sub(seen_tx) > 0 {
s.last_active.0 = now;
}
if current_rx.saturating_sub(seen_rx) > 0 {
s.last_active.1 = now;
}
s.last_stats = (current_rx, current_tx);
let (mut upload_mbps, mut download_mbps) = s.last_rates;
let (mut last_upload, mut last_download) = s.last_rate_update;
let (mut rate_rx, mut rate_tx) = s.last_rate_stats;
if now.duration_since(last_upload) >= Self::RATE_INTERVAL {
upload_mbps = bytes_to_mbps(
current_tx.saturating_sub(rate_tx),
now.duration_since(last_upload),
);
rate_tx = current_tx;
last_upload = now;
}
if now.duration_since(last_download) >= Self::RATE_INTERVAL {
download_mbps = bytes_to_mbps(
current_rx.saturating_sub(rate_rx),
now.duration_since(last_download),
);
rate_rx = current_rx;
last_download = now;
}
s.last_update = now;
s.last_rate_update = (last_upload, last_download);
s.last_rate_stats = (rate_rx, rate_tx);
s.last_rates = (upload_mbps, download_mbps);
NetworkStats {
upload_mbps,
download_mbps,
upload_active: now.duration_since(s.last_active.0) < Self::ACTIVE_HOLD,
download_active: now.duration_since(s.last_active.1) < Self::ACTIVE_HOLD,
available: true,
}
}
}
impl Default for NetworkMonitor {
fn default() -> Self {
Self::new()
}
}
fn sum_traffic(networks: &Networks, selected: Option<&str>) -> (u64, u64, bool) {
if let Some(selected) = selected {
for (name, network) in networks {
if name == selected {
return (network.total_received(), network.total_transmitted(), true);
}
}
return (0, 0, false);
}
let mut rx: u64 = 0;
let mut tx: u64 = 0;
let mut any = false;
for (_, network) in networks {
any = true;
rx = rx.saturating_add(network.total_received());
tx = tx.saturating_add(network.total_transmitted());
}
(rx, tx, any)
}
fn bytes_to_mbps(bytes: u64, elapsed: Duration) -> f64 {
let secs = elapsed.as_secs_f64();
if secs <= 0.0 {
return 0.0;
}
(bytes as f64 * 8.0) / (1_000_000.0 * secs)
}
#[cfg(test)]
mod tests;