chie_core/
resource_mgmt.rs

1//! Advanced resource management and monitoring.
2//!
3//! This module provides resource tracking, quota enforcement, and automatic throttling
4//! for CPU, memory, disk I/O, and network bandwidth. It helps ensure the node operates
5//! within system constraints and prevents resource exhaustion.
6//!
7//! # Example
8//!
9//! ```rust
10//! use chie_core::resource_mgmt::{ResourceMonitor, ResourceLimits, ResourceType};
11//!
12//! #[tokio::main]
13//! async fn main() {
14//!     let limits = ResourceLimits::default();
15//!     let mut monitor = ResourceMonitor::new(limits);
16//!
17//!     // Check if we can allocate memory
18//!     if monitor.can_allocate(ResourceType::Memory, 1024 * 1024 * 100) {
19//!         println!("Can allocate 100MB");
20//!         monitor.record_allocation(ResourceType::Memory, 1024 * 1024 * 100);
21//!     }
22//!
23//!     // Get usage statistics
24//!     if let Some(stats) = monitor.get_stats(ResourceType::Memory) {
25//!         println!("Memory usage: {}/{}", stats.used, stats.limit);
26//!     }
27//! }
28//! ```
29
30use std::{
31    collections::HashMap,
32    sync::{Arc, Mutex},
33    time::{Duration, Instant},
34};
35use sysinfo::{CpuRefreshKind, MemoryRefreshKind, ProcessRefreshKind, RefreshKind, System};
36use tokio::task::JoinHandle;
37
38/// Type of system resource.
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40pub enum ResourceType {
41    /// CPU usage (in percent, 0-100).
42    Cpu,
43    /// Memory usage (in bytes).
44    Memory,
45    /// Disk I/O (in bytes/sec).
46    DiskIo,
47    /// Network bandwidth (in bytes/sec).
48    NetworkBandwidth,
49}
50
51/// Resource usage limits.
52#[derive(Debug, Clone)]
53pub struct ResourceLimits {
54    /// Maximum CPU usage percentage (0-100).
55    pub max_cpu_percent: u32,
56    /// Maximum memory in bytes.
57    pub max_memory_bytes: u64,
58    /// Maximum disk I/O in bytes/sec.
59    pub max_disk_io_bps: u64,
60    /// Maximum network bandwidth in bytes/sec.
61    pub max_network_bps: u64,
62    /// Enable automatic throttling when limits are approached.
63    pub auto_throttle: bool,
64    /// Throttle threshold (0.0 to 1.0) - at what utilization to start throttling.
65    pub throttle_threshold: f64,
66}
67
68impl Default for ResourceLimits {
69    #[inline]
70    fn default() -> Self {
71        Self {
72            max_cpu_percent: 80,
73            max_memory_bytes: 4 * 1024 * 1024 * 1024, // 4 GB
74            max_disk_io_bps: 100 * 1024 * 1024,       // 100 MB/s
75            max_network_bps: 100 * 1024 * 1024,       // 100 MB/s
76            auto_throttle: true,
77            throttle_threshold: 0.8, // 80%
78        }
79    }
80}
81
82/// Degradation level for graceful handling of resource pressure.
83#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
84pub enum DegradationLevel {
85    /// Normal operation - no degradation.
86    None = 0,
87    /// Minor degradation - reduce non-critical operations.
88    Minor = 1,
89    /// Moderate degradation - disable background tasks, reduce cache.
90    Moderate = 2,
91    /// Severe degradation - minimal operations only.
92    Severe = 3,
93    /// Critical - emergency mode, reject new requests.
94    Critical = 4,
95}
96
97impl DegradationLevel {
98    /// Get a descriptive message for this degradation level.
99    #[must_use]
100    pub const fn description(&self) -> &'static str {
101        match self {
102            Self::None => "Normal operation",
103            Self::Minor => "Minor resource pressure - reducing non-critical operations",
104            Self::Moderate => "Moderate resource pressure - disabling background tasks",
105            Self::Severe => "Severe resource pressure - minimal operations only",
106            Self::Critical => "Critical resource exhaustion - emergency mode",
107        }
108    }
109
110    /// Check if this level requires action.
111    #[must_use]
112    #[inline]
113    pub const fn requires_action(&self) -> bool {
114        !matches!(self, Self::None)
115    }
116
117    /// Get recommended cache size multiplier for this degradation level.
118    #[must_use]
119    #[inline]
120    pub const fn cache_multiplier(&self) -> f64 {
121        match self {
122            Self::None => 1.0,
123            Self::Minor => 0.8,
124            Self::Moderate => 0.5,
125            Self::Severe => 0.2,
126            Self::Critical => 0.0,
127        }
128    }
129
130    /// Get recommended concurrency limit multiplier.
131    #[must_use]
132    #[inline]
133    pub const fn concurrency_multiplier(&self) -> f64 {
134        match self {
135            Self::None => 1.0,
136            Self::Minor => 0.8,
137            Self::Moderate => 0.5,
138            Self::Severe => 0.25,
139            Self::Critical => 0.1,
140        }
141    }
142}
143
144impl Default for DegradationLevel {
145    #[inline]
146    fn default() -> Self {
147        Self::None
148    }
149}
150
151/// Resource usage statistics.
152#[derive(Debug, Clone, Default)]
153pub struct ResourceStats {
154    /// Current usage.
155    pub used: u64,
156    /// Configured limit.
157    pub limit: u64,
158    /// Peak usage observed.
159    pub peak: u64,
160    /// Number of allocation requests.
161    pub allocations: u64,
162    /// Number of deallocation requests.
163    pub deallocations: u64,
164    /// Number of times limit was exceeded.
165    pub limit_exceeded_count: u64,
166}
167
168impl ResourceStats {
169    /// Calculate utilization percentage (0.0 to 1.0).
170    #[inline]
171    #[must_use]
172    pub fn utilization(&self) -> f64 {
173        if self.limit == 0 {
174            return 0.0;
175        }
176        (self.used as f64) / (self.limit as f64)
177    }
178
179    /// Check if usage exceeds threshold.
180    #[inline]
181    #[must_use]
182    pub fn exceeds_threshold(&self, threshold: f64) -> bool {
183        self.utilization() > threshold
184    }
185
186    /// Check if at or over limit.
187    #[inline]
188    #[must_use]
189    pub const fn is_at_limit(&self) -> bool {
190        self.used >= self.limit
191    }
192
193    /// Calculate available capacity.
194    #[inline]
195    #[must_use]
196    pub const fn available(&self) -> u64 {
197        self.limit.saturating_sub(self.used)
198    }
199}
200
201/// Resource allocation record.
202#[derive(Debug, Clone)]
203struct AllocationRecord {
204    amount: u64,
205    timestamp: Instant,
206}
207
208/// Resource monitor for tracking and limiting resource usage.
209pub struct ResourceMonitor {
210    limits: ResourceLimits,
211    /// Usage statistics per resource type.
212    stats: Arc<Mutex<HashMap<ResourceType, ResourceStats>>>,
213    /// Recent allocations for rate calculation.
214    recent_allocations: Arc<Mutex<HashMap<ResourceType, Vec<AllocationRecord>>>>,
215    /// Throttling state.
216    throttled: Arc<Mutex<HashMap<ResourceType, bool>>>,
217    /// System information for actual resource sampling.
218    system: Arc<Mutex<System>>,
219    /// Current degradation level.
220    degradation_level: Arc<Mutex<DegradationLevel>>,
221}
222
223impl ResourceMonitor {
224    /// Create a new resource monitor with the given limits.
225    #[must_use]
226    pub fn new(limits: ResourceLimits) -> Self {
227        let mut stats = HashMap::new();
228        stats.insert(
229            ResourceType::Cpu,
230            ResourceStats {
231                limit: u64::from(limits.max_cpu_percent),
232                ..Default::default()
233            },
234        );
235        stats.insert(
236            ResourceType::Memory,
237            ResourceStats {
238                limit: limits.max_memory_bytes,
239                ..Default::default()
240            },
241        );
242        stats.insert(
243            ResourceType::DiskIo,
244            ResourceStats {
245                limit: limits.max_disk_io_bps,
246                ..Default::default()
247            },
248        );
249        stats.insert(
250            ResourceType::NetworkBandwidth,
251            ResourceStats {
252                limit: limits.max_network_bps,
253                ..Default::default()
254            },
255        );
256
257        let system = System::new_with_specifics(
258            RefreshKind::nothing()
259                .with_memory(MemoryRefreshKind::everything())
260                .with_cpu(CpuRefreshKind::everything())
261                .with_processes(ProcessRefreshKind::everything()),
262        );
263
264        Self {
265            limits,
266            stats: Arc::new(Mutex::new(stats)),
267            recent_allocations: Arc::new(Mutex::new(HashMap::new())),
268            throttled: Arc::new(Mutex::new(HashMap::new())),
269            system: Arc::new(Mutex::new(system)),
270            degradation_level: Arc::new(Mutex::new(DegradationLevel::None)),
271        }
272    }
273
274    /// Check if a resource allocation can be made.
275    #[must_use]
276    #[inline]
277    pub fn can_allocate(&self, resource_type: ResourceType, amount: u64) -> bool {
278        let stats = self.stats.lock().unwrap();
279        if let Some(stat) = stats.get(&resource_type) {
280            stat.used + amount <= stat.limit
281        } else {
282            false
283        }
284    }
285
286    /// Record a resource allocation.
287    pub fn record_allocation(&mut self, resource_type: ResourceType, amount: u64) {
288        {
289            let mut stats = self.stats.lock().unwrap();
290            if let Some(stat) = stats.get_mut(&resource_type) {
291                stat.used += amount;
292                stat.allocations += 1;
293
294                if stat.used > stat.peak {
295                    stat.peak = stat.used;
296                }
297
298                if stat.used > stat.limit {
299                    stat.limit_exceeded_count += 1;
300                }
301            }
302        } // Drop stats lock here
303
304        // Record for rate tracking
305        {
306            let mut recent = self.recent_allocations.lock().unwrap();
307            recent
308                .entry(resource_type)
309                .or_default()
310                .push(AllocationRecord {
311                    amount,
312                    timestamp: Instant::now(),
313                });
314        } // Drop recent lock here
315
316        // Update throttling state (acquires stats lock internally)
317        self.update_throttling(resource_type);
318    }
319
320    /// Record a resource deallocation.
321    pub fn record_deallocation(&mut self, resource_type: ResourceType, amount: u64) {
322        {
323            let mut stats = self.stats.lock().unwrap();
324            if let Some(stat) = stats.get_mut(&resource_type) {
325                stat.used = stat.used.saturating_sub(amount);
326                stat.deallocations += 1;
327            }
328        } // Drop stats lock here
329
330        // Update throttling state (acquires stats lock internally)
331        self.update_throttling(resource_type);
332    }
333
334    /// Update current usage (for absolute measurements like CPU).
335    pub fn update_usage(&mut self, resource_type: ResourceType, current: u64) {
336        {
337            let mut stats = self.stats.lock().unwrap();
338            if let Some(stat) = stats.get_mut(&resource_type) {
339                stat.used = current;
340
341                if current > stat.peak {
342                    stat.peak = current;
343                }
344
345                if current > stat.limit {
346                    stat.limit_exceeded_count += 1;
347                }
348            }
349        } // Drop stats lock here
350
351        // Update throttling state (acquires stats lock internally)
352        self.update_throttling(resource_type);
353    }
354
355    /// Update throttling state based on current usage.
356    #[inline]
357    fn update_throttling(&self, resource_type: ResourceType) {
358        if !self.limits.auto_throttle {
359            return;
360        }
361
362        let stats = self.stats.lock().unwrap();
363        if let Some(stat) = stats.get(&resource_type) {
364            let should_throttle = stat.exceeds_threshold(self.limits.throttle_threshold);
365            let mut throttled = self.throttled.lock().unwrap();
366            throttled.insert(resource_type, should_throttle);
367        }
368    }
369
370    /// Check if a resource type is currently throttled.
371    #[inline]
372    #[must_use]
373    pub fn is_throttled(&self, resource_type: ResourceType) -> bool {
374        self.throttled
375            .lock()
376            .unwrap()
377            .get(&resource_type)
378            .copied()
379            .unwrap_or(false)
380    }
381
382    /// Get usage statistics for a resource type.
383    #[must_use]
384    #[inline]
385    pub fn get_stats(&self, resource_type: ResourceType) -> Option<ResourceStats> {
386        self.stats.lock().unwrap().get(&resource_type).cloned()
387    }
388
389    /// Get all resource statistics.
390    #[must_use]
391    #[inline]
392    pub fn get_all_stats(&self) -> HashMap<ResourceType, ResourceStats> {
393        self.stats.lock().unwrap().clone()
394    }
395
396    /// Calculate recent allocation rate (bytes/sec or percent/sec).
397    #[must_use]
398    #[inline]
399    pub fn get_allocation_rate(&self, resource_type: ResourceType, window: Duration) -> u64 {
400        let recent = self.recent_allocations.lock().unwrap();
401        if let Some(records) = recent.get(&resource_type) {
402            let cutoff = Instant::now() - window;
403            let total: u64 = records
404                .iter()
405                .filter(|r| r.timestamp > cutoff)
406                .map(|r| r.amount)
407                .sum();
408
409            // Convert to rate per second
410            (total as f64 / window.as_secs_f64()) as u64
411        } else {
412            0
413        }
414    }
415
416    /// Clean old allocation records (older than specified duration).
417    pub fn cleanup_old_records(&mut self, older_than: Duration) {
418        let mut recent = self.recent_allocations.lock().unwrap();
419        let cutoff = Instant::now() - older_than;
420
421        for records in recent.values_mut() {
422            records.retain(|r| r.timestamp > cutoff);
423        }
424    }
425
426    /// Reset all statistics.
427    pub fn reset_stats(&mut self) {
428        let mut stats = self.stats.lock().unwrap();
429        for stat in stats.values_mut() {
430            stat.used = 0;
431            stat.peak = 0;
432            stat.allocations = 0;
433            stat.deallocations = 0;
434            stat.limit_exceeded_count = 0;
435        }
436    }
437
438    /// Get overall system health score (0.0 to 1.0).
439    #[must_use]
440    #[inline]
441    pub fn health_score(&self) -> f64 {
442        let stats = self.stats.lock().unwrap();
443        let mut total_utilization = 0.0;
444        let mut count = 0;
445
446        for stat in stats.values() {
447            total_utilization += stat.utilization();
448            count += 1;
449        }
450
451        if count == 0 {
452            return 1.0;
453        }
454
455        // Health score is inverse of utilization (lower utilization = better health)
456        1.0 - (total_utilization / count as f64)
457    }
458
459    /// Check if any resource is over limit.
460    #[must_use]
461    #[inline]
462    pub fn is_over_limit(&self) -> bool {
463        let stats = self.stats.lock().unwrap();
464        stats.values().any(|s| s.is_at_limit())
465    }
466
467    /// Calculate the appropriate degradation level based on current resource usage.
468    ///
469    /// This method analyzes resource utilization and determines what level of
470    /// degradation is appropriate to maintain system stability.
471    #[must_use]
472    #[inline]
473    pub fn calculate_degradation_level(&self) -> DegradationLevel {
474        let stats = self.stats.lock().unwrap();
475
476        // Find the maximum utilization across all resources
477        let max_utilization = stats
478            .values()
479            .map(|s| s.utilization())
480            .fold(0.0f64, f64::max);
481
482        // Determine degradation level based on utilization thresholds
483        if max_utilization >= 0.95 {
484            DegradationLevel::Critical
485        } else if max_utilization >= 0.90 {
486            DegradationLevel::Severe
487        } else if max_utilization >= 0.85 {
488            DegradationLevel::Moderate
489        } else if max_utilization >= 0.80 {
490            DegradationLevel::Minor
491        } else {
492            DegradationLevel::None
493        }
494    }
495
496    /// Update the degradation level based on current resource usage.
497    ///
498    /// This should be called periodically to adjust system behavior
499    /// based on resource pressure.
500    pub fn update_degradation_level(&mut self) {
501        let new_level = self.calculate_degradation_level();
502        let mut current_level = self.degradation_level.lock().unwrap();
503
504        if new_level != *current_level {
505            *current_level = new_level;
506            // In a production system, this would emit an event or log
507            eprintln!(
508                "Resource degradation level changed to: {:?} - {}",
509                new_level,
510                new_level.description()
511            );
512        }
513    }
514
515    /// Get the current degradation level.
516    #[must_use]
517    #[inline]
518    pub fn degradation_level(&self) -> DegradationLevel {
519        *self.degradation_level.lock().unwrap()
520    }
521
522    /// Check if the system should accept new requests based on degradation level.
523    #[must_use]
524    #[inline]
525    pub fn should_accept_requests(&self) -> bool {
526        self.degradation_level() != DegradationLevel::Critical
527    }
528
529    /// Check if background tasks should run based on degradation level.
530    #[must_use]
531    #[inline]
532    pub fn should_run_background_tasks(&self) -> bool {
533        matches!(
534            self.degradation_level(),
535            DegradationLevel::None | DegradationLevel::Minor
536        )
537    }
538
539    /// Get recommended cache size based on degradation level.
540    #[must_use]
541    #[inline]
542    pub fn recommended_cache_size(&self, base_size: usize) -> usize {
543        let multiplier = self.degradation_level().cache_multiplier();
544        ((base_size as f64) * multiplier) as usize
545    }
546
547    /// Get recommended concurrency limit based on degradation level.
548    #[must_use]
549    #[inline]
550    pub fn recommended_concurrency(&self, base_concurrency: usize) -> usize {
551        let multiplier = self.degradation_level().concurrency_multiplier();
552        ((base_concurrency as f64) * multiplier).max(1.0) as usize
553    }
554
555    /// Sample actual system CPU usage and update statistics.
556    ///
557    /// This method refreshes CPU usage from the operating system and updates
558    /// the internal statistics. Returns the current CPU usage percentage (0-100).
559    /// Also updates degradation level based on new resource usage.
560    #[must_use]
561    pub fn sample_cpu_usage(&mut self) -> f32 {
562        let mut sys = self.system.lock().unwrap();
563        sys.refresh_cpu_usage();
564
565        // Calculate average CPU usage across all cores
566        let cpus = sys.cpus();
567        let cpu_usage = if cpus.is_empty() {
568            0.0
569        } else {
570            cpus.iter().map(|cpu| cpu.cpu_usage()).sum::<f32>() / cpus.len() as f32
571        };
572
573        // Update stats with current CPU usage (convert percentage to integer for storage)
574        drop(sys); // Release lock before calling update_usage
575        self.update_usage(ResourceType::Cpu, cpu_usage as u64);
576
577        // Update degradation level based on new usage
578        self.update_degradation_level();
579
580        cpu_usage
581    }
582
583    /// Sample actual system memory usage and update statistics.
584    ///
585    /// This method refreshes memory usage from the operating system and updates
586    /// the internal statistics. Returns the current memory usage in bytes.
587    /// Also updates degradation level based on new resource usage.
588    #[must_use]
589    pub fn sample_memory_usage(&mut self) -> u64 {
590        let mut sys = self.system.lock().unwrap();
591        sys.refresh_memory();
592
593        // Get used memory in bytes
594        let used_memory = sys.used_memory();
595
596        // Update stats
597        drop(sys); // Release lock before calling update_usage
598        self.update_usage(ResourceType::Memory, used_memory);
599
600        // Update degradation level based on new usage
601        self.update_degradation_level();
602
603        used_memory
604    }
605
606    /// Sample all system resources (CPU and memory) at once.
607    ///
608    /// This is more efficient than calling individual sample methods separately.
609    /// Returns a tuple of (cpu_usage_percent, memory_used_bytes).
610    /// Also updates degradation level based on new resource usage.
611    #[must_use]
612    pub fn sample_all_system_resources(&mut self) -> (f32, u64) {
613        let mut sys = self.system.lock().unwrap();
614        sys.refresh_cpu_usage();
615        sys.refresh_memory();
616
617        // Calculate average CPU usage
618        let cpus = sys.cpus();
619        let cpu_usage = if cpus.is_empty() {
620            0.0
621        } else {
622            cpus.iter().map(|cpu| cpu.cpu_usage()).sum::<f32>() / cpus.len() as f32
623        };
624        let memory_used = sys.used_memory();
625
626        drop(sys); // Release lock before updating stats
627
628        // Update both stats
629        self.update_usage(ResourceType::Cpu, cpu_usage as u64);
630        self.update_usage(ResourceType::Memory, memory_used);
631
632        // Update degradation level based on new usage
633        self.update_degradation_level();
634
635        (cpu_usage, memory_used)
636    }
637
638    /// Get total system memory in bytes.
639    #[must_use]
640    #[inline]
641    pub fn total_system_memory(&self) -> u64 {
642        self.system.lock().unwrap().total_memory()
643    }
644
645    /// Get number of CPU cores.
646    #[must_use]
647    #[inline]
648    pub fn cpu_count(&self) -> usize {
649        self.system.lock().unwrap().cpus().len()
650    }
651
652    /// Predict future resource usage based on recent trends.
653    ///
654    /// Uses simple linear regression on recent allocation history to predict
655    /// usage at a future time.
656    ///
657    /// # Arguments
658    ///
659    /// * `resource_type` - The resource type to predict
660    /// * `window` - Duration of historical data to analyze
661    /// * `forecast_duration` - How far into the future to predict
662    ///
663    /// # Returns
664    ///
665    /// Returns predicted usage value, or None if insufficient data.
666    #[must_use]
667    pub fn predict_usage(
668        &self,
669        resource_type: ResourceType,
670        window: Duration,
671        forecast_duration: Duration,
672    ) -> Option<u64> {
673        let recent = self.recent_allocations.lock().unwrap();
674        let records = recent.get(&resource_type)?;
675
676        if records.is_empty() {
677            return None;
678        }
679
680        let cutoff = Instant::now() - window;
681        let relevant_records: Vec<_> = records.iter().filter(|r| r.timestamp > cutoff).collect();
682
683        if relevant_records.len() < 2 {
684            return None; // Need at least 2 points for trend
685        }
686
687        // Simple linear regression: y = mx + b
688        // where x is time offset, y is cumulative usage
689        let n = relevant_records.len() as f64;
690        let base_time = relevant_records[0].timestamp;
691
692        let mut sum_x = 0.0;
693        let mut sum_y = 0.0;
694        let mut sum_xy = 0.0;
695        let mut sum_xx = 0.0;
696        let mut cumulative = 0u64;
697
698        for record in &relevant_records {
699            cumulative += record.amount;
700            let x = record.timestamp.duration_since(base_time).as_secs_f64();
701            let y = cumulative as f64;
702
703            sum_x += x;
704            sum_y += y;
705            sum_xy += x * y;
706            sum_xx += x * x;
707        }
708
709        // Calculate slope (m) and intercept (b)
710        let denominator = n * sum_xx - sum_x * sum_x;
711        if denominator.abs() < 1e-10 {
712            return None; // Avoid division by zero
713        }
714
715        let slope = (n * sum_xy - sum_x * sum_y) / denominator;
716        let intercept = (sum_y - slope * sum_x) / n;
717
718        // Predict usage at forecast_duration from now
719        let forecast_x = window.as_secs_f64() + forecast_duration.as_secs_f64();
720        let predicted = slope * forecast_x + intercept;
721
722        Some(predicted.max(0.0) as u64)
723    }
724
725    /// Check if proactive throttling should be enabled based on predictions.
726    ///
727    /// Analyzes predicted resource usage and determines if throttling should
728    /// be enabled preemptively to avoid hitting limits.
729    ///
730    /// # Arguments
731    ///
732    /// * `resource_type` - The resource type to check
733    /// * `prediction_window` - Duration of historical data for prediction
734    /// * `forecast_duration` - How far ahead to predict
735    ///
736    /// # Returns
737    ///
738    /// Returns true if proactive throttling is recommended, false otherwise.
739    #[must_use]
740    pub fn should_proactive_throttle(
741        &self,
742        resource_type: ResourceType,
743        prediction_window: Duration,
744        forecast_duration: Duration,
745    ) -> bool {
746        // Get predicted usage
747        let predicted_usage =
748            match self.predict_usage(resource_type, prediction_window, forecast_duration) {
749                Some(pred) => pred,
750                None => return false, // Not enough data, don't throttle
751            };
752
753        // Get the limit for this resource
754        let stats = self.stats.lock().unwrap();
755        let limit = match stats.get(&resource_type) {
756            Some(stat) => stat.limit,
757            None => return false,
758        };
759        drop(stats);
760
761        // Calculate predicted utilization
762        if limit == 0 {
763            return false;
764        }
765
766        let predicted_utilization = (predicted_usage as f64) / (limit as f64);
767
768        // Proactively throttle if predicted to exceed threshold
769        // Use a more conservative threshold for predictions (70% instead of 80%)
770        predicted_utilization > 0.7
771    }
772
773    /// Get recommended throttle intensity based on predictions.
774    ///
775    /// Returns a value between 0.0 (no throttling) and 1.0 (maximum throttling)
776    /// based on predicted resource pressure.
777    ///
778    /// # Arguments
779    ///
780    /// * `resource_type` - The resource type to analyze
781    /// * `prediction_window` - Duration of historical data for prediction
782    /// * `forecast_duration` - How far ahead to predict
783    ///
784    /// # Returns
785    ///
786    /// Returns throttle intensity (0.0 to 1.0), where higher values mean more aggressive throttling.
787    #[must_use]
788    pub fn get_throttle_intensity(
789        &self,
790        resource_type: ResourceType,
791        prediction_window: Duration,
792        forecast_duration: Duration,
793    ) -> f64 {
794        let predicted_usage =
795            match self.predict_usage(resource_type, prediction_window, forecast_duration) {
796                Some(pred) => pred,
797                None => return 0.0, // No data, no throttling
798            };
799
800        let stats = self.stats.lock().unwrap();
801        let limit = match stats.get(&resource_type) {
802            Some(stat) => stat.limit,
803            None => return 0.0,
804        };
805        drop(stats);
806
807        if limit == 0 {
808            return 0.0;
809        }
810
811        let predicted_utilization = (predicted_usage as f64) / (limit as f64);
812
813        // Calculate intensity based on how close to limit
814        // 0-70%: no throttling (0.0)
815        // 70-85%: light throttling (0.0-0.5)
816        // 85-95%: moderate throttling (0.5-0.8)
817        // 95%+: heavy throttling (0.8-1.0)
818        if predicted_utilization < 0.7 {
819            0.0
820        } else if predicted_utilization < 0.85 {
821            (predicted_utilization - 0.7) / 0.15 * 0.5
822        } else if predicted_utilization < 0.95 {
823            0.5 + (predicted_utilization - 0.85) / 0.10 * 0.3
824        } else {
825            0.8 + ((predicted_utilization - 0.95) / 0.05 * 0.2).min(0.2)
826        }
827    }
828}
829
830/// Handle for managing background system resource monitoring.
831///
832/// This handle allows stopping the background monitoring task gracefully.
833pub struct MonitoringHandle {
834    task_handle: JoinHandle<()>,
835    stop_signal: Arc<Mutex<bool>>,
836}
837
838impl MonitoringHandle {
839    /// Stop the background monitoring task.
840    ///
841    /// # Errors
842    ///
843    /// Returns an error if the task has already panicked.
844    pub async fn stop(self) -> Result<(), tokio::task::JoinError> {
845        // Signal the task to stop
846        *self.stop_signal.lock().unwrap() = true;
847
848        // Wait for the task to complete
849        self.task_handle.await
850    }
851
852    /// Check if the monitoring task is still running.
853    #[must_use]
854    pub fn is_running(&self) -> bool {
855        !self.task_handle.is_finished()
856    }
857}
858
859/// Configuration for background system resource monitoring.
860#[derive(Debug, Clone)]
861pub struct MonitoringConfig {
862    /// Sampling interval for system resources.
863    pub sample_interval: Duration,
864    /// Whether to enable automatic degradation level updates.
865    pub auto_update_degradation: bool,
866    /// Whether to log sampling results (for debugging).
867    pub log_sampling: bool,
868}
869
870impl Default for MonitoringConfig {
871    fn default() -> Self {
872        Self {
873            sample_interval: Duration::from_secs(5),
874            auto_update_degradation: true,
875            log_sampling: false,
876        }
877    }
878}
879
880impl ResourceMonitor {
881    /// Start background monitoring of system resources.
882    ///
883    /// This spawns a background task that periodically samples CPU and memory usage
884    /// and updates the resource monitor's statistics. The returned handle can be used
885    /// to stop the monitoring gracefully.
886    ///
887    /// # Arguments
888    ///
889    /// * `config` - Configuration for the monitoring task
890    ///
891    /// # Example
892    ///
893    /// ```rust
894    /// use chie_core::resource_mgmt::{ResourceMonitor, ResourceLimits, MonitoringConfig};
895    /// use std::time::Duration;
896    ///
897    /// # async fn example() {
898    /// let limits = ResourceLimits::default();
899    /// let monitor = ResourceMonitor::new(limits);
900    ///
901    /// // Start background monitoring
902    /// let config = MonitoringConfig {
903    ///     sample_interval: Duration::from_secs(10),
904    ///     ..Default::default()
905    /// };
906    /// let handle = monitor.start_monitoring(config);
907    ///
908    /// // Monitor runs in background...
909    ///
910    /// // Stop when done
911    /// handle.stop().await.unwrap();
912    /// # }
913    /// ```
914    #[must_use]
915    pub fn start_monitoring(&self, config: MonitoringConfig) -> MonitoringHandle {
916        let stop_signal = Arc::new(Mutex::new(false));
917        let stop_signal_clone = Arc::clone(&stop_signal);
918
919        // Clone the necessary Arc fields for the background task
920        let stats = Arc::clone(&self.stats);
921        let system = Arc::clone(&self.system);
922        let degradation_level = Arc::clone(&self.degradation_level);
923
924        let task_handle = tokio::spawn(async move {
925            let mut interval = tokio::time::interval(config.sample_interval);
926
927            loop {
928                // Check if we should stop
929                if *stop_signal_clone.lock().unwrap() {
930                    break;
931                }
932
933                // Wait for next sampling interval
934                interval.tick().await;
935
936                // Sample system resources
937                let mut sys = system.lock().unwrap();
938                sys.refresh_cpu_usage();
939                sys.refresh_memory();
940
941                // Calculate average CPU usage
942                let cpus = sys.cpus();
943                let cpu_usage = if cpus.is_empty() {
944                    0.0
945                } else {
946                    cpus.iter().map(|cpu| cpu.cpu_usage()).sum::<f32>() / cpus.len() as f32
947                };
948                let memory_used = sys.used_memory();
949
950                drop(sys); // Release system lock
951
952                // Update statistics
953                {
954                    let mut stats_map = stats.lock().unwrap();
955
956                    // Update CPU stats
957                    if let Some(cpu_stats) = stats_map.get_mut(&ResourceType::Cpu) {
958                        let cpu_value = cpu_usage as u64;
959                        cpu_stats.used = cpu_value;
960                        if cpu_value > cpu_stats.peak {
961                            cpu_stats.peak = cpu_value;
962                        }
963                        if cpu_value > cpu_stats.limit {
964                            cpu_stats.limit_exceeded_count += 1;
965                        }
966                    }
967
968                    // Update memory stats
969                    if let Some(mem_stats) = stats_map.get_mut(&ResourceType::Memory) {
970                        mem_stats.used = memory_used;
971                        if memory_used > mem_stats.peak {
972                            mem_stats.peak = memory_used;
973                        }
974                        if memory_used > mem_stats.limit {
975                            mem_stats.limit_exceeded_count += 1;
976                        }
977                    }
978                }
979
980                // Update degradation level if enabled
981                if config.auto_update_degradation {
982                    let stats_map = stats.lock().unwrap();
983
984                    // Calculate max utilization across resources
985                    let mut max_utilization = 0.0;
986                    for stat in stats_map.values() {
987                        let util = stat.utilization();
988                        if util > max_utilization {
989                            max_utilization = util;
990                        }
991                    }
992
993                    // Determine degradation level based on utilization
994                    let new_level = if max_utilization < 0.7 {
995                        DegradationLevel::None
996                    } else if max_utilization < 0.8 {
997                        DegradationLevel::Minor
998                    } else if max_utilization < 0.9 {
999                        DegradationLevel::Moderate
1000                    } else if max_utilization < 0.95 {
1001                        DegradationLevel::Severe
1002                    } else {
1003                        DegradationLevel::Critical
1004                    };
1005
1006                    *degradation_level.lock().unwrap() = new_level;
1007                }
1008
1009                // Log if enabled
1010                if config.log_sampling {
1011                    eprintln!(
1012                        "[ResourceMonitor] CPU: {:.1}%, Memory: {} bytes",
1013                        cpu_usage, memory_used
1014                    );
1015                }
1016            }
1017        });
1018
1019        MonitoringHandle {
1020            task_handle,
1021            stop_signal,
1022        }
1023    }
1024}
1025
1026#[cfg(test)]
1027mod tests {
1028    use super::*;
1029
1030    #[test]
1031    fn test_resource_stats_utilization() {
1032        let stats = ResourceStats {
1033            used: 50,
1034            limit: 100,
1035            ..Default::default()
1036        };
1037        assert_eq!(stats.utilization(), 0.5);
1038    }
1039
1040    #[test]
1041    fn test_resource_stats_available() {
1042        let stats = ResourceStats {
1043            used: 30,
1044            limit: 100,
1045            ..Default::default()
1046        };
1047        assert_eq!(stats.available(), 70);
1048    }
1049
1050    #[test]
1051    fn test_resource_stats_exceeds_threshold() {
1052        let stats = ResourceStats {
1053            used: 85,
1054            limit: 100,
1055            ..Default::default()
1056        };
1057        assert!(stats.exceeds_threshold(0.8));
1058        assert!(!stats.exceeds_threshold(0.9));
1059    }
1060
1061    #[test]
1062    fn test_can_allocate() {
1063        let limits = ResourceLimits {
1064            max_memory_bytes: 1000,
1065            ..Default::default()
1066        };
1067        let monitor = ResourceMonitor::new(limits);
1068
1069        assert!(monitor.can_allocate(ResourceType::Memory, 500));
1070        assert!(monitor.can_allocate(ResourceType::Memory, 1000));
1071        assert!(!monitor.can_allocate(ResourceType::Memory, 1001));
1072    }
1073
1074    #[test]
1075    fn test_record_allocation() {
1076        let limits = ResourceLimits {
1077            max_memory_bytes: 1000,
1078            ..Default::default()
1079        };
1080        let mut monitor = ResourceMonitor::new(limits);
1081
1082        monitor.record_allocation(ResourceType::Memory, 300);
1083
1084        let stats = monitor.get_stats(ResourceType::Memory).unwrap();
1085        assert_eq!(stats.used, 300);
1086        assert_eq!(stats.allocations, 1);
1087        assert_eq!(stats.peak, 300);
1088    }
1089
1090    #[test]
1091    fn test_record_deallocation() {
1092        let limits = ResourceLimits {
1093            max_memory_bytes: 1000,
1094            ..Default::default()
1095        };
1096        let mut monitor = ResourceMonitor::new(limits);
1097
1098        monitor.record_allocation(ResourceType::Memory, 500);
1099        monitor.record_deallocation(ResourceType::Memory, 200);
1100
1101        let stats = monitor.get_stats(ResourceType::Memory).unwrap();
1102        assert_eq!(stats.used, 300);
1103        assert_eq!(stats.deallocations, 1);
1104    }
1105
1106    #[test]
1107    fn test_peak_tracking() {
1108        let limits = ResourceLimits::default();
1109        let mut monitor = ResourceMonitor::new(limits);
1110
1111        monitor.record_allocation(ResourceType::Memory, 100);
1112        monitor.record_allocation(ResourceType::Memory, 200);
1113        monitor.record_deallocation(ResourceType::Memory, 150);
1114
1115        let stats = monitor.get_stats(ResourceType::Memory).unwrap();
1116        assert_eq!(stats.peak, 300);
1117        assert_eq!(stats.used, 150);
1118    }
1119
1120    #[test]
1121    fn test_limit_exceeded_count() {
1122        let limits = ResourceLimits {
1123            max_memory_bytes: 100,
1124            ..Default::default()
1125        };
1126        let mut monitor = ResourceMonitor::new(limits);
1127
1128        monitor.record_allocation(ResourceType::Memory, 120);
1129        monitor.record_allocation(ResourceType::Memory, 50);
1130
1131        let stats = monitor.get_stats(ResourceType::Memory).unwrap();
1132        assert_eq!(stats.limit_exceeded_count, 2);
1133    }
1134
1135    #[test]
1136    fn test_throttling() {
1137        let limits = ResourceLimits {
1138            max_memory_bytes: 100,
1139            auto_throttle: true,
1140            throttle_threshold: 0.8,
1141            ..Default::default()
1142        };
1143        let mut monitor = ResourceMonitor::new(limits);
1144
1145        assert!(!monitor.is_throttled(ResourceType::Memory));
1146
1147        monitor.record_allocation(ResourceType::Memory, 85);
1148        assert!(monitor.is_throttled(ResourceType::Memory));
1149
1150        monitor.record_deallocation(ResourceType::Memory, 30);
1151        assert!(!monitor.is_throttled(ResourceType::Memory));
1152    }
1153
1154    #[test]
1155    fn test_allocation_rate() {
1156        let limits = ResourceLimits::default();
1157        let mut monitor = ResourceMonitor::new(limits);
1158
1159        monitor.record_allocation(ResourceType::DiskIo, 1000);
1160        monitor.record_allocation(ResourceType::DiskIo, 2000);
1161
1162        std::thread::sleep(Duration::from_millis(100));
1163
1164        let rate = monitor.get_allocation_rate(ResourceType::DiskIo, Duration::from_secs(1));
1165        // Rate should be approximately 3000 bytes/sec (with some variance)
1166        assert!(rate > 0);
1167    }
1168
1169    #[test]
1170    fn test_cleanup_old_records() {
1171        let limits = ResourceLimits::default();
1172        let mut monitor = ResourceMonitor::new(limits);
1173
1174        monitor.record_allocation(ResourceType::Memory, 100);
1175        std::thread::sleep(Duration::from_millis(150));
1176        monitor.record_allocation(ResourceType::Memory, 200);
1177
1178        monitor.cleanup_old_records(Duration::from_millis(100));
1179
1180        // Only the recent allocation should remain
1181        let rate = monitor.get_allocation_rate(ResourceType::Memory, Duration::from_secs(1));
1182        assert!(rate > 0);
1183    }
1184
1185    #[test]
1186    fn test_reset_stats() {
1187        let limits = ResourceLimits::default();
1188        let mut monitor = ResourceMonitor::new(limits);
1189
1190        monitor.record_allocation(ResourceType::Memory, 500);
1191        monitor.reset_stats();
1192
1193        let stats = monitor.get_stats(ResourceType::Memory).unwrap();
1194        assert_eq!(stats.used, 0);
1195        assert_eq!(stats.peak, 0);
1196        assert_eq!(stats.allocations, 0);
1197    }
1198
1199    #[test]
1200    fn test_health_score() {
1201        let limits = ResourceLimits {
1202            max_memory_bytes: 1000,
1203            max_cpu_percent: 100,
1204            ..Default::default()
1205        };
1206        let mut monitor = ResourceMonitor::new(limits);
1207
1208        // Empty system should have perfect health
1209        assert!(monitor.health_score() > 0.99);
1210
1211        // Half-utilized Memory and CPU with DiskIo and NetworkBandwidth at 0%
1212        // Average utilization = (50% + 50% + 0% + 0%) / 4 = 25%
1213        // Health score = 1.0 - 0.25 = 0.75
1214        monitor.record_allocation(ResourceType::Memory, 500);
1215        monitor.update_usage(ResourceType::Cpu, 50);
1216
1217        let health = monitor.health_score();
1218        // With 4 resource types and only 2 at 50%, average utilization is 25%
1219        // Health score should be around 0.75 (1.0 - 0.25)
1220        assert!(health > 0.7 && health < 0.8);
1221    }
1222
1223    #[test]
1224    fn test_is_over_limit() {
1225        let limits = ResourceLimits {
1226            max_memory_bytes: 100,
1227            ..Default::default()
1228        };
1229        let mut monitor = ResourceMonitor::new(limits);
1230
1231        assert!(!monitor.is_over_limit());
1232
1233        monitor.record_allocation(ResourceType::Memory, 150);
1234        assert!(monitor.is_over_limit());
1235    }
1236
1237    #[test]
1238    fn test_update_usage() {
1239        let limits = ResourceLimits::default();
1240        let mut monitor = ResourceMonitor::new(limits);
1241
1242        monitor.update_usage(ResourceType::Cpu, 75);
1243
1244        let stats = monitor.get_stats(ResourceType::Cpu).unwrap();
1245        assert_eq!(stats.used, 75);
1246    }
1247
1248    #[test]
1249    fn test_predict_usage_insufficient_data() {
1250        let limits = ResourceLimits::default();
1251        let monitor = ResourceMonitor::new(limits);
1252
1253        // No allocations yet - should return None
1254        let prediction = monitor.predict_usage(
1255            ResourceType::Memory,
1256            Duration::from_secs(10),
1257            Duration::from_secs(5),
1258        );
1259        assert!(prediction.is_none());
1260    }
1261
1262    #[test]
1263    fn test_predict_usage_with_trend() {
1264        let limits = ResourceLimits {
1265            max_memory_bytes: 10000,
1266            ..Default::default()
1267        };
1268        let mut monitor = ResourceMonitor::new(limits);
1269
1270        // Create a trend: allocating 100 bytes every 100ms
1271        for i in 0..5 {
1272            monitor.record_allocation(ResourceType::Memory, 100);
1273            if i < 4 {
1274                std::thread::sleep(Duration::from_millis(100));
1275            }
1276        }
1277
1278        // Predict usage
1279        let prediction = monitor.predict_usage(
1280            ResourceType::Memory,
1281            Duration::from_secs(1),
1282            Duration::from_millis(500),
1283        );
1284
1285        // Should predict something (exact value depends on timing)
1286        assert!(prediction.is_some());
1287        let predicted = prediction.unwrap();
1288        // Should predict continued growth
1289        assert!(predicted > 0);
1290    }
1291
1292    #[test]
1293    fn test_should_proactive_throttle_no_data() {
1294        let limits = ResourceLimits::default();
1295        let monitor = ResourceMonitor::new(limits);
1296
1297        // No data - should not throttle
1298        let should_throttle = monitor.should_proactive_throttle(
1299            ResourceType::Memory,
1300            Duration::from_secs(10),
1301            Duration::from_secs(5),
1302        );
1303        assert!(!should_throttle);
1304    }
1305
1306    #[test]
1307    fn test_should_proactive_throttle_high_prediction() {
1308        let limits = ResourceLimits {
1309            max_memory_bytes: 1000,
1310            ..Default::default()
1311        };
1312        let mut monitor = ResourceMonitor::new(limits);
1313
1314        // Simulate rapid growth that will exceed threshold
1315        for i in 0..5 {
1316            monitor.record_allocation(ResourceType::Memory, 200);
1317            if i < 4 {
1318                std::thread::sleep(Duration::from_millis(50));
1319            }
1320        }
1321
1322        // Should recommend throttling if trend continues
1323        let should_throttle = monitor.should_proactive_throttle(
1324            ResourceType::Memory,
1325            Duration::from_secs(1),
1326            Duration::from_millis(200),
1327        );
1328
1329        // With rapid growth, should predict high usage and recommend throttling
1330        assert!(should_throttle);
1331    }
1332
1333    #[test]
1334    fn test_get_throttle_intensity_no_data() {
1335        let limits = ResourceLimits::default();
1336        let monitor = ResourceMonitor::new(limits);
1337
1338        let intensity = monitor.get_throttle_intensity(
1339            ResourceType::Memory,
1340            Duration::from_secs(10),
1341            Duration::from_secs(5),
1342        );
1343
1344        // No data - no throttling
1345        assert_eq!(intensity, 0.0);
1346    }
1347
1348    #[test]
1349    fn test_get_throttle_intensity_low_usage() {
1350        let limits = ResourceLimits {
1351            max_memory_bytes: 10000,
1352            ..Default::default()
1353        };
1354        let mut monitor = ResourceMonitor::new(limits);
1355
1356        // Low, steady allocations
1357        for i in 0..3 {
1358            monitor.record_allocation(ResourceType::Memory, 100);
1359            if i < 2 {
1360                std::thread::sleep(Duration::from_millis(100));
1361            }
1362        }
1363
1364        let intensity = monitor.get_throttle_intensity(
1365            ResourceType::Memory,
1366            Duration::from_secs(1),
1367            Duration::from_millis(500),
1368        );
1369
1370        // Low predicted usage - minimal or no throttling
1371        assert!(intensity < 0.3);
1372    }
1373
1374    #[test]
1375    fn test_degradation_level_calculation() {
1376        let limits = ResourceLimits {
1377            max_memory_bytes: 1000,
1378            ..Default::default()
1379        };
1380        let mut monitor = ResourceMonitor::new(limits);
1381
1382        // Test different utilization levels
1383        monitor.record_allocation(ResourceType::Memory, 500); // 50%
1384        assert_eq!(
1385            monitor.calculate_degradation_level(),
1386            DegradationLevel::None
1387        );
1388
1389        monitor.record_allocation(ResourceType::Memory, 350); // 85%
1390        assert_eq!(
1391            monitor.calculate_degradation_level(),
1392            DegradationLevel::Moderate
1393        );
1394
1395        monitor.record_allocation(ResourceType::Memory, 60); // 91%
1396        assert_eq!(
1397            monitor.calculate_degradation_level(),
1398            DegradationLevel::Severe
1399        );
1400
1401        monitor.record_allocation(ResourceType::Memory, 50); // 96%
1402        assert_eq!(
1403            monitor.calculate_degradation_level(),
1404            DegradationLevel::Critical
1405        );
1406    }
1407
1408    #[tokio::test]
1409    async fn test_background_monitoring() {
1410        let limits = ResourceLimits::default();
1411        let monitor = ResourceMonitor::new(limits);
1412
1413        // Start monitoring with short interval for testing
1414        let config = MonitoringConfig {
1415            sample_interval: Duration::from_millis(100),
1416            auto_update_degradation: true,
1417            log_sampling: false,
1418        };
1419
1420        let handle = monitor.start_monitoring(config);
1421
1422        // Let it run for a bit
1423        tokio::time::sleep(Duration::from_millis(300)).await;
1424
1425        // Check that the monitor is running
1426        assert!(handle.is_running());
1427
1428        // Get stats - should have been updated by background monitoring
1429        let cpu_stats = monitor.get_stats(ResourceType::Cpu);
1430        let mem_stats = monitor.get_stats(ResourceType::Memory);
1431
1432        assert!(cpu_stats.is_some());
1433        assert!(mem_stats.is_some());
1434
1435        // Stop monitoring
1436        handle.stop().await.unwrap();
1437    }
1438
1439    #[tokio::test]
1440    async fn test_monitoring_handle_stop() {
1441        let limits = ResourceLimits::default();
1442        let monitor = ResourceMonitor::new(limits);
1443
1444        let config = MonitoringConfig {
1445            sample_interval: Duration::from_millis(100),
1446            ..Default::default()
1447        };
1448
1449        let handle = monitor.start_monitoring(config);
1450
1451        // Monitor should be running
1452        assert!(handle.is_running());
1453
1454        // Stop it
1455        handle.stop().await.unwrap();
1456
1457        // Give it a moment to actually stop
1458        tokio::time::sleep(Duration::from_millis(50)).await;
1459    }
1460
1461    #[tokio::test]
1462    async fn test_sample_cpu_usage() {
1463        let limits = ResourceLimits::default();
1464        let mut monitor = ResourceMonitor::new(limits);
1465
1466        // Sample CPU - should work without panicking
1467        let cpu_usage = monitor.sample_cpu_usage();
1468
1469        // CPU usage should be reasonable (0-100%)
1470        assert!(cpu_usage >= 0.0);
1471        assert!(cpu_usage <= 100.0);
1472
1473        // Stats should be updated
1474        let stats = monitor.get_stats(ResourceType::Cpu).unwrap();
1475        assert_eq!(stats.used, cpu_usage as u64);
1476    }
1477
1478    #[tokio::test]
1479    async fn test_sample_memory_usage() {
1480        let limits = ResourceLimits::default();
1481        let mut monitor = ResourceMonitor::new(limits);
1482
1483        // Sample memory - should work without panicking
1484        let memory_used = monitor.sample_memory_usage();
1485
1486        // Memory usage should be non-zero
1487        assert!(memory_used > 0);
1488
1489        // Stats should be updated
1490        let stats = monitor.get_stats(ResourceType::Memory).unwrap();
1491        assert_eq!(stats.used, memory_used);
1492    }
1493
1494    #[tokio::test]
1495    async fn test_sample_all_system_resources() {
1496        let limits = ResourceLimits::default();
1497        let mut monitor = ResourceMonitor::new(limits);
1498
1499        // Sample all resources at once
1500        let (cpu_usage, memory_used) = monitor.sample_all_system_resources();
1501
1502        // Validate results
1503        assert!(cpu_usage >= 0.0);
1504        assert!(cpu_usage <= 100.0);
1505        assert!(memory_used > 0);
1506
1507        // Check stats were updated
1508        let cpu_stats = monitor.get_stats(ResourceType::Cpu).unwrap();
1509        let mem_stats = monitor.get_stats(ResourceType::Memory).unwrap();
1510
1511        assert_eq!(cpu_stats.used, cpu_usage as u64);
1512        assert_eq!(mem_stats.used, memory_used);
1513    }
1514
1515    #[test]
1516    fn test_monitoring_config_default() {
1517        let config = MonitoringConfig::default();
1518
1519        assert_eq!(config.sample_interval, Duration::from_secs(5));
1520        assert!(config.auto_update_degradation);
1521        assert!(!config.log_sampling);
1522    }
1523
1524    #[tokio::test]
1525    async fn test_monitoring_updates_degradation() {
1526        let limits = ResourceLimits {
1527            max_cpu_percent: 10, // Very low limit to trigger degradation
1528            max_memory_bytes: 1000,
1529            ..Default::default()
1530        };
1531        let monitor = ResourceMonitor::new(limits);
1532
1533        let config = MonitoringConfig {
1534            sample_interval: Duration::from_millis(100),
1535            auto_update_degradation: true,
1536            log_sampling: false,
1537        };
1538
1539        let handle = monitor.start_monitoring(config);
1540
1541        // Wait for a few samples
1542        tokio::time::sleep(Duration::from_millis(350)).await;
1543
1544        // Degradation level may have been updated based on actual system usage
1545        let level = monitor.degradation_level();
1546        assert!(level >= DegradationLevel::None);
1547
1548        // Stop monitoring
1549        handle.stop().await.unwrap();
1550    }
1551
1552    #[test]
1553    fn test_total_system_memory() {
1554        let limits = ResourceLimits::default();
1555        let monitor = ResourceMonitor::new(limits);
1556
1557        let total_mem = monitor.total_system_memory();
1558
1559        // System should have some memory
1560        assert!(total_mem > 0);
1561    }
1562
1563    #[test]
1564    fn test_cpu_count() {
1565        let limits = ResourceLimits::default();
1566        let monitor = ResourceMonitor::new(limits);
1567
1568        let cpu_count = monitor.cpu_count();
1569
1570        // System should have at least one CPU
1571        assert!(cpu_count > 0);
1572    }
1573}