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}