1use serde::{Deserialize, Serialize};
5use std::sync::Arc;
6use std::time::{Duration, Instant, SystemTime};
7use tokio::sync::RwLock;
8use tracing::{debug, error, warn};
9
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
12pub enum HealthStatus {
13 Healthy,
15 Degraded,
17 Unhealthy,
19 Unknown,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct HealthCheckResult {
26 pub name: String,
28 pub status: HealthStatus,
30 pub message: Option<String>,
32 pub timestamp: SystemTime,
34 pub duration: Duration,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct HealthReport {
41 pub status: HealthStatus,
43 pub checks: Vec<HealthCheckResult>,
45 pub timestamp: SystemTime,
47 pub total_duration: Duration,
49}
50
51#[async_trait::async_trait]
53pub trait HealthCheck: Send + Sync {
54 fn name(&self) -> &str;
56
57 async fn check(&self) -> HealthCheckResult;
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct PerformanceMetrics {
64 pub total_requests: u64,
66 pub successful_requests: u64,
68 pub failed_requests: u64,
70 pub avg_response_time_ms: f64,
72 pub p95_response_time_ms: f64,
74 pub p99_response_time_ms: f64,
76 pub active_connections: u64,
78 pub memory_usage_bytes: u64,
80 pub cpu_usage_percent: f64,
82 pub timestamp: SystemTime,
84}
85
86#[derive(Debug, Clone)]
88pub struct RequestMetrics {
89 pub start_time: Instant,
91 pub method: String,
93 pub success: bool,
95 pub response_time: Duration,
97 pub error_message: Option<String>,
99}
100
101pub struct MonitoringSystem {
103 health_checks: Arc<RwLock<Vec<Box<dyn HealthCheck>>>>,
105 metrics: Arc<RwLock<PerformanceMetrics>>,
107 response_times: Arc<RwLock<Vec<Duration>>>,
109 config: MonitoringConfig,
111}
112
113#[derive(Debug, Clone)]
115pub struct MonitoringConfig {
116 pub max_response_times: usize,
118 pub health_check_interval: Duration,
120 pub detailed_logging: bool,
122}
123
124impl Default for MonitoringConfig {
125 fn default() -> Self {
126 Self {
127 max_response_times: 10000,
128 health_check_interval: Duration::from_secs(30),
129 detailed_logging: true,
130 }
131 }
132}
133
134impl MonitoringSystem {
135 pub fn new(config: MonitoringConfig) -> Self {
137 Self {
138 health_checks: Arc::new(RwLock::new(Vec::new())),
139 metrics: Arc::new(RwLock::new(PerformanceMetrics::default())),
140 response_times: Arc::new(RwLock::new(Vec::new())),
141 config,
142 }
143 }
144
145 pub async fn register_health_check(&self, check: Box<dyn HealthCheck>) {
147 let mut checks = self.health_checks.write().await;
148 checks.push(check);
149 }
150
151 pub async fn health_check(&self) -> HealthReport {
153 let start_time = Instant::now();
154 let mut results = Vec::new();
155 let mut overall_status = HealthStatus::Healthy;
156
157 let checks = self.health_checks.read().await;
158
159 for check in checks.iter() {
160 let result = check.check().await;
161
162 match (&overall_status, &result.status) {
164 (HealthStatus::Healthy, HealthStatus::Degraded) => {
165 overall_status = HealthStatus::Degraded
166 }
167 (HealthStatus::Healthy | HealthStatus::Degraded, HealthStatus::Unhealthy) => {
168 overall_status = HealthStatus::Unhealthy
169 }
170 (
171 HealthStatus::Healthy | HealthStatus::Degraded | HealthStatus::Unhealthy,
172 HealthStatus::Unknown,
173 ) => overall_status = HealthStatus::Unknown,
174 _ => {}
175 }
176
177 results.push(result);
178 }
179
180 let total_duration = start_time.elapsed();
181
182 HealthReport {
183 status: overall_status,
184 checks: results,
185 timestamp: SystemTime::now(),
186 total_duration,
187 }
188 }
189
190 pub async fn record_request(&self, request: RequestMetrics) {
192 let mut metrics = self.metrics.write().await;
193 let mut response_times = self.response_times.write().await;
194
195 metrics.total_requests += 1;
197 if request.success {
198 metrics.successful_requests += 1;
199 } else {
200 metrics.failed_requests += 1;
201 }
202
203 response_times.push(request.response_time);
205
206 let current_len = response_times.len();
208 if current_len > self.config.max_response_times {
209 response_times.drain(0..current_len - self.config.max_response_times);
210 }
211
212 let mut sorted_times = response_times.clone();
214 sorted_times.sort();
215
216 if !sorted_times.is_empty() {
217 let avg_ms = sorted_times.iter().sum::<Duration>().as_secs_f64() * 1000.0
218 / sorted_times.len() as f64;
219 let p95_idx = (sorted_times.len() as f64 * 0.95) as usize;
220 let p99_idx = (sorted_times.len() as f64 * 0.99) as usize;
221
222 metrics.avg_response_time_ms = avg_ms;
223 metrics.p95_response_time_ms = sorted_times
224 .get(p95_idx)
225 .unwrap_or(&Duration::ZERO)
226 .as_secs_f64()
227 * 1000.0;
228 metrics.p99_response_time_ms = sorted_times
229 .get(p99_idx)
230 .unwrap_or(&Duration::ZERO)
231 .as_secs_f64()
232 * 1000.0;
233 }
234
235 metrics.timestamp = SystemTime::now();
237
238 if self.config.detailed_logging {
240 if request.success {
241 debug!(
242 "Request completed: {} in {:?}",
243 request.method, request.response_time
244 );
245 } else {
246 warn!(
247 "Request failed: {} in {:?} - {}",
248 request.method,
249 request.response_time,
250 request
251 .error_message
252 .unwrap_or_else(|| "Unknown error".to_string())
253 );
254 }
255 }
256 }
257
258 pub async fn get_metrics(&self) -> PerformanceMetrics {
260 self.metrics.read().await.clone()
261 }
262
263 pub async fn start_periodic_health_checks(&self) {
265 let health_checks = self.health_checks.clone();
266 let interval = self.config.health_check_interval;
267
268 tokio::spawn(async move {
269 let mut interval_timer = tokio::time::interval(interval);
270
271 loop {
272 interval_timer.tick().await;
273
274 let checks = health_checks.read().await;
275 for check in checks.iter() {
276 let result = check.check().await;
277
278 match result.status {
279 HealthStatus::Healthy => {
280 debug!("Health check '{}' passed", result.name);
281 }
282 HealthStatus::Degraded => {
283 warn!(
284 "Health check '{}' degraded: {}",
285 result.name,
286 result.message.unwrap_or_else(|| "No details".to_string())
287 );
288 }
289 HealthStatus::Unhealthy => {
290 error!(
291 "Health check '{}' failed: {}",
292 result.name,
293 result.message.unwrap_or_else(|| "No details".to_string())
294 );
295 }
296 HealthStatus::Unknown => {
297 warn!(
298 "Health check '{}' status unknown: {}",
299 result.name,
300 result.message.unwrap_or_else(|| "No details".to_string())
301 );
302 }
303 }
304 }
305 }
306 });
307 }
308
309 pub async fn update_system_metrics(&self, active_connections: u64) {
311 let mut metrics = self.metrics.write().await;
312 metrics.active_connections = active_connections;
313
314 #[cfg(target_os = "linux")]
316 {
317 if let Ok(usage) = self.get_system_usage().await {
318 metrics.memory_usage_bytes = usage.memory_bytes;
319 metrics.cpu_usage_percent = usage.cpu_percent;
320 }
321 }
322 }
323
324 #[cfg(target_os = "linux")]
326 async fn get_system_usage(&self) -> Result<SystemUsage, Box<dyn std::error::Error>> {
327 use std::fs;
328
329 let status = fs::read_to_string("/proc/self/status")?;
331 let memory_kb = status
332 .lines()
333 .find(|line| line.starts_with("VmRSS:"))
334 .and_then(|line| line.split_whitespace().nth(1))
335 .and_then(|s| s.parse::<u64>().ok())
336 .unwrap_or(0);
337
338 let stat = fs::read_to_string("/proc/self/stat")?;
340 let fields: Vec<&str> = stat.split_whitespace().collect();
341 let utime = fields
342 .get(13)
343 .and_then(|s| s.parse::<u64>().ok())
344 .unwrap_or(0);
345 let stime = fields
346 .get(14)
347 .and_then(|s| s.parse::<u64>().ok())
348 .unwrap_or(0);
349
350 let cpu_percent = ((utime + stime) as f64 / 100.0) * 0.1; Ok(SystemUsage {
354 memory_bytes: memory_kb * 1024,
355 cpu_percent,
356 })
357 }
358}
359
360#[cfg(target_os = "linux")]
361struct SystemUsage {
362 memory_bytes: u64,
363 cpu_percent: f64,
364}
365
366impl Default for PerformanceMetrics {
367 fn default() -> Self {
368 Self {
369 total_requests: 0,
370 successful_requests: 0,
371 failed_requests: 0,
372 avg_response_time_ms: 0.0,
373 p95_response_time_ms: 0.0,
374 p99_response_time_ms: 0.0,
375 active_connections: 0,
376 memory_usage_bytes: 0,
377 cpu_usage_percent: 0.0,
378 timestamp: SystemTime::now(),
379 }
380 }
381}
382
383pub struct BasicHealthCheck {
385 name: String,
386}
387
388impl BasicHealthCheck {
389 pub fn new(name: String) -> Self {
391 Self { name }
392 }
393}
394
395#[async_trait::async_trait]
396impl HealthCheck for BasicHealthCheck {
397 fn name(&self) -> &str {
398 &self.name
399 }
400
401 async fn check(&self) -> HealthCheckResult {
402 let start_time = Instant::now();
403
404 let status = if std::env::var("HEALTH_CHECK_FAIL").is_ok() {
406 HealthStatus::Unhealthy
407 } else {
408 HealthStatus::Healthy
409 };
410
411 let duration = start_time.elapsed();
412
413 HealthCheckResult {
414 name: self.name.clone(),
415 status,
416 message: Some("Basic system health check".to_string()),
417 timestamp: SystemTime::now(),
418 duration,
419 }
420 }
421}
422
423pub struct FileSystemHealthCheck {
425 test_path: std::path::PathBuf,
426}
427
428impl FileSystemHealthCheck {
429 pub fn new(test_path: std::path::PathBuf) -> Self {
431 Self { test_path }
432 }
433}
434
435#[async_trait::async_trait]
436impl HealthCheck for FileSystemHealthCheck {
437 fn name(&self) -> &str {
438 "filesystem"
439 }
440
441 async fn check(&self) -> HealthCheckResult {
442 let start_time = Instant::now();
443
444 let (status, message) = match std::fs::metadata(&self.test_path) {
445 Ok(_) => (HealthStatus::Healthy, "File system accessible".to_string()),
446 Err(e) => (
447 HealthStatus::Unhealthy,
448 format!("File system check failed: {}", e),
449 ),
450 };
451
452 let duration = start_time.elapsed();
453
454 HealthCheckResult {
455 name: "filesystem".to_string(),
456 status,
457 message: Some(message),
458 timestamp: SystemTime::now(),
459 duration,
460 }
461 }
462}
463
464#[cfg(test)]
465mod tests {
466 use super::*;
467 use std::time::Duration;
468
469 #[tokio::test]
470 async fn test_monitoring_system() {
471 let config = MonitoringConfig::default();
472 let monitoring = MonitoringSystem::new(config);
473
474 let health_check = Box::new(BasicHealthCheck::new("test".to_string()));
476 monitoring.register_health_check(health_check).await;
477
478 let report = monitoring.health_check().await;
480 assert_eq!(report.status, HealthStatus::Healthy);
481 assert_eq!(report.checks.len(), 1);
482
483 let request = RequestMetrics {
485 start_time: Instant::now(),
486 method: "test_method".to_string(),
487 success: true,
488 response_time: Duration::from_millis(100),
489 error_message: None,
490 };
491
492 monitoring.record_request(request).await;
493
494 let metrics = monitoring.get_metrics().await;
496 assert_eq!(metrics.total_requests, 1);
497 assert_eq!(metrics.successful_requests, 1);
498 assert_eq!(metrics.failed_requests, 0);
499 }
500
501 #[tokio::test]
502 async fn test_health_check_aggregation() {
503 let config = MonitoringConfig::default();
504 let monitoring = MonitoringSystem::new(config);
505
506 let healthy_check = Box::new(BasicHealthCheck::new("healthy".to_string()));
508 let degraded_check = Box::new(FileSystemHealthCheck::new(std::path::PathBuf::from(
509 "/nonexistent",
510 )));
511
512 monitoring.register_health_check(healthy_check).await;
513 monitoring.register_health_check(degraded_check).await;
514
515 let report = monitoring.health_check().await;
517
518 assert_eq!(report.checks.len(), 2);
520 assert_eq!(report.status, HealthStatus::Unhealthy);
522 }
523}