use crate::error::Result;
use sysinfo::System;
#[derive(Debug, Clone)]
pub struct MemoryInfo {
pub total_bytes: u64,
pub used_bytes: u64,
pub available_bytes: u64,
pub swap_total_bytes: u64,
pub swap_used_bytes: u64,
}
pub fn collect() -> Result<MemoryInfo> {
let mut sys = System::new();
sys.refresh_memory();
let used_bytes = platform_used_bytes(sys.used_memory());
Ok(MemoryInfo {
total_bytes: sys.total_memory(),
used_bytes,
available_bytes: sys.available_memory(),
swap_total_bytes: sys.total_swap(),
swap_used_bytes: sys.used_swap(),
})
}
#[cfg(target_os = "macos")]
fn platform_used_bytes(sysinfo_used: u64) -> u64 {
if let Some(activity_monitor_used) = macos_activity_monitor_used_bytes() {
let baseline = sysinfo_used.max(1);
let delta = activity_monitor_used.abs_diff(baseline);
if delta as f64 / baseline as f64 > 0.05 {
return activity_monitor_used;
}
}
sysinfo_used
}
#[cfg(not(target_os = "macos"))]
fn platform_used_bytes(sysinfo_used: u64) -> u64 {
sysinfo_used
}
#[cfg(target_os = "macos")]
fn macos_activity_monitor_used_bytes() -> Option<u64> {
let stdout = crate::collectors::command::run_stdout(
"vm_stat",
std::iter::empty::<&str>(),
crate::collectors::command::CommandTimeout::Normal,
)?;
parse_vm_stat_used_bytes(&stdout)
}
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
fn parse_vm_stat_used_bytes(output: &str) -> Option<u64> {
let mut page_size = None;
let mut active = None;
let mut wired = None;
let mut compressed = None;
for line in output.lines() {
if line.contains("page size of") {
let digits = line
.split_whitespace()
.find_map(|word| word.parse::<u64>().ok());
page_size = digits;
continue;
}
let Some((key, value)) = line.split_once(':') else {
continue;
};
let pages = value
.trim()
.trim_end_matches('.')
.replace('.', "")
.parse::<u64>()
.ok();
match key.trim() {
"Pages active" => active = pages,
"Pages wired down" => wired = pages,
"Pages occupied by compressor" => compressed = pages,
_ => {}
}
}
Some(
active?
.saturating_add(wired?)
.saturating_add(compressed.unwrap_or(0))
.saturating_mul(page_size?),
)
}
impl MemoryInfo {
pub fn usage_percent(&self) -> f32 {
if self.total_bytes == 0 {
0.0
} else {
(self.used_bytes as f64 / self.total_bytes as f64 * 100.0) as f32
}
}
pub fn swap_usage_percent(&self) -> f32 {
if self.swap_total_bytes == 0 {
0.0
} else {
(self.swap_used_bytes as f64 / self.swap_total_bytes as f64 * 100.0) as f32
}
}
pub fn format_bytes(bytes: u64) -> String {
crate::format_bytes(bytes)
}
pub fn total_formatted(&self) -> String {
Self::format_bytes(self.total_bytes)
}
pub fn used_formatted(&self) -> String {
Self::format_bytes(self.used_bytes)
}
pub fn available_formatted(&self) -> String {
Self::format_bytes(self.available_bytes)
}
pub fn usage_string(&self) -> String {
format!(
"{} / {} ({:.1}%)",
self.used_formatted(),
self.total_formatted(),
self.usage_percent()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_vm_stat_activity_monitor_used_memory() {
let vm_stat = "\
Mach Virtual Memory Statistics: (page size of 16384 bytes)
Pages active: 10.
Pages wired down: 20.
Pages occupied by compressor: 30.
";
assert_eq!(parse_vm_stat_used_bytes(vm_stat), Some(60 * 16384));
}
}