diff --git a/Cargo.lock b/Cargo.lock index ac817d83..8e534df0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -249,6 +249,7 @@ dependencies = [ "fern", "fnv", "futures", + "futures-timer", "heim", "indexmap", "itertools", @@ -671,6 +672,12 @@ version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce" +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + [[package]] name = "hashbrown" version = "0.9.1" @@ -684,6 +691,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8a653442b9bdd11a77d3753a60443c60c4437d3acac8e6c3d4a6a9acd7cceed" dependencies = [ "heim-common", + "heim-cpu", "heim-disk", "heim-memory", "heim-net", @@ -710,6 +718,25 @@ dependencies = [ "winapi", ] +[[package]] +name = "heim-cpu" +version = "0.1.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba5fb13a3b90581d22b4edf99e87c54316444622ae123d36816a227a7caa6df" +dependencies = [ + "cfg-if 1.0.0", + "futures", + "glob", + "heim-common", + "heim-runtime", + "lazy_static", + "libc", + "mach", + "ntapi", + "smol", + "winapi", +] + [[package]] name = "heim-disk" version = "0.1.0-rc.1" diff --git a/Cargo.toml b/Cargo.toml index afba81ca..db319ea2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,6 @@ fnv = "1.0.7" futures = "0.3.8" indexmap = "~1.6" itertools = "0.9.0" -libc = "~0.2" once_cell = "1.5.2" regex = "1.4.2" serde = { version = "~1.0", features = ["derive"] } @@ -54,13 +53,22 @@ typed-builder = "0.8.0" unicode-segmentation = "1.7.1" unicode-width = "0.1" -# For debugging only... +# For debugging only... disable on release builds with --no-default-target for no? TODO: Redo this. fern = { version = "0.6.0", optional=true } -log = { version="0.4.11", optional=true } +log = { version = "0.4.11", optional=true } -heim = { version = "0.1.0-rc.1", features = ["disk", "memory", "net", "sensors"] } +[target.'cfg(unix)'.dependencies] +libc = "~0.2" -[target.'cfg(windows)'.dependencies] +[target.'cfg(target_os = "linux")'.dependencies] +heim = { version = "0.1.0-rc.1", features = ["cpu", "disk", "memory", "net", "sensors"] } +futures-timer = "3.0.2" + +[target.'cfg(target_os = "macos")'.dependencies] +heim = { version = "0.1.0-rc.1", features = ["disk", "memory", "net"] } + +[target.'cfg(target_os = "windows")'.dependencies] +heim = { version = "0.1.0-rc.1", features = ["disk", "memory"] } winapi = "0.3.9" [dev-dependencies] diff --git a/README.md b/README.md index 1b98321f..fc4a3bc0 100644 --- a/README.md +++ b/README.md @@ -65,10 +65,10 @@ A cross-platform graphical process/system monitor with a customizable interface Note that bottom is: - Built on the stable version of Rust -- Officially tested and released for only `x86_64` (and `i686` for Windows) +- Officially tested and released for only `x86_64` (and `i686` for Windows and Linux) - Developed mainly for macOS, Windows, and Linux -Anything outside of this (i.e: ARM builds, building on Nightly, building on another OS) is currently not guaranteed, even if it does happen to work. For example, ARM is compiled on the CI pipeline and release builds will be provided, but not all features may work (such as R/s and W/s for disks). +Anything outside of this (i.e: ARM builds, building on Nightly, building on another OS) is currently not guaranteed, even if it does happen to work. For example, ARM is compiled on the CI pipeline and release builds will be provided, but not all features may necessarily work. Feel free to file any ARM-related bugs, but know I might not be able to fix them. ### Manually diff --git a/src/app/data_harvester.rs b/src/app/data_harvester.rs index cff9bc2e..63d9a07c 100644 --- a/src/app/data_harvester.rs +++ b/src/app/data_harvester.rs @@ -5,6 +5,7 @@ use std::time::Instant; #[cfg(target_os = "linux")] use fnv::FnvHashMap; +#[cfg(not(target_os = "linux"))] use sysinfo::{System, SystemExt}; use battery::{Battery, Manager}; @@ -71,8 +72,13 @@ impl Data { #[derive(Debug)] pub struct DataCollector { pub data: Data, + #[cfg(not(target_os = "linux"))] sys: System, #[cfg(target_os = "linux")] + previous_cpu_times: Vec<(cpu::PastCpuWork, cpu::PastCpuTotal)>, + #[cfg(target_os = "linux")] + previous_average_cpu_time: Option<(cpu::PastCpuWork, cpu::PastCpuTotal)>, + #[cfg(target_os = "linux")] pid_mapping: FnvHashMap, #[cfg(target_os = "linux")] prev_idle: f64, @@ -97,8 +103,13 @@ impl Default for DataCollector { // trace!("Creating default data collector..."); DataCollector { data: Data::default(), + #[cfg(not(target_os = "linux"))] sys: System::new_with_specifics(sysinfo::RefreshKind::new()), // FIXME: Make this run on only macOS and Windows. #[cfg(target_os = "linux")] + previous_cpu_times: vec![], + #[cfg(target_os = "linux")] + previous_average_cpu_time: None, + #[cfg(target_os = "linux")] pid_mapping: FnvHashMap::default(), #[cfg(target_os = "linux")] prev_idle: 0_f64, @@ -127,17 +138,24 @@ impl Default for DataCollector { impl DataCollector { pub fn init(&mut self) { - self.sys.refresh_memory(); - self.mem_total_kb = self.sys.get_total_memory(); - - // Refresh components list once... - if self.widgets_to_harvest.use_temp { - self.sys.refresh_components_list(); + #[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.get_total_memory(); - // Refresh network list once... - if cfg!(target_os = "windows") && self.widgets_to_harvest.use_net { - self.sys.refresh_networks_list(); + // 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_battery { @@ -164,6 +182,15 @@ impl DataCollector { // 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_collected_data(&mut self, used_widgets: UsedWidgets) { self.widgets_to_harvest = used_widgets; } @@ -181,27 +208,44 @@ impl DataCollector { } pub async fn update_data(&mut self) { - if self.widgets_to_harvest.use_cpu { - self.sys.refresh_cpu(); - } - - if cfg!(not(target_os = "linux")) { + #[cfg(not(target_os = "linux"))] + { + if 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(); } - } - if cfg!(target_os = "windows") && self.widgets_to_harvest.use_net { - self.sys.refresh_networks(); + + if cfg!(target_os = "windows") && self.widgets_to_harvest.use_net { + self.sys.refresh_networks(); + } } let current_instant = std::time::Instant::now(); // CPU if self.widgets_to_harvest.use_cpu { - self.data.cpu = Some(cpu::get_cpu_data_list(&self.sys, self.show_average_cpu)); + #[cfg(not(target_os = "linux"))] + { + self.data.cpu = Some(cpu::get_cpu_data_list(&self.sys, self.show_average_cpu)); + } + + #[cfg(target_os = "linux")] + { + 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); + } + } } // Batteries diff --git a/src/app/data_harvester/cpu.rs b/src/app/data_harvester/cpu.rs index bf6bf6e2..c154cfa1 100644 --- a/src/app/data_harvester/cpu.rs +++ b/src/app/data_harvester/cpu.rs @@ -1,5 +1,3 @@ -use sysinfo::{ProcessorExt, System, SystemExt}; - #[derive(Default, Debug, Clone)] pub struct CpuData { pub cpu_prefix: String, @@ -9,6 +7,13 @@ pub struct CpuData { pub type CpuHarvest = Vec; +pub type PastCpuWork = f64; +pub type PastCpuTotal = f64; + +#[cfg(not(target_os = "linux"))] +use sysinfo::{ProcessorExt, System, SystemExt}; + +#[cfg(not(target_os = "linux"))] pub fn get_cpu_data_list(sys: &System, show_average_cpu: bool) -> CpuHarvest { let cpu_data = sys.get_processors(); let avg_cpu_usage = sys.get_global_processor_info().get_cpu_usage(); @@ -32,3 +37,157 @@ pub fn get_cpu_data_list(sys: &System, show_average_cpu: bool) -> CpuHarvest { cpu_vec } + +#[cfg(target_os = "linux")] +pub async fn get_cpu_data_list( + show_average_cpu: bool, previous_cpu_times: &mut Vec<(PastCpuWork, PastCpuTotal)>, + previous_average_cpu_time: &mut Option<(PastCpuWork, PastCpuTotal)>, +) -> crate::error::Result { + use futures::StreamExt; + use heim::cpu::os::linux::CpuTimeExt; + use std::collections::VecDeque; + + fn convert_cpu_times(cpu_time: &heim::cpu::CpuTime) -> (f64, f64) { + let working_time: f64 = (cpu_time.user() + + cpu_time.nice() + + cpu_time.system() + + cpu_time.irq() + + cpu_time.soft_irq() + + cpu_time.steal()) + .get::(); + ( + working_time, + working_time + + (cpu_time.idle() + cpu_time.io_wait()).get::(), + ) + } + + fn calculate_cpu_usage_percentage( + (previous_working_time, previous_total_time): (f64, f64), + (current_working_time, current_total_time): (f64, f64), + ) -> f64 { + ((if current_working_time > previous_working_time { + current_working_time - previous_working_time + } else { + 0.0 + }) * 100.0) + / (if current_total_time > previous_total_time { + current_total_time - previous_total_time + } else { + 1.0 + }) + } + + // Get all CPU times... + let cpu_times = heim::cpu::times().await?; + futures::pin_mut!(cpu_times); + + let mut cpu_deque: VecDeque = if previous_cpu_times.is_empty() { + // Must initialize ourselves. Use a very quick timeout to calculate an initial. + futures_timer::Delay::new(std::time::Duration::from_millis(100)).await; + + let second_cpu_times = heim::cpu::times().await?; + futures::pin_mut!(second_cpu_times); + + let mut new_cpu_times: Vec<(PastCpuWork, PastCpuTotal)> = Vec::new(); + let mut cpu_deque: VecDeque = VecDeque::new(); + let mut collected_zip = cpu_times.zip(second_cpu_times).enumerate(); // Gotta move it here, can't on while line. + + while let Some((itx, (past, present))) = collected_zip.next().await { + if let (Ok(past), Ok(present)) = (past, present) { + let present_times = convert_cpu_times(&present); + new_cpu_times.push(present_times); + cpu_deque.push_back(CpuData { + cpu_prefix: "CPU".to_string(), + cpu_count: Some(itx), + cpu_usage: calculate_cpu_usage_percentage( + convert_cpu_times(&past), + present_times, + ), + }); + } else { + new_cpu_times.push((0.0, 0.0)); + cpu_deque.push_back(CpuData { + cpu_prefix: "CPU".to_string(), + cpu_count: Some(itx), + cpu_usage: 0.0, + }); + } + } + + *previous_cpu_times = new_cpu_times; + cpu_deque + } else { + let (new_cpu_times, cpu_deque): (Vec<(PastCpuWork, PastCpuTotal)>, VecDeque) = + cpu_times + .collect::>() + .await + .iter() + .zip(&*previous_cpu_times) + .enumerate() + .map(|(itx, (current_cpu, (past_cpu_work, past_cpu_total)))| { + if let Ok(cpu_time) = current_cpu { + let present_times = convert_cpu_times(&cpu_time); + + ( + present_times, + CpuData { + cpu_prefix: "CPU".to_string(), + cpu_count: Some(itx), + cpu_usage: calculate_cpu_usage_percentage( + (*past_cpu_work, *past_cpu_total), + present_times, + ), + }, + ) + } else { + ( + (*past_cpu_work, *past_cpu_total), + CpuData { + cpu_prefix: "CPU".to_string(), + cpu_count: Some(itx), + cpu_usage: 0.0, + }, + ) + } + }) + .unzip(); + + *previous_cpu_times = new_cpu_times; + cpu_deque + }; + + // Get average CPU if needed... and slap it at the top + if show_average_cpu { + let cpu_time = heim::cpu::time().await?; + + let (cpu_usage, new_average_cpu_time) = if let Some((past_cpu_work, past_cpu_total)) = + previous_average_cpu_time + { + let present_times = convert_cpu_times(&cpu_time); + ( + calculate_cpu_usage_percentage((*past_cpu_work, *past_cpu_total), present_times), + present_times, + ) + } else { + // Again, we need to do a quick timeout... + futures_timer::Delay::new(std::time::Duration::from_millis(100)).await; + let second_cpu_time = heim::cpu::time().await?; + + let present_times = convert_cpu_times(&second_cpu_time); + ( + calculate_cpu_usage_percentage(convert_cpu_times(&cpu_time), present_times), + present_times, + ) + }; + + *previous_average_cpu_time = Some(new_average_cpu_time); + cpu_deque.push_front(CpuData { + cpu_prefix: "AVG".to_string(), + cpu_count: None, + cpu_usage, + }) + } + + Ok(Vec::from(cpu_deque)) +}