dmsc/observability/
metrics_collector.rs

1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of DMSC.
4//! The DMSC project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! You may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//!     http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18#![allow(non_snake_case)]
19
20//! # Metrics Collector
21//!
22//! This file implements a comprehensive metrics collection system for the DMSC framework. It provides
23//! tools for collecting, analyzing, and reporting performance and system metrics. The system includes
24//! sliding window data structures for time-series data, quantile calculators for performance analysis,
25//! and system metrics collectors for monitoring CPU, memory, disk, and network usage.
26//!
27//! ## Key Components
28//!
29//! - **DMSCSlidingWindow**: Sliding window for time-series data collection
30//! - **DMSCQuantileCalculator**: Quantile calculator for performance metrics
31//! - **DMSCPerformanceCollector**: Performance metrics collector with sliding window and quantile calculation
32//! - **DMSCPerformanceMetrics**: Performance metrics snapshot structure
33//! - **DMSCCPUMetrics**: CPU metrics structure
34//! - **DMSCMemoryMetrics**: Memory metrics structure
35//! - **DMSCDiskMetrics**: Disk metrics structure
36//! - **DMSCNetworkMetrics**: Network metrics structure
37//! - **DMSCSystemMetrics**: System metrics snapshot structure
38//! - **DMSCSystemMetricsCollector**: System metrics collector
39//!
40//! ## Design Principles
41//!
42//! 1. **Time-Series Data**: Uses sliding windows for efficient time-series data collection
43//! 2. **Performance Focus**: Includes quantile calculation for accurate performance analysis
44//! 3. **System Monitoring**: Comprehensive system metrics collection
45//! 4. **Serialization Support**: All metrics structures are serializable for easy reporting
46//! 5. **Low Overhead**: Efficient data structures to minimize performance impact
47//! 6. **Flexible Configuration**: Configurable window sizes and bucket sizes
48//! 7. **Cross-Platform**: Uses sysinfo crate for cross-platform system metrics collection
49//! 8. **Real-Time Analysis**: Provides real-time metrics calculation
50//!
51//! ## Usage
52//!
53//! ```rust
54//! use dmsc::observability::{DMSCPerformanceCollector, DMSCSystemMetricsCollector};
55//! use std::time::Duration;
56//!
57//! fn example() {
58//!     // Create a performance collector with a 1-minute window and 5-second buckets
59//!     let mut perf_collector = DMSCPerformanceCollector::new(
60//!         Duration::from_secs(60),
61//!         Duration::from_secs(5)
62//!     );
63//!     
64//!     // Record a request
65//!     perf_collector.record_request(12.5, false);
66//!     
67//!     // Get performance metrics
68//!     let perf_metrics = perf_collector.get_metrics();
69//!     println!("P50 latency: {}ms", perf_metrics.p50_latency_ms);
70//!     println!("Throughput: {} rps", perf_metrics.throughput_rps);
71//!     println!("Error rate: {:.2}%", perf_metrics.error_rate * 100.0);
72//!     
73//!     // Create a system metrics collector
74//!     let mut sys_collector = DMSCSystemMetricsCollector::new();
75//!     
76//!     // Collect system metrics
77//!     let sys_metrics = sys_collector.collect();
78//!     println!("CPU usage: {:.2}%", sys_metrics.cpu.total_usage_percent);
79//!     println!("Memory usage: {:.2}%", sys_metrics.memory.usage_percent);
80//!     println!("Network received: {} bytes/s", sys_metrics.network.received_bytes_per_sec);
81//! }
82//! ```
83
84use serde::{Deserialize, Serialize};
85use std::collections::VecDeque;
86use std::time::{Duration, Instant};
87use sysinfo::{CpuExt, DiskExt, NetworkExt, System, SystemExt};
88
89/// Sliding window for time-series data collection.
90///
91/// This struct implements a sliding window for efficient time-series data collection. It divides
92/// the window into buckets and automatically advances the window as time passes, removing old
93/// data points.
94#[allow(dead_code)]
95#[derive(Debug, Clone)]
96pub struct DMSCSlidingWindow<T> {
97    /// Total size of the sliding window
98    _window_size: Duration,
99    /// Size of each bucket within the window
100    bucket_size: Duration,
101    /// Queue of buckets containing data points
102    buckets: VecDeque<WindowBucket<T>>,
103    /// Current time for window advancement
104    current_time: Instant,
105}
106
107/// Internal bucket structure for the sliding window.
108///
109/// This struct represents a single bucket within the sliding window, containing a collection
110/// of data points for a specific time interval.
111#[allow(dead_code)]
112#[derive(Debug, Clone)]
113struct WindowBucket<T> {
114    /// Start time of the bucket
115    _start_time: Instant,
116    /// End time of the bucket
117    end_time: Instant,
118    /// Data points collected in this bucket
119    data_points: Vec<T>,
120}
121
122#[allow(dead_code)]
123impl<T> DMSCSlidingWindow<T> {
124    /// Creates a new sliding window with the specified window size and bucket size.
125    ///
126    /// # Parameters
127    ///
128    /// - `window_size`: Total size of the sliding window
129    /// - `bucket_size`: Size of each bucket within the window
130    ///
131    /// # Returns
132    ///
133    /// A new DMSCSlidingWindow instance
134    pub fn new(window_size: Duration, bucket_size: Duration) -> Self {
135        let bucket_count = (window_size.as_millis() / bucket_size.as_millis()).max(1) as usize;
136        let mut buckets = VecDeque::with_capacity(bucket_count);
137
138        let now = Instant::now();
139        for i in 0..bucket_count {
140            let bucket_start = now - window_size
141                + Duration::from_millis(i as u64 * bucket_size.as_millis() as u64);
142            buckets.push_back(WindowBucket {
143                _start_time: bucket_start,
144                end_time: bucket_start + bucket_size,
145                data_points: Vec::new(),
146            });
147        }
148
149        Self {
150            _window_size: window_size,
151            bucket_size,
152            buckets,
153            current_time: now,
154        }
155    }
156
157    /// Adds a data point to the current window.
158    ///
159    /// # Parameters
160    ///
161    /// - `value`: The data point to add
162    pub fn add(&mut self, value: T) {
163        self.advance_window();
164
165        if let Some(current_bucket) = self.buckets.back_mut() {
166            current_bucket.data_points.push(value);
167        }
168    }
169
170    /// Gets all data points in the current window.
171    ///
172    /// # Returns
173    ///
174    /// A vector of references to all data points in the current window
175    pub fn get_data_points(&self) -> Vec<&T> {
176        self.buckets
177            .iter()
178            .flat_map(|bucket| &bucket.data_points)
179            .collect()
180    }
181
182    /// Gets the count of data points in the current window.
183    ///
184    /// # Returns
185    ///
186    /// The number of data points in the current window
187    pub fn count(&self) -> usize {
188        self.buckets
189            .iter()
190            .map(|bucket| bucket.data_points.len())
191            .sum()
192    }
193
194    /// Advances the window to the current time, removing old buckets.
195    fn advance_window(&mut self) {
196        let now = Instant::now();
197        let elapsed = now.duration_since(self.current_time);
198
199        if elapsed >= self.bucket_size {
200            let buckets_to_advance = (elapsed.as_millis() / self.bucket_size.as_millis()) as usize;
201
202            for _ in 0..buckets_to_advance.min(self.buckets.len()) {
203                self.buckets.pop_front();
204
205                let new_bucket_start = match self.buckets.back() {
206                    Some(bucket) => bucket.end_time,
207                    None => {
208                        continue;
209                    }
210                };
211                self.buckets.push_back(WindowBucket {
212                    _start_time: new_bucket_start,
213                    end_time: new_bucket_start + self.bucket_size,
214                    data_points: Vec::new(),
215                });
216            }
217
218            self.current_time = now;
219        }
220    }
221}
222
223/// Quantile calculator for performance metrics.
224///
225/// This struct provides methods for calculating quantiles (percentiles) from a set of data points.
226/// It sorts the data and uses linear interpolation for non-integer indices.
227#[allow(dead_code)]
228#[derive(Debug, Clone, Serialize, Deserialize)]
229pub struct DMSCQuantileCalculator {
230    /// Sorted list of data points
231    sorted_data: Vec<f64>,
232}
233
234#[allow(dead_code)]
235impl Default for DMSCQuantileCalculator {
236    fn default() -> Self {
237        Self::new()
238    }
239}
240
241#[allow(dead_code)]
242impl DMSCQuantileCalculator {
243    /// Creates a new quantile calculator.
244    ///
245    /// # Returns
246    ///
247    /// A new DMSCQuantileCalculator instance
248    pub fn new() -> Self {
249        Self {
250            sorted_data: Vec::new(),
251        }
252    }
253
254    /// Adds a data point to the calculator.
255    ///
256    /// # Parameters
257    ///
258    /// - `value`: The data point to add
259    pub fn add(&mut self, value: f64) {
260        self.sorted_data.push(value);
261    }
262
263    /// Calculates the specified quantile (0.0 to 1.0).
264    ///
265    /// # Parameters
266    ///
267    /// - `q`: The quantile to calculate (0.0 to 1.0)
268    ///
269    /// # Returns
270    ///
271    /// An `Option<f64>` containing the calculated quantile, or None if the data is empty or q is out of range
272    pub fn quantile(&mut self, q: f64) -> Option<f64> {
273        if self.sorted_data.is_empty() || !(0.0..=1.0).contains(&q) {
274            return None;
275        }
276
277        self.sorted_data.sort_by(|a, b| {
278            a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
279        });
280
281        let n = self.sorted_data.len();
282        let index = q * (n - 1) as f64;
283        let lower = index.floor() as usize;
284        let upper = index.ceil() as usize;
285
286        if lower == upper {
287            Some(self.sorted_data[lower])
288        } else {
289            let weight = index - lower as f64;
290            let lower_val = self.sorted_data[lower];
291            let upper_val = self.sorted_data[upper];
292            Some(lower_val + weight * (upper_val - lower_val))
293        }
294    }
295
296    /// Calculates multiple quantiles at once.
297    ///
298    /// # Parameters
299    ///
300    /// - `quantiles`: A slice of quantiles to calculate (0.0 to 1.0)
301    ///
302    /// # Returns
303    ///
304    /// A vector of `Option<f64>` containing the calculated quantiles
305    pub fn quantiles(&mut self, quantiles: &[f64]) -> Vec<Option<f64>> {
306        quantiles.iter().map(|&q| self.quantile(q)).collect()
307    }
308
309    /// Gets the minimum value in the data set.
310    ///
311    /// # Returns
312    ///
313    /// An `Option<f64>` containing the minimum value, or None if the data is empty
314    pub fn min(&self) -> Option<f64> {
315        self.sorted_data.iter().fold(None, |min, &val| match min {
316            None => Some(val),
317            Some(m) => Some(m.min(val)),
318        })
319    }
320
321    /// Gets the maximum value in the data set.
322    ///
323    /// # Returns
324    ///
325    /// An `Option<f64>` containing the maximum value, or None if the data is empty
326    pub fn max(&self) -> Option<f64> {
327        self.sorted_data.iter().fold(None, |max, &val| match max {
328            None => Some(val),
329            Some(m) => Some(m.max(val)),
330        })
331    }
332
333    /// Gets the mean value of the data set.
334    ///
335    /// # Returns
336    ///
337    /// An `Option<f64>` containing the mean value, or None if the data is empty
338    pub fn mean(&self) -> Option<f64> {
339        if self.sorted_data.is_empty() {
340            return None;
341        }
342
343        let sum: f64 = self.sorted_data.iter().sum();
344        Some(sum / self.sorted_data.len() as f64)
345    }
346
347    /// Gets the standard deviation of the data set.
348    ///
349    /// # Returns
350    ///
351    /// An `Option<f64>` containing the standard deviation, or None if the data is empty
352    pub fn std_dev(&self) -> Option<f64> {
353        let mean = self.mean()?;
354        if self.sorted_data.len() <= 1 {
355            return Some(0.0);
356        }
357
358        let variance: f64 = self
359            .sorted_data
360            .iter()
361            .map(|&x| (x - mean).powi(2))
362            .sum::<f64>()
363            / (self.sorted_data.len() - 1) as f64;
364
365        Some(variance.sqrt())
366    }
367
368    /// Clears all data from the calculator.
369    pub fn clear(&mut self) {
370        self.sorted_data.clear();
371    }
372}
373
374/// Performance metrics collector with sliding window and quantile calculation.
375///
376/// This struct collects performance metrics using sliding windows and provides quantile-based
377/// performance analysis.
378#[allow(dead_code)]
379pub struct DMSCPerformanceCollector {
380    /// Sliding window for latency data
381    latency_window: DMSCSlidingWindow<f64>,
382    /// Sliding window for throughput data
383    throughput_window: DMSCSlidingWindow<u64>,
384    /// Sliding window for error rate data
385    error_rate_window: DMSCSlidingWindow<bool>,
386    /// Quantile calculator for performance analysis
387    quantile_calculator: DMSCQuantileCalculator,
388}
389
390#[allow(dead_code)]
391impl DMSCPerformanceCollector {
392    /// Creates a new performance collector with the specified window size and bucket size.
393    ///
394    /// # Parameters
395    ///
396    /// - `window_size`: Total size of the sliding window
397    /// - `bucket_size`: Size of each bucket within the window
398    ///
399    /// # Returns
400    ///
401    /// A new DMSCPerformanceCollector instance
402    pub fn new(window_size: Duration, bucket_size: Duration) -> Self {
403        Self {
404            latency_window: DMSCSlidingWindow::new(window_size, bucket_size),
405            throughput_window: DMSCSlidingWindow::new(window_size, bucket_size),
406            error_rate_window: DMSCSlidingWindow::new(window_size, bucket_size),
407            quantile_calculator: DMSCQuantileCalculator::new(),
408        }
409    }
410
411    /// Records a request with latency and error status.
412    ///
413    /// # Parameters
414    ///
415    /// - `latency_ms`: The request latency in milliseconds
416    /// - `is_error`: Whether the request resulted in an error
417    pub fn record_request(&mut self, latency_ms: f64, is_error: bool) {
418        self.latency_window.add(latency_ms);
419        self.throughput_window.add(1);
420        self.error_rate_window.add(is_error);
421    }
422
423    /// Gets the current performance metrics.
424    ///
425    /// # Returns
426    ///
427    /// A DMSCPerformanceMetrics instance containing the current performance metrics
428    pub fn get_metrics(&mut self) -> DMSCPerformanceMetrics {
429        let latencies: Vec<f64> = self
430            .latency_window
431            .get_data_points()
432            .iter()
433            .map(|&&x| x)
434            .collect();
435
436        // Update quantile calculator with current data
437        self.quantile_calculator.clear();
438        for &latency in &latencies {
439            self.quantile_calculator.add(latency);
440        }
441
442        let p50 = self.quantile_calculator.quantile(0.5).unwrap_or(0.0);
443        let p95 = self.quantile_calculator.quantile(0.95).unwrap_or(0.0);
444        let p99 = self.quantile_calculator.quantile(0.99).unwrap_or(0.0);
445
446        let errors: Vec<bool> = self
447            .error_rate_window
448            .get_data_points()
449            .iter()
450            .map(|&&x| x)
451            .collect();
452
453        let error_count = errors.iter().filter(|&&x| x).count();
454        let total_requests = errors.len();
455        let error_rate = if total_requests > 0 {
456            error_count as f64 / total_requests as f64
457        } else {
458            0.0
459        };
460
461        DMSCPerformanceMetrics {
462            p50_latency_ms: p50,
463            p95_latency_ms: p95,
464            p99_latency_ms: p99,
465            mean_latency_ms: self.quantile_calculator.mean().unwrap_or(0.0),
466            min_latency_ms: self.quantile_calculator.min().unwrap_or(0.0),
467            max_latency_ms: self.quantile_calculator.max().unwrap_or(0.0),
468            throughput_rps: self.throughput_window.count() as f64,
469            error_rate,
470            total_requests: total_requests as u64,
471        }
472    }
473}
474
475/// Performance metrics snapshot.
476///
477/// This struct represents a snapshot of performance metrics at a specific point in time.
478#[derive(Debug, Clone, Serialize, Deserialize)]
479pub struct DMSCPerformanceMetrics {
480    /// 50th percentile latency in milliseconds
481    pub p50_latency_ms: f64,
482    /// 95th percentile latency in milliseconds
483    pub p95_latency_ms: f64,
484    /// 99th percentile latency in milliseconds
485    pub p99_latency_ms: f64,
486    /// Mean latency in milliseconds
487    pub mean_latency_ms: f64,
488    /// Minimum latency in milliseconds
489    pub min_latency_ms: f64,
490    /// Maximum latency in milliseconds
491    pub max_latency_ms: f64,
492    /// Throughput in requests per second
493    pub throughput_rps: f64,
494    /// Error rate (0.0 to 1.0)
495    pub error_rate: f64,
496    /// Total number of requests
497    pub total_requests: u64,
498}
499
500/// CPU metrics.
501///
502/// This struct represents CPU usage metrics.
503#[derive(Debug, Clone, Serialize, Deserialize)]
504#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass(get_all))]
505pub struct DMSCCPUMetrics {
506    /// Total CPU usage percentage
507    pub total_usage_percent: f64,
508    /// Per-core CPU usage percentages
509    pub per_core_usage: Vec<f64>,
510    /// Number of context switches (platform dependent)
511    pub context_switches: u64,
512    /// Number of interrupts (platform dependent)
513    pub interrupts: u64,
514}
515
516/// Memory metrics.
517///
518/// This struct represents memory usage metrics.
519#[derive(Debug, Clone, Serialize, Deserialize)]
520#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass(get_all))]
521pub struct DMSCMemoryMetrics {
522    /// Total memory in bytes
523    pub total_bytes: u64,
524    /// Used memory in bytes
525    pub used_bytes: u64,
526    /// Free memory in bytes
527    pub free_bytes: u64,
528    /// Memory usage percentage
529    pub usage_percent: f64,
530    /// Total swap memory in bytes
531    pub swap_total_bytes: u64,
532    /// Used swap memory in bytes
533    pub swap_used_bytes: u64,
534    /// Free swap memory in bytes
535    pub swap_free_bytes: u64,
536    /// Swap usage percentage
537    pub swap_usage_percent: f64,
538}
539
540/// Disk metrics.
541///
542/// This struct represents disk usage metrics.
543#[derive(Debug, Clone, Serialize, Deserialize)]
544#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass(get_all))]
545pub struct DMSCDiskMetrics {
546    /// Total disk space in bytes
547    pub total_bytes: u64,
548    /// Used disk space in bytes
549    pub used_bytes: u64,
550    /// Free disk space in bytes
551    pub free_bytes: u64,
552    /// Disk usage percentage
553    pub usage_percent: f64,
554    /// Total bytes read (platform dependent)
555    pub read_bytes: u64,
556    /// Total bytes written (platform dependent)
557    pub write_bytes: u64,
558    /// Total read operations (platform dependent)
559    pub read_count: u64,
560    /// Total write operations (platform dependent)
561    pub write_count: u64,
562}
563
564/// Network metrics.
565///
566/// This struct represents network usage metrics.
567#[derive(Debug, Clone, Serialize, Deserialize)]
568#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass(get_all))]
569pub struct DMSCNetworkMetrics {
570    /// Total bytes received
571    pub total_received_bytes: u64,
572    /// Total bytes transmitted
573    pub total_transmitted_bytes: u64,
574    /// Bytes received per second
575    pub received_bytes_per_sec: u64,
576    /// Bytes transmitted per second
577    pub transmitted_bytes_per_sec: u64,
578    /// Total packets received
579    pub total_received_packets: u64,
580    /// Total packets transmitted
581    pub total_transmitted_packets: u64,
582    /// Packets received per second
583    pub received_packets_per_sec: u64,
584    /// Packets transmitted per second
585    pub transmitted_packets_per_sec: u64,
586}
587
588/// System metrics snapshot.
589///
590/// This struct represents a snapshot of system metrics at a specific point in time.
591#[derive(Debug, Clone, Serialize, Deserialize)]
592#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
593pub struct DMSCSystemMetrics {
594    /// CPU metrics
595    pub cpu: DMSCCPUMetrics,
596    /// Memory metrics
597    pub memory: DMSCMemoryMetrics,
598    /// Disk metrics
599    pub disk: DMSCDiskMetrics,
600    /// Network metrics
601    pub network: DMSCNetworkMetrics,
602    /// Timestamp of the metrics collection (Unix timestamp in milliseconds)
603    pub timestamp: u64,
604}
605
606/// System metrics collector.
607///
608/// This struct collects system metrics using the sysinfo crate, providing cross-platform
609/// system monitoring capabilities.
610#[allow(dead_code)]
611#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
612pub struct DMSCSystemMetricsCollector {
613    /// sysinfo System instance for collecting metrics
614    system: System,
615    /// Last network bytes received
616    last_network_received: u64,
617    /// Last network bytes transmitted
618    last_network_transmitted: u64,
619    /// Last network packets received
620    last_network_received_packets: u64,
621    /// Last network packets transmitted
622    last_network_transmitted_packets: u64,
623    /// Last network metrics collection time
624    last_network_time: Instant,
625}
626
627#[allow(dead_code)]
628impl Default for DMSCSystemMetricsCollector {
629    fn default() -> Self {
630        Self::new()
631    }
632}
633
634#[allow(dead_code)]
635impl DMSCSystemMetricsCollector {
636    /// Creates a new system metrics collector.
637    ///
638    /// # Returns
639    ///
640    /// A new DMSCSystemMetricsCollector instance
641    pub fn new() -> Self {
642        let mut system = System::new_all();
643        system.refresh_all();
644
645        let (received_bytes, transmitted_bytes, received_packets, transmitted_packets) =
646            Self::get_network_total(&system);
647
648        Self {
649            system,
650            last_network_received: received_bytes,
651            last_network_transmitted: transmitted_bytes,
652            last_network_received_packets: received_packets,
653            last_network_transmitted_packets: transmitted_packets,
654            last_network_time: Instant::now(),
655        }
656    }
657
658    /// Collects the current system metrics.
659    ///
660    /// # Returns
661    ///
662    /// A DMSCSystemMetrics instance containing the current system metrics
663    pub fn collect(&mut self) -> DMSCSystemMetrics {
664        self.system.refresh_all();
665
666        let cpu = self.get_cpu_metrics();
667        let memory = self.get_memory_metrics();
668        let disk = self.get_disk_metrics();
669        let network = self.get_network_metrics();
670
671        let timestamp = chrono::Utc::now().timestamp_millis() as u64;
672        
673        DMSCSystemMetrics {
674            cpu,
675            memory,
676            disk,
677            network,
678            timestamp,
679        }
680    }
681
682    /// Gets CPU metrics from the system.
683    ///
684    /// # Returns
685    ///
686    /// A DMSCCPUMetrics instance containing the CPU metrics
687    fn get_cpu_metrics(&self) -> DMSCCPUMetrics {
688        let total_usage = self.system.global_cpu_info().cpu_usage();
689        let per_core_usage: Vec<f64> = self
690            .system
691            .cpus()
692            .iter()
693            .map(|cpu| cpu.cpu_usage() as f64)
694            .collect();
695
696        // Note: sysinfo crate doesn't expose context switches and interrupts on all platforms
697        // These values will be 0 on platforms where they're not available
698        DMSCCPUMetrics {
699            total_usage_percent: total_usage as f64,
700            per_core_usage,
701            context_switches: 0,
702            interrupts: 0,
703        }
704    }
705
706    /// Gets memory metrics from the system.
707    ///
708    /// # Returns
709    ///
710    /// A DMSCMemoryMetrics instance containing the memory metrics
711    fn get_memory_metrics(&self) -> DMSCMemoryMetrics {
712        let total = self.system.total_memory();
713        let used = self.system.used_memory();
714        let free = self.system.free_memory();
715        let usage_percent = (used as f64 / total as f64) * 100.0;
716
717        let swap_total = self.system.total_swap();
718        let swap_used = self.system.used_swap();
719        let swap_free = self.system.free_swap();
720        let swap_usage_percent = if swap_total > 0 {
721            (swap_used as f64 / swap_total as f64) * 100.0
722        } else {
723            0.0
724        };
725
726        DMSCMemoryMetrics {
727            total_bytes: total,
728            used_bytes: used,
729            free_bytes: free,
730            usage_percent,
731            swap_total_bytes: swap_total,
732            swap_used_bytes: swap_used,
733            swap_free_bytes: swap_free,
734            swap_usage_percent,
735        }
736    }
737
738    /// Gets disk metrics from the system.
739    ///
740    /// # Returns
741    ///
742    /// A DMSCDiskMetrics instance containing the disk metrics
743    fn get_disk_metrics(&self) -> DMSCDiskMetrics {
744        // Get first disk for now
745        if let Some(disk) = self.system.disks().first() {
746            let total = disk.total_space();
747            let available = disk.available_space();
748            let used = total - available;
749            let usage_percent = (used as f64 / total as f64) * 100.0;
750
751            // Note: sysinfo crate doesn't expose I/O statistics on all platforms
752            // These values will be 0 on platforms where they're not available
753            DMSCDiskMetrics {
754                total_bytes: total,
755                used_bytes: used,
756                free_bytes: available,
757                usage_percent,
758                read_bytes: 0,
759                write_bytes: 0,
760                read_count: 0,
761                write_count: 0,
762            }
763        } else {
764            DMSCDiskMetrics {
765                total_bytes: 0,
766                used_bytes: 0,
767                free_bytes: 0,
768                usage_percent: 0.0,
769                read_bytes: 0,
770                write_bytes: 0,
771                read_count: 0,
772                write_count: 0,
773            }
774        }
775    }
776
777    /// Gets network metrics from the system.
778    ///
779    /// # Returns
780    ///
781    /// A DMSCNetworkMetrics instance containing the network metrics
782    fn get_network_metrics(&mut self) -> DMSCNetworkMetrics {
783        let (received_bytes, transmitted_bytes, received_packets, transmitted_packets) =
784            Self::get_network_total(&self.system);
785
786        let now = Instant::now();
787        let elapsed = now
788            .duration_since(self.last_network_time)
789            .as_secs_f64()
790            .max(1.0);
791
792        let received_bytes_per_sec =
793            ((received_bytes - self.last_network_received) as f64 / elapsed) as u64;
794        let transmitted_bytes_per_sec =
795            ((transmitted_bytes - self.last_network_transmitted) as f64 / elapsed) as u64;
796        let received_packets_per_sec =
797            ((received_packets - self.last_network_received_packets) as f64 / elapsed) as u64;
798        let transmitted_packets_per_sec =
799            ((transmitted_packets - self.last_network_transmitted_packets) as f64 / elapsed) as u64;
800
801        // Update last values
802        self.last_network_received = received_bytes;
803        self.last_network_transmitted = transmitted_bytes;
804        self.last_network_received_packets = received_packets;
805        self.last_network_transmitted_packets = transmitted_packets;
806        self.last_network_time = now;
807
808        DMSCNetworkMetrics {
809            total_received_bytes: received_bytes,
810            total_transmitted_bytes: transmitted_bytes,
811            received_bytes_per_sec,
812            transmitted_bytes_per_sec,
813            total_received_packets: received_packets,
814            total_transmitted_packets: transmitted_packets,
815            received_packets_per_sec,
816            transmitted_packets_per_sec,
817        }
818    }
819
820    /// Gets total network metrics from all interfaces.
821    ///
822    /// # Parameters
823    ///
824    /// - `system`: The sysinfo System instance
825    ///
826    /// # Returns
827    ///
828    /// A tuple containing (received_bytes, transmitted_bytes, received_packets, transmitted_packets)
829    fn get_network_total(system: &System) -> (u64, u64, u64, u64) {
830        let mut received_bytes = 0;
831        let mut transmitted_bytes = 0;
832        let mut received_packets = 0;
833        let mut transmitted_packets = 0;
834
835        for (_, data) in system.networks() {
836            received_bytes += data.received();
837            transmitted_bytes += data.transmitted();
838            received_packets += data.packets_received();
839            transmitted_packets += data.packets_transmitted();
840        }
841
842        (
843            received_bytes,
844            transmitted_bytes,
845            received_packets,
846            transmitted_packets,
847        )
848    }
849}
850
851#[cfg(feature = "pyo3")]
852#[pyo3::prelude::pymethods]
853impl DMSCSystemMetricsCollector {
854    #[new]
855    fn py_new() -> Self {
856        Self::new()
857    }
858    
859    #[pyo3(name = "collect")]
860    fn py_collect(&mut self) -> DMSCSystemMetrics {
861        self.collect()
862    }
863    
864    #[pyo3(name = "refresh")]
865    fn py_refresh(&mut self) {
866        self.system.refresh_all();
867    }
868}
869
870#[cfg(feature = "pyo3")]
871#[pyo3::prelude::pymethods]
872impl DMSCSystemMetrics {
873    #[pyo3(name = "cpu")]
874    fn py_cpu(&self) -> DMSCCPUMetrics {
875        self.cpu.clone()
876    }
877    
878    #[pyo3(name = "memory")]
879    fn py_memory(&self) -> DMSCMemoryMetrics {
880        self.memory.clone()
881    }
882    
883    #[pyo3(name = "disk")]
884    fn py_disk(&self) -> DMSCDiskMetrics {
885        self.disk.clone()
886    }
887    
888    #[pyo3(name = "network")]
889    fn py_network(&self) -> DMSCNetworkMetrics {
890        self.network.clone()
891    }
892    
893    #[pyo3(name = "timestamp")]
894    fn py_timestamp(&self) -> u64 {
895        self.timestamp
896    }
897}