//! This is the main file to house data collection functions. use std::time::Instant; use futures::join; #[cfg(target_os = "linux")] use fxhash::FxHashMap; #[cfg(feature = "battery")] use starship_battery::{Battery, Manager}; #[cfg(not(target_os = "linux"))] use sysinfo::{System, SystemExt}; use super::DataFilters; use crate::app::layout_manager::UsedWidgets; #[cfg(feature = "nvidia")] pub mod nvidia; #[cfg(feature = "battery")] pub mod batteries; pub mod cpu; pub mod disks; pub mod memory; pub mod network; pub mod processes; pub mod temperature; #[derive(Clone, Debug)] pub struct Data { pub last_collection_time: Instant, pub cpu: Option, pub load_avg: Option, pub memory: Option, pub swap: Option, pub temperature_sensors: Option>, pub network: Option, pub list_of_processes: Option>, pub disks: Option>, pub io: Option, #[cfg(feature = "battery")] pub list_of_batteries: Option>, #[cfg(feature = "zfs")] pub arc: Option, #[cfg(feature = "gpu")] pub gpu: Option>, } impl Default for Data { fn default() -> Self { Data { last_collection_time: Instant::now(), cpu: None, load_avg: None, memory: None, swap: None, temperature_sensors: None, list_of_processes: None, disks: None, io: None, network: None, #[cfg(feature = "battery")] list_of_batteries: None, #[cfg(feature = "zfs")] arc: None, #[cfg(feature = "gpu")] gpu: None, } } } impl Data { pub fn cleanup(&mut self) { self.io = None; self.temperature_sensors = None; self.list_of_processes = None; self.disks = None; self.memory = None; self.swap = None; self.cpu = None; self.load_avg = None; if let Some(network) = &mut self.network { network.first_run_cleanup(); } #[cfg(feature = "zfs")] { self.arc = None; } #[cfg(feature = "gpu")] { self.gpu = None; } } } #[derive(Debug)] pub struct DataCollector { pub data: Data, #[cfg(not(target_os = "linux"))] sys: System, previous_cpu_times: Vec<(cpu::PastCpuWork, cpu::PastCpuTotal)>, previous_average_cpu_time: Option<(cpu::PastCpuWork, cpu::PastCpuTotal)>, #[cfg(target_os = "linux")] pid_mapping: FxHashMap, #[cfg(target_os = "linux")] prev_idle: f64, #[cfg(target_os = "linux")] prev_non_idle: f64, mem_total_kb: u64, temperature_type: temperature::TemperatureType, use_current_cpu_total: bool, unnormalized_cpu: bool, last_collection_time: Instant, total_rx: u64, total_tx: u64, show_average_cpu: bool, widgets_to_harvest: UsedWidgets, #[cfg(feature = "battery")] battery_manager: Option, #[cfg(feature = "battery")] battery_list: Option>, filters: DataFilters, #[cfg(target_family = "unix")] user_table: self::processes::UserTable, } impl DataCollector { pub fn new(filters: DataFilters) -> Self { DataCollector { data: Data::default(), #[cfg(not(target_os = "linux"))] sys: System::new_with_specifics(sysinfo::RefreshKind::new()), previous_cpu_times: vec![], previous_average_cpu_time: None, #[cfg(target_os = "linux")] pid_mapping: FxHashMap::default(), #[cfg(target_os = "linux")] prev_idle: 0_f64, #[cfg(target_os = "linux")] prev_non_idle: 0_f64, mem_total_kb: 0, temperature_type: temperature::TemperatureType::Celsius, use_current_cpu_total: false, unnormalized_cpu: false, last_collection_time: Instant::now(), total_rx: 0, total_tx: 0, show_average_cpu: false, widgets_to_harvest: UsedWidgets::default(), #[cfg(feature = "battery")] battery_manager: None, #[cfg(feature = "battery")] battery_list: None, filters, #[cfg(target_family = "unix")] user_table: Default::default(), } } pub fn init(&mut self) { #[cfg(target_os = "linux")] { futures::executor::block_on(self.initialize_memory_size()); } #[cfg(not(target_os = "linux"))] { self.sys.refresh_memory(); self.mem_total_kb = self.sys.total_memory(); // TODO: Would be good to get this and network list running on a timer instead...? // Refresh components list once... if self.widgets_to_harvest.use_temp { self.sys.refresh_components_list(); } // Refresh network list once... if cfg!(target_os = "windows") && self.widgets_to_harvest.use_net { self.sys.refresh_networks_list(); } if self.widgets_to_harvest.use_proc || self.widgets_to_harvest.use_cpu { self.sys.refresh_cpu(); } // Refresh disk list once... if cfg!(target_os = "freebsd") && self.widgets_to_harvest.use_disk { self.sys.refresh_disks_list(); } } #[cfg(feature = "battery")] { if self.widgets_to_harvest.use_battery { if let Ok(battery_manager) = Manager::new() { if let Ok(batteries) = battery_manager.batteries() { let battery_list: Vec = batteries.filter_map(Result::ok).collect(); if !battery_list.is_empty() { self.battery_list = Some(battery_list); self.battery_manager = Some(battery_manager); } } } } } futures::executor::block_on(self.update_data()); std::thread::sleep(std::time::Duration::from_millis(250)); self.data.cleanup(); // trace!("Enabled widgets to harvest: {:#?}", self.widgets_to_harvest); } #[cfg(target_os = "linux")] async fn initialize_memory_size(&mut self) { self.mem_total_kb = if let Ok(mem) = heim::memory::memory().await { mem.total().get::() } else { 1 }; } pub fn set_data_collection(&mut self, used_widgets: UsedWidgets) { self.widgets_to_harvest = used_widgets; } pub fn set_temperature_type(&mut self, temperature_type: temperature::TemperatureType) { self.temperature_type = temperature_type; } pub fn set_use_current_cpu_total(&mut self, use_current_cpu_total: bool) { self.use_current_cpu_total = use_current_cpu_total; } pub fn set_unnormalized_cpu(&mut self, unnormalized_cpu: bool) { self.unnormalized_cpu = unnormalized_cpu; } pub fn set_show_average_cpu(&mut self, show_average_cpu: bool) { self.show_average_cpu = show_average_cpu; } pub async fn update_data(&mut self) { #[cfg(not(target_os = "linux"))] { if self.widgets_to_harvest.use_proc || self.widgets_to_harvest.use_cpu { self.sys.refresh_cpu(); } if self.widgets_to_harvest.use_proc { self.sys.refresh_processes(); } if self.widgets_to_harvest.use_temp { self.sys.refresh_components(); } #[cfg(target_os = "windows")] { if self.widgets_to_harvest.use_net { self.sys.refresh_networks(); } } #[cfg(target_os = "freebsd")] { if self.widgets_to_harvest.use_disk { self.sys.refresh_disks(); } if self.widgets_to_harvest.use_mem { self.sys.refresh_memory(); } } } let current_instant = std::time::Instant::now(); // CPU if self.widgets_to_harvest.use_cpu { #[cfg(not(target_os = "freebsd"))] { if let Ok(cpu_data) = cpu::get_cpu_data_list( self.show_average_cpu, &mut self.previous_cpu_times, &mut self.previous_average_cpu_time, ) .await { self.data.cpu = Some(cpu_data); } } #[cfg(target_os = "freebsd")] { if let Ok(cpu_data) = cpu::get_cpu_data_list( &self.sys, self.show_average_cpu, &mut self.previous_cpu_times, &mut self.previous_average_cpu_time, ) .await { self.data.cpu = Some(cpu_data); } } #[cfg(target_family = "unix")] { // Load Average if let Ok(load_avg_data) = cpu::get_load_avg().await { self.data.load_avg = Some(load_avg_data); } } } // Batteries #[cfg(feature = "battery")] { if let Some(battery_manager) = &self.battery_manager { if let Some(battery_list) = &mut self.battery_list { self.data.list_of_batteries = Some(batteries::refresh_batteries(battery_manager, battery_list)); } } } if self.widgets_to_harvest.use_proc { if let Ok(mut process_list) = { #[cfg(target_os = "linux")] { // Must do this here since we otherwise have to make `get_process_data` async. use self::processes::CpuUsageStrategy; let normalize_cpu = if self.unnormalized_cpu { heim::cpu::logical_count() .await .map(|v| CpuUsageStrategy::NonNormalized(v as f64)) .unwrap_or(CpuUsageStrategy::Normalized) } else { CpuUsageStrategy::Normalized }; processes::get_process_data( &mut self.prev_idle, &mut self.prev_non_idle, &mut self.pid_mapping, self.use_current_cpu_total, normalize_cpu, current_instant .duration_since(self.last_collection_time) .as_secs(), self.mem_total_kb, &mut self.user_table, ) } #[cfg(not(target_os = "linux"))] { #[cfg(target_family = "unix")] { processes::get_process_data( &self.sys, self.use_current_cpu_total, self.unnormalized_cpu, self.mem_total_kb, &mut self.user_table, ) } #[cfg(not(target_family = "unix"))] { processes::get_process_data( &self.sys, self.use_current_cpu_total, self.unnormalized_cpu, self.mem_total_kb, ) } } } { // NB: To avoid duplicate sorts on rerenders/events, we sort the processes by PID here. // We also want to avoid re-sorting *again* later on if we're sorting by PID, since we already // did it here! process_list.sort_unstable_by_key(|p| p.pid); self.data.list_of_processes = Some(process_list); } } if self.widgets_to_harvest.use_temp { #[cfg(not(target_os = "linux"))] { if let Ok(data) = temperature::get_temperature_data( &self.sys, &self.temperature_type, &self.filters.temp_filter, ) { self.data.temperature_sensors = data; } } #[cfg(target_os = "linux")] { if let Ok(data) = temperature::get_temperature_data( &self.temperature_type, &self.filters.temp_filter, ) { self.data.temperature_sensors = data; } } } let network_data_fut = { #[cfg(any(target_os = "windows", target_os = "freebsd"))] { network::get_network_data( &self.sys, self.last_collection_time, &mut self.total_rx, &mut self.total_tx, current_instant, self.widgets_to_harvest.use_net, &self.filters.net_filter, ) } #[cfg(not(any(target_os = "windows", target_os = "freebsd")))] { network::get_network_data( self.last_collection_time, &mut self.total_rx, &mut self.total_tx, current_instant, self.widgets_to_harvest.use_net, &self.filters.net_filter, ) } }; let mem_data_fut = { #[cfg(not(target_os = "freebsd"))] { memory::get_mem_data( self.widgets_to_harvest.use_mem, self.widgets_to_harvest.use_gpu, ) } #[cfg(target_os = "freebsd")] { memory::get_mem_data( &self.sys, self.widgets_to_harvest.use_mem, self.widgets_to_harvest.use_gpu, ) } }; let disk_data_fut = disks::get_disk_usage( self.widgets_to_harvest.use_disk, &self.filters.disk_filter, &self.filters.mount_filter, ); let disk_io_usage_fut = disks::get_io_usage(self.widgets_to_harvest.use_disk); let (net_data, mem_res, disk_res, io_res) = join!( network_data_fut, mem_data_fut, disk_data_fut, disk_io_usage_fut, ); if let Ok(net_data) = net_data { if let Some(net_data) = &net_data { self.total_rx = net_data.total_rx; self.total_tx = net_data.total_tx; } self.data.network = net_data; } if let Ok(memory) = mem_res.ram { self.data.memory = memory; } if let Ok(swap) = mem_res.swap { self.data.swap = swap; } #[cfg(feature = "zfs")] if let Ok(arc) = mem_res.arc { self.data.arc = arc; } #[cfg(feature = "gpu")] if let Ok(gpu) = mem_res.gpus { self.data.gpu = gpu; } if let Ok(disks) = disk_res { self.data.disks = disks; } if let Ok(io) = io_res { self.data.io = io; } // Update time self.data.last_collection_time = current_instant; self.last_collection_time = current_instant; } } #[cfg(target_os = "freebsd")] /// Deserialize [libxo](https://www.freebsd.org/cgi/man.cgi?query=libxo&apropos=0&sektion=0&manpath=FreeBSD+13.1-RELEASE+and+Ports&arch=default&format=html) JSON data fn deserialize_xo(key: &str, data: &[u8]) -> Result where T: serde::de::DeserializeOwned, { let mut value: serde_json::Value = serde_json::from_slice(data)?; value .as_object_mut() .and_then(|map| map.remove(key)) .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "key not found")) .and_then(|val| serde_json::from_value(val).map_err(|err| err.into())) }