oxirs_gql/
system_monitor.rs1use anyhow::Result;
7use serde::{Deserialize, Serialize};
8use std::sync::{Arc, Mutex};
9use std::time::{Duration, Instant};
10use sysinfo::{Pid, System};
11use tokio::sync::RwLock;
12use tracing::{debug, warn};
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct SystemMetrics {
17 pub memory_usage_mb: f64,
19 pub cpu_usage_percent: f64,
21 pub total_memory_mb: f64,
23 pub available_memory_mb: f64,
25 pub system_cpu_usage_percent: f64,
27 pub cpu_cores: usize,
29 pub process_id: u32,
31 #[serde(with = "duration_serde")]
33 pub timestamp: Duration,
34}
35
36mod duration_serde {
38 use super::*;
39 use serde::{Deserializer, Serializer};
40
41 pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
42 where
43 S: Serializer,
44 {
45 serializer.serialize_u64(duration.as_millis() as u64)
46 }
47
48 pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
49 where
50 D: Deserializer<'de>,
51 {
52 let millis = u64::deserialize(deserializer)?;
53 Ok(Duration::from_millis(millis))
54 }
55}
56
57impl Default for SystemMetrics {
58 fn default() -> Self {
59 Self {
60 memory_usage_mb: 0.0,
61 cpu_usage_percent: 0.0,
62 total_memory_mb: 0.0,
63 available_memory_mb: 0.0,
64 system_cpu_usage_percent: 0.0,
65 cpu_cores: std::thread::available_parallelism()
66 .map(|n| n.get())
67 .unwrap_or(1),
68 process_id: std::process::id(),
69 timestamp: Duration::from_secs(0),
70 }
71 }
72}
73
74#[derive(Debug, Clone)]
76pub struct SystemMonitorConfig {
77 pub update_interval: Duration,
79 pub enable_per_core_cpu: bool,
81 pub enable_memory_breakdown: bool,
83 pub enable_network_monitoring: bool,
85 pub enable_disk_monitoring: bool,
87}
88
89impl Default for SystemMonitorConfig {
90 fn default() -> Self {
91 Self {
92 update_interval: Duration::from_secs(1),
93 enable_per_core_cpu: false,
94 enable_memory_breakdown: true,
95 enable_network_monitoring: false,
96 enable_disk_monitoring: false,
97 }
98 }
99}
100
101pub struct SystemMonitor {
103 config: SystemMonitorConfig,
104 system: Arc<Mutex<System>>,
105 last_update: Arc<RwLock<Instant>>,
106 cached_metrics: Arc<RwLock<SystemMetrics>>,
107 process_id: u32,
108}
109
110impl Default for SystemMonitor {
111 fn default() -> Self {
112 Self::new()
113 }
114}
115
116impl SystemMonitor {
117 pub fn new() -> Self {
119 Self::with_config(SystemMonitorConfig::default())
120 }
121
122 pub fn with_config(config: SystemMonitorConfig) -> Self {
124 let mut system = System::new();
127 system.refresh_memory();
129 system.refresh_cpu_specifics(sysinfo::CpuRefreshKind::everything());
130
131 let process_id = std::process::id();
132
133 Self {
134 config,
135 system: Arc::new(Mutex::new(system)),
136 last_update: Arc::new(RwLock::new(Instant::now())),
137 cached_metrics: Arc::new(RwLock::new(SystemMetrics::default())),
138 process_id,
139 }
140 }
141
142 pub async fn get_metrics(&self) -> Result<SystemMetrics> {
144 let now = Instant::now();
145 let last_update = *self.last_update.read().await;
146
147 if now.duration_since(last_update) >= self.config.update_interval {
149 self.update_metrics().await?;
150 }
151
152 let metrics = self.cached_metrics.read().await.clone();
153 Ok(metrics)
154 }
155
156 pub async fn update_metrics(&self) -> Result<()> {
158 let metrics = {
159 let mut system = self
160 .system
161 .lock()
162 .map_err(|_| anyhow::anyhow!("System mutex poisoned"))?;
163
164 system.refresh_cpu_all();
166 system.refresh_memory();
167 system.refresh_processes(sysinfo::ProcessesToUpdate::All, true);
168
169 self.collect_metrics(&mut system)?
170 };
171
172 {
174 let mut cached = self.cached_metrics.write().await;
175 *cached = metrics;
176 }
177
178 {
179 let mut last_update = self.last_update.write().await;
180 *last_update = Instant::now();
181 }
182
183 debug!("System metrics updated");
184 Ok(())
185 }
186
187 fn collect_metrics(&self, system: &mut System) -> Result<SystemMetrics> {
189 let (memory_usage_mb, cpu_usage_percent) =
191 if let Some(process) = system.process(Pid::from_u32(self.process_id)) {
192 let memory_bytes = process.memory();
193 let memory_mb = memory_bytes as f64 / 1024.0 / 1024.0;
194 let cpu_percent = process.cpu_usage() as f64;
195 (memory_mb, cpu_percent)
196 } else {
197 warn!("Could not find current process in system info");
198 (0.0, 0.0)
199 };
200
201 let total_memory_mb = system.total_memory() as f64 / 1024.0 / 1024.0;
203 let available_memory_mb = system.available_memory() as f64 / 1024.0 / 1024.0;
204
205 let system_cpu_usage_percent = system.global_cpu_usage() as f64;
207
208 let cpu_cores = system.cpus().len();
209
210 let timestamp = std::time::SystemTime::now()
212 .duration_since(std::time::UNIX_EPOCH)
213 .unwrap_or(Duration::from_secs(0));
214
215 Ok(SystemMetrics {
216 memory_usage_mb,
217 cpu_usage_percent,
218 total_memory_mb,
219 available_memory_mb,
220 system_cpu_usage_percent,
221 cpu_cores,
222 process_id: self.process_id,
223 timestamp,
224 })
225 }
226
227 pub async fn get_memory_usage_mb(&self) -> Result<f64> {
229 let metrics = self.get_metrics().await?;
230 Ok(metrics.memory_usage_mb)
231 }
232
233 pub async fn get_cpu_usage_percent(&self) -> Result<f64> {
235 let metrics = self.get_metrics().await?;
236 Ok(metrics.cpu_usage_percent)
237 }
238
239 pub async fn get_cached_metrics(&self) -> SystemMetrics {
241 self.cached_metrics.read().await.clone()
242 }
243
244 pub async fn start_background_monitoring(self: Arc<Self>) -> tokio::task::JoinHandle<()> {
246 let monitor = self.clone();
247
248 tokio::spawn(async move {
249 let mut interval = tokio::time::interval(monitor.config.update_interval);
250
251 loop {
252 interval.tick().await;
253
254 if let Err(e) = monitor.update_metrics().await {
255 warn!("Failed to update system metrics: {}", e);
256 }
257 }
258 })
259 }
260}
261
262pub async fn get_current_memory_usage_mb() -> f64 {
264 #[cfg(test)]
265 {
266 0.0
268 }
269
270 #[cfg(not(test))]
271 {
272 static MONITOR: std::sync::OnceLock<Arc<SystemMonitor>> = std::sync::OnceLock::new();
273
274 let monitor = MONITOR.get_or_init(|| Arc::new(SystemMonitor::new()));
275
276 monitor.get_memory_usage_mb().await.unwrap_or_else(|e| {
277 warn!("Failed to get memory usage: {}", e);
278 0.0
279 })
280 }
281}
282
283pub async fn get_current_cpu_usage_percent() -> f64 {
285 #[cfg(test)]
286 {
287 0.0
289 }
290
291 #[cfg(not(test))]
292 {
293 static MONITOR: std::sync::OnceLock<Arc<SystemMonitor>> = std::sync::OnceLock::new();
294
295 let monitor = MONITOR.get_or_init(|| Arc::new(SystemMonitor::new()));
296
297 monitor.get_cpu_usage_percent().await.unwrap_or_else(|e| {
298 warn!("Failed to get CPU usage: {}", e);
299 0.0
300 })
301 }
302}
303
304pub fn calculate_throughput_mbps(requests_per_second: f64, avg_response_size_bytes: f64) -> f64 {
306 if requests_per_second <= 0.0 || avg_response_size_bytes <= 0.0 {
307 return 0.0;
308 }
309
310 let bytes_per_second = requests_per_second * avg_response_size_bytes;
312 let bits_per_second = bytes_per_second * 8.0;
313
314 bits_per_second / (1024.0 * 1024.0)
315}
316
317#[cfg(test)]
318mod tests {
319 use super::*;
320 use std::time::Duration;
321
322 #[tokio::test]
323 async fn test_system_monitor_creation() {
324 let monitor = SystemMonitor::new();
325
326 monitor.update_metrics().await.expect("should succeed");
328
329 let metrics = monitor.get_metrics().await.expect("should succeed");
331
332 assert!(metrics.cpu_cores > 0);
334 assert!(metrics.process_id > 0);
335 assert!(metrics.total_memory_mb >= 0.0);
337 }
338
339 #[tokio::test]
340 async fn test_memory_monitoring() {
341 let monitor = SystemMonitor::new();
342
343 let memory_usage = monitor.get_memory_usage_mb().await.expect("should succeed");
344
345 assert!(memory_usage >= 0.0);
347 }
348
349 #[tokio::test]
350 async fn test_cpu_monitoring() {
351 let monitor = SystemMonitor::new();
352
353 monitor.update_metrics().await.expect("should succeed");
355
356 let cpu_usage = monitor
357 .get_cpu_usage_percent()
358 .await
359 .expect("should succeed");
360
361 assert!(cpu_usage >= 0.0);
363 assert!(cpu_usage <= 100.0);
364 }
365
366 #[test]
367 fn test_throughput_calculation() {
368 let throughput = calculate_throughput_mbps(100.0, 1024.0); assert!(throughput > 0.0);
371
372 assert_eq!(calculate_throughput_mbps(0.0, 1024.0), 0.0);
374 assert_eq!(calculate_throughput_mbps(100.0, 0.0), 0.0);
375 assert_eq!(calculate_throughput_mbps(-1.0, 1024.0), 0.0);
376 }
377
378 #[tokio::test]
379 async fn test_convenience_functions() {
380 let memory = get_current_memory_usage_mb().await;
382 assert!(memory >= 0.0);
383
384 let cpu = get_current_cpu_usage_percent().await;
385 assert!(cpu >= 0.0);
386 assert!(cpu <= 100.0);
387 }
388
389 #[tokio::test]
390 async fn test_background_monitoring() {
391 let monitor = Arc::new(SystemMonitor::with_config(SystemMonitorConfig {
392 update_interval: Duration::from_millis(100),
393 ..SystemMonitorConfig::default()
394 }));
395
396 let handle = monitor.clone().start_background_monitoring().await;
397
398 tokio::time::sleep(Duration::from_millis(150)).await;
400
401 let metrics = monitor.get_cached_metrics().await;
403 assert!(metrics.cpu_cores > 0);
404
405 handle.abort();
406 }
407}