Implement support for FreeBSD (#766)
* WIP FreeBSD support * Implement get_cpu_data_list for FreeBSD * Implement disks for FreeBSD It doesn't work though as sysinfo doesn't make the device name available. * Use libxo to read process cpu info on FreeBSD * Populate get_io_usage with libxo too Actual I/O stats still aren't populated though as there's not an easy source for them. * Share more processes code between macos and freebsd * Extract function for deserializing libxo output on FreeBSD * Implement filtering of disks in FreeBSD * Clean up memory data collection * Update module docs
This commit is contained in:
parent
510aa5c404
commit
577fda96fc
|
@ -233,6 +233,7 @@ dependencies = [
|
|||
"procfs",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smol",
|
||||
"starship-battery",
|
||||
"sysinfo",
|
||||
|
@ -1351,6 +1352,12 @@ version = "0.1.21"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
|
@ -1377,6 +1384,17 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.1.17"
|
||||
|
|
|
@ -94,6 +94,9 @@ heim = { version = "0.1.0-rc.1", features = ["cpu", "disk", "memory", "net"] }
|
|||
heim = { version = "0.1.0-rc.1", features = ["cpu", "disk", "memory"] }
|
||||
winapi = "0.3.9"
|
||||
|
||||
[target.'cfg(target_os = "freebsd")'.dependencies]
|
||||
serde_json = { version = "1.0.82" }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2.0.4"
|
||||
predicates = "2.1.1"
|
||||
|
|
|
@ -158,6 +158,9 @@ const MAX_SIGNAL: usize = 1;
|
|||
const MAX_SIGNAL: usize = 64;
|
||||
#[cfg(target_os = "macos")]
|
||||
const MAX_SIGNAL: usize = 31;
|
||||
// https://www.freebsd.org/cgi/man.cgi?query=signal&apropos=0&sektion=3&manpath=FreeBSD+13.1-RELEASE+and+Ports&arch=default&format=html
|
||||
#[cfg(target_os = "freebsd")]
|
||||
const MAX_SIGNAL: usize = 33;
|
||||
|
||||
impl App {
|
||||
pub fn reset(&mut self) {
|
||||
|
|
|
@ -161,6 +161,15 @@ impl DataCollector {
|
|||
if cfg!(target_os = "windows") && self.widgets_to_harvest.use_net {
|
||||
self.sys.refresh_networks_list();
|
||||
}
|
||||
|
||||
if cfg!(target_os = "freebsd") && self.widgets_to_harvest.use_cpu {
|
||||
self.sys.refresh_cpu();
|
||||
}
|
||||
|
||||
// Refresh disk list once...
|
||||
if cfg!(target_os = "freebsd") && self.widgets_to_harvest.use_disk {
|
||||
self.sys.refresh_disks_list();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "battery")]
|
||||
|
@ -215,31 +224,54 @@ impl DataCollector {
|
|||
pub async fn update_data(&mut self) {
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
if self.widgets_to_harvest.use_proc {
|
||||
if self.widgets_to_harvest.use_proc || self.widgets_to_harvest.use_cpu {
|
||||
self.sys.refresh_cpu();
|
||||
}
|
||||
if self.widgets_to_harvest.use_proc {
|
||||
self.sys.refresh_processes();
|
||||
}
|
||||
if self.widgets_to_harvest.use_temp {
|
||||
self.sys.refresh_components();
|
||||
}
|
||||
|
||||
if cfg!(target_os = "windows") && self.widgets_to_harvest.use_net {
|
||||
self.sys.refresh_networks();
|
||||
}
|
||||
if cfg!(target_os = "freebsd") && self.widgets_to_harvest.use_disk {
|
||||
self.sys.refresh_disks();
|
||||
}
|
||||
if cfg!(target_os = "freebsd") && self.widgets_to_harvest.use_mem {
|
||||
self.sys.refresh_memory();
|
||||
}
|
||||
}
|
||||
|
||||
let current_instant = std::time::Instant::now();
|
||||
|
||||
// CPU
|
||||
if self.widgets_to_harvest.use_cpu {
|
||||
if let Ok(cpu_data) = cpu::get_cpu_data_list(
|
||||
self.show_average_cpu,
|
||||
&mut self.previous_cpu_times,
|
||||
&mut self.previous_average_cpu_time,
|
||||
)
|
||||
.await
|
||||
#[cfg(not(target_os = "freebsd"))]
|
||||
{
|
||||
self.data.cpu = Some(cpu_data);
|
||||
if let Ok(cpu_data) = cpu::get_cpu_data_list(
|
||||
self.show_average_cpu,
|
||||
&mut self.previous_cpu_times,
|
||||
&mut self.previous_average_cpu_time,
|
||||
)
|
||||
.await
|
||||
{
|
||||
self.data.cpu = Some(cpu_data);
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "freebsd")]
|
||||
{
|
||||
if let Ok(cpu_data) = cpu::get_cpu_data_list(
|
||||
&self.sys,
|
||||
self.show_average_cpu,
|
||||
&mut self.previous_cpu_times,
|
||||
&mut self.previous_average_cpu_time,
|
||||
)
|
||||
.await
|
||||
{
|
||||
self.data.cpu = Some(cpu_data);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
|
@ -304,7 +336,7 @@ impl DataCollector {
|
|||
}
|
||||
|
||||
let network_data_fut = {
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(any(target_os = "windows", target_os = "freebsd"))]
|
||||
{
|
||||
network::get_network_data(
|
||||
&self.sys,
|
||||
|
@ -316,7 +348,7 @@ impl DataCollector {
|
|||
&self.filters.net_filter,
|
||||
)
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
#[cfg(not(any(target_os = "windows", target_os = "freebsd")))]
|
||||
{
|
||||
network::get_network_data(
|
||||
self.last_collection_time,
|
||||
|
@ -328,7 +360,16 @@ impl DataCollector {
|
|||
)
|
||||
}
|
||||
};
|
||||
let mem_data_fut = memory::get_mem_data(self.widgets_to_harvest.use_mem);
|
||||
let mem_data_fut = {
|
||||
#[cfg(not(target_os = "freebsd"))]
|
||||
{
|
||||
memory::get_mem_data(self.widgets_to_harvest.use_mem)
|
||||
}
|
||||
#[cfg(target_os = "freebsd")]
|
||||
{
|
||||
memory::get_mem_data(&self.sys, self.widgets_to_harvest.use_mem)
|
||||
}
|
||||
};
|
||||
let disk_data_fut = disks::get_disk_usage(
|
||||
self.widgets_to_harvest.use_disk,
|
||||
&self.filters.disk_filter,
|
||||
|
@ -397,3 +438,17 @@ impl DataCollector {
|
|||
self.last_collection_time = current_instant;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
/// Deserialize [libxo](https://www.freebsd.org/cgi/man.cgi?query=libxo&apropos=0&sektion=0&manpath=FreeBSD+13.1-RELEASE+and+Ports&arch=default&format=html) JSON data
|
||||
fn deserialize_xo<T>(key: &str, data: &[u8]) -> Result<T, std::io::Error>
|
||||
where
|
||||
T: serde::de::DeserializeOwned,
|
||||
{
|
||||
let mut value: serde_json::Value = serde_json::from_slice(data)?;
|
||||
value
|
||||
.as_object_mut()
|
||||
.and_then(|map| map.remove(key))
|
||||
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "key not found"))
|
||||
.and_then(|val| serde_json::from_value(val).map_err(|err| err.into()))
|
||||
}
|
||||
|
|
|
@ -1,14 +1,29 @@
|
|||
//! Data collection for CPU usage and load average.
|
||||
//!
|
||||
//! For CPU usage, Linux, macOS, and Windows are handled by Heim.
|
||||
//! For CPU usage, Linux, macOS, and Windows are handled by Heim, FreeBSD by sysinfo.
|
||||
//!
|
||||
//! For load average, macOS and Linux are supported through Heim.
|
||||
//! For load average, macOS and Linux are supported through Heim, FreeBSD by sysinfo.
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))] {
|
||||
pub mod heim;
|
||||
pub use self::heim::*;
|
||||
} else if #[cfg(target_os = "freebsd")] {
|
||||
pub mod sysinfo;
|
||||
pub use self::sysinfo::*;
|
||||
}
|
||||
}
|
||||
|
||||
pub type LoadAvgHarvest = [f32; 3];
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct CpuData {
|
||||
pub cpu_prefix: String,
|
||||
pub cpu_count: Option<usize>,
|
||||
pub cpu_usage: f64,
|
||||
}
|
||||
|
||||
pub type CpuHarvest = Vec<CpuData>;
|
||||
|
||||
pub type PastCpuWork = f64;
|
||||
pub type PastCpuTotal = f64;
|
||||
|
|
|
@ -18,18 +18,7 @@ cfg_if::cfg_if! {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct CpuData {
|
||||
pub cpu_prefix: String,
|
||||
pub cpu_count: Option<usize>,
|
||||
pub cpu_usage: f64,
|
||||
}
|
||||
|
||||
pub type CpuHarvest = Vec<CpuData>;
|
||||
|
||||
pub type PastCpuWork = f64;
|
||||
pub type PastCpuTotal = f64;
|
||||
|
||||
use crate::data_harvester::cpu::{CpuData, CpuHarvest, PastCpuTotal, PastCpuWork};
|
||||
use futures::StreamExt;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
//! CPU stats through sysinfo.
|
||||
//! Supports FreeBSD.
|
||||
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use sysinfo::{LoadAvg, ProcessorExt, System, SystemExt};
|
||||
|
||||
use super::{CpuData, CpuHarvest, PastCpuTotal, PastCpuWork};
|
||||
use crate::app::data_harvester::cpu::LoadAvgHarvest;
|
||||
|
||||
pub async fn get_cpu_data_list(
|
||||
sys: &sysinfo::System, show_average_cpu: bool,
|
||||
_previous_cpu_times: &mut Vec<(PastCpuWork, PastCpuTotal)>,
|
||||
_previous_average_cpu_time: &mut Option<(PastCpuWork, PastCpuTotal)>,
|
||||
) -> crate::error::Result<CpuHarvest> {
|
||||
let mut cpu_deque: VecDeque<_> = sys
|
||||
.processors()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, cpu)| CpuData {
|
||||
cpu_prefix: "CPU".to_string(),
|
||||
cpu_count: Some(i),
|
||||
cpu_usage: cpu.cpu_usage() as f64,
|
||||
})
|
||||
.collect();
|
||||
|
||||
if show_average_cpu {
|
||||
let cpu = sys.global_processor_info();
|
||||
|
||||
cpu_deque.push_front(CpuData {
|
||||
cpu_prefix: "AVG".to_string(),
|
||||
cpu_count: None,
|
||||
cpu_usage: cpu.cpu_usage() as f64,
|
||||
})
|
||||
}
|
||||
|
||||
Ok(Vec::from(cpu_deque))
|
||||
}
|
||||
|
||||
pub async fn get_load_avg() -> crate::error::Result<LoadAvgHarvest> {
|
||||
let sys = System::new();
|
||||
let LoadAvg { one, five, fifteen } = sys.load_average();
|
||||
|
||||
Ok([one as f32, five as f32, fifteen as f32])
|
||||
}
|
|
@ -1,10 +1,31 @@
|
|||
//! Data collection for disks (IO, usage, space, etc.).
|
||||
//!
|
||||
//! For Linux, macOS, and Windows, this is handled by heim.
|
||||
//! For Linux, macOS, and Windows, this is handled by heim. For FreeBSD there is a custom
|
||||
//! implementation.
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))] {
|
||||
pub mod heim;
|
||||
pub use self::heim::*;
|
||||
} else if #[cfg(target_os = "freebsd")] {
|
||||
pub mod freebsd;
|
||||
pub use self::freebsd::*;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct DiskHarvest {
|
||||
pub name: String,
|
||||
pub mount_point: String,
|
||||
pub free_space: Option<u64>,
|
||||
pub used_space: Option<u64>,
|
||||
pub total_space: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct IoData {
|
||||
pub read_bytes: u64,
|
||||
pub write_bytes: u64,
|
||||
}
|
||||
|
||||
pub type IoHarvest = std::collections::HashMap<String, Option<IoData>>;
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
//! Disk stats for FreeBSD.
|
||||
|
||||
use serde::Deserialize;
|
||||
use std::io;
|
||||
|
||||
use super::{DiskHarvest, IoHarvest};
|
||||
use crate::app::Filter;
|
||||
use crate::data_harvester::deserialize_xo;
|
||||
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
struct StorageSystemInformation {
|
||||
filesystem: Vec<FileSystem>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
struct FileSystem {
|
||||
name: String,
|
||||
total_blocks: u64,
|
||||
used_blocks: u64,
|
||||
available_blocks: u64,
|
||||
mounted_on: String,
|
||||
}
|
||||
|
||||
pub async fn get_io_usage(actually_get: bool) -> crate::utils::error::Result<Option<IoHarvest>> {
|
||||
if !actually_get {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let io_harvest = get_disk_info().map(|storage_system_information| {
|
||||
storage_system_information
|
||||
.filesystem
|
||||
.into_iter()
|
||||
.map(|disk| (disk.name, None))
|
||||
.collect()
|
||||
})?;
|
||||
Ok(Some(io_harvest))
|
||||
}
|
||||
|
||||
pub async fn get_disk_usage(
|
||||
actually_get: bool, disk_filter: &Option<Filter>, mount_filter: &Option<Filter>,
|
||||
) -> crate::utils::error::Result<Option<Vec<DiskHarvest>>> {
|
||||
if !actually_get {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut vec_disks: Vec<DiskHarvest> = get_disk_info().map(|storage_system_information| {
|
||||
storage_system_information
|
||||
.filesystem
|
||||
.into_iter()
|
||||
.filter_map(|disk| {
|
||||
// Precedence ordering in the case where name and mount filters disagree, "allow"
|
||||
// takes precedence over "deny".
|
||||
//
|
||||
// For implementation, we do this as follows:
|
||||
//
|
||||
// 1. Is the entry allowed through any filter? That is, does it match an entry in a
|
||||
// filter where `is_list_ignored` is `false`? If so, we always keep this entry.
|
||||
// 2. Is the entry denied through any filter? That is, does it match an entry in a
|
||||
// filter where `is_list_ignored` is `true`? If so, we always deny this entry.
|
||||
// 3. Anything else is allowed.
|
||||
let filter_check_map =
|
||||
[(disk_filter, &disk.name), (mount_filter, &disk.mounted_on)];
|
||||
if matches_allow_list(filter_check_map.as_slice())
|
||||
|| !matches_ignore_list(filter_check_map.as_slice())
|
||||
{
|
||||
Some(DiskHarvest {
|
||||
free_space: Some(disk.available_blocks * 1024),
|
||||
used_space: Some(disk.used_blocks * 1024),
|
||||
total_space: Some(disk.total_blocks * 1024),
|
||||
mount_point: disk.mounted_on,
|
||||
name: disk.name,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
})?;
|
||||
|
||||
vec_disks.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
Ok(Some(vec_disks))
|
||||
}
|
||||
|
||||
fn matches_allow_list(filter_check_map: &[(&Option<Filter>, &String)]) -> bool {
|
||||
filter_check_map.iter().any(|(filter, text)| match filter {
|
||||
Some(f) if !f.is_list_ignored => f.list.iter().any(|r| r.is_match(text)),
|
||||
Some(_) | None => false,
|
||||
})
|
||||
}
|
||||
|
||||
fn matches_ignore_list(filter_check_map: &[(&Option<Filter>, &String)]) -> bool {
|
||||
filter_check_map.iter().any(|(filter, text)| match filter {
|
||||
Some(f) if f.is_list_ignored => f.list.iter().any(|r| r.is_match(text)),
|
||||
Some(_) | None => false,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_disk_info() -> io::Result<StorageSystemInformation> {
|
||||
let output = std::process::Command::new("df")
|
||||
.args(&["--libxo", "json", "-k", "-t", "ufs,msdosfs,zfs"])
|
||||
.output()?;
|
||||
deserialize_xo("storage-system-information", &output.stdout)
|
||||
}
|
|
@ -1,4 +1,8 @@
|
|||
//! Disk stats through heim.
|
||||
//! Supports macOS, Linux, and Windows.
|
||||
|
||||
use crate::app::Filter;
|
||||
use crate::data_harvester::disks::{DiskHarvest, IoData, IoHarvest};
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "linux")] {
|
||||
|
@ -10,23 +14,6 @@ cfg_if::cfg_if! {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct DiskHarvest {
|
||||
pub name: String,
|
||||
pub mount_point: String,
|
||||
pub free_space: Option<u64>,
|
||||
pub used_space: Option<u64>,
|
||||
pub total_space: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct IoData {
|
||||
pub read_bytes: u64,
|
||||
pub write_bytes: u64,
|
||||
}
|
||||
|
||||
pub type IoHarvest = std::collections::HashMap<String, Option<IoData>>;
|
||||
|
||||
pub async fn get_io_usage(actually_get: bool) -> crate::utils::error::Result<Option<IoHarvest>> {
|
||||
if !actually_get {
|
||||
return Ok(None);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
//! Data collection for memory.
|
||||
//!
|
||||
//! For Linux, macOS, and Windows, this is handled by Heim.
|
||||
//! For Linux, macOS, and Windows, this is handled by Heim. On FreeBSD it is handled by sysinfo.
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))] {
|
||||
if #[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "macos", target_os = "windows"))] {
|
||||
pub mod general;
|
||||
pub use self::general::*;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
//! Data collection for memory via heim.
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))] {
|
||||
pub mod heim;
|
||||
pub use self::heim::*;
|
||||
} else if #[cfg(target_os = "freebsd")] {
|
||||
pub mod sysinfo;
|
||||
pub use self::sysinfo::*;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct MemHarvest {
|
||||
|
@ -6,156 +14,3 @@ pub struct MemHarvest {
|
|||
pub mem_used_in_kib: u64,
|
||||
pub use_percent: Option<f64>,
|
||||
}
|
||||
|
||||
pub async fn get_mem_data(
|
||||
actually_get: bool,
|
||||
) -> (
|
||||
crate::utils::error::Result<Option<MemHarvest>>,
|
||||
crate::utils::error::Result<Option<MemHarvest>>,
|
||||
) {
|
||||
use futures::join;
|
||||
|
||||
if !actually_get {
|
||||
(Ok(None), Ok(None))
|
||||
} else {
|
||||
join!(get_ram_data(), get_swap_data())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_ram_data() -> crate::utils::error::Result<Option<MemHarvest>> {
|
||||
let (mem_total_in_kib, mem_used_in_kib) = {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
use smol::fs::read_to_string;
|
||||
let meminfo = read_to_string("/proc/meminfo").await?;
|
||||
|
||||
// All values are in KiB by default.
|
||||
let mut mem_total = 0;
|
||||
let mut cached = 0;
|
||||
let mut s_reclaimable = 0;
|
||||
let mut shmem = 0;
|
||||
let mut buffers = 0;
|
||||
let mut mem_free = 0;
|
||||
|
||||
let mut keys_read: u8 = 0;
|
||||
const TOTAL_KEYS_NEEDED: u8 = 6;
|
||||
|
||||
for line in meminfo.lines() {
|
||||
if let Some((label, value)) = line.split_once(':') {
|
||||
let to_write = match label {
|
||||
"MemTotal" => &mut mem_total,
|
||||
"MemFree" => &mut mem_free,
|
||||
"Buffers" => &mut buffers,
|
||||
"Cached" => &mut cached,
|
||||
"Shmem" => &mut shmem,
|
||||
"SReclaimable" => &mut s_reclaimable,
|
||||
_ => {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if let Some((number, _unit)) = value.trim_start().split_once(' ') {
|
||||
// Parse the value, remember it's in KiB!
|
||||
if let Ok(number) = number.parse::<u64>() {
|
||||
*to_write = number;
|
||||
|
||||
// We only need a few keys, so we can bail early.
|
||||
keys_read += 1;
|
||||
if keys_read == TOTAL_KEYS_NEEDED {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Let's preface this by saying that memory usage calculations are... not straightforward.
|
||||
// There are conflicting implementations everywhere.
|
||||
//
|
||||
// Now that we've added this preface (mainly for future reference), the current implementation below for usage
|
||||
// is based on htop's calculation formula. See
|
||||
// https://github.com/htop-dev/htop/blob/976c6123f41492aaf613b9d172eef1842fb7b0a3/linux/LinuxProcessList.c#L1584
|
||||
// for implementation details as of writing.
|
||||
//
|
||||
// Another implementation, commonly used in other things, is to skip the shmem part of the calculation,
|
||||
// which matches gopsutil and stuff like free.
|
||||
|
||||
let total = mem_total;
|
||||
let cached_mem = cached + s_reclaimable - shmem;
|
||||
let used_diff = mem_free + cached_mem + buffers;
|
||||
let used = if total >= used_diff {
|
||||
total - used_diff
|
||||
} else {
|
||||
total - mem_free
|
||||
};
|
||||
|
||||
(total, used)
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let memory = heim::memory::memory().await?;
|
||||
|
||||
use heim::memory::os::macos::MemoryExt;
|
||||
use heim::units::information::kibibyte;
|
||||
(
|
||||
memory.total().get::<kibibyte>(),
|
||||
memory.active().get::<kibibyte>() + memory.wire().get::<kibibyte>(),
|
||||
)
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let memory = heim::memory::memory().await?;
|
||||
|
||||
use heim::units::information::kibibyte;
|
||||
let mem_total_in_kib = memory.total().get::<kibibyte>();
|
||||
(
|
||||
mem_total_in_kib,
|
||||
mem_total_in_kib - memory.available().get::<kibibyte>(),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Some(MemHarvest {
|
||||
mem_total_in_kib,
|
||||
mem_used_in_kib,
|
||||
use_percent: if mem_total_in_kib == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(mem_used_in_kib as f64 / mem_total_in_kib as f64 * 100.0)
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn get_swap_data() -> crate::utils::error::Result<Option<MemHarvest>> {
|
||||
let memory = heim::memory::swap().await?;
|
||||
|
||||
let (mem_total_in_kib, mem_used_in_kib) = {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// Similar story to above - heim parses this information incorrectly as far as I can tell, so kilobytes = kibibytes here.
|
||||
use heim::units::information::kilobyte;
|
||||
(
|
||||
memory.total().get::<kilobyte>(),
|
||||
memory.used().get::<kilobyte>(),
|
||||
)
|
||||
}
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
{
|
||||
use heim::units::information::kibibyte;
|
||||
(
|
||||
memory.total().get::<kibibyte>(),
|
||||
memory.used().get::<kibibyte>(),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Some(MemHarvest {
|
||||
mem_total_in_kib,
|
||||
mem_used_in_kib,
|
||||
use_percent: if mem_total_in_kib == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(mem_used_in_kib as f64 / mem_total_in_kib as f64 * 100.0)
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
//! Data collection for memory via heim.
|
||||
|
||||
use crate::data_harvester::memory::MemHarvest;
|
||||
|
||||
pub async fn get_mem_data(
|
||||
actually_get: bool,
|
||||
) -> (
|
||||
crate::utils::error::Result<Option<MemHarvest>>,
|
||||
crate::utils::error::Result<Option<MemHarvest>>,
|
||||
) {
|
||||
use futures::join;
|
||||
|
||||
if !actually_get {
|
||||
(Ok(None), Ok(None))
|
||||
} else {
|
||||
join!(get_ram_data(), get_swap_data())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_ram_data() -> crate::utils::error::Result<Option<MemHarvest>> {
|
||||
let (mem_total_in_kib, mem_used_in_kib) = {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
use smol::fs::read_to_string;
|
||||
let meminfo = read_to_string("/proc/meminfo").await?;
|
||||
|
||||
// All values are in KiB by default.
|
||||
let mut mem_total = 0;
|
||||
let mut cached = 0;
|
||||
let mut s_reclaimable = 0;
|
||||
let mut shmem = 0;
|
||||
let mut buffers = 0;
|
||||
let mut mem_free = 0;
|
||||
|
||||
let mut keys_read: u8 = 0;
|
||||
const TOTAL_KEYS_NEEDED: u8 = 6;
|
||||
|
||||
for line in meminfo.lines() {
|
||||
if let Some((label, value)) = line.split_once(':') {
|
||||
let to_write = match label {
|
||||
"MemTotal" => &mut mem_total,
|
||||
"MemFree" => &mut mem_free,
|
||||
"Buffers" => &mut buffers,
|
||||
"Cached" => &mut cached,
|
||||
"Shmem" => &mut shmem,
|
||||
"SReclaimable" => &mut s_reclaimable,
|
||||
_ => {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if let Some((number, _unit)) = value.trim_start().split_once(' ') {
|
||||
// Parse the value, remember it's in KiB!
|
||||
if let Ok(number) = number.parse::<u64>() {
|
||||
*to_write = number;
|
||||
|
||||
// We only need a few keys, so we can bail early.
|
||||
keys_read += 1;
|
||||
if keys_read == TOTAL_KEYS_NEEDED {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Let's preface this by saying that memory usage calculations are... not straightforward.
|
||||
// There are conflicting implementations everywhere.
|
||||
//
|
||||
// Now that we've added this preface (mainly for future reference), the current implementation below for usage
|
||||
// is based on htop's calculation formula. See
|
||||
// https://github.com/htop-dev/htop/blob/976c6123f41492aaf613b9d172eef1842fb7b0a3/linux/LinuxProcessList.c#L1584
|
||||
// for implementation details as of writing.
|
||||
//
|
||||
// Another implementation, commonly used in other things, is to skip the shmem part of the calculation,
|
||||
// which matches gopsutil and stuff like free.
|
||||
|
||||
let total = mem_total;
|
||||
let cached_mem = cached + s_reclaimable - shmem;
|
||||
let used_diff = mem_free + cached_mem + buffers;
|
||||
let used = if total >= used_diff {
|
||||
total - used_diff
|
||||
} else {
|
||||
total - mem_free
|
||||
};
|
||||
|
||||
(total, used)
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let memory = heim::memory::memory().await?;
|
||||
|
||||
use heim::memory::os::macos::MemoryExt;
|
||||
use heim::units::information::kibibyte;
|
||||
(
|
||||
memory.total().get::<kibibyte>(),
|
||||
memory.active().get::<kibibyte>() + memory.wire().get::<kibibyte>(),
|
||||
)
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let memory = heim::memory::memory().await?;
|
||||
|
||||
use heim::units::information::kibibyte;
|
||||
let mem_total_in_kib = memory.total().get::<kibibyte>();
|
||||
(
|
||||
mem_total_in_kib,
|
||||
mem_total_in_kib - memory.available().get::<kibibyte>(),
|
||||
)
|
||||
}
|
||||
#[cfg(target_os = "freebsd")]
|
||||
{
|
||||
let mut s = System::new();
|
||||
s.refresh_memory();
|
||||
(s.total_memory(), s.used_memory())
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Some(MemHarvest {
|
||||
mem_total_in_kib,
|
||||
mem_used_in_kib,
|
||||
use_percent: if mem_total_in_kib == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(mem_used_in_kib as f64 / mem_total_in_kib as f64 * 100.0)
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn get_swap_data() -> crate::utils::error::Result<Option<MemHarvest>> {
|
||||
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
|
||||
let memory = heim::memory::swap().await?;
|
||||
#[cfg(target_os = "freebsd")]
|
||||
let mut memory = System::new();
|
||||
|
||||
let (mem_total_in_kib, mem_used_in_kib) = {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// Similar story to above - heim parses this information incorrectly as far as I can tell, so kilobytes = kibibytes here.
|
||||
use heim::units::information::kilobyte;
|
||||
(
|
||||
memory.total().get::<kilobyte>(),
|
||||
memory.used().get::<kilobyte>(),
|
||||
)
|
||||
}
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
{
|
||||
use heim::units::information::kibibyte;
|
||||
(
|
||||
memory.total().get::<kibibyte>(),
|
||||
memory.used().get::<kibibyte>(),
|
||||
)
|
||||
}
|
||||
#[cfg(target_os = "freebsd")]
|
||||
{
|
||||
memory.refresh_memory();
|
||||
(memory.total_swap(), memory.used_swap())
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Some(MemHarvest {
|
||||
mem_total_in_kib,
|
||||
mem_used_in_kib,
|
||||
use_percent: if mem_total_in_kib == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(mem_used_in_kib as f64 / mem_total_in_kib as f64 * 100.0)
|
||||
},
|
||||
}))
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
//! Data collection for memory via sysinfo.
|
||||
|
||||
use crate::data_harvester::memory::MemHarvest;
|
||||
use sysinfo::{System, SystemExt};
|
||||
|
||||
pub async fn get_mem_data(
|
||||
sys: &System, actually_get: bool,
|
||||
) -> (
|
||||
crate::utils::error::Result<Option<MemHarvest>>,
|
||||
crate::utils::error::Result<Option<MemHarvest>>,
|
||||
) {
|
||||
use futures::join;
|
||||
|
||||
if !actually_get {
|
||||
(Ok(None), Ok(None))
|
||||
} else {
|
||||
join!(get_ram_data(sys), get_swap_data(sys))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_ram_data(sys: &System) -> crate::utils::error::Result<Option<MemHarvest>> {
|
||||
let (mem_total_in_kib, mem_used_in_kib) = (sys.total_memory(), sys.used_memory());
|
||||
|
||||
Ok(Some(MemHarvest {
|
||||
mem_total_in_kib,
|
||||
mem_used_in_kib,
|
||||
use_percent: if mem_total_in_kib == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(mem_used_in_kib as f64 / mem_total_in_kib as f64 * 100.0)
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn get_swap_data(sys: &System) -> crate::utils::error::Result<Option<MemHarvest>> {
|
||||
let (mem_total_in_kib, mem_used_in_kib) = (sys.total_swap(), sys.used_swap());
|
||||
|
||||
Ok(Some(MemHarvest {
|
||||
mem_total_in_kib,
|
||||
mem_used_in_kib,
|
||||
use_percent: if mem_total_in_kib == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(mem_used_in_kib as f64 / mem_total_in_kib as f64 * 100.0)
|
||||
},
|
||||
}))
|
||||
}
|
|
@ -7,7 +7,7 @@ cfg_if::cfg_if! {
|
|||
if #[cfg(any(target_os = "linux", target_os = "macos"))] {
|
||||
pub mod heim;
|
||||
pub use self::heim::*;
|
||||
} else if #[cfg(target_os = "windows")] {
|
||||
} else if #[cfg(any(target_os = "freebsd", target_os = "windows"))] {
|
||||
pub mod sysinfo;
|
||||
pub use self::sysinfo::*;
|
||||
}
|
||||
|
|
|
@ -9,10 +9,15 @@ cfg_if::cfg_if! {
|
|||
pub use self::linux::*;
|
||||
} else if #[cfg(target_os = "macos")] {
|
||||
pub mod macos;
|
||||
mod macos_freebsd;
|
||||
pub 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::*;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
//! Process data collection for FreeBSD. Uses sysinfo.
|
||||
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use std::io;
|
||||
|
||||
use super::ProcessHarvest;
|
||||
use sysinfo::System;
|
||||
|
||||
use crate::data_harvester::deserialize_xo;
|
||||
use crate::data_harvester::processes::UserTable;
|
||||
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
struct ProcessInformation {
|
||||
process: Vec<ProcessRow>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
struct ProcessRow {
|
||||
#[serde(deserialize_with = "pid")]
|
||||
pid: i32,
|
||||
#[serde(deserialize_with = "percent_cpu")]
|
||||
percent_cpu: f64,
|
||||
}
|
||||
|
||||
pub fn get_process_data(
|
||||
sys: &System, use_current_cpu_total: bool, mem_total_kb: u64, user_table: &mut UserTable,
|
||||
) -> crate::utils::error::Result<Vec<ProcessHarvest>> {
|
||||
super::macos_freebsd::get_process_data(
|
||||
sys,
|
||||
use_current_cpu_total,
|
||||
mem_total_kb,
|
||||
user_table,
|
||||
get_freebsd_process_cpu_usage,
|
||||
)
|
||||
}
|
||||
|
||||
fn get_freebsd_process_cpu_usage(pids: &[i32]) -> io::Result<std::collections::HashMap<i32, f64>> {
|
||||
if pids.is_empty() {
|
||||
return Ok(std::collections::HashMap::new());
|
||||
}
|
||||
|
||||
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 pid<'de, D>(deserializer: D) -> Result<i32, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
s.parse().map_err(serde::de::Error::custom)
|
||||
}
|
||||
|
||||
fn percent_cpu<'de, D>(deserializer: D) -> Result<f64, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
s.parse().map_err(serde::de::Error::custom)
|
||||
}
|
|
@ -1,10 +1,22 @@
|
|||
//! Process data collection for macOS. Uses sysinfo.
|
||||
|
||||
use super::ProcessHarvest;
|
||||
use sysinfo::{PidExt, ProcessExt, ProcessStatus, ProcessorExt, System, SystemExt};
|
||||
use sysinfo::System;
|
||||
|
||||
use crate::data_harvester::processes::UserTable;
|
||||
|
||||
pub fn get_process_data(
|
||||
sys: &System, use_current_cpu_total: bool, mem_total_kb: u64, user_table: &mut UserTable,
|
||||
) -> crate::utils::error::Result<Vec<ProcessHarvest>> {
|
||||
super::macos_freebsd::get_process_data(
|
||||
sys,
|
||||
use_current_cpu_total,
|
||||
mem_total_kb,
|
||||
user_table,
|
||||
get_macos_process_cpu_usage,
|
||||
)
|
||||
}
|
||||
|
||||
fn get_macos_process_cpu_usage(
|
||||
pids: &[i32],
|
||||
) -> std::io::Result<std::collections::HashMap<i32, f64>> {
|
||||
|
@ -35,115 +47,3 @@ fn get_macos_process_cpu_usage(
|
|||
});
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn get_process_data(
|
||||
sys: &System, use_current_cpu_total: bool, mem_total_kb: u64, user_table: &mut UserTable,
|
||||
) -> crate::utils::error::Result<Vec<ProcessHarvest>> {
|
||||
let mut process_vector: Vec<ProcessHarvest> = Vec::new();
|
||||
let process_hashmap = sys.processes();
|
||||
let cpu_usage = sys.global_processor_info().cpu_usage() as f64 / 100.0;
|
||||
let num_processors = sys.processors().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 p = process_val.cpu_usage() as f64 / num_processors;
|
||||
if p.is_nan() {
|
||||
process_val.cpu_usage() as f64
|
||||
} else {
|
||||
p
|
||||
}
|
||||
};
|
||||
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.uid;
|
||||
process_vector.push(ProcessHarvest {
|
||||
pid: process_val.pid().as_u32() as _,
|
||||
parent_pid: process_val.parent().map(|p| p.as_u32() as _),
|
||||
name,
|
||||
command,
|
||||
mem_usage_percent: if mem_total_kb > 0 {
|
||||
process_val.memory() as f64 * 100.0 / mem_total_kb as f64
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
mem_usage_bytes: process_val.memory() * 1024,
|
||||
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: user_table
|
||||
.get_uid_to_username_mapping(uid)
|
||||
.map(Into::into)
|
||||
.unwrap_or_else(|_| "N/A".into()),
|
||||
});
|
||||
}
|
||||
|
||||
let unknown_state = ProcessStatus::Unknown(0).to_string();
|
||||
let cpu_usage_unknown_pids: Vec<i32> = process_vector
|
||||
.iter()
|
||||
.filter(|process| process.process_state.0 == unknown_state)
|
||||
.map(|process| process.pid)
|
||||
.collect();
|
||||
let cpu_usages = get_macos_process_cpu_usage(&cpu_usage_unknown_pids)?;
|
||||
for process in &mut process_vector {
|
||||
if cpu_usages.contains_key(&process.pid) {
|
||||
process.cpu_usage_percent = if 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',
|
||||
_ => '?',
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
//! Shared process data harvesting code from macOS and FreeBSD via sysinfo.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
|
||||
use super::ProcessHarvest;
|
||||
use sysinfo::{PidExt, ProcessExt, ProcessStatus, ProcessorExt, System, SystemExt};
|
||||
|
||||
use crate::data_harvester::processes::UserTable;
|
||||
|
||||
pub fn get_process_data(
|
||||
sys: &System, use_current_cpu_total: bool, mem_total_kb: u64, user_table: &mut UserTable,
|
||||
get_process_cpu_usage: impl Fn(&[i32]) -> io::Result<HashMap<i32, f64>>,
|
||||
) -> crate::utils::error::Result<Vec<ProcessHarvest>> {
|
||||
let mut process_vector: Vec<ProcessHarvest> = Vec::new();
|
||||
let process_hashmap = sys.processes();
|
||||
let cpu_usage = sys.global_processor_info().cpu_usage() as f64 / 100.0;
|
||||
let num_processors = sys.processors().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 p = process_val.cpu_usage() as f64 / num_processors;
|
||||
if p.is_nan() {
|
||||
process_val.cpu_usage() as f64
|
||||
} else {
|
||||
p
|
||||
}
|
||||
};
|
||||
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.uid;
|
||||
process_vector.push(ProcessHarvest {
|
||||
pid: process_val.pid().as_u32() as _,
|
||||
parent_pid: process_val.parent().map(|p| p.as_u32() as _),
|
||||
name,
|
||||
command,
|
||||
mem_usage_percent: if mem_total_kb > 0 {
|
||||
process_val.memory() as f64 * 100.0 / mem_total_kb as f64
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
mem_usage_bytes: process_val.memory() * 1024,
|
||||
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: user_table
|
||||
.get_uid_to_username_mapping(uid)
|
||||
.map(Into::into)
|
||||
.unwrap_or_else(|_| "N/A".into()),
|
||||
});
|
||||
}
|
||||
|
||||
let unknown_state = ProcessStatus::Unknown(0).to_string();
|
||||
let cpu_usage_unknown_pids: Vec<i32> = process_vector
|
||||
.iter()
|
||||
.filter(|process| process.process_state.0 == unknown_state)
|
||||
.map(|process| process.pid)
|
||||
.collect();
|
||||
let cpu_usages = get_process_cpu_usage(&cpu_usage_unknown_pids)?;
|
||||
for process in &mut process_vector {
|
||||
if cpu_usages.contains_key(&process.pid) {
|
||||
process.cpu_usage_percent = if 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',
|
||||
_ => '?',
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ cfg_if::cfg_if! {
|
|||
if #[cfg(target_os = "linux")] {
|
||||
pub mod heim;
|
||||
pub use self::heim::*;
|
||||
} else if #[cfg(any(target_os = "macos", target_os = "windows"))] {
|
||||
} else if #[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "windows"))] {
|
||||
pub mod sysinfo;
|
||||
pub use self::sysinfo::*;
|
||||
}
|
||||
|
|
|
@ -239,6 +239,45 @@ impl Painter {
|
|||
"31: USR2",
|
||||
];
|
||||
}
|
||||
#[cfg(target_os = "freebsd")]
|
||||
{
|
||||
signal_text = vec![
|
||||
"0: Cancel",
|
||||
"1: HUP",
|
||||
"2: INT",
|
||||
"3: QUIT",
|
||||
"4: ILL",
|
||||
"5: TRAP",
|
||||
"6: ABRT",
|
||||
"7: EMT",
|
||||
"8: FPE",
|
||||
"9: KILL",
|
||||
"10: BUS",
|
||||
"11: SEGV",
|
||||
"12: SYS",
|
||||
"13: PIPE",
|
||||
"14: ALRM",
|
||||
"15: TERM",
|
||||
"16: URG",
|
||||
"17: STOP",
|
||||
"18: TSTP",
|
||||
"19: CONT",
|
||||
"20: CHLD",
|
||||
"21: TTIN",
|
||||
"22: TTOU",
|
||||
"23: IO",
|
||||
"24: XCPU",
|
||||
"25: XFSZ",
|
||||
"26: VTALRM",
|
||||
"27: PROF",
|
||||
"28: WINCH",
|
||||
"29: INFO",
|
||||
"30: USR1",
|
||||
"31: USR2",
|
||||
"32: THR",
|
||||
"33: LIBRT",
|
||||
];
|
||||
}
|
||||
|
||||
let button_rect = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
|
|
|
@ -49,6 +49,7 @@ impl From<std::io::Error> for BottomError {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "freebsd"))]
|
||||
impl From<heim::Error> for BottomError {
|
||||
fn from(err: heim::Error) -> Self {
|
||||
BottomError::InvalidHeim(err.to_string())
|
||||
|
|
Loading…
Reference in New Issue