use crate::utils::current_timestamp;
use serde::{Deserialize, Serialize};
use std::time::SystemTime;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum HealthStatus {
Healthy,
Degraded,
Unhealthy,
Down,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComponentHealth {
pub component: String,
pub status: HealthStatus,
pub message: Option<String>,
pub last_check: u64,
pub response_time_ms: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthReport {
pub overall_status: HealthStatus,
pub components: Vec<ComponentHealth>,
pub timestamp: u64,
pub uptime_seconds: u64,
}
pub struct HealthChecker {
start_time: SystemTime,
}
impl HealthChecker {
pub fn new() -> Self {
Self {
start_time: SystemTime::now(),
}
}
pub fn check_health(
&self,
network_healthy: bool,
storage_healthy: bool,
rpc_healthy: bool,
network_metrics: Option<&crate::node::metrics::NetworkMetrics>,
storage_metrics: Option<&crate::node::metrics::StorageMetrics>,
) -> HealthReport {
let timestamp = current_timestamp();
let uptime = SystemTime::now()
.duration_since(self.start_time)
.unwrap_or_default()
.as_secs();
let mut components = Vec::new();
let network_status = if network_healthy {
HealthStatus::Healthy
} else {
HealthStatus::Unhealthy
};
components.push(ComponentHealth {
component: "network".to_string(),
status: network_status.clone(),
message: network_metrics.map(|m| {
format!(
"Peers: {}, Connections: {}, Banned: {}",
m.peer_count, m.active_connections, m.banned_peers
)
}),
last_check: timestamp,
response_time_ms: None,
});
let storage_status = if storage_healthy {
if let Some(metrics) = storage_metrics {
if metrics.within_bounds {
HealthStatus::Healthy
} else {
HealthStatus::Degraded
}
} else {
HealthStatus::Healthy
}
} else {
HealthStatus::Unhealthy
};
components.push(ComponentHealth {
component: "storage".to_string(),
status: storage_status.clone(),
message: storage_metrics.map(|m| {
format!(
"Blocks: {}, UTXOs: {}, Disk: {} bytes",
m.block_count, m.utxo_count, m.disk_size
)
}),
last_check: timestamp,
response_time_ms: None,
});
let rpc_status = if rpc_healthy {
HealthStatus::Healthy
} else {
HealthStatus::Unhealthy
};
components.push(ComponentHealth {
component: "rpc".to_string(),
status: rpc_status.clone(),
message: None,
last_check: timestamp,
response_time_ms: None,
});
let overall_status = if components.iter().any(|c| c.status == HealthStatus::Down) {
HealthStatus::Down
} else if components
.iter()
.any(|c| c.status == HealthStatus::Unhealthy)
{
HealthStatus::Unhealthy
} else if components
.iter()
.any(|c| c.status == HealthStatus::Degraded)
{
HealthStatus::Degraded
} else {
HealthStatus::Healthy
};
HealthReport {
overall_status,
components,
timestamp,
uptime_seconds: uptime,
}
}
pub fn quick_check(
&self,
network_healthy: bool,
storage_healthy: bool,
rpc_healthy: bool,
) -> HealthStatus {
if !network_healthy || !storage_healthy || !rpc_healthy {
HealthStatus::Unhealthy
} else {
HealthStatus::Healthy
}
}
}
impl Default for HealthChecker {
fn default() -> Self {
Self::new()
}
}