From a10eadd2d52fc3f4a10563ff13f8f6611b6a6d81 Mon Sep 17 00:00:00 2001 From: Clement Tsang <34804052+ClementTsang@users.noreply.github.com> Date: Thu, 13 Aug 2026 05:22:40 -0400 Subject: [PATCH] bug: fix cgroups v1 swap collection around sentinel max value (#2195) Problem was that with cgroups v1, if the swap had an unlimited value set, it would be done by just setting a huge integer, which would break things. --- CHANGELOG.md | 3 ++- src/collection.rs | 6 +++++- src/collection/linux/cgroups.rs | 32 ++++++++++++++++++++++++-------- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dab1e4e9..994cb7fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,11 +20,12 @@ That said, these are more guidelines rather than hard rules, though the project --- -## 0.14.8/0.15.0 - Unreleased +## 0.14.8 - 2026-08-13 ### Bug Fixes - [2191](https://github.com/ClementTsang/bottom/pull/2191): Fix close logic when pressing enter in sort menu. +- [2195](https://github.com/ClementTsang/bottom/pull/2195): Fix cgroups v1 swap collection around sentinel max value. ## 0.14.7 - 2026-07-27 diff --git a/src/collection.rs b/src/collection.rs index 2711e261..3b158941 100644 --- a/src/collection.rs +++ b/src/collection.rs @@ -381,7 +381,11 @@ impl DataCollector { self.refresh_sysinfo_data(); #[cfg(target_os = "linux")] - self.cgroup_memory_data.refresh(); + { + let total_memory = self.sys.system.total_memory(); + let total_swap = self.sys.system.total_swap(); + self.cgroup_memory_data.refresh(total_memory, total_swap); + } #[cfg(target_os = "linux")] self.cgroup_cpu_data.refresh(); diff --git a/src/collection/linux/cgroups.rs b/src/collection/linux/cgroups.rs index 0136f423..7065b0f8 100644 --- a/src/collection/linux/cgroups.rs +++ b/src/collection/linux/cgroups.rs @@ -44,19 +44,32 @@ pub(crate) struct CgroupMemCollector { pub swap: Option, } +/// Computes the cgroup v1 swap limit. Calculated by getting memsw and subtracting the memory limits. +#[inline] +fn cgroup_v1_swap_limit( + memsw_limit: u64, mem_limit: Option, total_memory: u64, total_swap: u64, +) -> u64 { + let effective_memsw_limit = memsw_limit.min(total_memory + total_swap); + let effective_mem_limit = mem_limit.map_or(0, |mem_limit| mem_limit.min(total_memory)); + + effective_memsw_limit.saturating_sub(effective_mem_limit) +} + impl CgroupMemCollector { /// Refresh the cgroup memory data. /// /// Based on [docker's CLI](https://github.com/docker/cli/blob/master/cli/command/container/stats_helpers.go#L254). - pub(crate) fn refresh(&mut self) { - if !self.try_update_memory_cgroup_v1() && !self.try_update_memory_cgroup_v2() { + pub(crate) fn refresh(&mut self, total_memory: u64, total_swap: u64) { + if !self.try_update_memory_cgroup_v1(total_memory, total_swap) + && !self.try_update_memory_cgroup_v2() + { self.ram = None; self.swap = None; } } /// Try and update the memory using cgroup v1 semantics. If successful, returns `true`. - fn try_update_memory_cgroup_v1(&mut self) -> bool { + fn try_update_memory_cgroup_v1(&mut self, total_memory: u64, total_swap: u64) -> bool { if let Some(mem_usage) = read_u64("/sys/fs/cgroup/memory/memory.usage_in_bytes") { // --- Memory --- let inactive = @@ -66,8 +79,8 @@ impl CgroupMemCollector { _ => mem_usage, }; - // Technically if it's like, some insanely high value (https://unix.stackexchange.com/a/421182) - // then it's "unlimited" but we can just make it so we take the max of the main and this anyway. + // Technically if it's some insanely high value (https://unix.stackexchange.com/a/421182), + // then it's "unlimited", but we can just make it so we take the max of the main and this anyway. let mem_limit_raw = read_u64("/sys/fs/cgroup/memory/memory.limit_in_bytes"); let mem_limit = mem_limit_raw.map(CgroupMemLimit::Bytes); @@ -78,12 +91,15 @@ impl CgroupMemCollector { // --- Swap --- // Since swap is dependent on the normal memory usage, we couple it together. - if let Some(memsw) = read_u64("/sys/fs/cgroup/memory/memory.memsw.usage_in_bytes") { - let used_bytes = memsw.saturating_sub(mem_usage); + if let Some(memsw_usage) = read_u64("/sys/fs/cgroup/memory/memory.memsw.usage_in_bytes") + { + let used_bytes = memsw_usage.saturating_sub(mem_usage); // Same idea for here. let swap_limit = read_u64("/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes") - .map(|memsw_limit| memsw_limit.saturating_sub(mem_limit_raw.unwrap_or(0))) + .map(|memsw_limit| { + cgroup_v1_swap_limit(memsw_limit, mem_limit_raw, total_memory, total_swap) + }) .map(CgroupMemLimit::Bytes); self.swap = Some(CgroupMemData {