use std::{cell::RefCell, rc::Rc, time::Duration};
pub trait DeviceInfo {
fn total_memory_bytes(&self) -> Option<u64>;
fn resident_memory_bytes(&self) -> Option<u64> {
None
}
fn available_memory_bytes(&self) -> Option<u64> {
None
}
fn process_cpu_time(&self) -> Option<Duration> {
None
}
fn release_free_memory(&self) -> bool {
false
}
}
pub type DeviceInfoRef = Rc<dyn DeviceInfo>;
struct DefaultDeviceInfo;
impl DeviceInfo for DefaultDeviceInfo {
fn total_memory_bytes(&self) -> Option<u64> {
#[cfg(any(target_os = "linux", target_os = "android"))]
{
let text = std::fs::read_to_string("/proc/meminfo").ok()?;
for line in text.lines() {
if let Some(rest) = line.strip_prefix("MemTotal:") {
let kb: u64 = rest.split_whitespace().next()?.parse().ok()?;
return Some(kb * 1024);
}
}
None
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
{
None
}
}
fn resident_memory_bytes(&self) -> Option<u64> {
#[cfg(any(target_os = "linux", target_os = "android"))]
{
let text = std::fs::read_to_string("/proc/self/statm").ok()?;
resident_bytes_from_statm(&text, page_size_bytes())
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
{
None
}
}
}
#[cfg(any(target_os = "linux", target_os = "android", test))]
fn resident_bytes_from_statm(text: &str, page_size: u64) -> Option<u64> {
text.split_whitespace()
.nth(1)?
.parse::<u64>()
.ok()?
.checked_mul(page_size)
}
#[cfg(any(target_os = "linux", target_os = "android", test))]
const fn page_size_bytes() -> u64 {
4096
}
thread_local! {
static PLATFORM_DEVICE_INFO: RefCell<Option<DeviceInfoRef>> = const { RefCell::new(None) };
}
pub fn set_platform_device_info(info: DeviceInfoRef) {
PLATFORM_DEVICE_INFO.with(|cell| *cell.borrow_mut() = Some(info));
}
pub fn clear_platform_device_info() {
PLATFORM_DEVICE_INFO.with(|cell| *cell.borrow_mut() = None);
}
pub fn device_info() -> DeviceInfoRef {
PLATFORM_DEVICE_INFO
.with(|cell| cell.borrow().clone())
.unwrap_or_else(|| Rc::new(DefaultDeviceInfo))
}
pub fn release_free_memory() -> bool {
device_info().release_free_memory()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn registered_device_info_takes_precedence() {
clear_platform_device_info();
struct Fake;
impl DeviceInfo for Fake {
fn total_memory_bytes(&self) -> Option<u64> {
Some(8 * 1024 * 1024 * 1024)
}
}
set_platform_device_info(Rc::new(Fake));
assert_eq!(device_info().total_memory_bytes(), Some(8 << 30));
clear_platform_device_info();
}
#[test]
fn a_platform_that_will_not_say_reports_nothing_rather_than_zero() {
clear_platform_device_info();
struct Silent;
impl DeviceInfo for Silent {
fn total_memory_bytes(&self) -> Option<u64> {
None
}
}
set_platform_device_info(Rc::new(Silent));
let info = device_info();
assert_eq!(info.total_memory_bytes(), None);
assert_eq!(info.resident_memory_bytes(), None);
assert_eq!(info.available_memory_bytes(), None);
assert_eq!(info.process_cpu_time(), None);
assert!(!info.release_free_memory());
assert!(!release_free_memory());
clear_platform_device_info();
}
#[test]
fn a_platform_that_can_answer_is_asked_through_the_free_function() {
clear_platform_device_info();
struct Rich;
impl DeviceInfo for Rich {
fn total_memory_bytes(&self) -> Option<u64> {
Some(4 << 30)
}
fn resident_memory_bytes(&self) -> Option<u64> {
Some(256 << 20)
}
fn available_memory_bytes(&self) -> Option<u64> {
Some(512 << 20)
}
fn process_cpu_time(&self) -> Option<Duration> {
Some(Duration::from_millis(1_250))
}
fn release_free_memory(&self) -> bool {
true
}
}
set_platform_device_info(Rc::new(Rich));
let info = device_info();
assert_eq!(info.resident_memory_bytes(), Some(256 << 20));
assert_eq!(info.available_memory_bytes(), Some(512 << 20));
assert_eq!(info.process_cpu_time(), Some(Duration::from_millis(1_250)));
assert!(release_free_memory());
clear_platform_device_info();
}
#[test]
fn the_resident_set_is_the_second_field_of_statm_in_pages() {
let statm = "123456 2048 512 64 0 1024 0\n";
assert_eq!(resident_bytes_from_statm(statm, 4096), Some(2048 * 4096));
assert_eq!(resident_bytes_from_statm(statm, 16384), Some(2048 * 16384));
}
#[test]
fn an_unreadable_statm_line_is_unknown_rather_than_no_memory() {
for broken in ["", "123456", "123456 notanumber 512", " "] {
assert_eq!(
resident_bytes_from_statm(broken, page_size_bytes()),
None,
"{broken:?} should read as unknown"
);
}
}
}