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.
This commit is contained in:
Clement Tsang
2026-08-13 05:22:40 -04:00
committed by GitHub
parent ded4585cf0
commit a10eadd2d5
3 changed files with 31 additions and 10 deletions
+2 -1
View File
@@ -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
+5 -1
View File
@@ -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();
+24 -8
View File
@@ -44,19 +44,32 @@ pub(crate) struct CgroupMemCollector {
pub swap: Option<CgroupMemData>,
}
/// 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<u64>, 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 {