1use scirs2_core::numeric::Float;
7use std::collections::HashMap;
8use std::fmt::Debug;
9use std::marker::PhantomData;
10use std::sync::{Arc, Mutex};
11use std::time::{Duration, Instant};
12
13use super::config::ResourceConstraints;
14use super::results::{ResourceUsage, ResourceUsageSummary};
15use super::telemetry::{StdTelemetry, TelemetrySample, TelemetrySource};
16use crate::error::Result;
17
18pub struct ResourceMonitor<T: Float + Debug + Send + Sync + 'static> {
20 constraints: ResourceConstraints<T>,
22
23 current_usage: Arc<Mutex<ResourceUsage<T>>>,
25
26 usage_history: Arc<Mutex<Vec<ResourceSnapshot<T>>>>,
28
29 monitoring_config: MonitoringConfig,
31
32 monitors: Vec<Box<dyn ResourceTracker<T>>>,
34
35 alert_handlers: Vec<Box<dyn AlertHandler<T>>>,
37
38 monitoring_state: MonitoringState,
40
41 optimization_strategies: Vec<Box<dyn ResourceOptimizer<T>>>,
43}
44
45impl<T: Float + Debug + Send + Sync + 'static> Debug for ResourceMonitor<T> {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 f.debug_struct("ResourceMonitor")
48 .field("constraints", &self.constraints)
49 .field("current_usage", &self.current_usage)
50 .field("usage_history", &self.usage_history)
51 .field("monitoring_config", &self.monitoring_config)
52 .field("monitoring_state", &self.monitoring_state)
53 .field("monitors_count", &self.monitors.len())
54 .field("alert_handlers_count", &self.alert_handlers.len())
55 .field(
56 "optimization_strategies_count",
57 &self.optimization_strategies.len(),
58 )
59 .finish()
60 }
61}
62
63fn lock_recovering<T>(mutex: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
71 mutex
72 .lock()
73 .unwrap_or_else(|poisoned| poisoned.into_inner())
74}
75
76fn memory_pressure_from_sample(sample: &TelemetrySample) -> Option<MemoryPressure> {
79 let total = sample.total_memory_gb?;
80 let available = sample.available_memory_gb?;
81 if total <= 0.0 {
82 return None;
83 }
84 let used_fraction = ((total - available) / total).clamp(0.0, 1.0);
85 Some(if used_fraction >= 0.95 {
86 MemoryPressure::Critical
87 } else if used_fraction >= 0.85 {
88 MemoryPressure::High
89 } else if used_fraction >= 0.7 {
90 MemoryPressure::Medium
91 } else {
92 MemoryPressure::Low
93 })
94}
95
96#[derive(Debug, Clone)]
98pub struct ResourceSnapshot<T: Float + Debug + Send + Sync + 'static> {
99 pub timestamp: Instant,
101
102 pub usage: ResourceUsage<T>,
104
105 pub system_metrics: SystemMetrics<T>,
107
108 pub active_processes: Option<usize>,
110
111 pub memory_pressure: Option<MemoryPressure>,
115
116 pub cpu_load_average: Option<T>,
119
120 pub gpu_utilization: Option<T>,
122}
123
124#[derive(Debug, Clone, Default, PartialEq)]
136pub struct SystemMetrics<T: Float + Debug + Send + Sync + 'static> {
137 pub available_memory_gb: Option<T>,
139
140 pub available_cpu_cores: Option<usize>,
142
143 pub available_gpu_devices: Option<usize>,
145
146 pub available_disk_gb: Option<T>,
148
149 pub network_bandwidth: Option<T>,
151
152 pub system_temperature: Option<T>,
154
155 pub power_consumption: Option<T>,
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub enum MemoryPressure {
162 Low,
163 Medium,
164 High,
165 Critical,
166}
167
168#[derive(Debug, Clone)]
170pub struct MonitoringConfig {
171 pub monitoring_interval: Duration,
173
174 pub history_retention: Duration,
176
177 pub enable_detailed_monitoring: bool,
179
180 pub enable_predictive_monitoring: bool,
182
183 pub alert_thresholds: AlertThresholds,
185
186 pub enable_auto_optimization: bool,
188}
189
190#[derive(Debug, Clone)]
192pub struct AlertThresholds {
193 pub memory_threshold: f64,
195
196 pub cpu_threshold: f64,
198
199 pub gpu_threshold: f64,
201
202 pub disk_threshold: f64,
204
205 pub temperature_threshold: f64,
207
208 pub power_threshold: f64,
210}
211
212#[derive(Debug, Clone, Copy, PartialEq)]
214pub enum MonitoringState {
215 Stopped,
216 Starting,
217 Running,
218 Paused,
219 Stopping,
220 Error,
221}
222
223pub trait ResourceTracker<T: Float + Debug + Send + Sync + 'static>: Send + Sync {
225 fn get_current_usage(&self) -> Result<ResourceUsage<T>>;
227
228 fn get_system_metrics(&self) -> Result<SystemMetrics<T>>;
230
231 fn check_limits(
233 &self,
234 constraints: &ResourceConstraints<T>,
235 ) -> Result<Vec<ResourceViolation<T>>>;
236
237 fn name(&self) -> &str;
239
240 fn initialize(&mut self) -> Result<()>;
242
243 fn cleanup(&mut self) -> Result<()>;
245
246 fn telemetry_sample(&self) -> Option<TelemetrySample> {
254 None
255 }
256}
257
258#[derive(Debug, Clone)]
260pub struct ResourceViolation<T: Float + Debug + Send + Sync + 'static> {
261 pub violation_type: ViolationType,
263
264 pub current_value: T,
266
267 pub limit_value: T,
269
270 pub severity: ViolationSeverity,
272
273 pub violation_time: Instant,
275
276 pub affected_resources: Vec<String>,
278
279 pub suggested_actions: Vec<String>,
281}
282
283#[derive(Debug, Clone, Copy)]
285pub enum ViolationType {
286 MemoryExceeded,
287 CPUExceeded,
288 GPUExceeded,
289 DiskExceeded,
290 TimeExceeded,
291 EnergyExceeded,
292 TemperatureExceeded,
293 PowerExceeded,
294 NetworkExceeded,
295 ComputationTimeExceeded,
296 CostExceeded,
297}
298
299#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
301pub enum ViolationSeverity {
302 Low,
303 Medium,
304 High,
305 Critical,
306}
307
308pub trait AlertHandler<T: Float + Debug + Send + Sync + 'static>: Send + Sync {
310 fn handle_violation(&self, violation: &ResourceViolation<T>) -> Result<()>;
312
313 fn handle_warning(&self, warning: &ResourceWarning<T>) -> Result<()>;
315
316 fn name(&self) -> &str;
318}
319
320#[derive(Debug, Clone)]
322pub struct ResourceWarning<T: Float + Debug + Send + Sync + 'static> {
323 pub warning_type: WarningType,
325
326 pub current_value: T,
328
329 pub threshold_value: T,
331
332 pub trend: TrendDirection,
334
335 pub time_to_violation: Option<Duration>,
337
338 pub message: String,
340}
341
342#[derive(Debug, Clone, Copy)]
344pub enum WarningType {
345 MemoryApproachingLimit,
346 CPUApproachingLimit,
347 GPUApproachingLimit,
348 DiskApproachingLimit,
349 TimeApproachingLimit,
350 EnergyApproachingLimit,
351 TemperatureRising,
352 PowerIncreasing,
353}
354
355#[derive(Debug, Clone, Copy)]
357pub enum TrendDirection {
358 Increasing,
359 Decreasing,
360 Stable,
361 Volatile,
362}
363
364pub trait ResourceOptimizer<T: Float + Debug + Send + Sync + 'static>: Send + Sync {
366 fn optimize(
368 &self,
369 current_usage: &ResourceUsage<T>,
370 constraints: &ResourceConstraints<T>,
371 ) -> Result<OptimizationAction<T>>;
372
373 fn name(&self) -> &str;
375
376 fn priority(&self) -> OptimizationPriority;
378}
379
380#[derive(Debug, Clone)]
382pub struct OptimizationAction<T: Float + Debug + Send + Sync + 'static> {
383 pub action_type: ActionType,
385
386 pub parameters: HashMap<String, T>,
388
389 pub expected_savings: ResourceUsage<T>,
391
392 pub implementation_cost: T,
394
395 pub description: String,
397
398 pub priority: OptimizationPriority,
400}
401
402#[derive(Debug, Clone, Copy)]
404pub enum ActionType {
405 ReduceMemoryUsage,
406 OptimizeCPUUsage,
407 OptimizeGPUUsage,
408 ClearCaches,
409 GarbageCollection,
410 ProcessThrottling,
411 ResourceReallocation,
412 TaskMigration,
413 PowerManagement,
414 TemperatureControl,
415}
416
417#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
419pub enum OptimizationPriority {
420 Low,
421 Medium,
422 High,
423 Critical,
424}
425
426pub struct SystemResourceTracker {
439 name: String,
441
442 monitoring_interval: Duration,
444
445 last_update: Instant,
447
448 telemetry: Box<dyn TelemetrySource>,
451
452 cached_sample: Option<TelemetrySample>,
454}
455
456impl Debug for SystemResourceTracker {
457 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
458 f.debug_struct("SystemResourceTracker")
459 .field("name", &self.name)
460 .field("monitoring_interval", &self.monitoring_interval)
461 .field("telemetry", &self.telemetry.name())
462 .field("cached_sample", &self.cached_sample)
463 .finish()
464 }
465}
466
467impl SystemResourceTracker {
468 pub fn new(name: String, monitoring_interval: Duration) -> Self {
470 Self::with_telemetry(name, monitoring_interval, Box::new(StdTelemetry::new()))
471 }
472
473 pub fn with_telemetry(
477 name: String,
478 monitoring_interval: Duration,
479 telemetry: Box<dyn TelemetrySource>,
480 ) -> Self {
481 Self {
482 name,
483 monitoring_interval,
484 last_update: Instant::now(),
485 telemetry,
486 cached_sample: None,
487 }
488 }
489
490 pub fn telemetry_name(&self) -> &str {
492 self.telemetry.name()
493 }
494
495 pub fn last_sample(&self) -> Option<&TelemetrySample> {
497 self.cached_sample.as_ref()
498 }
499
500 pub fn sample(&self) -> TelemetrySample {
502 self.telemetry.sample()
503 }
504
505 pub fn memory_pressure(&self) -> Option<MemoryPressure> {
508 memory_pressure_from_sample(&self.telemetry.sample())
509 }
510}
511
512impl<T: Float + Debug + Send + Sync + 'static> ResourceTracker<T> for SystemResourceTracker
513where
514 T: From<f64> + std::fmt::Debug,
515{
516 fn get_current_usage(&self) -> Result<ResourceUsage<T>> {
526 let sample = self.telemetry.sample();
527 let mut usage = ResourceUsage::default();
528 if let Some(process_memory_gb) = sample.process_memory_gb {
529 let value: T =
530 scirs2_core::numeric::NumCast::from(process_memory_gb).unwrap_or_else(|| T::zero());
531 usage.memory_gb = value;
532 usage.peak_memory_gb = value;
533 }
534 Ok(usage)
535 }
536
537 fn get_system_metrics(&self) -> Result<SystemMetrics<T>> {
538 let sample = self.telemetry.sample();
539 let convert = |value: Option<f64>| -> Option<T> {
540 value.and_then(|v| scirs2_core::numeric::NumCast::from(v))
541 };
542 Ok(SystemMetrics {
543 available_memory_gb: convert(sample.available_memory_gb),
544 available_cpu_cores: sample.logical_cpus,
545 available_gpu_devices: sample.gpu_devices,
546 available_disk_gb: convert(sample.available_disk_gb),
547 network_bandwidth: convert(sample.network_bandwidth_mbps),
548 system_temperature: convert(sample.temperature_celsius),
549 power_consumption: convert(sample.power_watts),
550 })
551 }
552
553 fn check_limits(
561 &self,
562 constraints: &ResourceConstraints<T>,
563 ) -> Result<Vec<ResourceViolation<T>>> {
564 let sample = self.telemetry.sample();
565 let mut violations = Vec::new();
566
567 if let Some(process_memory_gb) = sample.process_memory_gb {
569 let current: T =
570 scirs2_core::numeric::NumCast::from(process_memory_gb).unwrap_or_else(|| T::zero());
571 let limit: T =
572 scirs2_core::numeric::NumCast::from(constraints.hardware_resources.max_memory_gb)
573 .unwrap_or_else(|| T::zero());
574 if limit > T::zero() && current > limit {
575 violations.push(ResourceViolation {
576 violation_type: ViolationType::MemoryExceeded,
577 current_value: current,
578 limit_value: limit,
579 severity: ViolationSeverity::High,
580 violation_time: Instant::now(),
581 affected_resources: vec!["Memory".to_string()],
582 suggested_actions: vec![
583 "Clear caches".to_string(),
584 "Reduce batch size".to_string(),
585 "Enable memory optimization".to_string(),
586 ],
587 });
588 }
589 }
590
591 if let Some(power_watts) = sample.power_watts {
593 let current: T =
594 scirs2_core::numeric::NumCast::from(power_watts).unwrap_or_else(|| T::zero());
595 let limit: T =
596 scirs2_core::numeric::NumCast::from(POWER_LIMIT_WATTS).unwrap_or_else(|| T::zero());
597 if current > limit {
598 violations.push(ResourceViolation {
599 violation_type: ViolationType::PowerExceeded,
600 current_value: current,
601 limit_value: limit,
602 severity: ViolationSeverity::Medium,
603 violation_time: Instant::now(),
604 affected_resources: vec!["Power".to_string()],
605 suggested_actions: vec![
606 "Reduce clock speeds".to_string(),
607 "Throttle processes".to_string(),
608 "Enable power saving mode".to_string(),
609 ],
610 });
611 }
612 }
613
614 if let Some(temperature_celsius) = sample.temperature_celsius {
616 let current: T = scirs2_core::numeric::NumCast::from(temperature_celsius)
617 .unwrap_or_else(|| T::zero());
618 let limit: T = scirs2_core::numeric::NumCast::from(TEMPERATURE_LIMIT_CELSIUS)
619 .unwrap_or_else(|| T::zero());
620 if current > limit {
621 violations.push(ResourceViolation {
622 violation_type: ViolationType::TemperatureExceeded,
623 current_value: current,
624 limit_value: limit,
625 severity: ViolationSeverity::Critical,
626 violation_time: Instant::now(),
627 affected_resources: vec!["CPU".to_string(), "GPU".to_string()],
628 suggested_actions: vec![
629 "Increase cooling".to_string(),
630 "Reduce workload".to_string(),
631 "Enable thermal throttling".to_string(),
632 ],
633 });
634 }
635 }
636
637 Ok(violations)
638 }
639
640 fn name(&self) -> &str {
641 &self.name
642 }
643
644 fn initialize(&mut self) -> Result<()> {
645 self.last_update = Instant::now();
646 let sample = self.telemetry.sample();
647 if sample.is_fully_unknown() {
648 log::warn!(
649 "resource tracker {} initialized with telemetry source '{}', which can measure \
650 nothing on this platform; every resource constraint will be treated as \
651 unlimited",
652 self.name,
653 self.telemetry.name()
654 );
655 } else {
656 log::info!(
657 "resource tracker {} initialized with telemetry source '{}': {:?}",
658 self.name,
659 self.telemetry.name(),
660 sample
661 );
662 }
663 self.cached_sample = Some(sample);
664 Ok(())
665 }
666
667 fn cleanup(&mut self) -> Result<()> {
668 self.cached_sample = None;
669 log::info!("cleaned up system resource tracker: {}", self.name);
670 Ok(())
671 }
672
673 fn telemetry_sample(&self) -> Option<TelemetrySample> {
674 Some(self.telemetry.sample())
675 }
676}
677
678const POWER_LIMIT_WATTS: f64 = 1000.0;
681
682const TEMPERATURE_LIMIT_CELSIUS: f64 = 80.0;
685
686pub struct ConsoleAlertHandler {
688 name: String,
690
691 verbose: bool,
693}
694
695impl ConsoleAlertHandler {
696 pub fn new(name: String, verbose: bool) -> Self {
698 Self { name, verbose }
699 }
700}
701
702impl<T: Float + Debug + Send + Sync + 'static> AlertHandler<T> for ConsoleAlertHandler
703where
704 T: std::fmt::Display + std::fmt::Debug,
705{
706 fn handle_violation(&self, violation: &ResourceViolation<T>) -> Result<()> {
711 let summary = format!(
712 "resource violation {:?}: current={}, limit={}, severity={:?}",
713 violation.violation_type,
714 violation.current_value,
715 violation.limit_value,
716 violation.severity
717 );
718 match violation.severity {
719 ViolationSeverity::Critical | ViolationSeverity::High => log::error!("{}", summary),
720 ViolationSeverity::Medium => log::warn!("{}", summary),
721 ViolationSeverity::Low => log::info!("{}", summary),
722 }
723
724 if self.verbose {
725 log::debug!(
726 "violation detail: time={:?}, affected={:?}, suggested={:?}",
727 violation.violation_time,
728 violation.affected_resources,
729 violation.suggested_actions
730 );
731 }
732
733 Ok(())
734 }
735
736 fn handle_warning(&self, warning: &ResourceWarning<T>) -> Result<()> {
738 log::warn!(
739 "resource warning {:?}: current={}, threshold={}, trend={:?}",
740 warning.warning_type,
741 warning.current_value,
742 warning.threshold_value,
743 warning.trend
744 );
745
746 if self.verbose {
747 log::debug!(
748 "warning detail: time_to_violation={:?}, message={}",
749 warning.time_to_violation,
750 warning.message
751 );
752 }
753
754 Ok(())
755 }
756
757 fn name(&self) -> &str {
758 &self.name
759 }
760}
761
762pub struct MemoryOptimizer {
764 name: String,
766
767 aggressiveness: f64,
769}
770
771impl MemoryOptimizer {
772 pub fn new(name: String, aggressiveness: f64) -> Self {
774 Self {
775 name,
776 aggressiveness,
777 }
778 }
779}
780
781impl<T: Float + Debug + Send + Sync + 'static> ResourceOptimizer<T> for MemoryOptimizer
782where
783 T: From<f64> + std::cmp::PartialOrd,
784{
785 fn optimize(
786 &self,
787 current_usage: &ResourceUsage<T>,
788 constraints: &ResourceConstraints<T>,
789 ) -> Result<OptimizationAction<T>> {
790 let memory_limit =
791 scirs2_core::numeric::NumCast::from(constraints.hardware_resources.max_memory_gb)
792 .unwrap_or_else(|| T::zero());
793
794 if current_usage.memory_gb
795 > memory_limit * scirs2_core::numeric::NumCast::from(0.8).unwrap_or_else(|| T::zero())
796 {
797 let reduction_target = current_usage.memory_gb
798 * scirs2_core::numeric::NumCast::from(self.aggressiveness * 0.2)
799 .unwrap_or_else(|| T::zero());
800
801 let mut parameters = HashMap::new();
802 parameters.insert("memory_reduction_gb".to_string(), reduction_target);
803 parameters.insert(
804 "aggressiveness".to_string(),
805 scirs2_core::numeric::NumCast::from(self.aggressiveness)
806 .unwrap_or_else(|| T::zero()),
807 );
808
809 Ok(OptimizationAction {
810 action_type: ActionType::ReduceMemoryUsage,
811 parameters,
812 expected_savings: ResourceUsage {
813 memory_gb: reduction_target,
814 cpu_time_seconds: scirs2_core::numeric::NumCast::from(0.0)
815 .unwrap_or_else(|| T::zero()),
816 gpu_time_seconds: scirs2_core::numeric::NumCast::from(0.0)
817 .unwrap_or_else(|| T::zero()),
818 energy_kwh: scirs2_core::numeric::NumCast::from(0.0)
819 .unwrap_or_else(|| T::zero()),
820 network_io_gb: scirs2_core::numeric::NumCast::from(0.0)
821 .unwrap_or_else(|| T::zero()),
822 disk_io_gb: scirs2_core::numeric::NumCast::from(0.0)
823 .unwrap_or_else(|| T::zero()),
824 peak_memory_gb: reduction_target,
825 efficiency_score: scirs2_core::numeric::NumCast::from(0.1)
826 .unwrap_or_else(|| T::zero()),
827 cost_usd: scirs2_core::numeric::NumCast::from(0.0).unwrap_or_else(|| T::zero()),
828 network_gb: scirs2_core::numeric::NumCast::from(0.0)
829 .unwrap_or_else(|| T::zero()),
830 },
831 implementation_cost: scirs2_core::numeric::NumCast::from(0.05)
832 .unwrap_or_else(|| T::zero()),
833 description: "Reduce memory usage through cache clearing and optimization"
834 .to_string(),
835 priority: OptimizationPriority::High,
836 })
837 } else {
838 Ok(OptimizationAction {
839 action_type: ActionType::ReduceMemoryUsage,
840 parameters: HashMap::new(),
841 expected_savings: ResourceUsage::default(),
842 implementation_cost: scirs2_core::numeric::NumCast::from(0.0)
843 .unwrap_or_else(|| T::zero()),
844 description: "No memory optimization needed".to_string(),
845 priority: OptimizationPriority::Low,
846 })
847 }
848 }
849
850 fn name(&self) -> &str {
851 &self.name
852 }
853
854 fn priority(&self) -> OptimizationPriority {
855 OptimizationPriority::High
856 }
857}
858
859impl<T: Float + Debug + Send + Sync + 'static> ResourceMonitor<T>
860where
861 T: From<f64> + std::fmt::Display,
862{
863 pub fn new(constraints: ResourceConstraints<T>) -> Self {
865 let monitoring_config = MonitoringConfig {
866 monitoring_interval: Duration::from_secs(5),
867 history_retention: Duration::from_secs(3600), enable_detailed_monitoring: true,
869 enable_predictive_monitoring: false,
870 alert_thresholds: AlertThresholds {
871 memory_threshold: 0.8,
872 cpu_threshold: 0.9,
873 gpu_threshold: 0.9,
874 disk_threshold: 0.9,
875 temperature_threshold: 75.0,
876 power_threshold: 800.0,
877 },
878 enable_auto_optimization: true,
879 };
880
881 let monitors: Vec<Box<dyn ResourceTracker<T>>> =
882 vec![Box::new(SystemResourceTracker::new(
883 "SystemTracker".to_string(),
884 monitoring_config.monitoring_interval,
885 ))];
886
887 let alert_handlers: Vec<Box<dyn AlertHandler<T>>> = vec![Box::new(
888 ConsoleAlertHandler::new("ConsoleHandler".to_string(), true),
889 )];
890
891 let optimization_strategies: Vec<Box<dyn ResourceOptimizer<T>>> = vec![Box::new(
892 MemoryOptimizer::new("MemoryOptimizer".to_string(), 0.8),
893 )];
894
895 Self {
896 constraints,
897 current_usage: Arc::new(Mutex::new(ResourceUsage::default())),
898 usage_history: Arc::new(Mutex::new(Vec::new())),
899 monitoring_config,
900 monitors,
901 alert_handlers,
902 monitoring_state: MonitoringState::Stopped,
903 optimization_strategies,
904 }
905 }
906
907 pub fn start_monitoring(&mut self) -> Result<()> {
909 self.monitoring_state = MonitoringState::Starting;
910
911 for monitor in &mut self.monitors {
913 monitor.initialize()?;
914 }
915
916 self.monitoring_state = MonitoringState::Running;
917 log::info!(
918 "resource monitoring started with {} tracker(s)",
919 self.monitors.len()
920 );
921 Ok(())
922 }
923
924 pub fn stop_monitoring(&mut self) -> Result<()> {
926 self.monitoring_state = MonitoringState::Stopping;
927
928 for monitor in &mut self.monitors {
930 monitor.cleanup()?;
931 }
932
933 self.monitoring_state = MonitoringState::Stopped;
934 log::info!("resource monitoring stopped");
935 Ok(())
936 }
937
938 pub fn update_usage(&self) -> Result<()> {
953 if self.monitoring_state != MonitoringState::Running {
954 return Ok(());
955 }
956
957 let mut total_usage = ResourceUsage::default();
958 let mut system_metrics = None;
959 let mut telemetry_sample: Option<TelemetrySample> = None;
960
961 for monitor in &self.monitors {
962 let usage = monitor.get_current_usage()?;
963 total_usage.memory_gb = total_usage.memory_gb + usage.memory_gb;
964 total_usage.cpu_time_seconds = total_usage.cpu_time_seconds + usage.cpu_time_seconds;
965 total_usage.gpu_time_seconds = total_usage.gpu_time_seconds + usage.gpu_time_seconds;
966 total_usage.energy_kwh = total_usage.energy_kwh + usage.energy_kwh;
967 if total_usage.peak_memory_gb < usage.peak_memory_gb {
968 total_usage.peak_memory_gb = usage.peak_memory_gb;
969 }
970
971 if system_metrics.is_none() {
972 system_metrics = Some(monitor.get_system_metrics()?);
973 }
974 if telemetry_sample.is_none() {
975 telemetry_sample = monitor.telemetry_sample();
976 }
977 }
978
979 {
980 let mut current = lock_recovering(&self.current_usage);
981 *current = total_usage.clone();
982 }
983
984 if let Some(metrics) = system_metrics {
985 let sample = telemetry_sample.unwrap_or_default();
986 let now = Instant::now();
987 let snapshot = ResourceSnapshot {
988 timestamp: now,
989 usage: total_usage,
990 system_metrics: metrics,
991 active_processes: sample.process_count,
992 memory_pressure: memory_pressure_from_sample(&sample),
993 cpu_load_average: sample
994 .load_average_per_cpu
995 .and_then(scirs2_core::numeric::NumCast::from),
996 gpu_utilization: sample
997 .gpu_utilization
998 .and_then(scirs2_core::numeric::NumCast::from),
999 };
1000
1001 {
1002 let mut history = lock_recovering(&self.usage_history);
1003 history.push(snapshot);
1004 let cutoff_time = now
1005 .checked_sub(self.monitoring_config.history_retention)
1006 .unwrap_or(now);
1007 history.retain(|s| s.timestamp >= cutoff_time);
1008 }
1009 }
1010
1011 Ok(())
1012 }
1013
1014 pub fn check_violations(&self) -> Result<Vec<ResourceViolation<T>>> {
1016 let mut all_violations = Vec::new();
1017
1018 for monitor in &self.monitors {
1019 let violations = monitor.check_limits(&self.constraints)?;
1020 all_violations.extend(violations);
1021 }
1022
1023 for violation in &all_violations {
1025 for handler in &self.alert_handlers {
1026 handler.handle_violation(violation)?;
1027 }
1028 }
1029
1030 Ok(all_violations)
1031 }
1032
1033 pub fn optimize_resources(&self) -> Result<Vec<OptimizationAction<T>>> {
1035 if !self.monitoring_config.enable_auto_optimization {
1036 return Ok(Vec::new());
1037 }
1038
1039 let current_usage = {
1040 let usage = lock_recovering(&self.current_usage);
1041 usage.clone()
1042 };
1043
1044 let mut optimization_actions = Vec::new();
1045
1046 for optimizer in &self.optimization_strategies {
1047 let action = optimizer.optimize(¤t_usage, &self.constraints)?;
1048 if action.priority >= OptimizationPriority::Medium {
1049 optimization_actions.push(action);
1050 }
1051 }
1052
1053 optimization_actions.sort_by_key(|action| std::cmp::Reverse(action.priority));
1058
1059 Ok(optimization_actions)
1060 }
1061
1062 pub fn get_current_usage(&self) -> ResourceUsage<T> {
1068 lock_recovering(&self.current_usage).clone()
1069 }
1070
1071 pub fn get_usage_history(&self) -> Vec<ResourceSnapshot<T>> {
1074 lock_recovering(&self.usage_history).clone()
1075 }
1076
1077 pub fn get_monitoring_state(&self) -> MonitoringState {
1079 self.monitoring_state
1080 }
1081
1082 pub fn set_trackers(&mut self, monitors: Vec<Box<dyn ResourceTracker<T>>>) {
1086 self.monitors = monitors;
1087 }
1088
1089 pub fn set_alert_handlers(&mut self, handlers: Vec<Box<dyn AlertHandler<T>>>) {
1091 self.alert_handlers = handlers;
1092 }
1093
1094 pub fn set_optimization_strategies(&mut self, strategies: Vec<Box<dyn ResourceOptimizer<T>>>) {
1096 self.optimization_strategies = strategies;
1097 }
1098
1099 pub fn telemetry_source_names(&self) -> Vec<&str> {
1101 self.monitors.iter().map(|monitor| monitor.name()).collect()
1102 }
1103
1104 #[cfg(test)]
1107 pub(crate) fn poison_locks_for_test(&self) {
1108 for _ in 0..2 {
1109 let usage = std::sync::Arc::clone(&self.current_usage);
1110 let history = std::sync::Arc::clone(&self.usage_history);
1111 let handle = std::thread::spawn(move || {
1112 let _usage_guard = usage.lock();
1113 let _history_guard = history.lock();
1114 panic!("deliberate panic to poison the resource-monitor mutexes");
1115 });
1116 let _ = handle.join();
1117 }
1118 debug_assert!(self.current_usage.is_poisoned());
1119 debug_assert!(self.usage_history.is_poisoned());
1120 }
1121
1122 pub fn update_constraints(&mut self, constraints: ResourceConstraints<T>) {
1124 self.constraints = constraints;
1125 }
1126
1127 pub fn generate_report(&self) -> ResourceReport<T> {
1129 let current_usage = self.get_current_usage();
1130 let history = self.get_usage_history();
1131
1132 let usage_trend = self.calculate_usage_trend(&history);
1133 let efficiency_score = self.calculate_efficiency_score(¤t_usage);
1134 let recommendations = self.generate_recommendations(¤t_usage, &history);
1135
1136 ResourceReport {
1137 timestamp: Instant::now(),
1138 current_usage,
1139 usage_trend,
1140 efficiency_score,
1141 total_samples: history.len(),
1142 monitoring_duration: match (history.first(), history.last()) {
1143 (Some(first), Some(last)) => {
1147 last.timestamp.saturating_duration_since(first.timestamp)
1148 }
1149 _ => Duration::from_secs(0),
1150 },
1151 recommendations,
1152 constraint_violations: self.check_violations().unwrap_or_default(),
1153 }
1154 }
1155
1156 fn calculate_usage_trend(&self, history: &[ResourceSnapshot<T>]) -> UsageTrend<T> {
1158 if history.len() < 2 {
1159 return UsageTrend::default();
1160 }
1161
1162 let recent_window = history.len().min(10);
1163 let recent = &history[history.len() - recent_window..];
1164
1165 let memory_trend =
1166 self.calculate_metric_trend(recent.iter().map(|s| s.usage.memory_gb).collect());
1167 let cpu_trend =
1168 self.calculate_metric_trend(recent.iter().map(|s| s.usage.cpu_time_seconds).collect());
1169 let gpu_trend =
1170 self.calculate_metric_trend(recent.iter().map(|s| s.usage.gpu_time_seconds).collect());
1171
1172 UsageTrend {
1173 memory_trend,
1174 cpu_trend,
1175 gpu_trend,
1176 energy_trend: TrendDirection::Stable, overall_trend: TrendDirection::Stable, _phantom: std::marker::PhantomData,
1179 }
1180 }
1181
1182 fn calculate_metric_trend(&self, values: Vec<T>) -> TrendDirection {
1184 if values.len() < 2 {
1185 return TrendDirection::Stable;
1186 }
1187
1188 let first_half_avg = values.iter().take(values.len() / 2).fold(
1189 scirs2_core::numeric::NumCast::from(0.0).unwrap_or_else(|| T::zero()),
1190 |acc, &x| acc + x,
1191 ) / scirs2_core::numeric::NumCast::from(values.len() / 2)
1192 .unwrap_or_else(|| T::one());
1193 let second_half_avg =
1194 values.iter().skip(values.len() / 2).fold(
1195 scirs2_core::numeric::NumCast::from(0.0).unwrap_or_else(|| T::zero()),
1196 |acc, &x| acc + x,
1197 ) / scirs2_core::numeric::NumCast::from(values.len() - values.len() / 2)
1198 .unwrap_or_else(|| T::one());
1199
1200 let change_threshold =
1201 scirs2_core::numeric::NumCast::from(0.05).unwrap_or_else(|| T::zero()); if second_half_avg > first_half_avg + change_threshold {
1204 TrendDirection::Increasing
1205 } else if second_half_avg < first_half_avg - change_threshold {
1206 TrendDirection::Decreasing
1207 } else {
1208 TrendDirection::Stable
1209 }
1210 }
1211
1212 fn calculate_efficiency_score(&self, usage: &ResourceUsage<T>) -> T {
1214 usage.efficiency_score
1216 }
1217
1218 fn generate_recommendations(
1220 &self,
1221 _usage: &ResourceUsage<T>,
1222 _history: &[ResourceSnapshot<T>],
1223 ) -> Vec<String> {
1224 vec![
1225 "Consider enabling memory optimization".to_string(),
1226 "Monitor GPU utilization for potential improvements".to_string(),
1227 "Review energy consumption patterns".to_string(),
1228 ]
1229 }
1230
1231 pub fn check_resource_violations(&self) -> Result<Vec<ResourceViolation<T>>> {
1233 let current_usage = self.get_current_usage();
1234 let mut violations = Vec::new();
1235
1236 if current_usage.memory_gb > self.constraints.max_memory_gb {
1238 violations.push(ResourceViolation {
1239 violation_type: ViolationType::MemoryExceeded,
1240 severity: ViolationSeverity::High,
1241 current_value: current_usage.memory_gb,
1242 limit_value: self.constraints.max_memory_gb,
1243 violation_time: Instant::now(),
1244 affected_resources: vec!["System Memory".to_string(), "RAM".to_string()],
1245 suggested_actions: vec![
1246 "Reduce batch size".to_string(),
1247 "Clear memory cache".to_string(),
1248 "Enable memory optimization".to_string(),
1249 ],
1250 });
1251 }
1252
1253 if current_usage.cpu_time_seconds
1255 > self.constraints.max_computation_hours
1256 * scirs2_core::numeric::NumCast::from(3600.0).unwrap_or_else(|| T::zero())
1257 {
1258 violations.push(ResourceViolation {
1259 violation_type: ViolationType::ComputationTimeExceeded,
1260 severity: ViolationSeverity::Medium,
1261 current_value: current_usage.cpu_time_seconds,
1262 limit_value: self.constraints.max_computation_hours
1263 * scirs2_core::numeric::NumCast::from(3600.0).unwrap_or_else(|| T::zero()),
1264 violation_time: Instant::now(),
1265 affected_resources: vec!["CPU".to_string(), "Computation Time".to_string()],
1266 suggested_actions: vec![
1267 "Optimize algorithms".to_string(),
1268 "Enable parallelization".to_string(),
1269 "Reduce search space".to_string(),
1270 ],
1271 });
1272 }
1273
1274 if current_usage.energy_kwh > self.constraints.max_energy_kwh {
1276 violations.push(ResourceViolation {
1277 violation_type: ViolationType::EnergyExceeded,
1278 severity: ViolationSeverity::Low,
1279 current_value: current_usage.energy_kwh,
1280 limit_value: self.constraints.max_energy_kwh,
1281 violation_time: Instant::now(),
1282 affected_resources: vec!["Energy".to_string(), "Power Consumption".to_string()],
1283 suggested_actions: vec![
1284 "Enable power-saving mode".to_string(),
1285 "Reduce GPU usage".to_string(),
1286 "Optimize computation schedule".to_string(),
1287 ],
1288 });
1289 }
1290
1291 if current_usage.cost_usd > self.constraints.max_cost_usd {
1293 violations.push(ResourceViolation {
1294 violation_type: ViolationType::CostExceeded,
1295 severity: ViolationSeverity::High,
1296 current_value: current_usage.cost_usd,
1297 limit_value: self.constraints.max_cost_usd,
1298 violation_time: Instant::now(),
1299 affected_resources: vec!["Budget".to_string(), "Cost".to_string()],
1300 suggested_actions: vec![
1301 "Stop non-critical tasks".to_string(),
1302 "Switch to cheaper resources".to_string(),
1303 "Optimize resource allocation".to_string(),
1304 ],
1305 });
1306 }
1307
1308 Ok(violations)
1309 }
1310
1311 pub fn get_usage_summary(&self) -> ResourceUsageSummary<T> {
1313 let current_usage = self.get_current_usage();
1314 let history = self.get_usage_history();
1315
1316 let mut total_cpu_hours = T::zero();
1318 let mut total_gpu_hours = T::zero();
1319 let mut total_energy_kwh = T::zero();
1320 let mut total_cost_usd = T::zero();
1321
1322 for snapshot in &history {
1323 total_cpu_hours = total_cpu_hours
1324 + snapshot.usage.cpu_time_seconds
1325 / scirs2_core::numeric::NumCast::from(3600.0).unwrap_or_else(|| T::one());
1326 total_gpu_hours = total_gpu_hours
1327 + snapshot.usage.gpu_time_seconds
1328 / scirs2_core::numeric::NumCast::from(3600.0).unwrap_or_else(|| T::one());
1329 total_energy_kwh = total_energy_kwh + snapshot.usage.energy_kwh;
1330 total_cost_usd = total_cost_usd + snapshot.usage.cost_usd;
1331 }
1332
1333 ResourceUsageSummary {
1334 total_memory_gb: current_usage.memory_gb,
1335 total_cpu_hours,
1336 total_gpu_hours,
1337 total_energy_kwh,
1338 total_cost_usd,
1339 average_efficiency: current_usage.efficiency_score,
1340 }
1341 }
1342}
1343
1344#[derive(Debug, Clone)]
1346pub struct UsageTrend<T: Float + Debug + Send + Sync + 'static> {
1347 pub memory_trend: TrendDirection,
1348 pub cpu_trend: TrendDirection,
1349 pub gpu_trend: TrendDirection,
1350 pub energy_trend: TrendDirection,
1351 pub overall_trend: TrendDirection,
1352 _phantom: PhantomData<T>,
1353}
1354
1355impl<T: Float + Debug + Send + Sync + 'static> Default for UsageTrend<T> {
1356 fn default() -> Self {
1357 Self {
1358 memory_trend: TrendDirection::Stable,
1359 cpu_trend: TrendDirection::Stable,
1360 gpu_trend: TrendDirection::Stable,
1361 energy_trend: TrendDirection::Stable,
1362 overall_trend: TrendDirection::Stable,
1363 _phantom: PhantomData,
1364 }
1365 }
1366}
1367
1368#[derive(Debug, Clone)]
1370pub struct ResourceReport<T: Float + Debug + Send + Sync + 'static> {
1371 pub timestamp: Instant,
1373
1374 pub current_usage: ResourceUsage<T>,
1376
1377 pub usage_trend: UsageTrend<T>,
1379
1380 pub efficiency_score: T,
1382
1383 pub total_samples: usize,
1385
1386 pub monitoring_duration: Duration,
1388
1389 pub recommendations: Vec<String>,
1391
1392 pub constraint_violations: Vec<ResourceViolation<T>>,
1394}
1395
1396impl Default for MonitoringConfig {
1398 fn default() -> Self {
1399 Self {
1400 monitoring_interval: Duration::from_secs(5),
1401 history_retention: Duration::from_secs(3600),
1402 enable_detailed_monitoring: true,
1403 enable_predictive_monitoring: false,
1404 alert_thresholds: AlertThresholds::default(),
1405 enable_auto_optimization: true,
1406 }
1407 }
1408}
1409
1410impl Default for AlertThresholds {
1411 fn default() -> Self {
1412 Self {
1413 memory_threshold: 0.8,
1414 cpu_threshold: 0.9,
1415 gpu_threshold: 0.9,
1416 disk_threshold: 0.9,
1417 temperature_threshold: 75.0,
1418 power_threshold: 800.0,
1419 }
1420 }
1421}
1422
1423#[cfg(test)]
1424mod tests {
1425 use super::*;
1426
1427 #[test]
1428 fn test_resource_monitor_creation() {
1429 let constraints = ResourceConstraints::default();
1430 let monitor = ResourceMonitor::<f64>::new(constraints);
1431 assert_eq!(monitor.get_monitoring_state(), MonitoringState::Stopped);
1432 }
1433
1434 #[test]
1435 fn test_system_resource_tracker() {
1436 use super::ResourceTracker;
1437
1438 let mut tracker =
1439 SystemResourceTracker::new("TestTracker".to_string(), Duration::from_secs(1));
1440
1441 assert!(<SystemResourceTracker as ResourceTracker<f64>>::initialize(&mut tracker).is_ok());
1442
1443 let usage = <SystemResourceTracker as ResourceTracker<f64>>::get_current_usage(&tracker);
1444 assert!(usage.is_ok());
1445
1446 let metrics = <SystemResourceTracker as ResourceTracker<f64>>::get_system_metrics(&tracker);
1447 assert!(metrics.is_ok());
1448
1449 assert!(<SystemResourceTracker as ResourceTracker<f64>>::cleanup(&mut tracker).is_ok());
1450 }
1451
1452 #[test]
1453 fn test_memory_optimizer() {
1454 let optimizer = MemoryOptimizer::new("TestOptimizer".to_string(), 0.5);
1455
1456 let usage = ResourceUsage {
1457 memory_gb: 20.0,
1458 cpu_time_seconds: 100.0,
1459 gpu_time_seconds: 50.0,
1460 energy_kwh: 1.0,
1461 network_io_gb: 1.0,
1462 disk_io_gb: 2.0,
1463 peak_memory_gb: 22.0,
1464 efficiency_score: 0.8,
1465 cost_usd: 0.0,
1466 network_gb: 1.0,
1467 };
1468
1469 let constraints = ResourceConstraints::default();
1470 let action = optimizer.optimize(&usage, &constraints);
1471 assert!(action.is_ok());
1472 }
1473
1474 #[test]
1475 fn test_alert_handler() {
1476 let handler = ConsoleAlertHandler::new("TestHandler".to_string(), false);
1477
1478 let violation = ResourceViolation {
1479 violation_type: ViolationType::MemoryExceeded,
1480 current_value: 25.0,
1481 limit_value: 20.0,
1482 severity: ViolationSeverity::High,
1483 violation_time: Instant::now(),
1484 affected_resources: vec!["Memory".to_string()],
1485 suggested_actions: vec!["Clear cache".to_string()],
1486 };
1487
1488 assert!(handler.handle_violation(&violation).is_ok());
1489 }
1490
1491 #[test]
1492 fn test_trend_calculation() {
1493 let constraints = ResourceConstraints::default();
1494 let monitor = ResourceMonitor::<f64>::new(constraints);
1495
1496 let values = vec![1.0, 1.1, 1.2, 1.3, 1.4];
1497 let trend = monitor.calculate_metric_trend(values);
1498 assert!(matches!(trend, TrendDirection::Increasing));
1499
1500 let stable_values = vec![1.0, 1.0, 1.0, 1.0, 1.0];
1501 let stable_trend = monitor.calculate_metric_trend(stable_values);
1502 assert!(matches!(stable_trend, TrendDirection::Stable));
1503 }
1504 use super::super::telemetry::FixedTelemetry;
1505
1506 fn monitor_with(
1508 constraints: ResourceConstraints<f64>,
1509 sample: TelemetrySample,
1510 ) -> ResourceMonitor<f64> {
1511 let mut monitor = ResourceMonitor::<f64>::new(constraints);
1512 monitor.set_trackers(vec![Box::new(SystemResourceTracker::with_telemetry(
1513 "fixed".to_string(),
1514 Duration::from_secs(1),
1515 Box::new(FixedTelemetry::new("fixed", sample)),
1516 ))]);
1517 monitor
1518 }
1519
1520 #[test]
1525 fn the_default_tracker_reports_no_fabricated_metrics() {
1526 use super::ResourceTracker;
1527 let tracker = SystemResourceTracker::new("t".to_string(), Duration::from_secs(1));
1528 assert_eq!(tracker.telemetry_name(), "std");
1529
1530 let metrics = <SystemResourceTracker as ResourceTracker<f64>>::get_system_metrics(&tracker)
1531 .expect("metrics");
1532
1533 assert_eq!(metrics.available_gpu_devices, None);
1535 assert_eq!(metrics.available_disk_gb, None);
1536 assert_eq!(metrics.network_bandwidth, None);
1537 assert_eq!(metrics.system_temperature, None);
1538 assert_eq!(metrics.power_consumption, None);
1539 assert!(metrics.available_cpu_cores.is_some_and(|cores| cores >= 1));
1541
1542 let usage = <SystemResourceTracker as ResourceTracker<f64>>::get_current_usage(&tracker)
1543 .expect("usage");
1544 assert_ne!(usage.memory_gb, 16.0);
1547 assert_ne!(usage.peak_memory_gb, 19.2);
1548 assert!(usage.memory_gb >= 0.0 && usage.memory_gb.is_finite());
1549 assert_eq!(usage.energy_kwh, 0.0);
1551 assert_eq!(usage.network_io_gb, 0.0);
1552 assert_eq!(usage.disk_io_gb, 0.0);
1553 assert_eq!(usage.cost_usd, 0.0);
1554 }
1555
1556 #[test]
1559 fn unknown_measurements_are_treated_as_unlimited() {
1560 use super::ResourceTracker;
1561 let tracker = SystemResourceTracker::with_telemetry(
1562 "blind".to_string(),
1563 Duration::from_secs(1),
1564 Box::new(FixedTelemetry::new("blind", TelemetrySample::unknown())),
1565 );
1566
1567 let mut constraints = ResourceConstraints::<f64>::default();
1569 constraints.hardware_resources.max_memory_gb = 0.001;
1570 constraints.max_memory_gb = 0.001;
1571 constraints.max_computation_hours = 0.0;
1572 constraints.max_energy_kwh = 0.0;
1573 constraints.max_cost_usd = 0.0;
1574
1575 let violations = tracker.check_limits(&constraints).expect("check_limits");
1576 assert!(
1577 violations.is_empty(),
1578 "an unmeasurable resource must never produce a violation, got {:?}",
1579 violations
1580 .iter()
1581 .map(|v| v.violation_type)
1582 .collect::<Vec<_>>()
1583 );
1584 }
1585
1586 #[test]
1589 fn measured_overruns_are_still_reported() {
1590 use super::ResourceTracker;
1591 let sample = TelemetrySample {
1592 process_memory_gb: Some(9.0),
1593 temperature_celsius: Some(95.0),
1594 power_watts: Some(1500.0),
1595 ..TelemetrySample::unknown()
1596 };
1597 let tracker = SystemResourceTracker::with_telemetry(
1598 "hot".to_string(),
1599 Duration::from_secs(1),
1600 Box::new(FixedTelemetry::new("hot", sample)),
1601 );
1602
1603 let mut constraints = ResourceConstraints::<f64>::default();
1604 constraints.hardware_resources.max_memory_gb = 4.0;
1605
1606 let violations = tracker.check_limits(&constraints).expect("check_limits");
1607 let kinds: Vec<String> = violations
1608 .iter()
1609 .map(|v| format!("{:?}", v.violation_type))
1610 .collect();
1611 assert!(kinds.contains(&"MemoryExceeded".to_string()), "{kinds:?}");
1612 assert!(
1613 kinds.contains(&"TemperatureExceeded".to_string()),
1614 "{kinds:?}"
1615 );
1616 assert!(kinds.contains(&"PowerExceeded".to_string()), "{kinds:?}");
1617
1618 constraints.hardware_resources.max_memory_gb = 64.0;
1620 let relaxed = tracker.check_limits(&constraints).expect("check_limits");
1621 assert!(!relaxed
1622 .iter()
1623 .any(|v| matches!(v.violation_type, ViolationType::MemoryExceeded)));
1624 }
1625
1626 #[test]
1630 fn update_usage_records_measured_values_and_no_hardcoded_ones() {
1631 let sample = TelemetrySample {
1632 process_memory_gb: Some(2.5),
1633 total_memory_gb: Some(16.0),
1634 available_memory_gb: Some(2.0),
1635 logical_cpus: Some(8),
1636 load_average_per_cpu: Some(0.375),
1637 process_count: Some(412),
1638 ..TelemetrySample::unknown()
1639 };
1640 let mut monitor = monitor_with(ResourceConstraints::default(), sample);
1641
1642 monitor.update_usage().expect("update while stopped");
1644 assert!(monitor.get_usage_history().is_empty());
1645
1646 monitor.start_monitoring().expect("start");
1647 monitor.update_usage().expect("update");
1648
1649 let usage = monitor.get_current_usage();
1650 assert_eq!(usage.memory_gb, 2.5, "the measured RSS must be recorded");
1651
1652 let history = monitor.get_usage_history();
1653 assert_eq!(history.len(), 1);
1654 let snapshot = &history[0];
1655 assert_eq!(snapshot.active_processes, Some(412));
1656 assert_eq!(snapshot.cpu_load_average, Some(0.375));
1657 assert_ne!(
1658 snapshot.cpu_load_average,
1659 Some(0.6),
1660 "0.6 was the hardcoded load average"
1661 );
1662 assert_eq!(
1663 snapshot.gpu_utilization, None,
1664 "GPU utilization is unmeasurable here; 0.8 was fabricated"
1665 );
1666 assert!(
1668 matches!(snapshot.memory_pressure, Some(MemoryPressure::High)),
1669 "got {:?}",
1670 snapshot.memory_pressure
1671 );
1672 assert_eq!(snapshot.system_metrics.available_cpu_cores, Some(8));
1673
1674 let summary = monitor.get_usage_summary();
1676 assert_eq!(summary.total_memory_gb, 2.5);
1677 }
1678
1679 #[test]
1680 fn a_blind_monitor_records_a_snapshot_with_everything_unknown() {
1681 let mut monitor = monitor_with(ResourceConstraints::default(), TelemetrySample::unknown());
1682 monitor.start_monitoring().expect("start");
1683 monitor.update_usage().expect("update");
1684
1685 let history = monitor.get_usage_history();
1686 assert_eq!(history.len(), 1);
1687 assert_eq!(history[0].active_processes, None);
1688 assert_eq!(history[0].memory_pressure, None);
1689 assert_eq!(history[0].cpu_load_average, None);
1690 assert_eq!(history[0].gpu_utilization, None);
1691 assert!(monitor
1692 .check_resource_violations()
1693 .expect("violations")
1694 .is_empty());
1695 }
1696
1697 #[test]
1700 fn a_poisoned_lock_is_recovered_instead_of_panicking_forever() {
1701 use std::sync::{Arc, Mutex};
1702
1703 let shared: Arc<Mutex<u32>> = Arc::new(Mutex::new(7));
1704 let poisoner = Arc::clone(&shared);
1705 let handle = std::thread::spawn(move || {
1706 let _guard = poisoner.lock().expect("first lock succeeds");
1707 panic!("deliberate panic to poison the mutex");
1708 });
1709 assert!(
1710 handle.join().is_err(),
1711 "the helper thread must have panicked"
1712 );
1713 assert!(shared.is_poisoned(), "the mutex must be poisoned");
1714
1715 assert_eq!(*lock_recovering(&shared), 7);
1717 }
1718
1719 #[test]
1720 fn getters_survive_a_poisoned_usage_lock() {
1721 let sample = TelemetrySample {
1722 process_memory_gb: Some(1.25),
1723 ..TelemetrySample::unknown()
1724 };
1725 let mut monitor = monitor_with(ResourceConstraints::default(), sample);
1726 monitor.start_monitoring().expect("start");
1727 monitor.update_usage().expect("update");
1728
1729 monitor.poison_locks_for_test();
1730
1731 assert_eq!(monitor.get_current_usage().memory_gb, 1.25);
1733 assert_eq!(monitor.get_usage_history().len(), 1);
1734 let report = monitor.generate_report();
1735 assert_eq!(report.total_samples, 1);
1736 assert!(monitor.optimize_resources().is_ok());
1737 }
1738
1739 #[derive(Debug)]
1746 struct FixedPriorityOptimizer {
1747 name: String,
1748 priority: OptimizationPriority,
1749 }
1750
1751 impl ResourceOptimizer<f64> for FixedPriorityOptimizer {
1752 fn optimize(
1753 &self,
1754 _current_usage: &ResourceUsage<f64>,
1755 _constraints: &ResourceConstraints<f64>,
1756 ) -> Result<OptimizationAction<f64>> {
1757 Ok(OptimizationAction {
1758 action_type: ActionType::ClearCaches,
1759 parameters: HashMap::new(),
1760 expected_savings: ResourceUsage::default(),
1761 implementation_cost: 0.0,
1762 description: format!("{} action", self.name),
1763 priority: self.priority,
1764 })
1765 }
1766
1767 fn name(&self) -> &str {
1768 &self.name
1769 }
1770
1771 fn priority(&self) -> OptimizationPriority {
1772 self.priority
1773 }
1774 }
1775
1776 #[test]
1777 fn optimization_actions_are_returned_most_urgent_first() {
1778 let mut monitor = monitor_with(ResourceConstraints::default(), TelemetrySample::unknown());
1779 monitor.set_optimization_strategies(vec![
1782 Box::new(FixedPriorityOptimizer {
1783 name: "medium".to_string(),
1784 priority: OptimizationPriority::Medium,
1785 }),
1786 Box::new(FixedPriorityOptimizer {
1787 name: "critical".to_string(),
1788 priority: OptimizationPriority::Critical,
1789 }),
1790 Box::new(FixedPriorityOptimizer {
1791 name: "high".to_string(),
1792 priority: OptimizationPriority::High,
1793 }),
1794 ]);
1795 monitor.start_monitoring().expect("start");
1796 monitor.update_usage().expect("update");
1797
1798 let actions = monitor.optimize_resources().expect("optimize");
1799 let priorities: Vec<OptimizationPriority> =
1800 actions.iter().map(|action| action.priority).collect();
1801 assert_eq!(
1802 priorities,
1803 vec![
1804 OptimizationPriority::Critical,
1805 OptimizationPriority::High,
1806 OptimizationPriority::Medium
1807 ],
1808 "the previous ascending `sort_by_key(|a| a.priority)` yielded \
1809 [Medium, High, Critical] — least urgent first"
1810 );
1811 }
1812
1813 #[test]
1814 fn optimization_can_be_disabled() {
1815 let mut monitor = monitor_with(ResourceConstraints::default(), TelemetrySample::unknown());
1816 monitor.set_optimization_strategies(vec![Box::new(FixedPriorityOptimizer {
1817 name: "critical".to_string(),
1818 priority: OptimizationPriority::Critical,
1819 })]);
1820 monitor.monitoring_config.enable_auto_optimization = false;
1821 assert!(monitor.optimize_resources().expect("optimize").is_empty());
1822 }
1823}