nntp_proxy/tui/
system_stats.rs1use sysinfo::{ProcessRefreshKind, ProcessesToUpdate, RefreshKind, System};
6
7#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
9pub struct SystemStats {
10 pub cpu_usage: f32,
12 pub peak_cpu_usage: f32,
14 pub memory_bytes: u64,
16 pub peak_memory_bytes: u64,
18}
19
20pub struct SystemMonitor {
25 system: System,
26 pid: sysinfo::Pid,
27 peak_cpu: f32,
28 peak_memory: u64,
29}
30
31impl SystemMonitor {
32 fn refresh_kind() -> ProcessRefreshKind {
33 ProcessRefreshKind::nothing()
34 .with_cpu()
35 .with_memory()
36 .without_tasks()
37 }
38
39 #[must_use]
44 pub fn new() -> Self {
45 let mut system =
46 System::new_with_specifics(RefreshKind::nothing().with_processes(Self::refresh_kind()));
47 let pid = sysinfo::get_current_pid().expect("Failed to get current PID");
48 system.refresh_processes_specifics(
49 ProcessesToUpdate::Some(&[pid]),
50 true,
51 Self::refresh_kind(),
52 );
53
54 Self {
55 system,
56 pid,
57 peak_cpu: 0.0,
58 peak_memory: 0,
59 }
60 }
61
62 #[must_use]
67 pub fn update(&mut self) -> SystemStats {
68 self.system.refresh_processes_specifics(
69 ProcessesToUpdate::Some(&[self.pid]),
70 true,
71 Self::refresh_kind(),
72 );
73
74 if let Some(process) = self.system.process(self.pid) {
75 let cpu = process.cpu_usage();
76 let memory = process.memory();
77
78 if cpu > self.peak_cpu {
79 self.peak_cpu = cpu;
80 }
81 if memory > self.peak_memory {
82 self.peak_memory = memory;
83 }
84
85 SystemStats {
86 cpu_usage: cpu,
87 peak_cpu_usage: self.peak_cpu,
88 memory_bytes: memory,
89 peak_memory_bytes: self.peak_memory,
90 }
91 } else {
92 SystemStats::default()
93 }
94 }
95
96 #[must_use]
100 pub fn current(&self) -> SystemStats {
101 self.system
102 .process(self.pid)
103 .map_or_else(SystemStats::default, |process| SystemStats {
104 cpu_usage: process.cpu_usage(),
105 peak_cpu_usage: self.peak_cpu,
106 memory_bytes: process.memory(),
107 peak_memory_bytes: self.peak_memory,
108 })
109 }
110}
111
112impl Default for SystemMonitor {
113 fn default() -> Self {
114 Self::new()
115 }
116}
117
118#[cfg(test)]
119#[allow(clippy::float_cmp)] mod tests {
121 use super::*;
122
123 #[test]
124 fn test_refresh_kind_only_enables_needed_process_fields() {
125 let kind = SystemMonitor::refresh_kind();
126
127 assert!(kind.cpu());
128 assert!(kind.memory());
129 assert!(!kind.tasks());
130 assert!(!kind.disk_usage());
131 }
132
133 #[test]
134 fn test_system_monitor_creation() {
135 let monitor = SystemMonitor::new();
136 let stats = monitor.current();
137
138 assert!(stats.memory_bytes > 0);
140 assert!(stats.cpu_usage >= 0.0);
142 }
143
144 #[test]
145 fn test_system_monitor_update() {
146 let mut monitor = SystemMonitor::new();
147
148 let stats1 = monitor.update();
150 assert!(stats1.memory_bytes > 0);
151
152 let stats2 = monitor.update();
154 assert!(stats2.memory_bytes > 0);
155
156 assert!(stats2.cpu_usage >= 0.0);
158 assert!(stats2.peak_memory_bytes >= stats1.memory_bytes);
159 assert!(stats2.peak_memory_bytes >= stats2.memory_bytes);
160 assert!(stats2.peak_cpu_usage >= stats2.cpu_usage);
161 }
162
163 #[test]
164 fn test_current_without_update() {
165 let monitor = SystemMonitor::new();
166 let stats1 = monitor.current();
167 let stats2 = monitor.current();
168
169 assert_eq!(stats1.memory_bytes, stats2.memory_bytes);
171 assert_eq!(stats1.cpu_usage, stats2.cpu_usage);
172 }
173
174 #[test]
175 fn test_system_stats_default() {
176 let stats = SystemStats::default();
177 assert_eq!(stats.cpu_usage, 0.0);
178 assert_eq!(stats.peak_cpu_usage, 0.0);
179 assert_eq!(stats.memory_bytes, 0);
180 assert_eq!(stats.peak_memory_bytes, 0);
181 }
182}