#[must_use]
pub fn physical_memory() -> Option<u64> {
platform::physical_memory()
}
pub const DEFAULT_FRACTION: f64 = 0.8;
#[must_use]
pub fn default_memory_limit() -> Option<u64> {
const MIB: u64 = 1 << 20;
#[expect(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "a byte count of a real machine is far under the 2^53 a double holds exactly, \
and the product of a positive count and a positive fraction under one is \
positive and smaller than the count"
)]
let share = |bytes: u64| (bytes as f64 * DEFAULT_FRACTION) as u64 / MIB * MIB;
physical_memory().map(share).filter(|&bytes| bytes > 0)
}
#[cfg(target_os = "linux")]
mod platform {
pub(super) fn physical_memory() -> Option<u64> {
let total = meminfo(&std::fs::read_to_string("/proc/meminfo").ok()?)?;
Some(cgroup().map_or(total, |limit| total.min(limit)))
}
fn meminfo(text: &str) -> Option<u64> {
let line = text.lines().find(|line| line.starts_with("MemTotal:"))?;
let mut fields = line.split_whitespace().skip(1);
let value: u64 = fields.next()?.parse().ok()?;
match fields.next() {
Some("kB") => value.checked_mul(1024),
None => Some(value),
Some(_) => None,
}
}
fn cgroup() -> Option<u64> {
let own = std::fs::read_to_string("/proc/self/cgroup").unwrap_or_default();
let v2 = smallest("/sys/fs/cgroup", v2_path(&own), "memory.max");
let v1 = smallest("/sys/fs/cgroup/memory", v1_path(&own), "memory.limit_in_bytes");
match (v2, v1) {
(Some(two), Some(one)) => Some(two.min(one)),
(two, one) => two.or(one),
}
}
fn v2_path(own: &str) -> &str {
own.lines().find_map(|line| line.strip_prefix("0::")).unwrap_or("")
}
fn v1_path(own: &str) -> &str {
own.lines()
.find_map(|line| {
let mut fields = line.splitn(3, ':');
let controllers = fields.nth(1)?;
let path = fields.next()?;
controllers.split(',').any(|name| name == "memory").then_some(path)
})
.unwrap_or("")
}
fn smallest(mount: &str, path: &str, file: &str) -> Option<u64> {
let mut parts: Vec<&str> = path.split('/').filter(|part| !part.is_empty()).collect();
let mut smallest: Option<u64> = None;
loop {
let mut at = std::path::PathBuf::from(mount);
at.extend(&parts);
at.push(file);
if let Some(bytes) = limit(&at) {
smallest = Some(smallest.map_or(bytes, |had| had.min(bytes)));
}
if parts.pop().is_none() {
return smallest;
}
}
}
fn limit(at: &std::path::Path) -> Option<u64> {
let text = std::fs::read_to_string(at).ok()?;
let bytes: u64 = text.trim().parse().ok()?;
(bytes < 1 << 60).then_some(bytes)
}
#[cfg(test)]
mod tests {
use super::{limit, meminfo, smallest, v1_path, v2_path};
use crate::scratch::TempDir;
#[test]
fn the_version_two_group_is_the_one_line_that_names_no_controller() {
let own = "0::/system.slice/run-r867.scope\n";
assert_eq!(v2_path(own), "/system.slice/run-r867.scope");
assert_eq!(v2_path("11:memory:/docker/abc\n"), "");
assert_eq!(v2_path(""), "");
}
#[test]
fn the_version_one_group_is_the_line_whose_controllers_include_memory() {
let own = "12:pids:/user.slice\n11:memory:/docker/abc\n0::/\n";
assert_eq!(v1_path(own), "/docker/abc");
}
#[test]
fn a_controller_named_memory_is_not_one_whose_name_merely_contains_it() {
assert_eq!(v1_path("9:hugetlb,memory:/here\n"), "/here");
assert_eq!(v1_path("9:memory_pressure:/elsewhere\n"), "");
}
#[test]
fn a_group_with_no_limit_says_nothing_rather_than_a_number() {
let dir = TempDir::new("cgroup-none");
let at = dir.join("memory.max");
std::fs::write(&at, "max\n").expect("a file to read back");
assert_eq!(limit(&at), None);
std::fs::write(&at, "9223372036854771712\n").expect("a file to read back");
assert_eq!(limit(&at), None);
std::fs::write(&at, "12884901888\n").expect("a file to read back");
assert_eq!(limit(&at), Some(12_884_901_888));
assert_eq!(limit(&dir.join("no-such-file")), None);
}
#[test]
fn the_walk_takes_the_smallest_limit_on_the_way_up() {
let mount = TempDir::new("cgroup-walk");
let deep = mount.join("system.slice/run.scope");
std::fs::create_dir_all(&deep).expect("a temporary hierarchy");
std::fs::write(deep.join("memory.max"), "12884901888").expect("a file");
std::fs::write(mount.join("system.slice/memory.max"), "8589934592").expect("a file");
std::fs::write(mount.join("memory.max"), "max").expect("a file");
let root = mount.path().to_str().expect("a path this test wrote");
let walked = smallest(root, "/system.slice/run.scope", "memory.max");
assert_eq!(walked, Some(8_589_934_592));
assert_eq!(smallest(root, "/docker/abc", "memory.max"), None);
std::fs::write(mount.join("memory.max"), "2147483648").expect("a file");
assert_eq!(smallest(root, "/docker/abc", "memory.max"), Some(2_147_483_648));
}
#[test]
fn memtotal_is_read_in_kibibytes() {
let text = "MemTotal: 32773868 kB\nMemFree: 4113928 kB\n";
assert_eq!(meminfo(text), Some(32_773_868 * 1024));
}
#[test]
fn a_file_without_memtotal_is_no_answer_rather_than_a_wrong_one() {
assert_eq!(meminfo("MemFree: 4113928 kB\n"), None);
assert_eq!(meminfo(""), None);
}
#[test]
fn a_unit_this_does_not_know_is_refused() {
assert_eq!(meminfo("MemTotal: 32 GB\n"), None);
}
#[test]
fn the_machine_this_runs_on_says_something_sensible() {
let bytes = super::physical_memory().expect("a Linux box knows how much memory it has");
assert!(bytes >= 1 << 28, "no machine builds this in under 256 MiB, got {bytes}");
}
}
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[allow(unsafe_code, reason = "the only way to ask this platform is a C call into sysctl")]
mod platform {
use std::ffi::{c_char, c_int, c_void};
unsafe extern "C" {
fn sysctlbyname(
name: *const c_char,
oldp: *mut c_void,
oldlenp: *mut usize,
newp: *mut c_void,
newlen: usize,
) -> c_int;
}
pub(super) fn physical_memory() -> Option<u64> {
let name = c"hw.memsize";
let mut bytes: u64 = 0;
let mut size = size_of::<u64>();
let rc = unsafe {
sysctlbyname(
name.as_ptr(),
std::ptr::from_mut(&mut bytes).cast::<c_void>(),
&raw mut size,
std::ptr::null_mut(),
0,
)
};
if rc != 0 || size != size_of::<u64>() || bytes == 0 {
return None;
}
Some(bytes)
}
#[cfg(test)]
mod tests {
#[test]
fn the_machine_this_runs_on_says_something_sensible() {
let bytes = super::physical_memory().expect("a Mac knows how much memory it has");
assert!(bytes >= 1 << 28, "no machine builds this in under 256 MiB, got {bytes}");
}
}
}
#[cfg(target_os = "windows")]
#[allow(unsafe_code, reason = "the only way to ask this platform is a call into kernel32")]
mod platform {
#[repr(C)]
struct MemoryStatusEx {
length: u32,
memory_load: u32,
total_physical: u64,
available_physical: u64,
total_page_file: u64,
available_page_file: u64,
total_virtual: u64,
available_virtual: u64,
available_extended_virtual: u64,
}
unsafe extern "system" {
fn GlobalMemoryStatusEx(buffer: *mut MemoryStatusEx) -> i32;
}
pub(super) fn physical_memory() -> Option<u64> {
let mut status = MemoryStatusEx {
length: u32::try_from(size_of::<MemoryStatusEx>()).ok()?,
memory_load: 0,
total_physical: 0,
available_physical: 0,
total_page_file: 0,
available_page_file: 0,
total_virtual: 0,
available_virtual: 0,
available_extended_virtual: 0,
};
let ok = unsafe { GlobalMemoryStatusEx(&raw mut status) };
if ok == 0 || status.total_physical == 0 {
return None;
}
Some(status.total_physical)
}
#[cfg(test)]
mod tests {
#[test]
fn the_machine_this_runs_on_says_something_sensible() {
let bytes = super::physical_memory().expect("Windows knows how much memory it has");
assert!(bytes >= 1 << 28, "no machine builds this in under 256 MiB, got {bytes}");
}
}
}
#[cfg(not(any(
target_os = "linux",
target_os = "macos",
target_os = "ios",
target_os = "windows"
)))]
mod platform {
pub(super) fn physical_memory() -> Option<u64> {
None
}
}
#[cfg(test)]
mod tests {
use super::{DEFAULT_FRACTION, default_memory_limit, physical_memory};
#[test]
fn the_default_is_four_fifths_of_what_the_machine_has() {
let Some(bytes) = physical_memory() else {
eprintln!("skipping, this platform does not say how much memory it has");
return;
};
let limit = default_memory_limit().expect("a machine that says its size has a default");
assert!(limit < bytes, "the default leaves room for everything the budget does not count");
#[expect(clippy::cast_precision_loss, reason = "a byte count is far under 2^53")]
let want = (bytes as f64 * DEFAULT_FRACTION) as u64;
assert!(want - limit < (1 << 20), "{limit} is a mebibyte or less under {want}");
}
#[test]
fn the_default_is_a_whole_number_of_mebibytes_so_that_it_prints_as_one() {
let Some(limit) = default_memory_limit() else {
eprintln!("skipping, this platform does not say how much memory it has");
return;
};
assert_eq!(limit % (1 << 20), 0, "{limit} would print as a byte count");
}
#[test]
fn asking_twice_gives_the_same_answer() {
assert_eq!(physical_memory(), physical_memory());
}
}