#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] use sysinfo::{ System, ProcessStatus, NetworksExt, NetworkExt, Disk, DiskExt, SystemExt, CpuExt, ProcessExt, PidExt, }; use tauri::State; use std::sync::Mutex; use std::collections::HashMap; use std::time::{Instant, SystemTime, UNIX_EPOCH}; struct AppState { sys: Mutex, process_cache: Mutex>, last_network_update: Mutex<(Instant, u64, u64)>, } impl AppState { pub fn new() -> Self { let mut sys = System::new(); sys.refresh_all(); // Initialize network stats let initial_rx = sys.networks().iter().map(|(_, data)| data.total_received()).sum(); let initial_tx = sys.networks().iter().map(|(_, data)| data.total_transmitted()).sum(); Self { sys: Mutex::new(sys), process_cache: Mutex::new(HashMap::new()), last_network_update: Mutex::new((Instant::now(), initial_rx, initial_tx)), } } } #[derive(Clone)] struct ProcessStaticInfo { name: String, command: String, user: String, } #[derive(serde::Serialize)] struct ProcessInfo { pid: u32, ppid: u32, name: String, cpu_usage: f32, memory_usage: u64, status: String, user: String, command: String, threads: Option, environ: Vec, root: String, virtual_memory: u64, start_time: u64, run_time: u64, disk_usage: (u64, u64), // (read_bytes, written_bytes) session_id: Option, } #[derive(serde::Serialize)] pub struct SystemStats { pub cpu_usage: Vec, pub memory_total: u64, pub memory_used: u64, pub memory_free: u64, pub memory_cached: u64, pub uptime: u64, pub load_avg: [f64; 3], pub network_rx_bytes: u64, pub network_tx_bytes: u64, pub disk_total_bytes: u64, pub disk_used_bytes: u64, pub disk_free_bytes: u64, } // Assume MacOS or Linux #[cfg(not(target_os = "windows"))] fn filter_disks(disks: &[Disk]) -> Vec<&sysinfo::Disk> { disks.iter().filter(|disk| { // Filter for physical disks - typically those mounted at "/" disk.mount_point() == std::path::Path::new("/") }) .collect() } #[cfg(target_os = "windows")] fn filter_disks(disks: &[Disk]) -> Vec<&sysinfo::Disk> { disks.iter().collect() } #[tauri::command] async fn get_processes(state: State<'_, AppState>) -> Result<(Vec, SystemStats), String> { let processes_data; let system_stats; // Get current time once for all calculations let current_time = SystemTime::now() .duration_since(UNIX_EPOCH) .map_err(|e| e.to_string())? .as_secs(); // Scope for system lock { let mut sys = state.sys.lock().map_err(|_| "Failed to lock system state")?; sys.refresh_all(); sys.refresh_networks(); sys.refresh_disks_list(); sys.refresh_disks(); // Collect all the process data we need while holding sys lock processes_data = sys .processes() .iter() .map(|(pid, process)| { let start_time = process.start_time(); let run_time = if start_time > 0 { current_time.saturating_sub(start_time) } else { 0 }; ( pid.as_u32(), process.name().to_string(), process.cmd().to_vec(), process.user_id().map(|uid| uid.to_string()), process.cpu_usage(), process.memory(), process.status(), process.parent().map(|p| p.as_u32()), process.environ().to_vec(), process.root().to_string_lossy().into_owned(), process.virtual_memory(), start_time, run_time, // Use calculated run_time process.disk_usage().read_bytes, process.disk_usage().written_bytes, process.session_id().map(|id| id.as_u32()), ) }) .collect::>(); // Calculate total network I/O let mut last_update = state.last_network_update.lock().map_err(|_| "Failed to lock network state")?; let elapsed = last_update.0.elapsed().as_secs_f64(); let current_time = Instant::now(); let current_rx: u64 = sys.networks().iter().map(|(_, data)| data.total_received()).sum(); let current_tx: u64 = sys.networks().iter().map(|(_, data)| data.total_transmitted()).sum(); let network_stats = ( ((current_rx - last_update.1) as f64 / elapsed) as u64, ((current_tx - last_update.2) as f64 / elapsed) as u64, ); *last_update = (current_time, current_rx, current_tx); // Calculate total disk usage let disk_stats = filter_disks(&sys.disks()) .iter() .fold((0, 0, 0), |acc, disk| { ( acc.0 + disk.total_space(), acc.1 + disk.total_space() - disk.available_space(), acc.2 + disk.available_space() ) }); system_stats = SystemStats { cpu_usage: sys.cpus().iter().map(|cpu| cpu.cpu_usage()).collect(), memory_total: sys.total_memory(), memory_used: sys.used_memory(), memory_free: sys.total_memory() - sys.used_memory(), memory_cached: sys.total_memory() - (sys.used_memory() + (sys.total_memory() - sys.used_memory())), uptime: sys.uptime(), load_avg: [sys.load_average().one, sys.load_average().five, sys.load_average().fifteen], network_rx_bytes: network_stats.0, network_tx_bytes: network_stats.1, disk_total_bytes: disk_stats.0, disk_used_bytes: disk_stats.1, disk_free_bytes: disk_stats.2, }; } // sys lock is automatically dropped here // Now lock the process cache let mut process_cache = state.process_cache.lock().map_err(|_| "Failed to lock process cache")?; // Build the process info list let processes = processes_data .into_iter() .map(|(pid, name, cmd, user_id, cpu_usage, memory, status, ppid, environ, root, virtual_memory, start_time, run_time, disk_read, disk_written, session_id)| { let static_info = process_cache.entry(pid).or_insert_with(|| { ProcessStaticInfo { name: name.clone(), command: cmd.join(" "), user: user_id.unwrap_or_else(|| "-".to_string()), } }); let status_str = match status { ProcessStatus::Run => "Running", ProcessStatus::Sleep => "Sleeping", ProcessStatus::Idle => "Idle", _ => "Unknown" }; ProcessInfo { pid, ppid: ppid.unwrap_or(0), name: static_info.name.clone(), cpu_usage, memory_usage: memory, status: status_str.to_string(), user: static_info.user.clone(), command: static_info.command.clone(), threads: None, environ, root, virtual_memory, start_time, run_time, disk_usage: (disk_read, disk_written), session_id, } }) .collect(); Ok((processes, system_stats)) } #[tauri::command] async fn kill_process(pid: u32, state: State<'_, AppState>) -> Result { let sys = state.sys.lock().map_err(|_| "Failed to lock system state")?; if let Some(process) = sys.process(sysinfo::Pid::from(pid as usize)) { Ok(process.kill()) } else { Ok(false) } } fn main() { tauri::Builder::default() .manage(AppState::new()) .invoke_handler(tauri::generate_handler![ get_processes, kill_process ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); }