use std::io;
#[cfg(target_os = "linux")]
use std::fs;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ProcMemoryStats {
pub rss_bytes: u64,
pub vms_bytes: u64,
}
pub fn read_proc_memory() -> io::Result<ProcMemoryStats> {
#[cfg(target_os = "linux")]
{
let content = fs::read_to_string("/proc/self/status")?;
parse_proc_status(&content)
}
#[cfg(target_os = "macos")]
{
read_macos_memory()
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
{
Err(io::Error::new(
io::ErrorKind::Unsupported,
"Memory stats reading is not supported on this platform",
))
}
}
#[cfg(target_os = "macos")]
fn read_macos_memory() -> io::Result<ProcMemoryStats> {
use std::mem;
#[allow(deprecated)]
unsafe {
let mut info: libc::mach_task_basic_info = mem::zeroed();
let mut info_count: u32 =
(mem::size_of::<libc::mach_task_basic_info>() / mem::size_of::<u32>()) as u32;
#[allow(non_upper_case_globals)]
const TASK_BASIC_INFO: u32 = 4;
let status = libc::task_info(
libc::mach_task_self(),
TASK_BASIC_INFO,
&mut info as *mut _ as *mut i32,
&mut info_count,
);
if status != libc::KERN_SUCCESS {
return Err(io::Error::other(format!(
"Failed to call task_info: status {}",
status
)));
}
Ok(ProcMemoryStats {
rss_bytes: info.resident_size,
vms_bytes: info.virtual_size,
})
}
}
#[cfg(target_os = "linux")]
fn parse_proc_status(content: &str) -> io::Result<ProcMemoryStats> {
let mut stats = ProcMemoryStats::default();
for line in content.lines() {
if let Some(value) = line.strip_prefix("VmRSS:") {
stats.rss_bytes = parse_memory_kb(value)?;
} else if let Some(value) = line.strip_prefix("VmSize:") {
stats.vms_bytes = parse_memory_kb(value)?;
}
}
Ok(stats)
}
#[cfg(target_os = "linux")]
fn parse_memory_kb(value: &str) -> io::Result<u64> {
let trimmed = value.trim().trim_end_matches("kB").trim();
trimmed.parse::<u64>().map(|kb| kb * 1024).map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("Failed to parse memory value '{}': {}", trimmed, e),
)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(target_os = "linux")]
#[test]
fn test_parse_memory_kb() {
assert_eq!(parse_memory_kb(" 12345 kB").unwrap(), 12345 * 1024);
assert_eq!(parse_memory_kb("100 kB").unwrap(), 100 * 1024);
assert_eq!(parse_memory_kb("0 kB").unwrap(), 0);
}
#[cfg(target_os = "linux")]
#[test]
fn test_parse_memory_kb_invalid() {
assert!(parse_memory_kb("invalid").is_err());
assert!(parse_memory_kb("").is_err());
}
#[cfg(target_os = "linux")]
#[test]
fn test_parse_proc_status() {
let content = r#"
Name: lazydns
VmSize: 524288 kB
VmRSS: 102400 kB
VmData: 51200 kB
"#;
let stats = parse_proc_status(content).unwrap();
assert_eq!(stats.vms_bytes, 524288 * 1024);
assert_eq!(stats.rss_bytes, 102400 * 1024);
}
#[cfg(target_os = "linux")]
#[test]
fn test_parse_proc_status_partial() {
let content = "VmRSS: 10240 kB\n";
let stats = parse_proc_status(content).unwrap();
assert_eq!(stats.rss_bytes, 10240 * 1024);
assert_eq!(stats.vms_bytes, 0); }
#[cfg(target_os = "linux")]
#[test]
fn test_parse_proc_status_empty() {
let stats = parse_proc_status("").unwrap();
assert_eq!(stats.rss_bytes, 0);
assert_eq!(stats.vms_bytes, 0);
}
#[cfg(target_os = "linux")]
#[test]
fn test_read_proc_memory_integration() {
let result = read_proc_memory();
assert!(
result.is_ok(),
"Failed to read /proc/self/status: {:?}",
result
);
let stats = result.unwrap();
assert!(stats.rss_bytes > 0, "RSS should be > 0");
assert!(stats.vms_bytes > 0, "VMS should be > 0");
assert!(stats.rss_bytes <= stats.vms_bytes, "RSS should be <= VMS");
}
#[cfg(target_os = "macos")]
#[test]
fn test_read_macos_memory_integration() {
let result = read_proc_memory();
assert!(
result.is_ok(),
"Failed to read macOS memory stats: {:?}",
result
);
let stats = result.unwrap();
assert!(stats.rss_bytes > 0, "RSS should be > 0");
assert!(stats.vms_bytes > 0, "VMS should be > 0");
assert!(stats.rss_bytes <= stats.vms_bytes, "RSS should be <= VMS");
}
}