#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct MemoryBreakdown {
pub heap_bytes: u64,
pub file_backed_bytes: u64,
pub compressed_bytes: u64,
}
#[must_use]
pub fn self_memory_breakdown() -> Option<MemoryBreakdown> {
#[cfg(target_os = "macos")]
{
macos::read()
}
#[cfg(not(target_os = "macos"))]
{
None
}
}
#[cfg(target_os = "macos")]
mod macos {
use super::MemoryBreakdown;
const TASK_VM_INFO: libc::task_flavor_t = 22;
unsafe extern "C" {
static mach_task_self_: libc::mach_port_t;
}
#[repr(C)]
#[derive(Default)]
struct TaskVmInfoRev1 {
virtual_size: u64,
region_count: i32,
page_size: i32,
resident_size: u64,
resident_size_peak: u64,
device: u64,
device_peak: u64,
internal: u64,
internal_peak: u64,
external: u64,
external_peak: u64,
reusable: u64,
reusable_peak: u64,
purgeable_volatile_pmap: u64,
purgeable_volatile_resident: u64,
purgeable_volatile_virtual: u64,
compressed: u64,
compressed_peak: u64,
compressed_lifetime: u64,
phys_footprint: u64,
}
const VM_INFO_COUNT: libc::mach_msg_type_number_t = (size_of::<TaskVmInfoRev1>()
/ size_of::<libc::natural_t>())
as libc::mach_msg_type_number_t;
pub(super) fn read() -> Option<MemoryBreakdown> {
let mut info = TaskVmInfoRev1::default();
let mut count = VM_INFO_COUNT;
let ret = unsafe {
libc::task_info(
mach_task_self_,
TASK_VM_INFO,
std::ptr::addr_of_mut!(info).cast(),
std::ptr::addr_of_mut!(count),
)
};
if ret != 0 || count < VM_INFO_COUNT {
return None;
}
Some(MemoryBreakdown {
heap_bytes: info.internal,
file_backed_bytes: info.external,
compressed_bytes: info.compressed,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(target_os = "macos")]
#[test]
fn self_breakdown_is_plausible() {
let b = self_memory_breakdown().expect("macOS must report TASK_VM_INFO for its own task");
assert!(
b.heap_bytes > 64 * 1024,
"heap_bytes reads as {} — too small to be a live process",
b.heap_bytes
);
assert!(
b.heap_bytes < 1024_u64.pow(4),
"heap_bytes reads as {} — beyond any plausible test process",
b.heap_bytes
);
assert!(
b.file_backed_bytes > 0,
"a linked binary always has file-backed pages"
);
}
#[cfg(target_os = "macos")]
#[test]
fn self_breakdown_components_fit_inside_the_footprint() {
let b = self_memory_breakdown().expect("breakdown");
let footprint = crate::sys_metrics::physical_footprint_bytes(std::process::id())
.expect("footprint for our own pid");
assert!(
b.heap_bytes <= footprint.saturating_mul(4),
"heap {} is implausibly far above the {footprint}-byte footprint",
b.heap_bytes
);
assert!(
b.compressed_bytes <= footprint.saturating_mul(4),
"compressed {} is implausibly far above the {footprint}-byte footprint",
b.compressed_bytes
);
}
#[cfg(not(target_os = "macos"))]
#[test]
fn breakdown_is_none_off_macos() {
assert!(self_memory_breakdown().is_none());
}
}