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>) {
|
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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -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.
|
//! 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 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;
|
||||||
|
|
Loading…
Reference in New Issue