use crate::core::error::PlottingError;
use std::process::Command;
pub fn get_total_memory() -> Result<u64, PlottingError> {
let output = Command::new("sysctl")
.arg("-n")
.arg("hw.physmem")
.output()
.map_err(|e| PlottingError::SystemError(format!("Failed to run sysctl: {}", e)))?;
let mem_str = String::from_utf8_lossy(&output.stdout);
mem_str
.trim()
.parse::<u64>()
.map_err(|e| PlottingError::SystemError(format!("Failed to parse memory size: {}", e)))
}
pub fn get_available_memory() -> Result<u64, PlottingError> {
let pagesize = get_pagesize()?;
let free_pages = get_sysctl_u64("vm.stats.vm.v_free_count")?;
let inactive_pages = get_sysctl_u64("vm.stats.vm.v_inactive_count")?;
let cache_pages = get_sysctl_u64("vm.stats.vm.v_cache_count").unwrap_or(0);
let available_pages = free_pages + inactive_pages + cache_pages;
Ok(available_pages * pagesize)
}
fn get_pagesize() -> Result<u64, PlottingError> {
get_sysctl_u64("hw.pagesize")
}
fn get_sysctl_u64(name: &str) -> Result<u64, PlottingError> {
let output = Command::new("sysctl")
.arg("-n")
.arg(name)
.output()
.map_err(|e| PlottingError::SystemError(format!("Failed to run sysctl {}: {}", name, e)))?;
let value_str = String::from_utf8_lossy(&output.stdout);
value_str
.trim()
.parse::<u64>()
.map_err(|e| PlottingError::SystemError(format!("Failed to parse sysctl {}: {}", name, e)))
}
pub fn get_numa_nodes() -> usize {
if let Ok(domains) = get_sysctl_u64("vm.ndomains") {
if domains > 0 {
return domains as usize;
}
}
if let Ok(output) = Command::new("sysctl")
.arg("-n")
.arg("kern.sched.topology_spec")
.output()
{
let topology = String::from_utf8_lossy(&output.stdout);
let domain_count = topology.matches("<group level=\"1\"").count();
if domain_count > 1 {
return domain_count;
}
}
1 }
pub fn check_hugepage_support() -> bool {
if let Ok(enabled) = get_sysctl_u64("vm.pmap.pg_ps_enabled") {
return enabled != 0;
}
if let Ok(output) = Command::new("sysctl").arg("vm.pmap").output() {
let output_str = String::from_utf8_lossy(&output.stdout);
return output_str.contains("superpage") || output_str.contains("pagesizes");
}
false
}
pub fn check_memory_mapping_support() -> Result<bool, PlottingError> {
Ok(true)
}
pub fn get_hugepage_info() -> Option<HugepageInfo> {
let mut info = HugepageInfo::default();
if let Ok(output) = Command::new("sysctl")
.arg("-n")
.arg("vm.pmap.pagesizes")
.output()
{
let sizes_str = String::from_utf8_lossy(&output.stdout);
let sizes: Vec<u64> = sizes_str
.split_whitespace()
.filter_map(|s| s.parse().ok())
.collect();
if sizes.len() > 1 {
info.page_size_kb = sizes[1] / 1024; }
}
if let Ok(promotions) = get_sysctl_u64("vm.stats.vm.v_page_promotions") {
info.total_pages = promotions;
}
if info.page_size_kb > 0 || info.total_pages > 0 {
Some(info)
} else {
None
}
}
pub fn configure_transparent_hugepages(enabled: bool) -> Result<(), PlottingError> {
let value = if enabled { "1" } else { "0" };
let output = Command::new("sysctl")
.arg(format!("vm.pmap.pg_ps_enabled={}", value))
.output()
.map_err(|e| {
PlottingError::SystemError(format!("Failed to configure superpages: {}", e))
})?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(PlottingError::SystemError(format!(
"Failed to configure superpages: {}",
stderr
)));
}
Ok(())
}
pub fn get_memory_pressure() -> Option<MemoryPressure> {
let output = Command::new("vmstat").arg("-s").output().ok()?;
let vmstat_str = String::from_utf8_lossy(&output.stdout);
let mut pressure = MemoryPressure::default();
for line in vmstat_str.lines() {
let line = line.trim();
if line.contains("pages paged out") {
if let Some(num_str) = line.split_whitespace().next() {
if let Ok(pages) = num_str.parse::<u64>() {
pressure.some_avg10 = (pages as f32 / 1000.0).min(100.0);
}
}
}
if line.contains("pages freed by daemon") {
if let Some(num_str) = line.split_whitespace().next() {
if let Ok(pages) = num_str.parse::<u64>() {
pressure.full_avg10 = (pages as f32 / 1000.0).min(100.0);
}
}
}
}
if let Ok(shortage) = get_sysctl_u64("vm.stats.vm.v_page_shortage") {
pressure.some_avg60 = (shortage as f32 / 10.0).min(100.0);
}
Some(pressure)
}
pub fn configure_swappiness(value: u32) -> Result<(), PlottingError> {
if value > 100 {
return Err(PlottingError::InvalidInput(
"Swappiness must be between 0 and 100".to_string(),
));
}
let output = Command::new("sysctl")
.arg(format!("vm.swap_idle_threshold2={}", value))
.output()
.map_err(|e| {
PlottingError::SystemError(format!("Failed to configure swap threshold: {}", e))
})?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(PlottingError::SystemError(format!(
"Failed to configure swap threshold: {}",
stderr
)));
}
Ok(())
}
pub fn get_swappiness() -> Result<u32, PlottingError> {
get_sysctl_u64("vm.swap_idle_threshold2")
.map(|v| v as u32)
.or_else(|_| {
get_sysctl_u64("vm.swap_idle_threshold1").map(|v| v as u32)
})
}
#[derive(Debug, Clone, Default)]
pub struct HugepageInfo {
pub total_pages: u64,
pub free_pages: u64,
pub page_size_kb: u64,
}
#[derive(Debug, Clone, Default)]
pub struct MemoryPressure {
pub some_avg10: f32, pub some_avg60: f32, pub full_avg10: f32, pub full_avg60: f32, }
pub fn get_detailed_memory_stats() -> Result<DetailedMemoryStats, PlottingError> {
let pagesize = get_pagesize()?;
let mut stats = DetailedMemoryStats::default();
stats.total = get_total_memory()?;
let free_count = get_sysctl_u64("vm.stats.vm.v_free_count")?;
let active_count = get_sysctl_u64("vm.stats.vm.v_active_count")?;
let inactive_count = get_sysctl_u64("vm.stats.vm.v_inactive_count")?;
let cache_count = get_sysctl_u64("vm.stats.vm.v_cache_count").unwrap_or(0);
let wire_count = get_sysctl_u64("vm.stats.vm.v_wire_count")?;
stats.free = free_count * pagesize;
stats.available = (free_count + inactive_count + cache_count) * pagesize;
let used_pages = active_count + wire_count;
stats.buffers = get_sysctl_u64("vfs.bufspace").unwrap_or(0);
stats.cached = cache_count * pagesize;
if let Ok(swap_total) = get_sysctl_u64("vm.swap_total") {
stats.swap_total = swap_total;
if let Ok(swap_used) = get_sysctl_u64("vm.swap_reserved") {
stats.swap_free = swap_total.saturating_sub(swap_used);
}
}
stats.slab = wire_count * pagesize;
if let Ok(resident) = get_sysctl_u64("vm.stats.vm.v_user_wire_count") {
stats.mapped = resident * pagesize;
}
Ok(stats)
}
#[derive(Debug, Clone, Default)]
pub struct DetailedMemoryStats {
pub total: u64,
pub free: u64,
pub available: u64,
pub buffers: u64,
pub cached: u64,
pub swap_total: u64,
pub swap_free: u64,
pub slab: u64,
pub slab_reclaimable: u64,
pub slab_unreclaimable: u64,
pub page_tables: u64,
pub mapped: u64,
}
pub fn get_memory_zones() -> Result<Vec<MemoryZone>, PlottingError> {
let output = Command::new("vmstat")
.arg("-z")
.output()
.map_err(|e| PlottingError::SystemError(format!("Failed to run vmstat -z: {}", e)))?;
let output_str = String::from_utf8_lossy(&output.stdout);
let mut zones = Vec::new();
for line in output_str.lines().skip(1) {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 5 {
zones.push(MemoryZone {
name: parts[0].trim_end_matches(':').to_string(),
size: parts[1].parse().unwrap_or(0),
used: parts[3].parse().unwrap_or(0),
free: parts[4].parse().unwrap_or(0),
});
}
}
Ok(zones)
}
#[derive(Debug, Clone)]
pub struct MemoryZone {
pub name: String,
pub size: u64,
pub used: u64,
pub free: u64,
}