diff options
author | Wesley Moore <wes@wezm.net> | 2022-07-24 10:44:29 +1000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-07-23 20:44:29 -0400 |
commit | 577fda96fc30165fe613ae2131b70844ba47c3b2 (patch) | |
tree | b981baebc49dd47e2813ad585a0f7787da6b505b /src | |
parent | 510aa5c4042d9933310d24da38f08ff4647875d9 (diff) |
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
Diffstat (limited to 'src')
-rw-r--r-- | src/app.rs | 3 | ||||
-rw-r--r-- | src/app/data_harvester.rs | 79 | ||||
-rw-r--r-- | src/app/data_harvester/cpu.rs | 19 | ||||
-rw-r--r-- | src/app/data_harvester/cpu/heim.rs | 13 | ||||
-rw-r--r-- | src/app/data_harvester/cpu/sysinfo.rs | 45 | ||||
-rw-r--r-- | src/app/data_harvester/disks.rs | 23 | ||||
-rw-r--r-- | src/app/data_harvester/disks/freebsd.rs | 105 | ||||
-rw-r--r-- | src/app/data_harvester/disks/heim.rs | 21 | ||||
-rw-r--r-- | src/app/data_harvester/memory.rs | 4 | ||||
-rw-r--r-- | src/app/data_harvester/memory/general.rs | 163 | ||||
-rw-r--r-- | src/app/data_harvester/memory/general/heim.rs | 170 | ||||
-rw-r--r-- | src/app/data_harvester/memory/general/sysinfo.rs | 47 | ||||
-rw-r--r-- | src/app/data_harvester/network.rs | 2 | ||||
-rw-r--r-- | src/app/data_harvester/processes.rs | 5 | ||||
-rw-r--r-- | src/app/data_harvester/processes/freebsd.rs | 71 | ||||
-rw-r--r-- | src/app/data_harvester/processes/macos.rs | 126 | ||||
-rw-r--r-- | src/app/data_harvester/processes/macos_freebsd.rs | 122 | ||||
-rw-r--r-- | src/app/data_harvester/temperature.rs | 2 | ||||
-rw-r--r-- | src/canvas/dialogs/dd_dialog.rs | 39 | ||||
-rw-r--r-- | src/utils/error.rs | 1 |
20 files changed, 745 insertions, 315 deletions
@@ -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) { diff --git a/src/app/data_harvester.rs b/src/app/data_harvester.rs index 979bdadb..3342ed17 100644 --- a/src/app/data_harvester.rs +++ b/src/app/data_harvester.rs @@ -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())) +} diff --git a/src/app/data_harvester/cpu.rs b/src/app/data_harvester/cpu.rs index 81a0db4c..23073606 100644 --- a/src/app/data_harvester/cpu.rs +++ b/src/app/data_harvester/cpu.rs @@ -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; diff --git a/src/app/data_harvester/cpu/heim.rs b/src/app/data_harvester/cpu/heim.rs index 6941dd0c..be2b251f 100644 --- a/src/app/data_harvester/cpu/heim.rs +++ b/src/app/data_harvester/cpu/heim.rs @@ -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; diff --git a/src/app/data_harvester/cpu/sysinfo.rs b/src/app/data_harvester/cpu/sysinfo.rs new file mode 100644 index 00000000..3096e556 --- /dev/null +++ b/src/app/data_harvester/cpu/sysinfo.rs @@ -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]) +} diff --git a/src/app/data_harvester/disks.rs b/src/app/data_harvester/disks.rs index e5a52336..b1d554ef 100644 --- a/src/app/data_harvester/disks.rs +++ b/src/app/data_harvester/disks.rs @@ -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>>; diff --git a/src/app/data_harvester/disks/freebsd.rs b/src/app/data_harvester/disks/freebsd.rs new file mode 100644 index 00000000..3f15dff4 --- /dev/null +++ b/src/app/data_harvester/disks/freebsd.rs @@ -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) +} diff --git a/src/app/data_harvester/disks/heim.rs b/src/app/data_harvester/disks/heim.rs index c99ae105..85608858 100644 --- a/src/app/data_harvester/disks/heim.rs +++ b/src/app/data_harvester/disks/heim.rs @@ -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); diff --git a/src/app/data_harvester/memory.rs b/src/app/data_harvester/memory.rs index 25fccf59..bf80427f 100644 --- a/src/app/data_harvester/memory.rs +++ b/src/app/data_harvester/memory.rs @@ -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::*; } diff --git a/src/app/data_harvester/memory/general.rs b/src/app/data_harvester/memory/general.rs index 2afe119f..45fb5fdd 100644 --- a/src/app/data_harvester/memory/general.rs +++ b/src/app/data_harvester/memory/general.rs @@ -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) - }, - })) -} diff --git a/src/app/data_harvester/memory/general/heim.rs b/src/app/data_harvester/memory/general/heim.rs new file mode 100644 index 00000000..9f845b27 --- /dev/null +++ b/src/app/data_harvester/memory/general/heim.rs @@ -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) + }, + })) +} diff --git a/sr |