use sysinfo::System;
use std::process::Command;
use std::time::Instant;
use tracing::{info, warn, debug};
#[derive(Debug, Clone)]
pub struct MemoryStatus {
pub total_physical_mb: f64,
pub available_physical_mb: f64,
pub memory_load_percent: u32,
pub total_swap_mb: f64,
pub available_swap_mb: f64,
pub pressure_level: u32,
pub is_apple_silicon: bool,
}
impl MemoryStatus {
pub fn used_physical_mb(&self) -> f64 {
self.total_physical_mb - self.available_physical_mb
}
pub fn is_high_pressure(&self) -> bool {
self.memory_load_percent > 80 || self.pressure_level >= 2
}
pub fn is_critical(&self) -> bool {
self.memory_load_percent > 95 || self.pressure_level >= 3
}
}
#[derive(Debug, Clone)]
pub struct OptimizationResult {
pub freed_mb: f64,
pub before_available_mb: f64,
pub after_available_mb: f64,
pub processes_affected: usize,
pub duration_ms: u64,
pub method: OptimizationMethod,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum OptimizationMethod {
PurgeTool,
MadviseHints,
SystemCleanup,
None,
}
pub struct MacMemoryOptimizer {
has_sudo: bool,
is_apple_silicon: bool,
}
impl MacMemoryOptimizer {
pub fn new() -> Self {
let has_sudo = Self::check_sudo();
let is_apple_silicon = Self::detect_apple_silicon();
if has_sudo {
info!("Running with sudo access - full optimization available");
} else {
warn!("Running without sudo - limited optimization (madvise hints only)");
}
if is_apple_silicon {
info!("Apple Silicon detected - unified memory architecture");
}
Self {
has_sudo,
is_apple_silicon,
}
}
fn check_sudo() -> bool {
unsafe { libc::geteuid() == 0 }
}
fn detect_apple_silicon() -> bool {
#[cfg(target_arch = "aarch64")]
{
true
}
#[cfg(not(target_arch = "aarch64"))]
{
false
}
}
pub fn get_memory_status() -> Result<MemoryStatus, String> {
let mut sys = System::new();
sys.refresh_memory();
let total = sys.total_memory() as f64 / 1024.0 / 1024.0;
let available = sys.available_memory() as f64 / 1024.0 / 1024.0;
let used = sys.used_memory() as f64 / 1024.0 / 1024.0;
let load = if total > 0.0 {
((used / total) * 100.0) as u32
} else {
0
};
let total_swap = sys.total_swap() as f64 / 1024.0 / 1024.0;
let used_swap = sys.used_swap() as f64 / 1024.0 / 1024.0;
let available_swap = total_swap - used_swap;
let pressure_level = Self::get_memory_pressure_level();
Ok(MemoryStatus {
total_physical_mb: total,
available_physical_mb: available,
memory_load_percent: load,
total_swap_mb: total_swap,
available_swap_mb: available_swap,
pressure_level,
is_apple_silicon: Self::detect_apple_silicon(),
})
}
fn get_memory_pressure_level() -> u32 {
if let Ok(output) = Command::new("memory_pressure").output() {
let stdout = String::from_utf8_lossy(&output.stdout);
if stdout.contains("normal") {
return 0;
} else if stdout.contains("warn") {
return 1;
} else if stdout.contains("critical") {
return 2;
}
}
if let Ok(status) = Self::get_memory_status_basic() {
if status.memory_load_percent > 95 {
return 3;
} else if status.memory_load_percent > 85 {
return 2;
} else if status.memory_load_percent > 75 {
return 1;
}
}
0
}
fn get_memory_status_basic() -> Result<MemoryStatus, String> {
let mut sys = System::new();
sys.refresh_memory();
let total = sys.total_memory() as f64 / 1024.0 / 1024.0;
let available = sys.available_memory() as f64 / 1024.0 / 1024.0;
let used = sys.used_memory() as f64 / 1024.0 / 1024.0;
let load = if total > 0.0 { ((used / total) * 100.0) as u32 } else { 0 };
Ok(MemoryStatus {
total_physical_mb: total,
available_physical_mb: available,
memory_load_percent: load,
total_swap_mb: 0.0,
available_swap_mb: 0.0,
pressure_level: 0,
is_apple_silicon: false,
})
}
pub fn optimize(&self, aggressive: bool) -> Result<OptimizationResult, String> {
let start = Instant::now();
let before = Self::get_memory_status()?;
let (method, processes_affected) = if self.has_sudo && aggressive {
self.run_purge()?
} else {
self.run_madvise_hints()?
};
std::thread::sleep(std::time::Duration::from_millis(500));
let after = Self::get_memory_status()?;
let freed = after.available_physical_mb - before.available_physical_mb;
info!(
"Optimized: method={:?}, affected {} processes, freed {:.1} MB in {}ms",
method,
processes_affected,
freed.max(0.0),
start.elapsed().as_millis()
);
Ok(OptimizationResult {
freed_mb: freed.max(0.0),
before_available_mb: before.available_physical_mb,
after_available_mb: after.available_physical_mb,
processes_affected,
duration_ms: start.elapsed().as_millis() as u64,
method,
})
}
fn run_purge(&self) -> Result<(OptimizationMethod, usize), String> {
info!("Running purge command (sudo required)");
let output = Command::new("sudo")
.arg("purge")
.output()
.map_err(|e| format!("Failed to run purge: {}", e))?;
if output.status.success() {
Ok((OptimizationMethod::PurgeTool, 0))
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
warn!("Purge failed: {}", stderr);
self.run_madvise_hints()
}
}
fn run_madvise_hints(&self) -> Result<(OptimizationMethod, usize), String> {
debug!("Using madvise hints for memory optimization");
let processes = super::process::list_user_processes()?;
let mut affected = 0;
for (pid, _name, memory_mb) in processes.iter().take(50) {
if *memory_mb > 100.0 {
if self.hint_process_memory(*pid) {
affected += 1;
}
}
}
Ok((OptimizationMethod::MadviseHints, affected))
}
fn hint_process_memory(&self, _pid: u32) -> bool {
false
}
pub fn clear_disk_cache(&self) -> Result<f64, String> {
if !self.has_sudo {
return Err("sudo required to clear disk cache".into());
}
let before = Self::get_memory_status()?;
let _ = Command::new("sync").output();
let _ = Command::new("sudo").arg("purge").output();
std::thread::sleep(std::time::Duration::from_millis(500));
let after = Self::get_memory_status()?;
let freed = after.available_physical_mb - before.available_physical_mb;
Ok(freed.max(0.0))
}
pub fn has_sudo_privileges(&self) -> bool {
self.has_sudo
}
pub fn is_apple_silicon(&self) -> bool {
self.is_apple_silicon
}
}
impl Default for MacMemoryOptimizer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_memory_status() {
let status = MacMemoryOptimizer::get_memory_status().unwrap();
assert!(status.total_physical_mb > 0.0);
assert!(status.available_physical_mb > 0.0);
assert!(status.memory_load_percent <= 100);
}
}