use crate::ActorSystem;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct HealthConfig {
pub max_actors: usize,
pub min_actors: usize,
pub max_panic_rate: f64,
pub max_restart_rate: f64,
pub max_backpressure_rate: f64,
}
impl Default for HealthConfig {
fn default() -> Self {
Self {
max_actors: 10_000, min_actors: 0, max_panic_rate: 1.0, max_restart_rate: 10.0, max_backpressure_rate: 100.0, }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HealthStatus {
Healthy,
Degraded,
Unhealthy,
}
#[derive(Debug, Clone)]
pub struct HealthIssue {
pub severity: IssueSeverity,
pub description: String,
pub current_value: f64,
pub threshold: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IssueSeverity {
Warning,
Critical,
}
pub struct SystemHealth {
system: Arc<ActorSystem>,
config: HealthConfig,
}
impl SystemHealth {
pub fn new(system: &Arc<ActorSystem>, config: HealthConfig) -> Self {
Self {
system: Arc::clone(system),
config,
}
}
pub fn is_healthy(&self) -> bool {
matches!(self.get_status(), HealthStatus::Healthy)
}
pub fn is_ready(&self) -> bool {
self.is_healthy()
}
pub fn get_status(&self) -> HealthStatus {
let issues = self.get_issues();
if issues.is_empty() {
return HealthStatus::Healthy;
}
let has_critical = issues.iter().any(|i| i.severity == IssueSeverity::Critical);
if has_critical {
HealthStatus::Unhealthy
} else {
HealthStatus::Degraded
}
}
pub fn get_issues(&self) -> Vec<HealthIssue> {
let mut issues = Vec::new();
let actor_count = self.system.actor_count();
if self.config.max_actors > 0 && actor_count > self.config.max_actors {
issues.push(HealthIssue {
severity: IssueSeverity::Warning,
description: format!("Too many actors: {}", actor_count),
current_value: actor_count as f64,
threshold: self.config.max_actors as f64,
});
}
if actor_count < self.config.min_actors {
issues.push(HealthIssue {
severity: IssueSeverity::Critical,
description: format!("Too few actors: {}", actor_count),
current_value: actor_count as f64,
threshold: self.config.min_actors as f64,
});
}
issues
}
pub fn get_report(&self) -> String {
let status = self.get_status();
let issues = self.get_issues();
let actor_count = self.system.actor_count();
let mut report = format!("Status: {:?}\nActors: {}\n", status, actor_count);
if !issues.is_empty() {
report.push_str("\nIssues:\n");
for issue in issues {
report.push_str(&format!(
" [{:?}] {} (current: {:.1}, threshold: {:.1})\n",
issue.severity, issue.description, issue.current_value, issue.threshold
));
}
}
report
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Actor, ActorContext, Message};
use async_trait::async_trait;
struct TestActor;
#[async_trait]
impl Actor for TestActor {
async fn handle_message(&mut self, _msg: Message, _ctx: &mut ActorContext) {}
}
#[tokio::test]
async fn test_health_check_healthy() {
let system = ActorSystem::new();
let config = HealthConfig::default();
let health = SystemHealth::new(&system, config);
assert!(health.is_healthy());
assert_eq!(health.get_status(), HealthStatus::Healthy);
assert!(health.get_issues().is_empty());
}
#[tokio::test]
async fn test_health_check_too_many_actors() {
let system = ActorSystem::new();
let config = HealthConfig {
max_actors: 2,
..Default::default()
};
let _a1 = system.spawn(TestActor);
let _a2 = system.spawn(TestActor);
let _a3 = system.spawn(TestActor);
let health = SystemHealth::new(&system, config);
assert_eq!(health.get_status(), HealthStatus::Degraded);
assert_eq!(health.get_issues().len(), 1);
assert_eq!(health.get_issues()[0].severity, IssueSeverity::Warning);
}
#[tokio::test]
async fn test_health_check_ready() {
let system = ActorSystem::new();
let config = HealthConfig::default();
let health = SystemHealth::new(&system, config);
assert!(health.is_ready());
}
#[tokio::test]
async fn test_health_report() {
let system = ActorSystem::new();
let config = HealthConfig::default();
let health = SystemHealth::new(&system, config);
let report = health.get_report();
assert!(report.contains("Status: Healthy"));
assert!(report.contains("Actors: 0"));
}
}