1use super::config::*;
8use super::optimizer::{Adaptation, AdaptationPriority, AdaptationType};
9
10use serde::Serialize;
11use std::collections::{HashMap, VecDeque};
12use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
13use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
14use std::time::{Duration, Instant};
15
16mod alerts;
17mod optimization;
18mod prediction;
19mod probe;
20
21#[cfg(test)]
22mod regression_tests;
23
24pub use probe::SystemProbe;
25
26const SHUTDOWN_POLL_INTERVAL: Duration = Duration::from_millis(50);
30
31pub(crate) fn lock_recovered<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
36 mutex.lock().unwrap_or_else(PoisonError::into_inner)
37}
38
39#[derive(Debug, Clone, Serialize)]
41pub struct ResourceUsage {
42 pub memory_usage_mb: usize,
44 pub total_memory_mb: usize,
52 pub process_memory_mb: Option<usize>,
64 pub cpu_usage_percent: f64,
74 pub cpu_usage_percent_valid: bool,
76 pub gpu_usage_percent: Option<f64>,
80 pub network_io_mbps: Option<f64>,
83 pub disk_io_mbps: Option<f64>,
87 pub active_threads: usize,
89 #[serde(skip)]
91 pub timestamp: Instant,
92}
93
94impl ResourceUsage {
95 pub fn process_memory(&self) -> Option<usize> {
97 self.process_memory_mb
98 }
99
100 pub fn cpu_usage(&self) -> Option<f64> {
102 if self.cpu_usage_percent_valid {
103 Some(self.cpu_usage_percent)
104 } else {
105 None
106 }
107 }
108
109 pub fn memory_usage_percent(&self) -> Option<f64> {
113 if self.total_memory_mb == 0 {
114 None
115 } else {
116 Some((self.memory_usage_mb as f64 / self.total_memory_mb as f64) * 100.0)
117 }
118 }
119}
120
121#[derive(Debug, Clone)]
123pub struct ResourceBudget {
124 pub memory_budget: MemoryBudget,
126 pub cpu_budget: CpuBudget,
128 pub network_budget: NetworkBudget,
130 pub time_budget: TimeBudget,
132 pub enforcement_strategy: BudgetEnforcementStrategy,
134 pub flexibility: f64,
136}
137
138#[derive(Debug, Clone)]
140pub struct MemoryBudget {
141 pub max_allocation_mb: usize,
143 pub soft_limit_mb: usize,
145 pub cleanup_threshold: f64,
147 pub enable_compression: bool,
149 pub priority_levels: Vec<MemoryPriority>,
151}
152
153#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
155pub enum MemoryPriority {
156 Critical,
158 High,
160 Normal,
162 Low,
164 Temporary,
166}
167
168#[derive(Debug, Clone)]
170pub struct CpuBudget {
171 pub max_utilization: f64,
173 pub target_utilization: f64,
175 pub max_threads: usize,
177 pub thread_priority: ThreadPriorityConfig,
179 pub cpu_affinity: Option<Vec<usize>>,
181}
182
183#[derive(Debug, Clone)]
185pub struct ThreadPriorityConfig {
186 pub high_priority_threads: usize,
188 pub normal_priority_threads: usize,
190 pub background_threads: usize,
192 pub dynamic_priority: bool,
194}
195
196#[derive(Debug, Clone)]
198pub struct NetworkBudget {
199 pub max_bandwidth_mbps: f64,
201 pub priority_allocation: HashMap<String, f64>,
203 pub enable_traffic_shaping: bool,
205 pub qos_settings: QoSSettings,
207}
208
209#[derive(Debug, Clone)]
211pub struct QoSSettings {
212 pub max_latency_ms: u64,
214 pub jitter_tolerance_ms: u64,
216 pub packet_loss_tolerance: f64,
218 pub traffic_classes: Vec<TrafficClass>,
220}
221
222#[derive(Debug, Clone)]
224pub struct TrafficClass {
225 pub name: String,
227 pub priority: u8,
229 pub bandwidth_guarantee: f64,
231 pub max_bandwidth: f64,
233}
234
235#[derive(Debug, Clone)]
237pub struct TimeBudget {
238 pub max_batch_processing_time: Duration,
240 pub target_batch_processing_time: Duration,
242 pub operation_timeout: Duration,
244 pub deadline_enforcement: DeadlineEnforcement,
246}
247
248#[derive(Debug, Clone)]
250pub enum DeadlineEnforcement {
251 Strict,
253 Soft,
255 BestEffort,
257 Adaptive,
259}
260
261#[derive(Debug, Clone)]
263pub enum BudgetEnforcementStrategy {
264 Strict,
266 Throttling,
268 LoadShedding,
270 GracefulDegradation,
272 Adaptive,
274}
275
276pub struct ResourceManager {
278 config: ResourceConfig,
280 current_usage: Arc<Mutex<ResourceUsage>>,
282 usage_history: Arc<Mutex<VecDeque<ResourceUsage>>>,
284 budget: ResourceBudget,
286 allocations: Arc<Mutex<HashMap<String, ResourceAllocation>>>,
288 monitoring_handle: Option<std::thread::JoinHandle<()>>,
290 shutdown: Arc<AtomicBool>,
293 probe: Arc<Mutex<SystemProbe>>,
295 last_synchronous_sample: Option<Instant>,
297 predictor: ResourcePredictor,
299 optimizer: ResourceOptimizer,
301 alert_system: ResourceAlertSystem,
303 budget_violations: Arc<AtomicU64>,
306 budget_penalty: f64,
309}
310
311#[derive(Debug, Clone)]
313pub struct ResourceAllocation {
314 pub component_name: String,
316 pub allocated_memory_mb: usize,
318 pub allocated_cpu_percent: f64,
320 pub allocated_bandwidth_mbps: f64,
322 pub priority: ResourcePriority,
324 pub allocation_time: Instant,
326 pub last_access: Instant,
328 pub usage_stats: ComponentUsageStats,
330}
331
332#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
334pub enum ResourcePriority {
335 Critical = 0,
337 High = 1,
339 Normal = 2,
341 Low = 3,
343 Temporary = 4,
345}
346
347#[derive(Debug, Clone)]
349pub struct ComponentUsageStats {
350 pub peak_memory_mb: usize,
352 pub avg_memory_mb: usize,
354 pub peak_cpu_percent: f64,
356 pub avg_cpu_percent: f64,
358 pub total_processing_time: Duration,
360 pub operation_count: u64,
362 pub efficiency_score: f64,
364}
365
366pub struct ResourcePredictor {
368 pub(crate) usage_patterns: VecDeque<ResourceUsage>,
370 pub(crate) prediction_horizon: usize,
372 pub(crate) prediction_accuracy: HashMap<String, f64>,
375 pub(crate) seasonal_patterns: HashMap<String, Vec<f64>>,
377 pub(crate) trend_analysis: ResourceTrendAnalysis,
379 pub(crate) enabled: bool,
382 pub(crate) seasonal_counts: HashMap<String, Vec<u64>>,
384 pub(crate) pending_prediction: Option<(usize, ResourceUsage)>,
386}
387
388#[derive(Debug, Clone)]
390pub struct ResourceTrendAnalysis {
391 pub memory_trend: TrendDirection,
393 pub cpu_trend: TrendDirection,
395 pub network_trend: TrendDirection,
397 pub trend_confidence: f64,
399 pub trend_stability: f64,
401}
402
403#[derive(Debug, Clone, PartialEq, Eq)]
405pub enum TrendDirection {
406 Increasing,
408 Decreasing,
410 Stable,
412 Oscillating,
414 Unknown,
416}
417
418pub struct ResourceOptimizer {
420 pub(crate) strategy: ResourceOptimizationStrategy,
422 pub(crate) optimization_history: VecDeque<OptimizationEvent>,
424 pub(crate) performance_impact: HashMap<String, f64>,
426 pub(crate) constraints: OptimizationConstraints,
428 pub(crate) last_change: HashMap<String, (Instant, f64)>,
431 pub(crate) pending_change: HashMap<String, f64>,
433}
434
435#[derive(Debug, Clone)]
437pub enum ResourceOptimizationStrategy {
438 Conservative,
440 Aggressive,
442 Balanced,
444 PowerEfficient,
446 LatencyOptimized,
448 ThroughputOptimized,
450}
451
452#[derive(Debug, Clone)]
454pub struct OptimizationEvent {
455 pub timestamp: Instant,
457 pub optimization_type: String,
459 pub affected_resources: Vec<String>,
461 pub resource_deltas: HashMap<String, f64>,
463 pub performance_impact: f64,
465 pub success: bool,
467}
468
469#[derive(Debug, Clone)]
471pub struct OptimizationConstraints {
472 pub min_guarantees: HashMap<String, f64>,
474 pub max_limits: HashMap<String, f64>,
476 pub change_rate_limits: HashMap<String, f64>,
478 pub stability_requirements: StabilityRequirements,
480}
481
482#[derive(Debug, Clone)]
484pub struct StabilityRequirements {
485 pub min_stable_period: Duration,
487 pub max_change_frequency: f64,
489 pub prevent_oscillation: bool,
491 pub hysteresis_factor: f64,
493}
494
495pub struct ResourceAlertSystem {
497 pub(crate) thresholds: ResourceThresholds,
499 pub(crate) active_alerts: VecDeque<ResourceAlert>,
501 pub(crate) alert_history: VecDeque<ResourceAlert>,
503 pub(crate) alert_handlers: Vec<Box<dyn AlertHandler>>,
505 pub(crate) next_alert_id: u64,
510}
511
512#[derive(Debug, Clone)]
514pub struct ResourceThresholds {
515 pub memory_thresholds: ThresholdSet,
517 pub cpu_thresholds: ThresholdSet,
519 pub network_thresholds: ThresholdSet,
521 pub response_time_thresholds: ThresholdSet,
523}
524
525#[derive(Debug, Clone)]
527pub struct ThresholdSet {
528 pub warning: f64,
530 pub critical: f64,
532 pub emergency: f64,
534 pub recovery: f64,
536}
537
538#[derive(Debug, Clone)]
540pub struct ResourceAlert {
541 pub id: String,
543 pub timestamp: Instant,
545 pub severity: AlertSeverity,
547 pub resource_type: String,
549 pub current_value: f64,
551 pub threshold_value: f64,
553 pub message: String,
555 pub suggested_actions: Vec<String>,
557 pub auto_resolution_attempts: u32,
559}
560
561#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
563pub enum AlertSeverity {
564 Info,
566 Warning,
568 Error,
570 Critical,
572 Emergency,
574}
575
576pub trait AlertHandler: Send + Sync {
578 fn handle_alert(&self, alert: &ResourceAlert) -> Result<(), String>;
580
581 fn priority(&self) -> u32;
583
584 fn can_handle(&self, alert: &ResourceAlert) -> bool;
586}
587
588impl ResourceManager {
589 pub fn new(config: &StreamingConfig) -> Result<Self, String> {
591 let resource_config = config.resource_config.clone();
592 let constraints = resource_config.budget_constraints.clone();
595
596 let available_cpus = num_cpus::get().max(1);
597 let high_priority_threads = available_cpus.min(2);
600 let normal_priority_threads = available_cpus.saturating_sub(high_priority_threads);
601
602 let soft_limit_mb = ((resource_config.max_memory_mb as f64 * 0.8) as usize)
605 .min(constraints.memory_budget_mb.max(1));
606
607 let budget = ResourceBudget {
608 memory_budget: MemoryBudget {
609 max_allocation_mb: resource_config.max_memory_mb,
610 soft_limit_mb,
611 cleanup_threshold: resource_config.cleanup_threshold,
612 enable_compression: true,
613 priority_levels: vec![
614 MemoryPriority::Critical,
615 MemoryPriority::High,
616 MemoryPriority::Normal,
617 MemoryPriority::Low,
618 ],
619 },
620 cpu_budget: CpuBudget {
621 max_utilization: resource_config.max_cpu_percent,
622 target_utilization: constraints
624 .cpu_budget_percent
625 .min(resource_config.max_cpu_percent),
626 max_threads: available_cpus,
627 thread_priority: ThreadPriorityConfig {
628 high_priority_threads,
629 normal_priority_threads,
630 background_threads: 1,
631 dynamic_priority: true,
632 },
633 cpu_affinity: None,
634 },
635 network_budget: NetworkBudget {
636 max_bandwidth_mbps: 100.0, priority_allocation: HashMap::new(),
638 enable_traffic_shaping: false,
639 qos_settings: QoSSettings {
640 max_latency_ms: 100,
641 jitter_tolerance_ms: 10,
642 packet_loss_tolerance: 0.1,
643 traffic_classes: Vec::new(),
644 },
645 },
646 time_budget: TimeBudget {
647 max_batch_processing_time: constraints.time_budget.saturating_mul(3),
651 target_batch_processing_time: constraints.time_budget,
652 operation_timeout: constraints.time_budget,
653 deadline_enforcement: if constraints.strict_enforcement {
654 DeadlineEnforcement::Strict
655 } else {
656 DeadlineEnforcement::Soft
657 },
658 },
659 enforcement_strategy: if constraints.strict_enforcement {
660 BudgetEnforcementStrategy::Strict
661 } else {
662 match resource_config.allocation_strategy {
663 ResourceAllocationStrategy::Static => BudgetEnforcementStrategy::Strict,
664 ResourceAllocationStrategy::Dynamic => BudgetEnforcementStrategy::Throttling,
665 ResourceAllocationStrategy::Adaptive => BudgetEnforcementStrategy::Adaptive,
666 _ => BudgetEnforcementStrategy::GracefulDegradation,
667 }
668 },
669 flexibility: if constraints.strict_enforcement {
671 0.0
672 } else {
673 0.2
674 },
675 };
676
677 let predictor = ResourcePredictor::new(resource_config.enable_resource_prediction);
678 let optimizer = ResourceOptimizer::new(match resource_config.allocation_strategy {
679 ResourceAllocationStrategy::Static => ResourceOptimizationStrategy::Conservative,
680 ResourceAllocationStrategy::Dynamic => {
681 ResourceOptimizationStrategy::ThroughputOptimized
682 }
683 ResourceAllocationStrategy::PriorityBased => {
684 ResourceOptimizationStrategy::LatencyOptimized
685 }
686 _ => ResourceOptimizationStrategy::Balanced,
687 });
688 let alert_system = ResourceAlertSystem::from_config(&resource_config, &budget);
691
692 Ok(Self {
693 config: resource_config,
694 current_usage: Arc::new(Mutex::new(ResourceUsage::default())),
695 usage_history: Arc::new(Mutex::new(VecDeque::with_capacity(1000))),
696 budget,
697 allocations: Arc::new(Mutex::new(HashMap::new())),
698 monitoring_handle: None,
699 shutdown: Arc::new(AtomicBool::new(false)),
700 probe: Arc::new(Mutex::new(SystemProbe::new())),
701 last_synchronous_sample: None,
702 predictor,
703 optimizer,
704 alert_system,
705 budget_violations: Arc::new(AtomicU64::new(0)),
706 budget_penalty: 0.0,
707 })
708 }
709
710 pub fn start_monitoring(&mut self) -> Result<(), String> {
717 if self.monitoring_handle.is_some() {
718 return Ok(()); }
720
721 let current_usage = Arc::clone(&self.current_usage);
722 let usage_history = Arc::clone(&self.usage_history);
723 let shutdown = Arc::clone(&self.shutdown);
724 let probe = Arc::clone(&self.probe);
725 let monitoring_frequency = self.config.monitoring_frequency;
726
727 shutdown.store(false, Ordering::SeqCst);
728 let handle = std::thread::Builder::new()
729 .name("optirs-resource-monitor".to_string())
730 .spawn(move || {
731 let mut next_sample = Instant::now();
732 while !shutdown.load(Ordering::SeqCst) {
733 let now = Instant::now();
734 if now >= next_sample {
735 let usage = lock_recovered(&probe).sample();
736 {
737 let mut current = lock_recovered(¤t_usage);
738 *current = usage.clone();
739 }
740 {
741 let mut history = lock_recovered(&usage_history);
742 if history.len() >= 1000 {
743 history.pop_front();
744 }
745 history.push_back(usage);
746 }
747 next_sample = now + monitoring_frequency.max(SHUTDOWN_POLL_INTERVAL);
748 }
749 std::thread::sleep(SHUTDOWN_POLL_INTERVAL);
750 }
751 })
752 .map_err(|error| format!("failed to spawn the resource monitor thread: {error}"))?;
753
754 self.monitoring_handle = Some(handle);
755 Ok(())
756 }
757
758 pub fn stop_monitoring(&mut self) -> Result<(), String> {
760 self.shutdown.store(true, Ordering::SeqCst);
761 if let Some(handle) = self.monitoring_handle.take() {
762 handle
763 .join()
764 .map_err(|_| "the resource monitor thread panicked".to_string())?;
765 }
766 Ok(())
767 }
768
769 pub fn is_monitoring(&self) -> bool {
771 self.monitoring_handle.is_some()
772 }
773
774 pub fn collect_resource_usage(&self) -> ResourceUsage {
776 lock_recovered(&self.probe).sample()
777 }
778
779 pub fn allocate_resources(
781 &mut self,
782 component_name: &str,
783 memory_mb: usize,
784 cpu_percent: f64,
785 priority: ResourcePriority,
786 ) -> Result<(), String> {
787 self.check_budget_constraints(memory_mb, cpu_percent)?;
789
790 let allocation = ResourceAllocation {
791 component_name: component_name.to_string(),
792 allocated_memory_mb: memory_mb,
793 allocated_cpu_percent: cpu_percent,
794 allocated_bandwidth_mbps: 0.0, priority,
796 allocation_time: Instant::now(),
797 last_access: Instant::now(),
798 usage_stats: ComponentUsageStats {
799 peak_memory_mb: 0,
800 avg_memory_mb: 0,
801 peak_cpu_percent: 0.0,
802 avg_cpu_percent: 0.0,
803 total_processing_time: Duration::ZERO,
804 operation_count: 0,
805 efficiency_score: 1.0,
806 },
807 };
808
809 let mut allocations = lock_recovered(&self.allocations);
810 allocations.insert(component_name.to_string(), allocation);
811
812 Ok(())
813 }
814
815 fn check_budget_constraints(&self, memory_mb: usize, cpu_percent: f64) -> Result<(), String> {
821 let allocations = lock_recovered(&self.allocations);
822
823 let total_memory: usize = allocations
825 .values()
826 .map(|a| a.allocated_memory_mb)
827 .sum::<usize>()
828 + memory_mb;
829
830 let total_cpu: f64 = allocations
831 .values()
832 .map(|a| a.allocated_cpu_percent)
833 .sum::<f64>()
834 + cpu_percent;
835
836 let flexibility = 1.0 + self.budget.flexibility;
837 let memory_limit =
838 (self.budget.memory_budget.max_allocation_mb as f64 * flexibility) as usize;
839 let cpu_limit = self.budget.cpu_budget.max_utilization * flexibility;
840
841 if total_memory > memory_limit {
843 self.record_budget_violation();
844 return Err(format!(
845 "Memory allocation would exceed budget: {} MB > {} MB",
846 total_memory, memory_limit
847 ));
848 }
849
850 if total_cpu > cpu_limit {
851 self.record_budget_violation();
852 return Err(format!(
853 "CPU allocation would exceed budget: {:.2}% > {:.2}%",
854 total_cpu, cpu_limit
855 ));
856 }
857
858 Ok(())
859 }
860
861 fn record_budget_violation(&self) {
862 self.budget_violations.fetch_add(1, Ordering::Relaxed);
863 }
864
865 pub fn budget_violations(&self) -> u64 {
867 self.budget_violations.load(Ordering::Relaxed)
868 }
869
870 pub fn budget_penalty(&self) -> f64 {
873 self.budget_penalty
874 }
875
876 pub fn update_utilization(&mut self) -> Result<(), String> {
883 if self.monitoring_handle.is_none() {
884 let due = self
885 .last_synchronous_sample
886 .map(|last| last.elapsed() >= self.config.monitoring_frequency)
887 .unwrap_or(true);
888 if due {
889 let usage = self.collect_resource_usage();
890 self.last_synchronous_sample = Some(Instant::now());
891 {
892 let mut current = lock_recovered(&self.current_usage);
893 *current = usage.clone();
894 }
895 let mut history = lock_recovered(&self.usage_history);
896 if history.len() >= 1000 {
897 history.pop_front();
898 }
899 history.push_back(usage);
900 }
901 }
902
903 let current_usage = lock_recovered(&self.current_usage).clone();
904
905 self.alert_system.update(¤t_usage)?;
907
908 let mut violated = false;
912 if let Some(process_mb) = current_usage.process_memory() {
913 if process_mb > self.budget.memory_budget.max_allocation_mb {
914 violated = true;
915 }
916 }
917 if let Some(cpu) = current_usage.cpu_usage() {
918 if cpu > self.budget.cpu_budget.max_utilization {
919 violated = true;
920 }
921 }
922 if violated {
923 self.record_budget_violation();
924 self.budget_penalty += self.config.budget_constraints.violation_penalty;
925 }
926
927 self.predictor.update(¤t_usage)?;
929
930 if self.config.enable_dynamic_allocation {
932 self.optimizer
933 .check_optimization_opportunities(¤t_usage, &self.allocations)?;
934 }
935
936 Ok(())
937 }
938
939 pub fn has_sufficient_resources_for_processing(&self) -> Result<bool, String> {
945 let current_usage = lock_recovered(&self.current_usage);
946
947 let memory_available = match current_usage.process_memory() {
950 Some(process_mb) => {
951 process_mb < (self.budget.memory_budget.soft_limit_mb as f64 * 0.9) as usize
952 }
953 None => true,
954 };
955
956 let cpu_available = match current_usage.cpu_usage() {
958 Some(cpu) => cpu < self.budget.cpu_budget.target_utilization * 0.9,
959 None => true,
960 };
961
962 Ok(memory_available && cpu_available)
963 }
964
965 pub fn compute_allocation_adaptation(&mut self) -> Result<Option<Adaptation<f32>>, String> {
967 let current_usage = lock_recovered(&self.current_usage);
968
969 let process_memory_mb = current_usage.process_memory();
973 if process_memory_mb
974 .is_some_and(|process_mb| process_mb > self.budget.memory_budget.soft_limit_mb)
975 {
976 let overshoot = (process_memory_mb.unwrap_or(0) as f64
981 - self.budget.memory_budget.soft_limit_mb as f64)
982 / (self.budget.memory_budget.soft_limit_mb.max(1) as f64);
983 let magnitude = self.optimizer.clamp_change("memory", -overshoot);
984 let adaptation = Adaptation {
985 adaptation_type: AdaptationType::ResourceAllocation,
986 magnitude: magnitude as f32,
987 target_component: "memory_manager".to_string(),
988 parameters: std::collections::HashMap::new(),
989 priority: AdaptationPriority::High,
990 timestamp: Instant::now(),
991 };
992
993 drop(current_usage);
994 if self.optimizer.accept_change("memory_manager") {
995 return Ok(Some(adaptation));
996 }
997 return Ok(None);
998 }
999
1000 if let Some(cpu) = current_usage.cpu_usage() {
1003 if cpu > self.budget.cpu_budget.target_utilization {
1004 let overshoot = (cpu - self.budget.cpu_budget.target_utilization)
1005 / self.budget.cpu_budget.target_utilization.max(1.0);
1006 let magnitude = self.optimizer.clamp_change("cpu", -overshoot);
1007 let adaptation = Adaptation {
1008 adaptation_type: AdaptationType::ResourceAllocation,
1009 magnitude: magnitude as f32,
1010 target_component: "cpu_manager".to_string(),
1011 parameters: std::collections::HashMap::new(),
1012 priority: AdaptationPriority::High,
1013 timestamp: Instant::now(),
1014 };
1015
1016 drop(current_usage);
1017 if self.optimizer.accept_change("cpu_manager") {
1018 return Ok(Some(adaptation));
1019 }
1020 return Ok(None);
1021 }
1022 }
1023
1024 Ok(None)
1025 }
1026
1027 pub fn predict_usage(&self) -> Option<ResourceUsage> {
1030 self.predictor.predict()
1031 }
1032
1033 pub fn prediction_accuracy(&self) -> &HashMap<String, f64> {
1035 self.predictor.accuracy()
1036 }
1037
1038 pub fn trend_analysis(&self) -> &ResourceTrendAnalysis {
1040 self.predictor.trend_analysis()
1041 }
1042
1043 pub fn register_alert_handler(&mut self, handler: Box<dyn AlertHandler>) {
1045 self.alert_system.register_handler(handler);
1046 }
1047
1048 pub fn active_alerts(&self) -> Vec<ResourceAlert> {
1050 self.alert_system.active_alerts.iter().cloned().collect()
1051 }
1052
1053 pub fn alert_history(&self) -> Vec<ResourceAlert> {
1055 self.alert_system.alert_history.iter().cloned().collect()
1056 }
1057
1058 pub fn apply_allocation_adaptation(
1060 &mut self,
1061 adaptation: &Adaptation<f32>,
1062 ) -> Result<(), String> {
1063 if adaptation.adaptation_type == AdaptationType::ResourceAllocation {
1064 match adaptation.target_component.as_str() {
1065 "memory_manager" => {
1066 let factor = (1.0 + adaptation.magnitude).max(0.0);
1068 let mut allocations = lock_recovered(&self.allocations);
1069
1070 for allocation in allocations.values_mut() {
1071 if allocation.priority >= ResourcePriority::Normal {
1072 allocation.allocated_memory_mb =
1073 ((allocation.allocated_memory_mb as f32) * factor) as usize;
1074 }
1075 }
1076 drop(allocations);
1077 self.optimizer.record_applied_change("memory_manager");
1078 }
1079 "cpu_manager" => {
1080 let factor = (1.0 + adaptation.magnitude).max(0.0);
1082 let mut allocations = lock_recovered(&self.allocations);
1083
1084 for allocation in allocations.values_mut() {
1085 if allocation.priority >= ResourcePriority::Normal {
1086 allocation.allocated_cpu_percent *= factor as f64;
1087 }
1088 }
1089 drop(allocations);
1090 self.optimizer.record_applied_change("cpu_manager");
1091 }
1092 other => {
1093 return Err(format!(
1094 "no resource adaptation is defined for target component '{other}'"
1095 ));
1096 }
1097 }
1098 }
1099
1100 Ok(())
1101 }
1102
1103 pub fn current_usage(&self) -> Result<ResourceUsage, String> {
1105 Ok(lock_recovered(&self.current_usage).clone())
1106 }
1107
1108 pub fn get_usage_history(&self, count: usize) -> Vec<ResourceUsage> {
1110 let history = lock_recovered(&self.usage_history);
1111 history.iter().rev().take(count).cloned().collect()
1112 }
1113
1114 pub fn get_diagnostics(&self) -> ResourceDiagnostics {
1116 let current_usage = lock_recovered(&self.current_usage);
1117 let allocations = lock_recovered(&self.allocations);
1118
1119 ResourceDiagnostics {
1120 current_usage: current_usage.clone(),
1121 total_allocations: allocations.len(),
1122 memory_utilization: current_usage.process_memory().map(|process_mb| {
1124 (process_mb as f64 / self.budget.memory_budget.max_allocation_mb.max(1) as f64)
1125 * 100.0
1126 }),
1127 system_memory_percent: current_usage.memory_usage_percent(),
1129 cpu_utilization: current_usage.cpu_usage(),
1130 active_alerts: self.alert_system.active_alerts.len(),
1131 budget_violations: self.budget_violations.load(Ordering::Relaxed) as usize,
1133 budget_penalty: self.budget_penalty,
1134 applied_change_magnitude: self.optimizer.performance_impact().clone(),
1139 }
1140 }
1141}
1142
1143impl Drop for ResourceManager {
1144 fn drop(&mut self) {
1146 self.shutdown.store(true, Ordering::SeqCst);
1147 if let Some(handle) = self.monitoring_handle.take() {
1148 let _ = handle.join();
1149 }
1150 }
1151}
1152
1153#[derive(Debug, Clone)]
1155pub struct ResourceDiagnostics {
1156 pub current_usage: ResourceUsage,
1157 pub total_allocations: usize,
1158 pub memory_utilization: Option<f64>,
1161 pub system_memory_percent: Option<f64>,
1164 pub cpu_utilization: Option<f64>,
1167 pub active_alerts: usize,
1168 pub budget_violations: usize,
1170 pub budget_penalty: f64,
1172 pub applied_change_magnitude: HashMap<String, f64>,
1175}
1176
1177impl Default for ResourceUsage {
1178 fn default() -> Self {
1179 Self {
1180 memory_usage_mb: 0,
1181 total_memory_mb: 0,
1182 process_memory_mb: None,
1183 cpu_usage_percent: 0.0,
1184 cpu_usage_percent_valid: false,
1185 gpu_usage_percent: None,
1186 network_io_mbps: None,
1187 disk_io_mbps: None,
1188 active_threads: 0,
1189 timestamp: Instant::now(),
1190 }
1191 }
1192}
1193
1194#[cfg(test)]
1195mod r2_memory_percent_tests {
1196 use super::*;
1197
1198 fn usage_with(memory_usage_mb: usize, total_memory_mb: usize) -> ResourceUsage {
1199 ResourceUsage {
1200 memory_usage_mb,
1201 total_memory_mb,
1202 ..Default::default()
1203 }
1204 }
1205
1206 #[test]
1212 fn memory_percent_is_correct_on_realistic_machine() {
1213 let usage = usage_with(4096, 32768);
1215 let percent = usage
1216 .memory_usage_percent()
1217 .expect("total_memory_mb is set, so this must be Some");
1218 assert!(
1219 (percent - 12.5).abs() < 1e-9,
1220 "R2 regression: expected ~12.5%, got {percent}%"
1221 );
1222 assert!(
1223 percent < 100.0,
1224 "R2 regression: realistic usage reported as over 100% (got {percent}%), \
1225 which pins alert severity at Emergency regardless of real pressure"
1226 );
1227 }
1228
1229 #[test]
1233 fn memory_percent_is_none_when_total_unknown() {
1234 let usage = usage_with(4096, 0);
1235 assert_eq!(usage.memory_usage_percent(), None);
1236 }
1237
1238 #[test]
1244 fn realistic_memory_load_raises_no_alert() {
1245 let mut alert_system = ResourceAlertSystem::new();
1246 let usage = usage_with(4096, 32768); let alerts = alert_system
1248 .check_thresholds(&usage)
1249 .expect("check_thresholds");
1250 assert!(
1251 alerts.is_empty(),
1252 "R2 regression: realistic 12.5% memory usage raised alert(s): {alerts:?}"
1253 );
1254 }
1255
1256 #[test]
1260 fn genuinely_high_memory_load_raises_alert() {
1261 let mut alert_system = ResourceAlertSystem::new();
1262 let usage = usage_with(31000, 32768); let alerts = alert_system
1264 .check_thresholds(&usage)
1265 .expect("check_thresholds");
1266 assert!(
1267 !alerts.is_empty(),
1268 "genuinely high memory usage (~94.6%) should raise an alert"
1269 );
1270 assert!(alerts
1274 .iter()
1275 .any(|a| a.resource_type == "memory" && a.severity >= AlertSeverity::Critical));
1276 }
1277}