diff options
author | Clement Tsang <34804052+ClementTsang@users.noreply.github.com> | 2023-06-10 01:44:15 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-06-10 01:44:15 -0400 |
commit | 13a8e5bf0e0453a03e94cdc05cbbfd8503efa915 (patch) | |
tree | 3286c2afeca9979d6a4c61dbcfe4a2bd94b1285c /src | |
parent | 6b421b48eaf0edfece3d9a8ad663d529e84c5a9b (diff) |
refactor: redo how we do get processes between different OSes (#1197)
* refactor: redo how we do some processes between different OSes
* cleanup
* more cleanup
* windows
* freebsd
* clean up linux more, fix broken FreeBSD import
* some more cleanup to remove some big imports
Diffstat (limited to 'src')
-rw-r--r-- | src/app/data_farmer.rs | 2 | ||||
-rw-r--r-- | src/app/data_harvester.rs | 91 | ||||
-rw-r--r-- | src/app/data_harvester/processes.rs | 47 | ||||
-rw-r--r-- | src/app/data_harvester/processes/freebsd.rs | 59 | ||||
-rw-r--r-- | src/app/data_harvester/processes/linux.rs | 27 | ||||
-rw-r--r-- | src/app/data_harvester/processes/macos.rs | 96 | ||||
-rw-r--r-- | src/app/data_harvester/processes/macos_freebsd.rs | 145 | ||||
-rw-r--r-- | src/app/data_harvester/processes/unix.rs | 47 | ||||
-rw-r--r-- | src/app/data_harvester/processes/unix/process_ext.rs | 147 | ||||
-rw-r--r-- | src/app/data_harvester/processes/unix/user_table.rs | 31 | ||||
-rw-r--r-- | src/app/data_harvester/processes/windows.rs | 15 |
11 files changed, 370 insertions, 337 deletions
diff --git a/src/app/data_farmer.rs b/src/app/data_farmer.rs index c11595bc..d1db6070 100644 --- a/src/app/data_farmer.rs +++ b/src/app/data_farmer.rs @@ -204,7 +204,7 @@ impl DataCollection { } pub fn eat_data(&mut self, harvested_data: Box<Data>) { - let harvested_time = harvested_data.last_collection_time; + let harvested_time = harvested_data.collection_time; let mut new_entry = TimedData::default(); // Network diff --git a/src/app/data_harvester.rs b/src/app/data_harvester.rs index 2f4633bf..0ea6e3e9 100644 --- a/src/app/data_harvester.rs +++ b/src/app/data_harvester.rs @@ -27,7 +27,7 @@ pub mod temperature; #[derive(Clone, Debug)] pub struct Data { - pub last_collection_time: Instant, + pub collection_time: Instant, pub cpu: Option<cpu::CpuHarvest>, pub load_avg: Option<cpu::LoadAvgHarvest>, pub memory: Option<memory::MemHarvest>, @@ -50,7 +50,7 @@ pub struct Data { impl Default for Data { fn default() -> Self { Data { - last_collection_time: Instant::now(), + collection_time: Instant::now(), cpu: None, load_avg: None, memory: None, @@ -284,24 +284,20 @@ impl DataCollector { pub fn update_data(&mut self) { self.refresh_sysinfo_data(); - let current_instant = Instant::now(); + self.data.collection_time = Instant::now(); self.update_cpu_usage(); self.update_memory_usage(); - self.update_processes( - #[cfg(target_os = "linux")] - current_instant, - ); + self.update_processes(); self.update_temps(); - self.update_network_usage(current_instant); + self.update_network_usage(); self.update_disks(); #[cfg(feature = "battery")] self.update_batteries(); // Update times for future reference. - self.last_collection_time = current_instant; - self.data.last_collection_time = current_instant; + self.last_collection_time = self.data.collection_time; } #[inline] @@ -317,66 +313,9 @@ impl DataCollector { } #[inline] - fn update_processes(&mut self, #[cfg(target_os = "linux")] current_instant: Instant) { + fn update_processes(&mut self) { if self.widgets_to_harvest.use_proc { - if let Ok(mut process_list) = { - let total_memory = if let Some(memory) = &self.data.memory { - memory.total_bytes - } else { - self.sys.total_memory() - }; - - #[cfg(target_os = "linux")] - { - use self::processes::{PrevProc, ProcHarvestOptions}; - - let prev_proc = PrevProc { - prev_idle: &mut self.prev_idle, - prev_non_idle: &mut self.prev_non_idle, - }; - - let proc_harvest_options = ProcHarvestOptions { - use_current_cpu_total: self.use_current_cpu_total, - unnormalized_cpu: self.unnormalized_cpu, - }; - - let time_diff = current_instant - .duration_since(self.last_collection_time) - .as_secs(); - - processes::get_process_data( - &self.sys, - prev_proc, - &mut self.pid_mapping, - proc_harvest_options, - time_diff, - total_memory, - &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, - total_memory, - &mut self.user_table, - ) - } - #[cfg(not(target_family = "unix"))] - { - processes::get_process_data( - &self.sys, - self.use_current_cpu_total, - self.unnormalized_cpu, - total_memory, - ) - } - } - } { + if let Ok(mut process_list) = self.get_processes() { // 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! @@ -435,7 +374,9 @@ impl DataCollector { } #[inline] - fn update_network_usage(&mut self, current_instant: Instant) { + fn update_network_usage(&mut self) { + let current_instant = self.data.collection_time; + if self.widgets_to_harvest.use_net { let net_data = network::get_network_data( &self.sys, @@ -485,6 +426,16 @@ impl DataCollector { self.data.io = disks::get_io_usage().ok(); } } + + /// Returns the total memory of the system. + #[inline] + fn total_memory(&self) -> u64 { + if let Some(memory) = &self.data.memory { + memory.total_bytes + } else { + self.sys.total_memory() + } + } } /// We set a sleep duration between 10ms and 250ms, ideally sysinfo's [`System::MINIMUM_CPU_UPDATE_INTERVAL`] + 1. diff --git a/src/app/data_harvester/processes.rs b/src/app/data_harvester/processes.rs index 0f082e00..44f132d9 100644 --- a/src/app/data_harvester/processes.rs +++ b/src/app/data_harvester/processes.rs @@ -1,37 +1,41 @@ //! Data collection for processes. //! //! For Linux, this is handled by a custom set of functions. -//! For Windows and macOS, this is handled by sysinfo. +//! For Windows, macOS, FreeBSD, Android, and Linux, this is handled by sysinfo. -cfg_if::cfg_if! { +use cfg_if::cfg_if; +use std::{borrow::Cow, time::Duration}; + +use super::DataCollector; + +use crate::{utils::error, Pid}; + +cfg_if! { if #[cfg(target_os = "linux")] { pub mod linux; pub use self::linux::*; } else if #[cfg(target_os = "macos")] { pub mod macos; - mod macos_freebsd; - pub use self::macos::*; + pub(crate) use self::macos::*; } else if #[cfg(target_os = "windows")] { pub mod windows; pub use self::windows::*; } else if #[cfg(target_os = "freebsd")] { pub mod freebsd; - mod macos_freebsd; - pub use self::freebsd::*; + pub(crate) use self::freebsd::*; + } else if #[cfg(target_family = "unix")] { + pub(crate) struct GenericProcessExt; + impl UnixProcessExt for GenericProcessExt {} } } -cfg_if::cfg_if! { +cfg_if! { if #[cfg(target_family = "unix")] { pub mod unix; pub use self::unix::*; } } -use std::{borrow::Cow, time::Duration}; - -use crate::Pid; - #[derive(Debug, Clone, Default)] pub struct ProcessHarvest { /// The pid of the process. @@ -96,3 +100,24 @@ impl ProcessHarvest { self.time += rhs.time; } } + +impl DataCollector { + pub(crate) fn get_processes(&mut self) -> error::Result<Vec<ProcessHarvest>> { + cfg_if! { + if #[cfg(target_os = "linux")] { + let time_diff = self.data.collection_time + .duration_since(self.last_collection_time) + .as_secs(); + + linux_process_data( + self, + time_diff, + ) + } else if #[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "windows", target_os = "android", target_os = "ios"))] { + sysinfo_process_data(self) + } else { + Err(error::BottomError::GenericError("Unsupported OS".to_string())) + } + } + } +} diff --git a/src/app/data_harvester/processes/freebsd.rs b/src/app/data_harvester/processes/freebsd.rs index 43c373e0..0d92d873 100644 --- a/src/app/data_harvester/processes/freebsd.rs +++ b/src/app/data_harvester/processes/freebsd.rs @@ -1,14 +1,13 @@ //! Process data collection for FreeBSD. Uses sysinfo. use std::io; +use std::process::Command; use hashbrown::HashMap; use serde::{Deserialize, Deserializer}; -use sysinfo::System; -use super::ProcessHarvest; -use crate::data_harvester::deserialize_xo; -use crate::data_harvester::processes::UserTable; +use crate::data_harvester::{deserialize_xo, processes::UnixProcessExt}; +use crate::Pid; #[derive(Deserialize, Debug, Default)] #[serde(rename_all = "kebab-case")] @@ -25,36 +24,34 @@ struct ProcessRow { percent_cpu: f64, } -pub fn get_process_data( - sys: &System, use_current_cpu_total: bool, unnormalized_cpu: bool, total_memory: u64, - user_table: &mut UserTable, -) -> crate::utils::error::Result<Vec<ProcessHarvest>> { - super::macos_freebsd::get_process_data( - sys, - use_current_cpu_total, - unnormalized_cpu, - total_memory, - user_table, - get_freebsd_process_cpu_usage, - ) -} +pub(crate) struct FreeBSDProcessExt; -fn get_freebsd_process_cpu_usage(pids: &[i32]) -> io::Result<HashMap<i32, f64>> { - if pids.is_empty() { - return Ok(HashMap::new()); +impl UnixProcessExt for FreeBSDProcessExt { + #[inline] + fn has_backup_proc_cpu_fn() -> bool { + true } - let output = std::process::Command::new("ps") - .args(["--libxo", "json", "-o", "pid,pcpu", "-p"]) - .args(pids.iter().map(i32::to_string)) - .output()?; - deserialize_xo("process-information", &output.stdout).map(|process_info: ProcessInformation| { - process_info - .process - .into_iter() - .map(|row| (row.pid, row.percent_cpu)) - .collect() - }) + fn backup_proc_cpu(pids: &[Pid]) -> io::Result<HashMap<Pid, f64>> { + if pids.is_empty() { + return Ok(HashMap::new()); + } + + let output = Command::new("ps") + .args(["--libxo", "json", "-o", "pid,pcpu", "-p"]) + .args(pids.iter().map(i32::to_string)) + .output()?; + + deserialize_xo("process-information", &output.stdout).map( + |process_info: ProcessInformation| { + process_info + .process + .into_iter() + .map(|row| (row.pid, row.percent_cpu)) + .collect() + }, + ) + } } fn pid<'de, D>(deserializer: D) -> Result<i32, D::Error> diff --git a/src/app/data_harvester/processes/linux.rs b/src/app/data_harvester/processes/linux.rs index 1fd2ba79..37341865 100644 --- a/src/app/data_harvester/processes/linux.rs +++ b/src/app/data_harvester/processes/linux.rs @@ -7,10 +7,11 @@ use std::fs::{self, File}; use std::io::{BufRead, BufReader}; use std::time::Duration; -use hashbrown::{HashMap, HashSet}; -use sysinfo::{ProcessStatus, System}; +use hashbrown::HashSet; +use sysinfo::ProcessStatus; use super::{ProcessHarvest, UserTable}; +use crate::app::data_harvester::DataCollector; use crate::utils::error::{self, BottomError}; use crate::Pid; @@ -265,11 +266,21 @@ fn is_str_numeric(s: &str) -> bool { s.chars().all(|c| c.is_ascii_digit()) } -pub(crate) fn get_process_data( - sys: &System, prev_proc: PrevProc<'_>, pid_mapping: &mut HashMap<Pid, PrevProcDetails>, - proc_harvest_options: ProcHarvestOptions, time_difference_in_secs: u64, total_memory: u64, - user_table: &mut UserTable, -) -> crate::utils::error::Result<Vec<ProcessHarvest>> { +pub(crate) fn linux_process_data( + collector: &mut DataCollector, time_difference_in_secs: u64, +) -> error::Result<Vec<ProcessHarvest>> { + let total_memory = collector.total_memory(); + let prev_proc = PrevProc { + prev_idle: &mut collector.prev_idle, + prev_non_idle: &mut collector.prev_non_idle, + }; + let proc_harvest_options = ProcHarvestOptions { + use_current_cpu_total: collector.use_current_cpu_total, + unnormalized_cpu: collector.unnormalized_cpu, + }; + let pid_mapping = &mut collector.pid_mapping; + let user_table = &mut collector.user_table; + let ProcHarvestOptions { use_current_cpu_total, unnormalized_cpu, @@ -289,7 +300,7 @@ pub(crate) fn get_process_data( { if unnormalized_cpu { use sysinfo::SystemExt; - let num_processors = sys.cpus().len() as f64; + let num_processors = collector.sys.cpus().len() as f64; // Note we *divide* here because the later calculation divides `cpu_usage` - in effect, // multiplying over the number of cores. diff --git a/src/app/data_harvester/processes/macos.rs b/src/app/data_harvester/processes/macos.rs index 95e6fd9a..b895e1ec 100644 --- a/src/app/data_harvester/processes/macos.rs +++ b/src/app/data_harvester/processes/macos.rs @@ -1,57 +1,63 @@ //! Process data collection for macOS. Uses sysinfo and custom bindings. +use std::io; +use std::process::Command; + use hashbrown::HashMap; -use sysinfo::System; +use itertools::Itertools; +use sysinfo::{PidExt, ProcessExt}; + +use super::UnixProcessExt; -use super::ProcessHarvest; -use crate::{data_harvester::processes::UserTable, Pid}; +use crate::Pid; mod sysctl_bindings; -pub fn get_process_data( - sys: &System, use_current_cpu_total: bool, unnormalized_cpu: bool, mem_total: u64, - user_table: &mut UserTable, -) -> crate::utils::error::Result<Vec<ProcessHarvest>> { - super::macos_freebsd::get_process_data( - sys, - use_current_cpu_total, - unnormalized_cpu, - mem_total, - user_table, - get_macos_process_cpu_usage, - ) +pub(crate) struct MacOSProcessExt; + +impl UnixProcessExt for MacOSProcessExt { + #[inline] + fn has_backup_proc_cpu_fn() -> bool { + true + } + + fn backup_proc_cpu(pids: &[Pid]) -> io::Result<HashMap<Pid, f64>> { + let output = Command::new("ps") + .args(["-o", "pid=,pcpu=", "-p"]) + .arg( + // Has to look like this since otherwise, it you hit a `unstable_name_collisions` warning. + Itertools::intersperse(pids.iter().map(i32::to_string), ",".to_string()) + .collect::<String>(), + ) + .output()?; + let mut result = HashMap::new(); + String::from_utf8_lossy(&output.stdout) + .split_whitespace() + .chunks(2) + .into_iter() + .for_each(|chunk| { + let chunk: Vec<&str> = chunk.collect(); + if chunk.len() != 2 { + panic!("Unexpected `ps` output"); + } + let pid = chunk[0].parse(); + let usage = chunk[1].parse(); + if let (Ok(pid), Ok(usage)) = (pid, usage) { + result.insert(pid, usage); + } + }); + Ok(result) + } + + fn parent_pid(process_val: &sysinfo::Process) -> Option<Pid> { + process_val + .parent() + .map(|p| p.as_u32() as _) + .or_else(|| fallback_macos_ppid(process_val.pid().as_u32() as _)) + } } -pub(crate) fn fallback_macos_ppid(pid: Pid) -> Option<Pid> { +fn fallback_macos_ppid(pid: Pid) -> Option<Pid> { sysctl_bindings::kinfo_process(pid) .map(|kinfo| kinfo.kp_eproc.e_ppid) .ok() } - -fn get_macos_process_cpu_usage(pids: &[Pid]) -> std::io::Result<HashMap<i32, f64>> { - use itertools::Itertools; - let output = std::process::Command::new("ps") - .args(["-o", "pid=,pcpu=", "-p"]) - .arg( - // Has to look like this since otherwise, it you hit a `unstable_name_collisions` warning. - Itertools::intersperse(pids.iter().map(i32::to_string), ",".to_string()) - .collect::<String>(), - ) - .output()?; - let mut result = HashMap::new(); - String::from_utf8_lossy(&output.stdout) - .split_whitespace() - .chunks(2) - .into_iter() - .for_each(|chunk| { - let chunk: Vec<&str> = chunk.collect(); - if chunk.len() != 2 { - panic!("Unexpected `ps` output"); - } - let pid = chunk[0].parse(); - let usage = chunk[1].parse(); - if let (Ok(pid), Ok(usage)) = (pid, usage) { - result.insert(pid, usage); - } - }); - Ok(result) -} diff --git a/src/app/data_harvester/processes/macos_freebsd.rs b/src/app/data_harvester/processes/macos_freebsd.rs deleted file mode 100644 index 48bbe717..00000000 --- a/src/app/data_harvester/processes/macos_freebsd.rs +++ /dev/null @@ -1,145 +0,0 @@ -//! Shared process data harvesting code from macOS and FreeBSD via sysinfo. - -use std::io; -use std::time::Duration; - -use hashbrown::HashMap; -use sysinfo::{CpuExt, PidExt, ProcessExt, ProcessStatus, System, SystemExt}; - -use super::ProcessHarvest; -use crate::{data_harvester::processes::UserTable, utils::error::Result, Pid}; - -pub fn get_process_data<F>( - sys: &System, use_current_cpu_total: bool, unnormalized_cpu: bool, total_memory: u64, - user_table: &mut UserTable, backup_cpu_proc_usage: F, -) -> Result<Vec<ProcessHarvest>> -where - F: Fn(&[Pid]) -> io::Result<HashMap<Pid, f64>>, -{ - let mut process_vector: Vec<ProcessHarvest> = Vec::new(); - let process_hashmap = sys.processes(); - let cpu_usage = sys.global_cpu_info().cpu_usage() as f64 / 100.0; - let num_processors = sys.cpus().len() as f64; - - for process_val in process_hashmap.values() { - let name = if process_val.name().is_empty() { - let process_cmd = process_val.cmd(); - if process_cmd.len() > 1 { - process_cmd[0].clone() - } else { - let process_exe = process_val.exe().file_stem(); - if let Some(exe) = process_exe { - let process_exe_opt = exe.to_str(); - if let Some(exe_name) = process_exe_opt { - exe_name.to_string() - } else { - "".to_string() - } - } else { - "".to_string() - } - } - } else { - process_val.name().to_string() - }; - let command = { - let command = process_val.cmd().join(" "); - if command.is_empty() { - name.to_string() - } else { - command - } - }; - - let pcu = { - let usage = process_val.cpu_usage() as f64; - if unnormalized_cpu || num_processors == 0.0 { - usage - } else { - usage / num_processors - } - }; - let process_cpu_usage = if use_current_cpu_total && cpu_usage > 0.0 { - pcu / cpu_usage - } else { - pcu - }; - - let disk_usage = process_val.disk_usage(); - let process_state = { - let ps = process_val.status(); - (ps.to_string(), convert_process_status_to_char(ps)) - }; - let uid = process_val.user_id().map(|u| **u); - let pid = process_val.pid().as_u32() as Pid; - process_vector.push(ProcessHarvest { - pid, - parent_pid: { - #[cfg(target_os = "macos")] - { - process_val - .parent() - .map(|p| p.as_u32() as _) - .or_else(|| super::fallback_macos_ppid(pid)) - } - #[cfg(not(target_os = "macos"))] - { - process_val.parent().map(|p| p.as_u32() as _) - } - }, - name, - command, - mem_usage_percent: if total_memory > 0 { - process_val.memory() as f64 * 100.0 / total_memory as f64 - } else { - 0.0 - }, - mem_usage_bytes: process_val.memory(), - cpu_usage_percent: process_cpu_usage, - read_bytes_per_sec: disk_usage.read_bytes, - write_bytes_per_sec: disk_usage.written_bytes, - total_read_bytes: disk_usage.total_read_bytes, - total_write_bytes: disk_usage.total_written_bytes, - process_state, - uid, - user: uid - .and_then(|uid| { - user_table - .get_uid_to_username_mapping(uid) - .map(Into::into) - .ok() - }) - .unwrap_or_else(|| "N/A".into()), - time: Duration::from_secs(process_val.run_time()), - }); - } - - let unknown_state = ProcessStatus::Unknown(0).to_string(); - let cpu_usage_unknown_pids: Vec<Pid> = process_vector - .iter() - .filter(|process| process.process_state.0 == unknown_state) - .map(|process| process.pid) - .collect(); - let cpu_usages = backup_cpu_proc_usage(&cpu_usage_unknown_pids)?; - for process in &mut process_vector { - if cpu_usages.contains_key(&process.pid) { - process.cpu_usage_percent = if unnormalized_cpu || num_processors == 0.0 { - *cpu_usages.get(&process.pid).unwrap() - } else { - *cpu_usages.get(&process.pid).unwrap() / num_processors - }; - } - } - - Ok(process_vector) -} - -fn convert_process_status_to_char(status: ProcessStatus) -> char { - match status { - ProcessStatus::Run => 'R', - ProcessStatus::Sleep => 'S', - ProcessStatus::Idle => 'D', - ProcessStatus::Zombie => 'Z', - _ => '?', - } -} diff --git a/src/app/data_harvester/processes/unix.rs b/src/app/data_harvester/processes/unix.rs index b0d68e23..1d42c2ca 100644 --- a/src/app/data_harvester/processes/unix.rs +++ b/src/app/data_harvester/processes/unix.rs @@ -1,33 +1,36 @@ //! Unix-specific parts of process collection. -use hashbrown::HashMap; +mod user_table; +use cfg_if::cfg_if; +pub use user_table::*; -use crate::utils::error; +cfg_if! { + if #[cfg(all(target_family = "unix", not(target_os = "linux")))] { + mod process_ext; + pub(crate) use process_ext::*; -#[derive(Debug, Default)] -pub struct UserTable { - pub uid_user_mapping: HashMap<libc::uid_t, String>, -} + use super::ProcessHarvest; -impl UserTable { - pub fn get_uid_to_username_mapping(&mut self, uid: libc::uid_t) -> error::Result<String> { - if let Some(user) = self.uid_user_mapping.get(&uid) { - Ok(user.clone()) - } else { - // SAFETY: getpwuid returns a null pointer if no passwd entry is found for the uid - let passwd = unsafe { libc::getpwuid(uid) }; + use crate::app::data_harvester::{DataCollector, processes::*}; + use crate::utils::error; - if passwd.is_null() { - Err(error::BottomError::QueryError("Missing passwd".into())) - } else { - // SAFETY: We return early if passwd is null. - let username = unsafe { std::ffi::CStr::from_ptr((*passwd).pw_name) } - .to_str()? - .to_string(); - self.uid_user_mapping.insert(uid, username.clone()); + pub fn sysinfo_process_data(collector: &mut DataCollector) -> error::Result<Vec<ProcessHarvest>> { + let sys = &collector.sys; + let use_current_cpu_total = collector.use_current_cpu_total; + let unnormalized_cpu = collector.unnormalized_cpu; + let total_memory = collector.total_memory(); + let user_table = &mut collector.user_table; - Ok(username) + cfg_if! { + if #[cfg(target_os = "macos")] { + MacOSProcessExt::sysinfo_process_data(sys, use_current_cpu_total, unnormalized_cpu, total_memory, user_table) + } else if #[cfg(target_os = "freebsd")] { + FreeBSDProcessExt::sysinfo_process_data(sys, use_current_cpu_total, unnormalized_cpu, total_memory, user_table) + } else { + GenericProcessExt::sysinfo_process_data(sys, use_current_cpu_total, unnormalized_cpu, total_memory, user_table) + } } } + } } diff --git a/src/app/data_harvester/processes/unix/process_ext.rs b/src/app/data_harvester/processes/unix/process_ext.rs new file mode 100644 index 00000000..1048fd2d --- /dev/null +++ b/src/app/data_harvester/processes/unix/process_ext.rs @@ -0,0 +1,147 @@ +//! Shared process data harvesting code from macOS and FreeBSD via sysinfo. + +use std::io; +use std::time::Duration; + +use hashbrown::HashMap; +use sysinfo::{CpuExt, PidExt, ProcessExt, ProcessStatus, System, SystemExt}; + +use super::ProcessHarvest; +use crate::{data_harvester::processes::UserTable, utils::error, Pid}; + +pub(crate) trait UnixProcessExt { + fn sysinfo_process_data( + sys: &System, use_current_cpu_total: bool, unnormalized_cpu: bool, total_memory: u64, + user_table: &mut UserTable, + ) -> error::Result<Vec<ProcessHarvest>> { + let mut process_vector: Vec<ProcessHarvest> = Vec::new(); + let process_hashmap = sys.processes(); + let cpu_usage = sys.global_cpu_info().cpu_usage() as f64 / 100.0; + let num_processors = sys.cpus().len() as f64; + + for process_val in process_hashmap.values() { + let name = if process_val.name().is_empty() { + let process_cmd = process_val.cmd(); + if process_cmd.len() > 1 { + process_cmd[0].clone() + } else { + let process_exe = process_val.exe().file_stem(); + if let Some(exe) = process_exe { + let process_exe_opt = exe.to_str(); + if let Some(exe_name) = process_exe_opt { + exe_name.to_string() + } else { + "".to_string() + } + } else { + "".to_string() + } + } + } else { + process_val.name().to_string() + }; + let command = { + let command = process_val.cmd().join(" "); + if command.is_empty() { + name.to_string() + } else { + command + } + }; + + let pcu = { + let usage = process_val.cpu_usage() as f64; + if unnormalized_cpu || num_processors == 0.0 { + usage + } else { + usage / num_processors + } + }; + let process_cpu_usage = if use_current_cpu_total && cpu_usage > 0.0 { + pcu / cpu_usage + } else { + pcu + }; + + let disk_usage = process_val.disk_usage(); + let process_state = { + let ps = process_val.status(); + (ps.to_string(), convert_process_status_to_char(ps)) + }; + let uid = process_val.user_id().map(|u| **u); + let pid = process_val.pid().as_u32() as Pid; + process_vector.push(ProcessHarvest { + pid, + parent_pid: Self::parent_pid(process_val), + name, + command, + mem_usage_percent: if total_memory > 0 { + process_val.memory() as f64 * 100.0 / total_memory as f64 + } else { + 0.0 + }, + mem_usage_bytes: process_val.memory(), + cpu_usage_percent: process_cpu_usage, + read_bytes_per_sec: disk_usage.read_bytes, + write_bytes_per_sec: disk_usage.written_bytes, + total_read_bytes: disk_usage.total_read_bytes, + total_write_bytes: disk_usage.total_written_bytes, + process_state, + uid, + user: uid + .and_then(|uid| { + user_table + .get_uid_to_username_mapping(uid) + .map(Into::into) + .ok() + }) + .unwrap_or_else(|| "N/A".into()), + time: Duration::from_secs(process_val.run_time()), + }); + } + + if Self::has_backup_proc_cpu_fn() { + let unknown_state = ProcessStatus::Unknown(0).to_string(); + let cpu_usage_unknown_pids: Vec<Pid> = process_vector + .iter() + .filter(|process| process.process_state.0 == unknown_state) + .map(|process| process.pid) + .collect(); + let cpu_usages = Self::backup_proc_cpu(&cpu_usage_unknown_pids)?; + for process in &mut process_vector { + if cpu_usages.contains_key(&process.pid) { + process.cpu_usage_percent = if unnormalized_cpu || num_processors == 0.0 { + *cpu_usages.get(&process.pid).unwrap() + } else { + *cpu_usages.get(&process.pid).unwrap() / num_processors + }; + } + } + } + + Ok(process_vector) + } + + #[inline] + fn has_backup_proc_cpu_fn() -> bool { + false + } + + fn backup_proc_cpu(_pids: &[Pid]) -> io::Result<HashMap<Pid, f64>> { + Ok(HashMap::default()) + } + + fn parent_pid(process_val: &sysinfo::Process) -> Option<Pid> { + process_val.parent().map(|p| p.as_u32() as _) + } +} + +fn convert_process_status_to_char(status: ProcessStatus) -> char { + match status { + ProcessStatus::Run => 'R', + ProcessStatus::Sleep => 'S', + ProcessStatus::Idle => 'D', + ProcessStatus::Zombie => 'Z', + _ => '?', + } +} diff --git a/src/app/data_harvester/processes/unix/user_table.rs b/src/app/data_harvester/processes/unix/user_table.rs new file mode 100644 index 00000000..6245a858 --- /dev/null +++ b/src/app/data_harvester/processes/unix/user_table.rs @@ -0,0 +1,31 @@ +use hashbrown::HashMap; + +use crate::utils::error; + +#[derive(Debug, Default)] +pub struct UserTable { + pub uid_user_mapping: HashMap<libc::uid_t, String>, +} + +impl UserTable { + pub fn get_uid_to_username_mapping(&mut self, uid: libc::uid_t) -> error::Result<String> { + if let Some(user) = self.uid_user_mapping.get(&uid) { + |