use sysinfo::System;
pub(crate) struct SwappableFootprint {
#[cfg(any(target_os = "linux", target_os = "android"))]
status: Vec<u8>,
#[cfg(any(target_os = "macos", target_os = "ios"))]
pid: libc::c_int,
#[cfg(not(any(
target_os = "linux",
target_os = "android",
target_os = "macos",
target_os = "ios"
)))]
pid: sysinfo::Pid,
}
#[cfg(any(target_os = "linux", target_os = "android"))]
impl SwappableFootprint {
const STATUS_CAPACITY: usize = 4096;
pub(crate) fn new() -> Self {
Self {
status: Vec::with_capacity(Self::STATUS_CAPACITY),
}
}
pub(crate) fn read_bytes(&mut self, _system: &mut System) -> u64 {
use std::io::Read;
self.status.clear();
std::fs::File::open("/proc/self/status")
.and_then(|mut file| file.read_to_end(&mut self.status))
.expect("cannot read /proc/self/status");
let status = std::str::from_utf8(&self.status).expect("/proc/self/status is not UTF-8");
ProcStatus::new(status).swappable_bytes()
}
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
impl SwappableFootprint {
pub(crate) fn new() -> Self {
Self {
pid: unsafe { libc::getpid() },
}
}
pub(crate) fn read_bytes(&mut self, _system: &mut System) -> u64 {
let mut info = std::mem::MaybeUninit::<libc::rusage_info_v0>::zeroed();
let status = unsafe {
libc::proc_pid_rusage(self.pid, libc::RUSAGE_INFO_V0, info.as_mut_ptr().cast())
};
assert_eq!(
status,
0,
"proc_pid_rusage failed: {}",
std::io::Error::last_os_error()
);
unsafe { info.assume_init() }.ri_phys_footprint
}
}
#[cfg(not(any(
target_os = "linux",
target_os = "android",
target_os = "macos",
target_os = "ios"
)))]
impl SwappableFootprint {
pub(crate) fn new() -> Self {
Self {
pid: sysinfo::get_current_pid().expect("sysinfo cannot resolve current pid"),
}
}
pub(crate) fn read_bytes(&mut self, system: &mut System) -> u64 {
let refresh = sysinfo::ProcessRefreshKind::nothing().with_memory();
let refreshed = system.refresh_processes_specifics(
sysinfo::ProcessesToUpdate::Some(&[self.pid]),
true,
refresh,
);
assert_eq!(refreshed, 1, "sysinfo did not refresh the current process");
let process = system
.process(self.pid)
.expect("sysinfo lost the current process");
if cfg!(windows) {
process.virtual_memory()
} else {
process.memory()
}
}
}
#[cfg(any(target_os = "linux", target_os = "android", test))]
pub(crate) struct ProcStatus<'a> {
record: &'a str,
}
#[cfg(any(target_os = "linux", target_os = "android", test))]
impl<'a> ProcStatus<'a> {
const ANONYMOUS: &'static str = "RssAnon:";
const SHARED_MEMORY: &'static str = "RssShmem:";
const SWAPPED: &'static str = "VmSwap:";
pub(crate) fn new(record: &'a str) -> Self {
Self { record }
}
pub(crate) fn swappable_bytes(&self) -> u64 {
[Self::ANONYMOUS, Self::SHARED_MEMORY, Self::SWAPPED]
.into_iter()
.map(|field| self.kibibytes(field))
.try_fold(0_u64, u64::checked_add)
.and_then(|kib| kib.checked_mul(1024))
.expect("process memory overflows u64")
}
fn kibibytes(&self, field: &str) -> u64 {
let value = self
.record
.lines()
.find_map(|line| line.strip_prefix(field))
.unwrap_or_else(|| panic!("process status lacks {field}"));
let mut parts = value.split_whitespace();
let (Some(amount), Some("kB"), None) = (parts.next(), parts.next(), parts.next()) else {
panic!("malformed process status field {field}{value}");
};
amount
.parse()
.unwrap_or_else(|error| panic!("malformed process status field {field}: {error}"))
}
}