bug: fix cgroup v1 memory total using sentinel max as total

This commit is contained in:
ClementTsang
2026-06-20 04:37:30 -04:00
parent e13911d749
commit a0616b5300
+55 -14
View File
@@ -4,6 +4,9 @@ use std::num::NonZeroU64;
use crate::collection::{DataCollector, memory::MemData};
#[cfg(target_os = "linux")]
use crate::collection::linux::cgroups::CgroupMemLimit;
#[inline]
fn get_usage(used: u64, total: u64) -> Option<MemData> {
NonZeroU64::new(total).map(|total_bytes| MemData {
@@ -12,6 +15,20 @@ fn get_usage(used: u64, total: u64) -> Option<MemData> {
})
}
/// Resolves the total memory to report given an optional cgroup limit and the physical total.
///
/// cgroup v1 reports an "unlimited" limit as a very large value, which causes problems if taken
/// literally. This function caps it to the minimum of the cgroup total or the actual total to
/// avoid this problem.
#[cfg(target_os = "linux")]
#[inline]
fn resolve_cgroup_total(limit: Option<&CgroupMemLimit>, base_total: u64) -> u64 {
match limit {
Some(CgroupMemLimit::Bytes(bytes)) => (*bytes).min(base_total),
Some(CgroupMemLimit::Max) | None => base_total,
}
}
/// Returns memory (RAM) usage using sysinfo.
///
/// On Linux, this will take cgroup usage/limits into account.
@@ -20,19 +37,13 @@ pub(crate) fn get_ram_usage(collector: &DataCollector) -> Option<MemData> {
cfg_select! {
target_os = "linux" => {
use crate::collection::linux::cgroups;
let base_used = sys.used_memory();
let base_total = sys.total_memory();
let (used, total) = match &collector.cgroup_memory_data.ram {
Some(cgroup_data) => {
let used = cgroup_data.used_bytes;
let total = match cgroup_data.limit {
Some(cgroups::CgroupMemLimit::Bytes(bytes)) => bytes,
Some(cgroups::CgroupMemLimit::Max) => base_total,
None => base_total,
};
let total = resolve_cgroup_total(cgroup_data.limit.as_ref(), base_total);
(used, total)
}
@@ -56,19 +67,13 @@ pub(crate) fn get_swap_usage(collector: &DataCollector) -> Option<MemData> {
cfg_select! {
target_os = "linux" => {
use crate::collection::linux::cgroups;
let base_used = sys.used_swap();
let base_total = sys.total_swap();
let (used, total) = match &collector.cgroup_memory_data.swap {
Some(cgroup_data) => {
let used = cgroup_data.used_bytes;
let total = match cgroup_data.limit {
Some(cgroups::CgroupMemLimit::Bytes(bytes)) => bytes,
Some(cgroups::CgroupMemLimit::Max) => base_total,
None => base_total,
};
let total = resolve_cgroup_total(cgroup_data.limit.as_ref(), base_total);
(used, total)
}
@@ -101,3 +106,39 @@ pub(crate) fn get_cache_usage(sys: &sysinfo::System) -> Option<MemData> {
get_usage(mem_used, mem_total)
}
#[cfg(test)]
#[cfg(target_os = "linux")]
mod linux_tests {
use super::*;
const BASE_TOTAL: u64 = 16 * 1024 * 1024 * 1024; // 16 GiB
/// Regression test for <https://github.com/ClementTsang/bottom/issues/2092>.
#[test]
fn cap_cgroup_v1_limits() {
let sentinel = u64::MAX;
assert_eq!(
resolve_cgroup_total(Some(&CgroupMemLimit::Bytes(sentinel)), BASE_TOTAL),
BASE_TOTAL
);
}
#[test]
fn legit_cgroup_limit_works() {
let limit = 4 * 1024 * 1024 * 1024; // 4 GiB
assert_eq!(
resolve_cgroup_total(Some(&CgroupMemLimit::Bytes(limit)), BASE_TOTAL),
limit
);
}
#[test]
fn max_and_missing_limit_use_physical_total() {
assert_eq!(
resolve_cgroup_total(Some(&CgroupMemLimit::Max), BASE_TOTAL),
BASE_TOTAL
);
assert_eq!(resolve_cgroup_total(None, BASE_TOTAL), BASE_TOTAL);
}
}