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:
parent
6b421b48ea
commit
13a8e5bf0e
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -1,57 +1,63 @@
|
|||
//! Process data collection for macOS. Uses sysinfo and custom bindings.
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use sysinfo::System;
|
||||
use std::io;
|
||||
use std::process::Command;
|
||||
|
||||
use super::ProcessHarvest;
|
||||
use crate::{data_harvester::processes::UserTable, Pid};
|
||||
use hashbrown::HashMap;
|
||||
use itertools::Itertools;
|
||||
use sysinfo::{PidExt, ProcessExt};
|
||||
|
||||
use super::UnixProcessExt;
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
_ => '?',
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
_ => '?',
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +1,21 @@
|
|||
//! Process data collection for Windows. Uses sysinfo.
|
||||
//! Process data collection for Windows. Uses sysinfo.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use sysinfo::{CpuExt, PidExt, ProcessExt, System, SystemExt, UserExt};
|
||||
use sysinfo::{CpuExt, PidExt, ProcessExt, SystemExt, UserExt};
|
||||
|
||||
use super::ProcessHarvest;
|
||||
|
||||
pub fn get_process_data(
|
||||
sys: &System, use_current_cpu_total: bool, unnormalized_cpu: bool, total_memory: u64,
|
||||
use crate::data_harvester::DataCollector;
|
||||
|
||||
pub fn sysinfo_process_data(
|
||||
collector: &mut DataCollector,
|
||||
) -> 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 process_hashmap = sys.processes();
|
||||
let cpu_usage = sys.global_cpu_info().cpu_usage() as f64 / 100.0;
|
||||
|
|
Loading…
Reference in New Issue