diff --git a/src/collection/memory/sysinfo.rs b/src/collection/memory/sysinfo.rs index 66d8de8b..e1f5898b 100644 --- a/src/collection/memory/sysinfo.rs +++ b/src/collection/memory/sysinfo.rs @@ -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 { NonZeroU64::new(total).map(|total_bytes| MemData { @@ -12,6 +15,20 @@ fn get_usage(used: u64, total: u64) -> Option { }) } +/// 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 { 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 { 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 { 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 . + #[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); + } +}