From 7f24e6286735abcee6c3137dd9c22a7a178740a3 Mon Sep 17 00:00:00 2001 From: Clement Tsang <34804052+ClementTsang@users.noreply.github.com> Date: Sat, 17 Jul 2021 21:25:05 -0400 Subject: bug: switch over to procfs for linux mem usage (#547) Swap to manually calculating the mem total and usage via procfs. The usage calculation is now: total - (free + cached + buffers + slab_reclaimable - shmem) This follows the same usage calculation as htop. See the PR for more details. --- CHANGELOG.md | 6 +- docs/content/usage/widgets/memory.md | 10 +++ src/app/data_harvester/memory/general.rs | 121 +++++++++++++++++++++++++++++++ src/app/data_harvester/memory/heim.rs | 108 --------------------------- src/app/data_harvester/memory/mod.rs | 4 +- 5 files changed, 138 insertions(+), 111 deletions(-) create mode 100644 src/app/data_harvester/memory/general.rs delete mode 100644 src/app/data_harvester/memory/heim.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index e0ac5aa4..0a5ff9e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Bug Fixes - [#542](https://github.com/ClementTsang/bottom/pull/542): Fixes missing config options in the default generated config file. -- [#545](https://github.com/ClementTsang/bottom/pull/545): Fixes inaccurate memory usage/totals in macOS and Linux. +- [#545](https://github.com/ClementTsang/bottom/pull/545): Fixes inaccurate memory usage/totals in macOS and Linux, switch unit to binary prefix. + +## Changes + +- [#547](https://github.com/ClementTsang/bottom/pull/547): Switch memory usage calculation to match htop. ## [0.6.2] - 2021-06-26 diff --git a/docs/content/usage/widgets/memory.md b/docs/content/usage/widgets/memory.md index f92f18a8..f25dbe36 100644 --- a/docs/content/usage/widgets/memory.md +++ b/docs/content/usage/widgets/memory.md @@ -28,3 +28,13 @@ Note that key bindings are generally case-sensitive. | Binding | Action | | ------------ | -------------------------------------------------------------- | | ++"Scroll"++ | Scrolling up or down zooms in or out of the graph respectively | + +## Calculations + +Memory usage is calculated using the following formula based on values from `/proc/meminfo` (based on [htop's implementation](https://github.com/htop-dev/htop/blob/976c6123f41492aaf613b9d172eef1842fb7b0a3/linux/LinuxProcessList.c#L1584)): + +``` +MemTotal - MemFree - Buffers - (Cached + SReclaimable - Shmem) +``` + +You can find more info on `/proc/meminfo` and its fields [here](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/s2-proc-meminfo). diff --git a/src/app/data_harvester/memory/general.rs b/src/app/data_harvester/memory/general.rs new file mode 100644 index 00000000..4af287d7 --- /dev/null +++ b/src/app/data_harvester/memory/general.rs @@ -0,0 +1,121 @@ +//! Data collection for memory via heim. + +#[derive(Debug, Clone, Default)] +pub struct MemHarvest { + pub mem_total_in_kib: u64, + pub mem_used_in_kib: u64, + pub use_percent: Option, +} + +pub async fn get_mem_data( + actually_get: bool, +) -> ( + crate::utils::error::Result>, + crate::utils::error::Result>, +) { + 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> { + let (mem_total_in_kib, mem_used_in_kib) = { + #[cfg(target_os = "linux")] + { + let mem_info = procfs::Meminfo::new()?; + + // 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_info.mem_total / 1024; + let cached_mem = + mem_info.cached + mem_info.s_reclaimable.unwrap_or(0) - mem_info.shmem.unwrap_or(0); + let used_diff = (mem_info.mem_free + cached_mem + mem_info.buffers) / 1024; + let used = if total >= used_diff { + total - used_diff + } else { + total - mem_info.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::(), + memory.active().get::() + memory.wire().get::(), + ) + } + #[cfg(target_os = "windows")] + { + let memory = heim::memory::memory().await?; + + use heim::units::information::kibibyte; + let mem_total_in_kib = memory.total().get::(); + ( + mem_total_in_kib, + mem_total_in_kib - memory.available().get::(), + ) + } + }; + + 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> { + 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::(), + memory.used().get::(), + ) + } + #[cfg(any(target_os = "windows", target_os = "macos"))] + { + use heim::units::information::kibibyte; + ( + memory.total().get::(), + memory.used().get::(), + ) + } + }; + + 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/heim.rs b/src/app/data_harvester/memory/heim.rs deleted file mode 100644 index 29f16910..00000000 --- a/src/app/data_harvester/memory/heim.rs +++ /dev/null @@ -1,108 +0,0 @@ -//! Data collection for memory via heim. - -#[derive(Debug, Clone, Default)] -pub struct MemHarvest { - pub mem_total_in_kib: u64, - pub mem_used_in_kib: u64, - pub use_percent: Option, -} - -pub async fn get_mem_data( - actually_get: bool, -) -> ( - crate::utils::error::Result>, - crate::utils::error::Result>, -) { - 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> { - let memory = heim::memory::memory().await?; - - let (mem_total_in_kib, mem_used_in_kib) = { - #[cfg(target_os = "linux")] - { - use heim::memory::os::linux::MemoryExt; - use heim::units::information::kilobyte; - - // For Linux, the "kilobyte" value in the .total call is actually kibibytes - see - // https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/s2-proc-meminfo - // - // Heim parses this as kilobytes (https://github.com/heim-rs/heim/blob/master/heim-memory/src/sys/linux/memory.rs#L82) - // even though it probably shouldn't... - - ( - memory.total().get::(), - memory.used().get::(), - ) - } - #[cfg(target_os = "macos")] - { - use heim::memory::os::macos::MemoryExt; - use heim::units::information::kibibyte; - ( - memory.total().get::(), - memory.active().get::() + memory.wire().get::(), - ) - } - #[cfg(target_os = "windows")] - { - use heim::units::information::kibibyte; - let mem_total_in_kib = memory.total().get::(); - ( - mem_total_in_kib, - mem_total_in_kib - memory.available().get::(), - ) - } - }; - - 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> { - 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::(), - memory.used().get::(), - ) - } - #[cfg(any(target_os = "windows", target_os = "macos"))] - { - use heim::units::information::kibibyte; - ( - memory.total().get::(), - memory.used().get::(), - ) - } - }; - - 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/mod.rs b/src/app/data_harvester/memory/mod.rs index 588a3c3b..25fccf59 100644 --- a/src/app/data_harvester/memory/mod.rs +++ b/src/app/data_harvester/memory/mod.rs @@ -4,7 +4,7 @@ cfg_if::cfg_if! { if #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))] { - pub mod heim; - pub use self::heim::*; + pub mod general; + pub use self::general::*; } } -- cgit v1.2.3