Skip to main content

optirs_nas/nas_engine/
resources.rs

1// Resource Monitoring and Management for Neural Architecture Search
2//
3// This module provides comprehensive resource monitoring, constraint enforcement,
4// and resource optimization functionality for the NAS system.
5
6use 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
18/// Resource monitor for tracking and managing system resources
19pub struct ResourceMonitor<T: Float + Debug + Send + Sync + 'static> {
20    /// Resource constraints
21    constraints: ResourceConstraints<T>,
22
23    /// Current resource usage
24    current_usage: Arc<Mutex<ResourceUsage<T>>>,
25
26    /// Resource usage history
27    usage_history: Arc<Mutex<Vec<ResourceSnapshot<T>>>>,
28
29    /// Monitoring configuration
30    monitoring_config: MonitoringConfig,
31
32    /// Resource monitors
33    monitors: Vec<Box<dyn ResourceTracker<T>>>,
34
35    /// Alert handlers
36    alert_handlers: Vec<Box<dyn AlertHandler<T>>>,
37
38    /// Monitoring state
39    monitoring_state: MonitoringState,
40
41    /// Resource optimization strategies
42    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
63/// Lock a mutex, recovering the guarded value if the mutex was poisoned (F22).
64///
65/// Every mutex in this module guards a plain data snapshot: a `ResourceUsage`
66/// total or an append-only history vector. Neither has an invariant that a
67/// panicking writer could leave broken, so `PoisonError::into_inner` is the correct
68/// recovery — the previous `lock().expect("lock poisoned")` turned one unrelated
69/// panic into a permanently panicking getter.
70fn lock_recovering<T>(mutex: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
71    mutex
72        .lock()
73        .unwrap_or_else(|poisoned| poisoned.into_inner())
74}
75
76/// Derive memory pressure from a telemetry sample, or `None` when memory is not
77/// measurable. Shared by [`SystemResourceTracker`] and [`ResourceMonitor`].
78fn 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/// Resource snapshot for history tracking
97#[derive(Debug, Clone)]
98pub struct ResourceSnapshot<T: Float + Debug + Send + Sync + 'static> {
99    /// Timestamp
100    pub timestamp: Instant,
101
102    /// Resource usage at this time
103    pub usage: ResourceUsage<T>,
104
105    /// System metrics
106    pub system_metrics: SystemMetrics<T>,
107
108    /// Number of processes visible on the system, when measurable.
109    pub active_processes: Option<usize>,
110
111    /// Memory pressure level, derived from measured available/total memory. `None`
112    /// when memory is not measurable — it used to be hard-coded to
113    /// [`MemoryPressure::Low`] regardless.
114    pub memory_pressure: Option<MemoryPressure>,
115
116    /// 1-minute load average per logical CPU, when measurable. Previously a
117    /// hard-coded `0.6`.
118    pub cpu_load_average: Option<T>,
119
120    /// GPU utilization in `[0, 1]`, when measurable. Previously a hard-coded `0.8`.
121    pub gpu_utilization: Option<T>,
122}
123
124/// System metrics.
125///
126/// Every field is `Option` because most of them cannot be measured in pure Rust
127/// (F12). `None` means **not measured**, and consumers must treat it as
128/// *unconstrained* — never as zero, and never as a limit violation. The previous
129/// version made every field mandatory, which forced
130/// [`SystemResourceTracker`] to fabricate values (32 GB memory, 4 GPUs, 65 C,
131/// 250 W) that were then compared against the caller's real constraints.
132///
133/// Supply your own [`crate::nas_engine::telemetry::TelemetrySource`] to fill in
134/// the fields the default source cannot reach.
135#[derive(Debug, Clone, Default, PartialEq)]
136pub struct SystemMetrics<T: Float + Debug + Send + Sync + 'static> {
137    /// Memory the OS reports as available for new allocations (GB).
138    pub available_memory_gb: Option<T>,
139
140    /// Logical CPUs usable by this process.
141    pub available_cpu_cores: Option<usize>,
142
143    /// GPU devices visible to this process.
144    pub available_gpu_devices: Option<usize>,
145
146    /// Free space on the working-directory filesystem (GB).
147    pub available_disk_gb: Option<T>,
148
149    /// Network bandwidth (MB/s).
150    pub network_bandwidth: Option<T>,
151
152    /// System temperature (Celsius).
153    pub system_temperature: Option<T>,
154
155    /// Power consumption (watts).
156    pub power_consumption: Option<T>,
157}
158
159/// Memory pressure levels
160#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub enum MemoryPressure {
162    Low,
163    Medium,
164    High,
165    Critical,
166}
167
168/// Monitoring configuration
169#[derive(Debug, Clone)]
170pub struct MonitoringConfig {
171    /// Monitoring interval
172    pub monitoring_interval: Duration,
173
174    /// History retention period
175    pub history_retention: Duration,
176
177    /// Enable detailed monitoring
178    pub enable_detailed_monitoring: bool,
179
180    /// Enable predictive monitoring
181    pub enable_predictive_monitoring: bool,
182
183    /// Alert thresholds
184    pub alert_thresholds: AlertThresholds,
185
186    /// Enable automatic optimization
187    pub enable_auto_optimization: bool,
188}
189
190/// Alert thresholds
191#[derive(Debug, Clone)]
192pub struct AlertThresholds {
193    /// Memory usage threshold (0.0 to 1.0)
194    pub memory_threshold: f64,
195
196    /// CPU usage threshold (0.0 to 1.0)
197    pub cpu_threshold: f64,
198
199    /// GPU usage threshold (0.0 to 1.0)
200    pub gpu_threshold: f64,
201
202    /// Disk usage threshold (0.0 to 1.0)
203    pub disk_threshold: f64,
204
205    /// Temperature threshold (Celsius)
206    pub temperature_threshold: f64,
207
208    /// Power consumption threshold (watts)
209    pub power_threshold: f64,
210}
211
212/// Monitoring state
213#[derive(Debug, Clone, Copy, PartialEq)]
214pub enum MonitoringState {
215    Stopped,
216    Starting,
217    Running,
218    Paused,
219    Stopping,
220    Error,
221}
222
223/// Resource tracker trait
224pub trait ResourceTracker<T: Float + Debug + Send + Sync + 'static>: Send + Sync {
225    /// Get current resource usage
226    fn get_current_usage(&self) -> Result<ResourceUsage<T>>;
227
228    /// Get system metrics
229    fn get_system_metrics(&self) -> Result<SystemMetrics<T>>;
230
231    /// Check if resource limits are exceeded
232    fn check_limits(
233        &self,
234        constraints: &ResourceConstraints<T>,
235    ) -> Result<Vec<ResourceViolation<T>>>;
236
237    /// Get tracker name
238    fn name(&self) -> &str;
239
240    /// Initialize tracker
241    fn initialize(&mut self) -> Result<()>;
242
243    /// Cleanup tracker
244    fn cleanup(&mut self) -> Result<()>;
245
246    /// The raw telemetry reading behind [`ResourceTracker::get_system_metrics`], if
247    /// this tracker has one.
248    ///
249    /// [`ResourceMonitor::update_usage`] uses it for the snapshot fields that are not
250    /// part of [`SystemMetrics`] (process count, load average, GPU utilization).
251    /// Trackers with no underlying sample return `None`, and those snapshot fields
252    /// then stay `None` too — which is the honest outcome.
253    fn telemetry_sample(&self) -> Option<TelemetrySample> {
254        None
255    }
256}
257
258/// Resource violation
259#[derive(Debug, Clone)]
260pub struct ResourceViolation<T: Float + Debug + Send + Sync + 'static> {
261    /// Violation type
262    pub violation_type: ViolationType,
263
264    /// Current value
265    pub current_value: T,
266
267    /// Limit value
268    pub limit_value: T,
269
270    /// Severity
271    pub severity: ViolationSeverity,
272
273    /// Violation time
274    pub violation_time: Instant,
275
276    /// Affected resources
277    pub affected_resources: Vec<String>,
278
279    /// Suggested actions
280    pub suggested_actions: Vec<String>,
281}
282
283/// Types of resource violations
284#[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/// Violation severity levels
300#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
301pub enum ViolationSeverity {
302    Low,
303    Medium,
304    High,
305    Critical,
306}
307
308/// Alert handler trait
309pub trait AlertHandler<T: Float + Debug + Send + Sync + 'static>: Send + Sync {
310    /// Handle resource violation
311    fn handle_violation(&self, violation: &ResourceViolation<T>) -> Result<()>;
312
313    /// Handle resource warning
314    fn handle_warning(&self, warning: &ResourceWarning<T>) -> Result<()>;
315
316    /// Get handler name
317    fn name(&self) -> &str;
318}
319
320/// Resource warning
321#[derive(Debug, Clone)]
322pub struct ResourceWarning<T: Float + Debug + Send + Sync + 'static> {
323    /// Warning type
324    pub warning_type: WarningType,
325
326    /// Current value
327    pub current_value: T,
328
329    /// Threshold value
330    pub threshold_value: T,
331
332    /// Trend direction
333    pub trend: TrendDirection,
334
335    /// Estimated time to violation
336    pub time_to_violation: Option<Duration>,
337
338    /// Warning message
339    pub message: String,
340}
341
342/// Types of resource warnings
343#[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/// Trend directions
356#[derive(Debug, Clone, Copy)]
357pub enum TrendDirection {
358    Increasing,
359    Decreasing,
360    Stable,
361    Volatile,
362}
363
364/// Resource optimizer trait
365pub trait ResourceOptimizer<T: Float + Debug + Send + Sync + 'static>: Send + Sync {
366    /// Optimize resource usage
367    fn optimize(
368        &self,
369        current_usage: &ResourceUsage<T>,
370        constraints: &ResourceConstraints<T>,
371    ) -> Result<OptimizationAction<T>>;
372
373    /// Get optimizer name
374    fn name(&self) -> &str;
375
376    /// Get optimization priority
377    fn priority(&self) -> OptimizationPriority;
378}
379
380/// Optimization actions
381#[derive(Debug, Clone)]
382pub struct OptimizationAction<T: Float + Debug + Send + Sync + 'static> {
383    /// Action type
384    pub action_type: ActionType,
385
386    /// Action parameters
387    pub parameters: HashMap<String, T>,
388
389    /// Expected resource savings
390    pub expected_savings: ResourceUsage<T>,
391
392    /// Implementation cost
393    pub implementation_cost: T,
394
395    /// Action description
396    pub description: String,
397
398    /// Priority
399    pub priority: OptimizationPriority,
400}
401
402/// Types of optimization actions
403#[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/// Optimization priorities
418#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
419pub enum OptimizationPriority {
420    Low,
421    Medium,
422    High,
423    Critical,
424}
425
426/// System resource tracker backed by an injectable
427/// [`TelemetrySource`] (F12).
428///
429/// It previously returned hard-coded readings (32 GB memory / 16 GB used, 60% CPU,
430/// 4 GPUs at 80%, 1 TB disk, 1 GB/s network, 65 C, 250 W) from seven
431/// `get_*_info` helpers, each commented "in a real implementation, this would
432/// query system APIs". Those numbers reached [`ResourceTracker::check_limits`] and
433/// were compared against the caller's real constraints, so a tight memory budget
434/// aborted the search on invented data.
435///
436/// Now it reports whatever its source can actually measure and `None` for the rest,
437/// and `check_limits` **skips** any check whose measurement is missing.
438pub struct SystemResourceTracker {
439    /// Tracker name
440    name: String,
441
442    /// Monitoring interval
443    monitoring_interval: Duration,
444
445    /// Last update time
446    last_update: Instant,
447
448    /// Telemetry source. Defaults to
449    /// [`StdTelemetry`](crate::nas_engine::telemetry::StdTelemetry).
450    telemetry: Box<dyn TelemetrySource>,
451
452    /// Most recent reading, refreshed on every query.
453    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    /// Create a tracker using the default (honest, sparse) telemetry source.
469    pub fn new(name: String, monitoring_interval: Duration) -> Self {
470        Self::with_telemetry(name, monitoring_interval, Box::new(StdTelemetry::new()))
471    }
472
473    /// Create a tracker backed by a caller-supplied telemetry source. This is how
474    /// GPU / thermal / power constraints become enforceable: provide a source that
475    /// can measure them.
476    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    /// Name of the telemetry source in use.
491    pub fn telemetry_name(&self) -> &str {
492        self.telemetry.name()
493    }
494
495    /// The most recent reading, if one has been taken.
496    pub fn last_sample(&self) -> Option<&TelemetrySample> {
497        self.cached_sample.as_ref()
498    }
499
500    /// Take a fresh reading.
501    pub fn sample(&self) -> TelemetrySample {
502        self.telemetry.sample()
503    }
504
505    /// Memory pressure derived from the current reading, or `None` when memory is
506    /// not measurable here.
507    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    /// Resource usage attributable to **this search**.
517    ///
518    /// Only the process's own resident set size is reported, and only where it can
519    /// be measured; everything else stays at the accumulator identity (`0`), which
520    /// is honest for a *usage* total that nothing has been attributed to. The
521    /// previous version reported system-wide invented figures here, which is a
522    /// category error as well as a fabrication: `ResourceUsage` is compared against
523    /// a budget for the search, so charging it for the whole machine aborts the
524    /// search whenever the host is busy.
525    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    /// Report only violations backed by an actual measurement (F12).
554    ///
555    /// Every check is guarded by the presence of its measurement, so an
556    /// unmeasurable resource is treated as **unlimited** and can never abort a
557    /// search. This is the whole point: `check_violations` is called from
558    /// `NeuralArchitectureSearch::should_stop_search` on every iteration, so a
559    /// fabricated reading here terminated real searches.
560    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        // Memory: this process's RSS against the hardware memory budget.
568        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        // Power: only when a source can actually read it.
592        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        // Temperature: likewise.
615        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
678/// Package power above which a [`ViolationType::PowerExceeded`] is reported —
679/// checked **only** when a telemetry source can actually read power draw.
680const POWER_LIMIT_WATTS: f64 = 1000.0;
681
682/// Die temperature above which a [`ViolationType::TemperatureExceeded`] is
683/// reported — checked only when a source can actually read temperature.
684const TEMPERATURE_LIMIT_CELSIUS: f64 = 80.0;
685
686/// Console alert handler implementation
687pub struct ConsoleAlertHandler {
688    /// Handler name
689    name: String,
690
691    /// Enable verbose logging
692    verbose: bool,
693}
694
695impl ConsoleAlertHandler {
696    /// Create a new console alert handler
697    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    /// Report a violation through `log`, at a level matching its severity (F24).
707    ///
708    /// This used to `println!` unconditionally, which writes to a library consumer's
709    /// stdout with no way to filter or redirect it.
710    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    /// Report a warning through `log` (F24).
737    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
762/// Memory optimizer implementation
763pub struct MemoryOptimizer {
764    /// Optimizer name
765    name: String,
766
767    /// Optimization aggressiveness
768    aggressiveness: f64,
769}
770
771impl MemoryOptimizer {
772    /// Create a new memory optimizer
773    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    /// Create a new resource monitor
864    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), // 1 hour
868            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    /// Start resource monitoring
908    pub fn start_monitoring(&mut self) -> Result<()> {
909        self.monitoring_state = MonitoringState::Starting;
910
911        // Initialize all monitors
912        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    /// Stop resource monitoring
925    pub fn stop_monitoring(&mut self) -> Result<()> {
926        self.monitoring_state = MonitoringState::Stopping;
927
928        // Cleanup all monitors
929        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    /// Sample every tracker and record a snapshot (F12).
939    ///
940    /// This is called once per generation from
941    /// `NeuralArchitectureSearch::check_resource_constraints`; before that wiring it
942    /// was never called at all, so `current_usage` stayed at
943    /// `ResourceUsage::default()` forever and `check_resource_violations` could
944    /// never fire.
945    ///
946    /// The snapshot's derived fields are all measured or absent: `active_processes`,
947    /// `memory_pressure`, `cpu_load_average` and `gpu_utilization` used to be
948    /// hard-coded to `1` / `Low` / `0.6` / `0.8`.
949    ///
950    /// The clock is read once and reused for the timestamp and the retention cutoff,
951    /// so a snapshot cannot be pruned by an `Instant::now()` taken microseconds later.
952    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    /// Check for resource violations
1015    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        // Handle violations
1024        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    /// Optimize resource usage
1034    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(&current_usage, &self.constraints)?;
1048            if action.priority >= OptimizationPriority::Medium {
1049                optimization_actions.push(action);
1050            }
1051        }
1052
1053        // Most urgent first (F25). `OptimizationPriority` derives `Ord` with
1054        // `Low < Medium < High < Critical`, so the previous ascending
1055        // `sort_by_key(|a| a.priority)` put the *least* urgent action at the front —
1056        // the opposite of what a caller applying a prefix of the list needs.
1057        optimization_actions.sort_by_key(|action| std::cmp::Reverse(action.priority));
1058
1059        Ok(optimization_actions)
1060    }
1061
1062    /// Get current resource usage.
1063    ///
1064    /// Recovers from a poisoned mutex instead of panicking (F22): the guarded data
1065    /// is a plain snapshot with no invariants that a panicking writer could have
1066    /// broken, so one panic elsewhere must not make this getter panic forever.
1067    pub fn get_current_usage(&self) -> ResourceUsage<T> {
1068        lock_recovering(&self.current_usage).clone()
1069    }
1070
1071    /// Get usage history. Poison-recovering, for the same reason as
1072    /// [`ResourceMonitor::get_current_usage`].
1073    pub fn get_usage_history(&self) -> Vec<ResourceSnapshot<T>> {
1074        lock_recovering(&self.usage_history).clone()
1075    }
1076
1077    /// Get monitoring state
1078    pub fn get_monitoring_state(&self) -> MonitoringState {
1079        self.monitoring_state
1080    }
1081
1082    /// Replace the resource trackers. This is the injection point for real
1083    /// telemetry: hand in a [`SystemResourceTracker::with_telemetry`] built over a
1084    /// source that can measure what the default cannot (F12).
1085    pub fn set_trackers(&mut self, monitors: Vec<Box<dyn ResourceTracker<T>>>) {
1086        self.monitors = monitors;
1087    }
1088
1089    /// Replace the alert handlers.
1090    pub fn set_alert_handlers(&mut self, handlers: Vec<Box<dyn AlertHandler<T>>>) {
1091        self.alert_handlers = handlers;
1092    }
1093
1094    /// Replace the optimization strategies.
1095    pub fn set_optimization_strategies(&mut self, strategies: Vec<Box<dyn ResourceOptimizer<T>>>) {
1096        self.optimization_strategies = strategies;
1097    }
1098
1099    /// Names of the telemetry sources currently backing this monitor.
1100    pub fn telemetry_source_names(&self) -> Vec<&str> {
1101        self.monitors.iter().map(|monitor| monitor.name()).collect()
1102    }
1103
1104    /// Deliberately poison both internal mutexes, so a test can prove the getters
1105    /// recover (F22) rather than panicking forever.
1106    #[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    /// Update constraints
1123    pub fn update_constraints(&mut self, constraints: ResourceConstraints<T>) {
1124        self.constraints = constraints;
1125    }
1126
1127    /// Generate resource report
1128    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(&current_usage);
1134        let recommendations = self.generate_recommendations(&current_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                // `duration_since` on `Instant` panics if the argument is later;
1144                // history is append-only and monotonic, but `saturating_duration_since`
1145                // makes that structural rather than an assumption.
1146                (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    /// Calculate usage trend
1157    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,  // Simplified
1177            overall_trend: TrendDirection::Stable, // Simplified
1178            _phantom: std::marker::PhantomData,
1179        }
1180    }
1181
1182    /// Calculate metric trend
1183    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()); // 5% change threshold
1202
1203        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    /// Calculate efficiency score
1213    fn calculate_efficiency_score(&self, usage: &ResourceUsage<T>) -> T {
1214        // Simplified efficiency calculation
1215        usage.efficiency_score
1216    }
1217
1218    /// Generate recommendations
1219    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    /// Check for resource constraint violations
1232    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        // Check memory constraints
1237        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        // Check CPU time constraints
1254        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        // Check energy constraints
1275        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        // Check cost constraints
1292        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    /// Get a summary of resource usage
1312    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        // Calculate totals from history
1317        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/// Resource usage trend
1345#[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/// Resource monitoring report
1369#[derive(Debug, Clone)]
1370pub struct ResourceReport<T: Float + Debug + Send + Sync + 'static> {
1371    /// Report timestamp
1372    pub timestamp: Instant,
1373
1374    /// Current resource usage
1375    pub current_usage: ResourceUsage<T>,
1376
1377    /// Usage trends
1378    pub usage_trend: UsageTrend<T>,
1379
1380    /// Efficiency score
1381    pub efficiency_score: T,
1382
1383    /// Total samples in history
1384    pub total_samples: usize,
1385
1386    /// Monitoring duration
1387    pub monitoring_duration: Duration,
1388
1389    /// Optimization recommendations
1390    pub recommendations: Vec<String>,
1391
1392    /// Current constraint violations
1393    pub constraint_violations: Vec<ResourceViolation<T>>,
1394}
1395
1396// Default implementations
1397impl 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    /// Build a monitor whose single tracker reports exactly `sample`.
1507    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    // ---- F12: no fabricated telemetry -----------------------------------
1521
1522    /// The default tracker must not report the old invented constants, and every
1523    /// unmeasurable metric must be `None`.
1524    #[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        // These were 4 GPUs, 1 TB disk (500 GB free), 1 GB/s, 65 C and 250 W.
1534        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        // CPU count is genuinely measurable.
1540        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        // Memory was a fixed 16.0 GB with a 19.2 GB "peak"; now it is either a real
1545        // measurement or the accumulator identity.
1546        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        // These were 0.25 kWh, 1 GB network, 2 GB disk I/O, 0.8 efficiency, $0.50.
1550        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    /// The core F12 hazard: a tight constraint must not abort a search on the
1557    /// strength of an unmeasured resource.
1558    #[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        // An absurdly tight budget on every axis.
1568        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    /// A measured value over budget still must be reported — the guard must not
1587    /// have disabled enforcement altogether.
1588    #[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        // A generous budget produces no memory violation.
1619        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    /// `update_usage` must actually record what the tracker measured, and the
1627    /// snapshot's derived fields must be measured or `None` — never the old
1628    /// hard-coded `1` / `Low` / `0.6` / `0.8`.
1629    #[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        // Nothing is recorded while monitoring is stopped.
1643        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        // 14/16 = 87.5% used -> High, derived, not the hardcoded Low.
1667        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        // The summary now reflects real data instead of an untouched default.
1675        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    // ---- F22: poisoned locks are recovered, not fatal --------------------
1698
1699    #[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        // `lock().expect("lock poisoned")` would panic here forever.
1716        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        // Every getter must still work.
1732        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    // ---- F25: priority ordering ------------------------------------------
1740
1741    /// An optimizer that always emits an action at a chosen priority, so the sort
1742    /// direction can be observed. `MemoryOptimizer` alone cannot do this: it emits
1743    /// either `High` or `Low`, and `optimize_resources` filters `Low` out, so every
1744    /// surviving action has the same priority and any ordering passes.
1745    #[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        // Registered in an order that an ascending sort would preserve and a
1780        // descending sort must reverse.
1781        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}