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
This commit is contained in:
Clement Tsang 2023-06-10 01:44:15 -04:00 committed by GitHub
parent 6b421b48ea
commit 13a8e5bf0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 371 additions and 338 deletions

View File

@ -204,7 +204,7 @@ impl DataCollection {
} }
pub fn eat_data(&mut self, harvested_data: Box<Data>) { 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(); let mut new_entry = TimedData::default();
// Network // Network

View File

@ -27,7 +27,7 @@ pub mod temperature;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Data { pub struct Data {
pub last_collection_time: Instant, pub collection_time: Instant,
pub cpu: Option<cpu::CpuHarvest>, pub cpu: Option<cpu::CpuHarvest>,
pub load_avg: Option<cpu::LoadAvgHarvest>, pub load_avg: Option<cpu::LoadAvgHarvest>,
pub memory: Option<memory::MemHarvest>, pub memory: Option<memory::MemHarvest>,
@ -50,7 +50,7 @@ pub struct Data {
impl Default for Data { impl Default for Data {
fn default() -> Self { fn default() -> Self {
Data { Data {
last_collection_time: Instant::now(), collection_time: Instant::now(),
cpu: None, cpu: None,
load_avg: None, load_avg: None,
memory: None, memory: None,
@ -284,24 +284,20 @@ impl DataCollector {
pub fn update_data(&mut self) { pub fn update_data(&mut self) {
self.refresh_sysinfo_data(); self.refresh_sysinfo_data();
let current_instant = Instant::now(); self.data.collection_time = Instant::now();
self.update_cpu_usage(); self.update_cpu_usage();
self.update_memory_usage(); self.update_memory_usage();
self.update_processes( self.update_processes();
#[cfg(target_os = "linux")]
current_instant,
);
self.update_temps(); self.update_temps();
self.update_network_usage(current_instant); self.update_network_usage();
self.update_disks(); self.update_disks();
#[cfg(feature = "battery")] #[cfg(feature = "battery")]
self.update_batteries(); self.update_batteries();
// Update times for future reference. // Update times for future reference.
self.last_collection_time = current_instant; self.last_collection_time = self.data.collection_time;
self.data.last_collection_time = current_instant;
} }
#[inline] #[inline]
@ -317,66 +313,9 @@ impl DataCollector {
} }
#[inline] #[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 self.widgets_to_harvest.use_proc {
if let Ok(mut process_list) = { if let Ok(mut process_list) = self.get_processes() {
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,
)
}
}
} {
// NB: To avoid duplicate sorts on rerenders/events, we sort the processes by PID here. // 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 // We also want to avoid re-sorting *again* later on if we're sorting by PID, since we already
// did it here! // did it here!
@ -435,7 +374,9 @@ impl DataCollector {
} }
#[inline] #[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 { if self.widgets_to_harvest.use_net {
let net_data = network::get_network_data( let net_data = network::get_network_data(
&self.sys, &self.sys,
@ -485,6 +426,16 @@ impl DataCollector {
self.data.io = disks::get_io_usage().ok(); 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. /// We set a sleep duration between 10ms and 250ms, ideally sysinfo's [`System::MINIMUM_CPU_UPDATE_INTERVAL`] + 1.

View File

@ -1,37 +1,41 @@
//! Data collection for processes. //! Data collection for processes.
//! //!
//! For Linux, this is handled by a custom set of functions. //! 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")] { if #[cfg(target_os = "linux")] {
pub mod linux; pub mod linux;
pub use self::linux::*; pub use self::linux::*;
} else if #[cfg(target_os = "macos")] { } else if #[cfg(target_os = "macos")] {
pub mod macos; pub mod macos;
mod macos_freebsd; pub(crate) use self::macos::*;
pub use self::macos::*;
} else if #[cfg(target_os = "windows")] { } else if #[cfg(target_os = "windows")] {
pub mod windows; pub mod windows;
pub use self::windows::*; pub use self::windows::*;
} else if #[cfg(target_os = "freebsd")] { } else if #[cfg(target_os = "freebsd")] {
pub mod freebsd; pub mod freebsd;
mod macos_freebsd; pub(crate) use self::freebsd::*;
pub 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")] { if #[cfg(target_family = "unix")] {
pub mod unix; pub mod unix;
pub use self::unix::*; pub use self::unix::*;
} }
} }
use std::{borrow::Cow, time::Duration};
use crate::Pid;
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct ProcessHarvest { pub struct ProcessHarvest {
/// The pid of the process. /// The pid of the process.
@ -96,3 +100,24 @@ impl ProcessHarvest {
self.time += rhs.time; 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()))
}
}
}
}

View File

@ -1,14 +1,13 @@
//! Process data collection for FreeBSD. Uses sysinfo. //! Process data collection for FreeBSD. Uses sysinfo.
use std::io; use std::io;
use std::process::Command;
use hashbrown::HashMap; use hashbrown::HashMap;
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
use sysinfo::System;
use super::ProcessHarvest; use crate::data_harvester::{deserialize_xo, processes::UnixProcessExt};
use crate::data_harvester::deserialize_xo; use crate::Pid;
use crate::data_harvester::processes::UserTable;
#[derive(Deserialize, Debug, Default)] #[derive(Deserialize, Debug, Default)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
@ -25,36 +24,34 @@ struct ProcessRow {
percent_cpu: f64, percent_cpu: f64,
} }
pub fn get_process_data( pub(crate) struct FreeBSDProcessExt;
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,
)
}
fn get_freebsd_process_cpu_usage(pids: &[i32]) -> io::Result<HashMap<i32, f64>> { impl UnixProcessExt for FreeBSDProcessExt {
if pids.is_empty() { #[inline]
return Ok(HashMap::new()); fn has_backup_proc_cpu_fn() -> bool {
true
} }
let output = std::process::Command::new("ps") fn backup_proc_cpu(pids: &[Pid]) -> io::Result<HashMap<Pid, f64>> {
.args(["--libxo", "json", "-o", "pid,pcpu", "-p"]) if pids.is_empty() {
.args(pids.iter().map(i32::to_string)) return Ok(HashMap::new());
.output()?; }
deserialize_xo("process-information", &output.stdout).map(|process_info: ProcessInformation| {
process_info let output = Command::new("ps")
.process .args(["--libxo", "json", "-o", "pid,pcpu", "-p"])
.into_iter() .args(pids.iter().map(i32::to_string))
.map(|row| (row.pid, row.percent_cpu)) .output()?;
.collect()
}) 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> fn pid<'de, D>(deserializer: D) -> Result<i32, D::Error>

View File

@ -7,10 +7,11 @@ use std::fs::{self, File};
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader};
use std::time::Duration; use std::time::Duration;
use hashbrown::{HashMap, HashSet}; use hashbrown::HashSet;
use sysinfo::{ProcessStatus, System}; use sysinfo::ProcessStatus;
use super::{ProcessHarvest, UserTable}; use super::{ProcessHarvest, UserTable};
use crate::app::data_harvester::DataCollector;
use crate::utils::error::{self, BottomError}; use crate::utils::error::{self, BottomError};
use crate::Pid; use crate::Pid;
@ -265,11 +266,21 @@ fn is_str_numeric(s: &str) -> bool {
s.chars().all(|c| c.is_ascii_digit()) s.chars().all(|c| c.is_ascii_digit())
} }
pub(crate) fn get_process_data( pub(crate) fn linux_process_data(
sys: &System, prev_proc: PrevProc<'_>, pid_mapping: &mut HashMap<Pid, PrevProcDetails>, collector: &mut DataCollector, time_difference_in_secs: u64,
proc_harvest_options: ProcHarvestOptions, time_difference_in_secs: u64, total_memory: u64, ) -> error::Result<Vec<ProcessHarvest>> {
user_table: &mut UserTable, let total_memory = collector.total_memory();
) -> crate::utils::error::Result<Vec<ProcessHarvest>> { 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 { let ProcHarvestOptions {
use_current_cpu_total, use_current_cpu_total,
unnormalized_cpu, unnormalized_cpu,
@ -289,7 +300,7 @@ pub(crate) fn get_process_data(
{ {
if unnormalized_cpu { if unnormalized_cpu {
use sysinfo::SystemExt; 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, // Note we *divide* here because the later calculation divides `cpu_usage` - in effect,
// multiplying over the number of cores. // multiplying over the number of cores.

View File

@ -1,57 +1,63 @@
//! Process data collection for macOS. Uses sysinfo and custom bindings. //! Process data collection for macOS. Uses sysinfo and custom bindings.
use hashbrown::HashMap; use std::io;
use sysinfo::System; use std::process::Command;
use super::ProcessHarvest; use hashbrown::HashMap;
use crate::{data_harvester::processes::UserTable, Pid}; use itertools::Itertools;
use sysinfo::{PidExt, ProcessExt};
use super::UnixProcessExt;
use crate::Pid;
mod sysctl_bindings; mod sysctl_bindings;
pub fn get_process_data( pub(crate) struct MacOSProcessExt;
sys: &System, use_current_cpu_total: bool, unnormalized_cpu: bool, mem_total: u64,
user_table: &mut UserTable, impl UnixProcessExt for MacOSProcessExt {
) -> crate::utils::error::Result<Vec<ProcessHarvest>> { #[inline]
super::macos_freebsd::get_process_data( fn has_backup_proc_cpu_fn() -> bool {
sys, true
use_current_cpu_total, }
unnormalized_cpu,
mem_total, fn backup_proc_cpu(pids: &[Pid]) -> io::Result<HashMap<Pid, f64>> {
user_table, let output = Command::new("ps")
get_macos_process_cpu_usage, .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) sysctl_bindings::kinfo_process(pid)
.map(|kinfo| kinfo.kp_eproc.e_ppid) .map(|kinfo| kinfo.kp_eproc.e_ppid)
.ok() .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)
}

View File

@ -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',
_ => '?',
}
}

View File

@ -1,33 +1,36 @@
//! Unix-specific parts of process collection. //! 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)] use super::ProcessHarvest;
pub struct UserTable {
pub uid_user_mapping: HashMap<libc::uid_t, String>,
}
impl UserTable { use crate::app::data_harvester::{DataCollector, processes::*};
pub fn get_uid_to_username_mapping(&mut self, uid: libc::uid_t) -> error::Result<String> { use crate::utils::error;
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() { pub fn sysinfo_process_data(collector: &mut DataCollector) -> error::Result<Vec<ProcessHarvest>> {
Err(error::BottomError::QueryError("Missing passwd".into())) let sys = &collector.sys;
} else { let use_current_cpu_total = collector.use_current_cpu_total;
// SAFETY: We return early if passwd is null. let unnormalized_cpu = collector.unnormalized_cpu;
let username = unsafe { std::ffi::CStr::from_ptr((*passwd).pw_name) } let total_memory = collector.total_memory();
.to_str()? let user_table = &mut collector.user_table;
.to_string();
self.uid_user_mapping.insert(uid, username.clone());
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)
}
} }
} }
} }
} }

View File

@ -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',
_ => '?',
}
}

View File

@ -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());
Ok(username)
}
}
}
}

View File

@ -1,14 +1,21 @@
//! Process data collection for Windows. Uses sysinfo. //! Process data collection for Windows. Uses sysinfo.
use std::time::Duration; use std::time::Duration;
use sysinfo::{CpuExt, PidExt, ProcessExt, System, SystemExt, UserExt}; use sysinfo::{CpuExt, PidExt, ProcessExt, SystemExt, UserExt};
use super::ProcessHarvest; use super::ProcessHarvest;
pub fn get_process_data( use crate::data_harvester::DataCollector;
sys: &System, use_current_cpu_total: bool, unnormalized_cpu: bool, total_memory: u64,
pub fn sysinfo_process_data(
collector: &mut DataCollector,
) -> crate::utils::error::Result<Vec<ProcessHarvest>> { ) -> crate::utils::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 mut process_vector: Vec<ProcessHarvest> = Vec::new(); let mut process_vector: Vec<ProcessHarvest> = Vec::new();
let process_hashmap = sys.processes(); let process_hashmap = sys.processes();
let cpu_usage = sys.global_cpu_info().cpu_usage() as f64 / 100.0; let cpu_usage = sys.global_cpu_info().cpu_usage() as f64 / 100.0;