summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClement Tsang <34804052+ClementTsang@users.noreply.github.com>2023-06-10 01:44:15 -0400
committerGitHub <noreply@github.com>2023-06-10 01:44:15 -0400
commit13a8e5bf0e0453a03e94cdc05cbbfd8503efa915 (patch)
tree3286c2afeca9979d6a4c61dbcfe4a2bd94b1285c
parent6b421b48eaf0edfece3d9a8ad663d529e84c5a9b (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
-rw-r--r--src/app/data_farmer.rs2
-rw-r--r--src/app/data_harvester.rs91
-rw-r--r--src/app/data_harvester/processes.rs47
-rw-r--r--src/app/data_harvester/processes/freebsd.rs59
-rw-r--r--src/app/data_harvester/processes/linux.rs27
-rw-r--r--src/app/data_harvester/processes/macos.rs96
-rw-r--r--src/app/data_harvester/processes/macos_freebsd.rs145
-rw-r--r--src/app/data_harvester/processes/unix.rs47
-rw-r--r--src/app/data_harvester/processes/unix/process_ext.rs147
-rw-r--r--src/app/data_harvester/processes/unix/user_table.rs31
-rw-r--r--src/app/data_harvester/processes/windows.rs15
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) {
+ 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) };
+
+ 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());
+
+