pub fn detect_total_ram_mb() -> Option<u64> {
#[cfg(target_os = "macos")]
{
detect_macos_ram_mb()
}
#[cfg(target_os = "linux")]
{
detect_linux_ram_mb()
}
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
{
None
}
}
#[cfg(target_os = "macos")]
fn detect_macos_ram_mb() -> Option<u64> {
use std::process::Command;
let output = Command::new("sysctl")
.args(["-n", "hw.memsize"])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let text = String::from_utf8(output.stdout).ok()?;
let bytes: u64 = text.trim().parse().ok()?;
Some(bytes / (1024 * 1024))
}
#[cfg(target_os = "linux")]
const PROC_SELF_CGROUP_PATH: &str = "/proc/self/cgroup";
#[cfg(target_os = "linux")]
const CGROUP_V2_MOUNT_ROOT: &str = "/sys/fs/cgroup";
#[cfg(target_os = "linux")]
const CGROUP_V1_MEMORY_MOUNT_ROOT: &str = "/sys/fs/cgroup/memory";
#[cfg(target_os = "linux")]
fn detect_linux_ram_mb() -> Option<u64> {
let host_mb = detect_linux_host_ram_mb()?;
match detect_cgroup_memory_limit_mb_with_roots(
host_mb,
PROC_SELF_CGROUP_PATH,
CGROUP_V2_MOUNT_ROOT,
CGROUP_V1_MEMORY_MOUNT_ROOT,
) {
Some(cgroup_mb) if cgroup_mb < host_mb => {
tracing::info!(
"memory_policy: cgroup memory ceiling ({cgroup_mb} MB) is below host RAM \
({host_mb} MB) — using the cgroup ceiling for the 25%-of-RAM auto-tune so \
TRUSTY_MEMORY_LIMIT_MB stays under what systemd/Docker/Kubernetes actually \
enforces (issue #3657: on a host with far more physical RAM than the cgroup \
allows this service, auto-tuning off host RAM alone can compute a soft ceiling \
ABOVE the cgroup's hard limit, so the memory-pressure enforcement ticker never \
crosses its high-water mark before the kernel's cgroup OOM-killer fires)"
);
Some(cgroup_mb)
}
_ => Some(host_mb),
}
}
#[cfg(target_os = "linux")]
fn detect_linux_host_ram_mb() -> Option<u64> {
let text = std::fs::read_to_string("/proc/meminfo").ok()?;
for line in text.lines() {
if let Some(rest) = line.strip_prefix("MemTotal:") {
let mut parts = rest.split_whitespace();
let kb: u64 = parts.next()?.parse().ok()?;
return Some(kb / 1024);
}
}
None
}
#[cfg(any(test, target_os = "linux"))]
fn detect_cgroup_memory_limit_mb_with_roots(
host_ram_mb: u64,
self_cgroup_path: &str,
v2_mount_root: &str,
v1_mount_root: &str,
) -> Option<u64> {
let self_cgroup_text = std::fs::read_to_string(self_cgroup_path).ok();
let v2_max_path = match self_cgroup_text
.as_deref()
.and_then(parse_cgroup_v2_self_path)
{
Some(nested) => format!("{}/memory.max", join_cgroup_mount(v2_mount_root, &nested)),
None => format!("{}/memory.max", v2_mount_root.trim_end_matches('/')),
};
if let Ok(text) = std::fs::read_to_string(&v2_max_path)
&& let Some(mb) = parse_cgroup_v2_max(&text)
{
return Some(mb);
}
let v1_limit_path = match self_cgroup_text
.as_deref()
.and_then(parse_cgroup_v1_self_path)
{
Some(nested) => format!(
"{}/memory.limit_in_bytes",
join_cgroup_mount(v1_mount_root, &nested)
),
None => format!(
"{}/memory.limit_in_bytes",
v1_mount_root.trim_end_matches('/')
),
};
if let Ok(text) = std::fs::read_to_string(&v1_limit_path)
&& let Some(mb) = parse_cgroup_v1_limit(&text, host_ram_mb)
{
return Some(mb);
}
None
}
#[cfg(any(test, target_os = "linux"))]
fn join_cgroup_mount(mount_root: &str, cgroup_path: &str) -> String {
let root = mount_root.trim_end_matches('/');
let path = cgroup_path.trim_start_matches('/');
if path.is_empty() {
root.to_string()
} else {
format!("{root}/{path}")
}
}
#[cfg(any(test, target_os = "linux"))]
fn parse_cgroup_v2_self_path(text: &str) -> Option<String> {
for line in text.lines() {
if let Some(rest) = line.strip_prefix("0::") {
let path = rest.trim();
if !path.is_empty() {
return Some(path.to_string());
}
}
}
None
}
#[cfg(any(test, target_os = "linux"))]
fn parse_cgroup_v1_self_path(text: &str) -> Option<String> {
for line in text.lines() {
let mut parts = line.splitn(3, ':');
let (Some(_hierarchy_id), Some(controllers), Some(path)) =
(parts.next(), parts.next(), parts.next())
else {
continue;
};
if controllers.split(',').any(|c| c == "memory") {
let path = path.trim();
if !path.is_empty() {
return Some(path.to_string());
}
}
}
None
}
#[cfg(any(test, target_os = "linux"))]
fn parse_cgroup_v2_max(text: &str) -> Option<u64> {
let trimmed = text.trim();
if trimmed == "max" {
return None;
}
let bytes: u64 = trimmed.parse().ok()?;
Some(bytes / (1024 * 1024))
}
#[cfg(any(test, target_os = "linux"))]
fn parse_cgroup_v1_limit(text: &str, host_ram_mb: u64) -> Option<u64> {
let bytes: u64 = text.trim().parse().ok()?;
let mb = bytes / (1024 * 1024);
if mb == 0 || mb >= host_ram_mb {
return None;
}
Some(mb)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_cgroup_v2_max_bytes() {
let text = "30064771072\n";
assert_eq!(parse_cgroup_v2_max(text), Some(28 * 1024));
}
#[test]
fn test_parse_cgroup_v2_unlimited() {
assert_eq!(parse_cgroup_v2_max("max\n"), None);
assert_eq!(parse_cgroup_v2_max("max"), None);
}
#[test]
fn test_parse_cgroup_v1_limit_bytes() {
let text = "30064771072\n";
assert_eq!(parse_cgroup_v1_limit(text, 128 * 1024), Some(28 * 1024));
}
#[test]
fn test_parse_cgroup_v1_unlimited_sentinel() {
let text = "9223372036854771712\n";
assert_eq!(parse_cgroup_v1_limit(text, 128 * 1024), None);
}
#[test]
fn test_parse_cgroup_v1_at_host_ram_is_unlimited() {
let host_mb = 16 * 1024;
let bytes = host_mb * 1024 * 1024;
assert_eq!(parse_cgroup_v1_limit(&bytes.to_string(), host_mb), None);
}
#[test]
fn test_parse_cgroup_v2_self_path_root() {
assert_eq!(parse_cgroup_v2_self_path("0::/\n"), Some("/".to_string()));
}
#[test]
fn test_parse_cgroup_v2_self_path_nested_systemd_scope() {
let text = "0::/system.slice/trusty-search.service\n";
assert_eq!(
parse_cgroup_v2_self_path(text),
Some("/system.slice/trusty-search.service".to_string())
);
}
#[test]
fn test_parse_cgroup_v2_self_path_absent_on_v1_only_host() {
let text = "11:memory:/system.slice/trusty-search.service\n\
10:cpu,cpuacct:/system.slice/trusty-search.service\n";
assert_eq!(parse_cgroup_v2_self_path(text), None);
}
#[test]
fn test_parse_cgroup_v1_self_path_finds_memory_line_among_others() {
let text = "12:pids:/system.slice/trusty-search.service\n\
11:memory:/system.slice/trusty-search.service\n\
10:devices:/system.slice/trusty-search.service\n";
assert_eq!(
parse_cgroup_v1_self_path(text),
Some("/system.slice/trusty-search.service".to_string())
);
}
#[test]
fn test_parse_cgroup_v1_self_path_combined_controller_list() {
let text = "4:memory,ambient_capabilities:/docker/abc123\n";
assert_eq!(
parse_cgroup_v1_self_path(text),
Some("/docker/abc123".to_string())
);
}
#[test]
fn test_parse_cgroup_v1_self_path_absent_on_v2_only_host() {
assert_eq!(parse_cgroup_v1_self_path("0::/\n"), None);
}
#[test]
fn test_parse_cgroup_v1_self_path_skips_malformed_line_then_finds_memory() {
let text = "malformed-line-no-colons\n11:memory:/system.slice/x\n";
assert_eq!(
parse_cgroup_v1_self_path(text),
Some("/system.slice/x".to_string())
);
}
#[test]
fn test_parse_cgroup_v1_self_path_skips_blank_line_then_finds_memory() {
let text = "\n11:memory:/system.slice/x\n";
assert_eq!(
parse_cgroup_v1_self_path(text),
Some("/system.slice/x".to_string())
);
}
#[test]
fn test_join_cgroup_mount_root_cgroup_no_double_slash() {
assert_eq!(join_cgroup_mount("/sys/fs/cgroup", "/"), "/sys/fs/cgroup");
}
#[test]
fn test_join_cgroup_mount_nested_path() {
assert_eq!(
join_cgroup_mount("/sys/fs/cgroup", "/system.slice/trusty-search.service"),
"/sys/fs/cgroup/system.slice/trusty-search.service"
);
}
#[test]
fn test_join_cgroup_mount_v1_memory_root() {
assert_eq!(
join_cgroup_mount(
"/sys/fs/cgroup/memory",
"/system.slice/trusty-search.service"
),
"/sys/fs/cgroup/memory/system.slice/trusty-search.service"
);
}
#[test]
fn test_v2_nested_cgroup_path_resolves_and_reads() {
let dir = tempfile::tempdir().unwrap();
let proc_self_cgroup = dir.path().join("proc_self_cgroup");
std::fs::write(
&proc_self_cgroup,
"0::/system.slice/trusty-search.service\n",
)
.unwrap();
let v2_root = dir.path().join("sys_fs_cgroup");
std::fs::create_dir_all(&v2_root).unwrap();
std::fs::write(v2_root.join("memory.max"), "max\n").unwrap();
let nested = v2_root.join("system.slice").join("trusty-search.service");
std::fs::create_dir_all(&nested).unwrap();
std::fs::write(nested.join("memory.max"), "30064771072\n").unwrap();
let v1_root = dir.path().join("sys_fs_cgroup_memory_unused");
let mb = detect_cgroup_memory_limit_mb_with_roots(
128 * 1024,
proc_self_cgroup.to_str().unwrap(),
v2_root.to_str().unwrap(),
v1_root.to_str().unwrap(),
);
assert_eq!(
mb,
Some(28 * 1024),
"must resolve+read the NESTED memory.max (28 GiB), not the root's \
unconstrained \"max\""
);
}
#[test]
fn test_v1_nested_cgroup_path_resolves_and_reads() {
let dir = tempfile::tempdir().unwrap();
let proc_self_cgroup = dir.path().join("proc_self_cgroup");
std::fs::write(
&proc_self_cgroup,
"11:memory:/system.slice/trusty-search.service\n\
10:pids:/system.slice/trusty-search.service\n",
)
.unwrap();
let v2_root = dir.path().join("sys_fs_cgroup_unused");
let v1_root = dir.path().join("sys_fs_cgroup_memory");
std::fs::create_dir_all(&v1_root).unwrap();
std::fs::write(
v1_root.join("memory.limit_in_bytes"),
"9223372036854771712\n", )
.unwrap();
let nested = v1_root.join("system.slice").join("trusty-search.service");
std::fs::create_dir_all(&nested).unwrap();
std::fs::write(nested.join("memory.limit_in_bytes"), "30064771072\n").unwrap();
let mb = detect_cgroup_memory_limit_mb_with_roots(
128 * 1024,
proc_self_cgroup.to_str().unwrap(),
v2_root.to_str().unwrap(),
v1_root.to_str().unwrap(),
);
assert_eq!(
mb,
Some(28 * 1024),
"must resolve+read the NESTED memory.limit_in_bytes (28 GiB), not \
the root's unconstrained sentinel"
);
}
#[test]
fn test_falls_back_to_mount_root_when_proc_self_cgroup_missing() {
let dir = tempfile::tempdir().unwrap();
let missing_proc_self_cgroup = dir.path().join("does_not_exist");
let v2_root = dir.path().join("sys_fs_cgroup");
std::fs::create_dir_all(&v2_root).unwrap();
std::fs::write(v2_root.join("memory.max"), "30064771072\n").unwrap();
let v1_root = dir.path().join("sys_fs_cgroup_memory_unused");
let mb = detect_cgroup_memory_limit_mb_with_roots(
128 * 1024,
missing_proc_self_cgroup.to_str().unwrap(),
v2_root.to_str().unwrap(),
v1_root.to_str().unwrap(),
);
assert_eq!(mb, Some(28 * 1024));
}
#[test]
fn test_no_ceiling_when_root_is_unconstrained_and_no_proc_self_cgroup() {
let dir = tempfile::tempdir().unwrap();
let missing_proc_self_cgroup = dir.path().join("does_not_exist");
let v2_root = dir.path().join("sys_fs_cgroup");
std::fs::create_dir_all(&v2_root).unwrap();
std::fs::write(v2_root.join("memory.max"), "max\n").unwrap();
let v1_root = dir.path().join("sys_fs_cgroup_memory_unused");
let mb = detect_cgroup_memory_limit_mb_with_roots(
128 * 1024,
missing_proc_self_cgroup.to_str().unwrap(),
v2_root.to_str().unwrap(),
v1_root.to_str().unwrap(),
);
assert_eq!(mb, None);
}
#[test]
#[cfg(target_os = "linux")]
fn test_detect_cgroup_memory_limit_mb_smoke() {
if let Some(mb) = detect_cgroup_memory_limit_mb_with_roots(
u64::MAX,
PROC_SELF_CGROUP_PATH,
CGROUP_V2_MOUNT_ROOT,
CGROUP_V1_MEMORY_MOUNT_ROOT,
) {
assert!(mb > 0, "a detected cgroup ceiling must be > 0 MB, got {mb}");
}
}
}