use std::collections::HashSet;
use std::sync::mpsc::Sender;
use std::time::{Duration, Instant};
use sysinfo::{Networks, System};
use crate::mac::proc::ProcScanner;
use super::{FastSnap, ProcInfo, Snapshot};
const TICK: Duration = Duration::from_secs(2);
const TOP_N: usize = 50;
fn select_top(mut procs: Vec<ProcInfo>) -> Vec<ProcInfo> {
if procs.len() <= TOP_N {
return procs;
}
procs.sort_by(|a, b| b.cpu.total_cmp(&a.cpu));
let mut seen: HashSet<i32> = procs.iter().take(TOP_N).map(|p| p.pid).collect();
let mut result: Vec<ProcInfo> = procs.iter().take(TOP_N).cloned().collect();
procs.sort_by_key(|p| std::cmp::Reverse(p.mem));
for p in procs.into_iter().take(TOP_N) {
if seen.insert(p.pid) {
result.push(p);
}
}
result
}
pub fn run(tx: Sender<Snapshot>) {
let mut sys = System::new();
let mut networks = Networks::new_with_refreshed_list();
let mut scanner = ProcScanner::new();
let mut prev_totals: Option<(u64, u64, Instant)> = None;
let mut baseline_totals: Option<(u64, u64)> = None;
loop {
sys.refresh_cpu_usage();
sys.refresh_memory();
networks.refresh(true);
let (rx_total, tx_total) = networks.iter().fold((0u64, 0u64), |acc, (_, d)| {
(acc.0 + d.total_received(), acc.1 + d.total_transmitted())
});
let now = Instant::now();
let (rx_rate, tx_rate) = match prev_totals {
Some((pr, pt, pi)) => {
let dt = now.duration_since(pi).as_secs_f64().max(0.001);
(
rx_total.saturating_sub(pr) as f64 / dt,
tx_total.saturating_sub(pt) as f64 / dt,
)
}
None => (0.0, 0.0),
};
prev_totals = Some((rx_total, tx_total, now));
let (base_rx, base_tx) = *baseline_totals.get_or_insert((rx_total, tx_total));
let net_rx_total = rx_total.saturating_sub(base_rx);
let net_tx_total = tx_total.saturating_sub(base_tx);
let cores = sys.cpus();
let total_cpu = if cores.is_empty() {
0.0
} else {
cores.iter().map(sysinfo::Cpu::cpu_usage).sum::<f32>() / cores.len() as f32
};
let processes = select_top(scanner.scan());
let snap = FastSnap {
total_cpu,
processes,
net_rx_rate: rx_rate,
net_tx_rate: tx_rate,
net_rx_total,
net_tx_total,
load_avg: System::load_average().one,
mem_total: sys.total_memory(),
};
if tx.send(Snapshot::Fast(snap)).is_err() {
return; }
std::thread::sleep(TICK);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn proc(pid: i32, cpu: f32, mem: u64) -> ProcInfo {
ProcInfo { pid, name: format!("p{pid}"), cpu, mem }
}
#[test]
fn passthrough_when_at_or_under_top_n() {
let procs: Vec<ProcInfo> = (0..TOP_N).map(|i| proc(i as i32, 1.0, 1)).collect();
assert_eq!(select_top(procs.clone()).len(), procs.len());
}
#[test]
fn includes_big_memory_idle_cpu_process_beyond_cpu_top_n() {
let mut procs: Vec<ProcInfo> =
(0..TOP_N).map(|i| proc(i as i32, 50.0, 1)).collect();
let whale_pid = 9_999;
procs.push(proc(whale_pid, 0.0, u64::MAX));
let selected = select_top(procs);
assert!(selected.iter().any(|p| p.pid == whale_pid));
assert!(selected.len() <= TOP_N + 1);
}
#[test]
fn dedups_processes_that_are_top_by_both_metrics() {
let mut procs: Vec<ProcInfo> =
(0..TOP_N).map(|i| proc(i as i32, i as f32, i as u64)).collect();
procs.push(proc(12345, 1000.0, 1000));
let selected = select_top(procs);
let mut pids: Vec<i32> = selected.iter().map(|p| p.pid).collect();
let before = pids.len();
pids.sort_unstable();
pids.dedup();
assert_eq!(pids.len(), before, "select_top must not emit duplicate pids");
}
}