use crate::error::{Error, Result};
use crate::system::pool::AgentPool;
use serde::{Deserialize, Serialize};
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum HealthStatus {
Healthy,
Degraded,
Unhealthy,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthCheck {
pub status: HealthStatus,
pub last_check: i64,
pub duration_ms: u64,
pub message: Option<String>,
}
impl HealthCheck {
pub fn healthy(duration_ms: u64) -> Self {
Self {
status: HealthStatus::Healthy,
last_check: chrono::Utc::now().timestamp(),
duration_ms,
message: None,
}
}
pub fn degraded(duration_ms: u64, message: String) -> Self {
Self {
status: HealthStatus::Degraded,
last_check: chrono::Utc::now().timestamp(),
duration_ms,
message: Some(message),
}
}
pub fn unhealthy(duration_ms: u64, message: String) -> Self {
Self {
status: HealthStatus::Unhealthy,
last_check: chrono::Utc::now().timestamp(),
duration_ms,
message: Some(message),
}
}
}
pub struct SystemHealth {
status: HealthStatus,
last_check: Option<Instant>,
check_interval: Duration,
}
impl SystemHealth {
pub fn new() -> Self {
Self {
status: HealthStatus::Healthy,
last_check: None,
check_interval: Duration::from_secs(30),
}
}
pub fn status(&self) -> HealthStatus {
self.status
}
pub fn set_status(&mut self, status: HealthStatus) {
self.status = status;
}
pub fn is_check_due(&self) -> bool {
match self.last_check {
None => true,
Some(last) => last.elapsed() >= self.check_interval,
}
}
pub async fn check_system(&mut self) -> Result<HealthCheck> {
let start = Instant::now();
self.last_check = Some(start);
let duration_ms = start.elapsed().as_millis() as u64;
Ok(HealthCheck::healthy(duration_ms))
}
pub async fn check_agent_pool(&mut self, pool: &AgentPool) -> Result<HealthCheck> {
let start = Instant::now();
if pool.is_empty() {
let duration_ms = start.elapsed().as_millis() as u64;
self.status = HealthStatus::Unhealthy;
return Ok(HealthCheck::unhealthy(
duration_ms,
"Agent pool is empty".to_string(),
));
}
if pool.size() < 3 {
let duration_ms = start.elapsed().as_millis() as u64;
self.status = HealthStatus::Degraded;
return Ok(HealthCheck::degraded(
duration_ms,
format!("Only {} agents available (minimum: 3)", pool.size()),
));
}
if let Err(e) = pool.health_check_all().await {
let duration_ms = start.elapsed().as_millis() as u64;
self.status = HealthStatus::Degraded;
return Ok(HealthCheck::degraded(
duration_ms,
format!("Some agents failed health check: {}", e),
));
}
let duration_ms = start.elapsed().as_millis() as u64;
self.status = HealthStatus::Healthy;
Ok(HealthCheck::healthy(duration_ms))
}
}
impl Default for SystemHealth {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_health_status() {
let mut health = SystemHealth::new();
assert_eq!(health.status(), HealthStatus::Healthy);
health.set_status(HealthStatus::Degraded);
assert_eq!(health.status(), HealthStatus::Degraded);
}
#[test]
fn test_health_check_creation() {
let check = HealthCheck::healthy(100);
assert_eq!(check.status, HealthStatus::Healthy);
assert!(check.message.is_none());
let check = HealthCheck::degraded(100, "Warning".to_string());
assert_eq!(check.status, HealthStatus::Degraded);
assert!(check.message.is_some());
}
#[tokio::test]
async fn test_system_health_check() {
let mut health = SystemHealth::new();
let result = health.check_system().await.unwrap();
assert_eq!(result.status, HealthStatus::Healthy);
}
}