use std::time::{Duration, Instant};
use tracing::{info, warn};
pub const PROTECTED_PROCESSES: &[&str] = &[
"kernel_task",
"launchd",
"syslogd",
"configd",
"mDNSResponder",
"WindowServer",
"loginwindow",
"Dock",
"Finder",
"SystemUIServer",
"ControlCenter",
"NotificationCenter",
"securityd",
"trustd",
"secd",
"keybagd",
"BiometricKit",
"coreaudiod",
"bluetoothd",
"airportd",
"wifid",
"usbd",
"coreduetd",
"cfprefsd",
"diskarbitrationd",
"fseventsd",
"mds_stores",
"backupd",
"powerd",
"thermald",
"AMPDeviceDiscoveryAgent",
"AMPLibraryAgent",
"gpu_driver_spawn",
"mediaremoted",
"audiomxd",
"Xcode",
"SourceKitService",
"lldb",
"swift-frontend",
"clangd",
];
#[derive(Debug, Clone)]
pub struct SafetyConfig {
pub min_available_mb: f64,
pub min_interval: Duration,
pub max_processes_per_run: usize,
pub dry_run: bool,
pub additional_protected: Vec<String>,
pub respect_system_pressure: bool,
}
impl Default for SafetyConfig {
fn default() -> Self {
Self {
min_available_mb: 2048.0, min_interval: Duration::from_secs(60),
max_processes_per_run: 30,
dry_run: false,
additional_protected: vec![],
respect_system_pressure: true,
}
}
}
pub struct SafetyGuard {
config: SafetyConfig,
last_optimization: Option<Instant>,
consecutive_failures: usize,
total_optimizations: usize,
}
impl SafetyGuard {
pub fn new(config: SafetyConfig) -> Self {
Self {
config,
last_optimization: None,
consecutive_failures: 0,
total_optimizations: 0,
}
}
pub fn check_safe(&self, current_available_mb: f64) -> Result<(), String> {
if current_available_mb < self.config.min_available_mb {
return Err(format!(
"Available memory ({:.0}MB) already below safety floor ({:.0}MB) - no optimization needed",
current_available_mb, self.config.min_available_mb
));
}
if let Some(last) = self.last_optimization {
let elapsed = last.elapsed();
if elapsed < self.config.min_interval {
return Err(format!(
"Rate limited: {:?} remaining",
self.config.min_interval - elapsed
));
}
}
if self.consecutive_failures >= 3 {
return Err(format!(
"Too many consecutive failures ({}). Manual intervention needed.",
self.consecutive_failures
));
}
Ok(())
}
pub fn is_protected(&self, process_name: &str) -> bool {
let name_lower = process_name.to_lowercase();
if PROTECTED_PROCESSES
.iter()
.any(|p| name_lower.contains(&p.to_lowercase()))
{
return true;
}
self.config
.additional_protected
.iter()
.any(|p| name_lower.contains(&p.to_lowercase()))
}
pub fn record_attempt(&mut self, success: bool) {
self.last_optimization = Some(Instant::now());
self.total_optimizations += 1;
if success {
self.consecutive_failures = 0;
} else {
self.consecutive_failures += 1;
warn!(
"Optimization failed. Consecutive failures: {}",
self.consecutive_failures
);
}
}
pub fn is_dry_run(&self) -> bool {
self.config.dry_run
}
pub fn max_processes(&self) -> usize {
self.config.max_processes_per_run
}
pub fn emergency_stop(&mut self) {
self.consecutive_failures = 100;
warn!("Emergency stop activated - optimizations disabled");
}
pub fn reset(&mut self) {
self.consecutive_failures = 0;
self.last_optimization = None;
info!("Safety counters reset");
}
pub fn stats(&self) -> SafetyStats {
SafetyStats {
total_optimizations: self.total_optimizations,
consecutive_failures: self.consecutive_failures,
is_healthy: self.consecutive_failures < 3,
}
}
}
#[derive(Debug, Clone)]
pub struct SafetyStats {
pub total_optimizations: usize,
pub consecutive_failures: usize,
pub is_healthy: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_protected_processes() {
let guard = SafetyGuard::new(SafetyConfig::default());
assert!(guard.is_protected("kernel_task"));
assert!(guard.is_protected("WindowServer"));
assert!(guard.is_protected("Finder"));
assert!(!guard.is_protected("my_custom_app"));
}
#[test]
fn test_safety_check() {
let guard = SafetyGuard::new(SafetyConfig::default());
assert!(guard.check_safe(8000.0).is_ok());
assert!(guard.check_safe(1000.0).is_err());
}
}