summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorWesley Moore <wes@wezm.net>2022-07-24 10:44:29 +1000
committerGitHub <noreply@github.com>2022-07-23 20:44:29 -0400
commit577fda96fc30165fe613ae2131b70844ba47c3b2 (patch)
treeb981baebc49dd47e2813ad585a0f7787da6b505b /src
parent510aa5c4042d9933310d24da38f08ff4647875d9 (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.rs3
-rw-r--r--src/app/data_harvester.rs79
-rw-r--r--src/app/data_harvester/cpu.rs19
-rw-r--r--src/app/data_harvester/cpu/heim.rs13
-rw-r--r--src/app/data_harvester/cpu/sysinfo.rs45
-rw-r--r--src/app/data_harvester/disks.rs23
-rw-r--r--src/app/data_harvester/disks/freebsd.rs105
-rw-r--r--src/app/data_harvester/disks/heim.rs21
-rw-r--r--src/app/data_harvester/memory.rs4
-rw-r--r--src/app/data_harvester/memory/general.rs163
-rw-r--r--src/app/data_harvester/memory/general/heim.rs170
-rw-r--r--src/app/data_harvester/memory/general/sysinfo.rs47
-rw-r--r--src/app/data_harvester/network.rs2
-rw-r--r--src/app/data_harvester/processes.rs5
-rw-r--r--src/app/data_harvester/processes/freebsd.rs71
-rw-r--r--src/app/data_harvester/processes/macos.rs126
-rw-r--r--src/app/data_harvester/processes/macos_freebsd.rs122
-rw-r--r--src/app/data_harvester/temperature.rs2
-rw-r--r--src/canvas/dialogs/dd_dialog.rs39
-rw-r--r--src/utils/error.rs1
20 files changed, 745 insertions, 315 deletions
diff --git a/src/app.rs b/src/app.rs
index 9b354d43..c1a5c969 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -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