#![allow(non_snake_case)]
use crate::core::RiResult;
use serde::{Deserialize, Serialize};
use std::collections::HashMap as FxHashMap;
use std::time::{Duration, SystemTime};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
pub enum RiHealthStatus {
Healthy,
Degraded,
Unhealthy,
Unknown,
}
impl RiHealthStatus {
pub fn is_healthy(&self) -> bool {
matches!(self, RiHealthStatus::Healthy | RiHealthStatus::Degraded)
}
pub fn requires_attention(&self) -> bool {
matches!(self, RiHealthStatus::Unhealthy)
}
pub fn merge(statuses: &[RiHealthStatus]) -> RiHealthStatus {
if statuses.is_empty() {
return RiHealthStatus::Unknown;
}
let mut has_unhealthy = false;
let mut has_degraded = false;
let mut has_unknown = false;
for status in statuses {
match status {
RiHealthStatus::Unhealthy => has_unhealthy = true,
RiHealthStatus::Degraded => has_degraded = true,
RiHealthStatus::Unknown => has_unknown = true,
RiHealthStatus::Healthy => {}
}
}
if has_unhealthy {
RiHealthStatus::Unhealthy
} else if has_degraded {
RiHealthStatus::Degraded
} else if has_unknown {
RiHealthStatus::Unknown
} else {
RiHealthStatus::Healthy
}
}
}
impl std::fmt::Display for RiHealthStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RiHealthStatus::Healthy => write!(f, "healthy"),
RiHealthStatus::Degraded => write!(f, "degraded"),
RiHealthStatus::Unhealthy => write!(f, "unhealthy"),
RiHealthStatus::Unknown => write!(f, "unknown"),
}
}
}
#[cfg(feature = "pyo3")]
#[pyo3::prelude::pymethods]
impl RiHealthStatus {
fn __str__(&self) -> String {
self.to_string()
}
fn __repr__(&self) -> String {
format!("RiHealthStatus::{}", self)
}
#[staticmethod]
fn merge_statuses(statuses: Vec<RiHealthStatus>) -> Self {
RiHealthStatus::merge(&statuses)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
pub struct RiHealthCheckResult {
pub name: String,
pub status: RiHealthStatus,
pub message: Option<String>,
pub timestamp: SystemTime,
pub duration: Duration,
}
impl RiHealthCheckResult {
pub fn healthy(name: String, message: Option<String>) -> Self {
Self {
name,
status: RiHealthStatus::Healthy,
message,
timestamp: SystemTime::now(),
duration: Duration::ZERO,
}
}
pub fn degraded(name: String, message: Option<String>) -> Self {
Self {
name,
status: RiHealthStatus::Degraded,
message,
timestamp: SystemTime::now(),
duration: Duration::ZERO,
}
}
pub fn unhealthy(name: String, message: Option<String>) -> Self {
Self {
name,
status: RiHealthStatus::Unhealthy,
message,
timestamp: SystemTime::now(),
duration: Duration::ZERO,
}
}
pub fn unknown(name: String, message: Option<String>) -> Self {
Self {
name,
status: RiHealthStatus::Unknown,
message,
timestamp: SystemTime::now(),
duration: Duration::ZERO,
}
}
}
#[cfg(feature = "pyo3")]
#[pyo3::prelude::pymethods]
impl RiHealthCheckResult {
#[new]
fn new_py(name: String, status: RiHealthStatus, message: Option<String>) -> Self {
Self {
name,
status,
message,
timestamp: SystemTime::now(),
duration: Duration::ZERO,
}
}
#[staticmethod]
fn create_healthy(name: String, message: Option<String>) -> Self {
Self::healthy(name, message)
}
#[staticmethod]
fn create_degraded(name: String, message: Option<String>) -> Self {
Self::degraded(name, message)
}
#[staticmethod]
fn create_unhealthy(name: String, message: Option<String>) -> Self {
Self::unhealthy(name, message)
}
#[staticmethod]
fn create_unknown(name: String, message: Option<String>) -> Self {
Self::unknown(name, message)
}
#[getter]
fn name(&self) -> String {
self.name.clone()
}
#[getter]
fn status(&self) -> RiHealthStatus {
self.status
}
#[getter]
fn message(&self) -> Option<String> {
self.message.clone()
}
fn __str__(&self) -> String {
format!("{}: {}", self.name, self.status)
}
fn __repr__(&self) -> String {
format!("RiHealthCheckResult {{ name: {:?}, status: {:?} }}", self.name, self.status)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
pub struct RiHealthCheckConfig {
pub check_interval: Duration,
pub timeout: Duration,
pub failure_threshold: u32,
pub success_threshold: u32,
pub enabled: bool,
}
impl Default for RiHealthCheckConfig {
fn default() -> Self {
Self {
check_interval: Duration::from_secs(30),
timeout: Duration::from_secs(5),
failure_threshold: 3,
success_threshold: 2,
enabled: true,
}
}
}
#[cfg(feature = "pyo3")]
#[pyo3::prelude::pymethods]
impl RiHealthCheckConfig {
#[new]
fn new_py(check_interval: u64, timeout: u64, failure_threshold: u32, success_threshold: u32, enabled: bool) -> Self {
Self {
check_interval: Duration::from_secs(check_interval),
timeout: Duration::from_secs(timeout),
failure_threshold,
success_threshold,
enabled,
}
}
#[staticmethod]
fn default_config() -> Self {
Self::default()
}
#[getter]
fn check_interval(&self) -> u64 {
self.check_interval.as_secs()
}
#[setter]
fn set_check_interval(&mut self, value: u64) {
self.check_interval = Duration::from_secs(value);
}
#[getter]
fn timeout(&self) -> u64 {
self.timeout.as_secs()
}
#[setter]
fn set_timeout(&mut self, value: u64) {
self.timeout = Duration::from_secs(value);
}
#[getter]
fn failure_threshold(&self) -> u32 {
self.failure_threshold
}
#[getter]
fn success_threshold(&self) -> u32 {
self.success_threshold
}
#[getter]
fn enabled(&self) -> bool {
self.enabled
}
fn __repr__(&self) -> String {
format!("RiHealthCheckConfig {{ check_interval: {}, timeout: {}, failure_threshold: {}, success_threshold: {}, enabled: {} }}",
self.check_interval.as_secs(), self.timeout.as_secs(), self.failure_threshold, self.success_threshold, self.enabled)
}
}
#[async_trait::async_trait]
pub trait HealthCheck: Send + Sync {
async fn check(&self) -> RiResult<RiHealthCheckResult>;
fn name(&self) -> &str;
fn config(&self) -> &RiHealthCheckConfig;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
pub struct RiHealthReport {
pub overall_status: RiHealthStatus,
pub components: FxHashMap<String, RiHealthCheckResult>,
pub timestamp: SystemTime,
pub total_components: usize,
pub healthy_count: usize,
pub degraded_count: usize,
pub unhealthy_count: usize,
pub unknown_count: usize,
}
impl RiHealthReport {
pub fn new() -> Self {
Self {
overall_status: RiHealthStatus::Unknown,
components: FxHashMap::default(),
timestamp: SystemTime::now(),
total_components: 0,
healthy_count: 0,
degraded_count: 0,
unhealthy_count: 0,
unknown_count: 0,
}
}
pub fn add_result(&mut self, result: RiHealthCheckResult) {
match result.status {
RiHealthStatus::Healthy => self.healthy_count += 1,
RiHealthStatus::Degraded => self.degraded_count += 1,
RiHealthStatus::Unhealthy => self.unhealthy_count += 1,
RiHealthStatus::Unknown => self.unknown_count += 1,
}
self.total_components += 1;
self.components.insert(result.name.clone(), result);
self.update_overall_status();
}
fn update_overall_status(&mut self) {
let statuses: Vec<RiHealthStatus> = self.components.values().map(|r| r.status).collect();
self.overall_status = RiHealthStatus::merge(&statuses);
}
}
impl Default for RiHealthReport {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "pyo3")]
#[pyo3::prelude::pymethods]
impl RiHealthReport {
#[new]
fn new_py() -> Self {
Self::new()
}
#[staticmethod]
fn create() -> Self {
Self::new()
}
#[staticmethod]
fn from_results(results: Vec<RiHealthCheckResult>) -> Self {
let mut report = Self::new();
for result in results {
report.add_result(result);
}
report
}
#[getter]
fn overall_status(&self) -> RiHealthStatus {
self.overall_status
}
#[getter]
fn total_components(&self) -> usize {
self.total_components
}
#[getter]
fn healthy_count(&self) -> usize {
self.healthy_count
}
#[getter]
fn degraded_count(&self) -> usize {
self.degraded_count
}
#[getter]
fn unhealthy_count(&self) -> usize {
self.unhealthy_count
}
#[getter]
fn unknown_count(&self) -> usize {
self.unknown_count
}
fn __str__(&self) -> String {
format!("RiHealthReport: {} ({}/{} healthy, {} degraded, {} unhealthy, {} unknown)",
self.overall_status, self.healthy_count, self.total_components,
self.degraded_count, self.unhealthy_count, self.unknown_count)
}
fn __repr__(&self) -> String {
format!("RiHealthReport {{ overall_status: {:?}, total_components: {} }}", self.overall_status, self.total_components)
}
}
pub struct RiHealthChecker {
checks: Vec<Box<dyn HealthCheck>>,
_config: RiHealthCheckConfig,
}
impl RiHealthChecker {
pub fn new() -> Self {
Self {
checks: Vec::new(),
_config: RiHealthCheckConfig::default(),
}
}
pub fn with_config(config: RiHealthCheckConfig) -> Self {
Self {
checks: Vec::new(),
_config: config,
}
}
pub fn register_check(&mut self, check: Box<dyn HealthCheck>) {
self.checks.push(check);
}
pub async fn check_all(&self) -> RiHealthReport {
let mut report = RiHealthReport::new();
for check in &self.checks {
if !check.config().enabled {
continue;
}
let start_time = SystemTime::now();
let result = match tokio::time::timeout(check.config().timeout, check.check()).await {
Ok(Ok(result)) => result,
Ok(Err(err)) => RiHealthCheckResult::unknown(
check.name().to_string(),
Some(format!("Check failed: {err}")),
),
Err(_) => RiHealthCheckResult::unknown(
check.name().to_string(),
Some("Check timed out".to_string()),
),
};
let duration = SystemTime::now()
.duration_since(start_time)
.unwrap_or(Duration::ZERO);
let mut result_with_duration = result;
result_with_duration.duration = duration;
report.add_result(result_with_duration);
}
report
}
pub fn check_count(&self) -> usize {
self.checks.len()
}
}
impl Default for RiHealthChecker {
fn default() -> Self {
Self::new()
}
}