#![ allow( clippy::missing_inline_in_public_items, clippy::unused_async ) ]
mod private
{
use std::
{
collections ::HashMap,
time ::Instant,
};
use serde::{ Deserialize, Serialize };
use crate::
{
Client,
client_api_accessors ::ClientApiAccessors,
environment ::{ OpenaiEnvironment, EnvironmentInterface },
};
#[ derive( Debug, Clone, PartialEq, Serialize, Deserialize ) ]
pub enum HealthStatus
{
Healthy,
Degraded,
Unhealthy,
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub enum HealthCheckStrategy
{
Ping,
LightweightApi,
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub struct HealthCheckResult
{
pub endpoint_url : String,
pub status : HealthStatus,
pub response_time_ms : u64,
pub error_message : Option< String >,
pub timestamp : std::time::SystemTime,
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub struct HealthCheckConfig
{
pub timeout_ms : u64,
pub strategy : HealthCheckStrategy,
pub degraded_threshold_ms : u64,
pub unhealthy_threshold_ms : u64,
}
impl Default for HealthCheckConfig
{
fn default() -> Self
{
Self
{
timeout_ms : 5000,
strategy : HealthCheckStrategy::LightweightApi,
degraded_threshold_ms : 1000,
unhealthy_threshold_ms : 5000,
}
}
}
#[ derive( Debug ) ]
pub struct HealthChecker;
impl HealthChecker
{
pub async fn check_endpoint< E >(
client : &Client< E >,
config : &HealthCheckConfig
) -> HealthCheckResult
where
E : OpenaiEnvironment + EnvironmentInterface + Send + Sync + 'static,
{
let start_time = Instant::now();
let endpoint_url = client.environment.base_url().to_string();
let ( status, error_message ) = match config.strategy
{
HealthCheckStrategy::Ping => Self::ping_check( client, config ).await,
HealthCheckStrategy::LightweightApi => Self::lightweight_api_check( client, config ).await,
};
let response_time_ms = u64::try_from( start_time.elapsed().as_millis() ).unwrap_or( u64::MAX );
let final_status = match status
{
HealthStatus::Healthy if response_time_ms >= config.unhealthy_threshold_ms => HealthStatus::Unhealthy,
HealthStatus::Healthy if response_time_ms >= config.degraded_threshold_ms => HealthStatus::Degraded,
other => other,
};
HealthCheckResult
{
endpoint_url,
status : final_status,
response_time_ms,
error_message,
timestamp : std::time::SystemTime::now(),
}
}
async fn ping_check< E >(
_client : &Client< E >,
_config : &HealthCheckConfig
) -> ( HealthStatus, Option< String > )
where
E : OpenaiEnvironment + EnvironmentInterface + Send + Sync + 'static,
{
( HealthStatus::Healthy, None )
}
async fn lightweight_api_check< E >(
client : &Client< E >,
_config : &HealthCheckConfig
) -> ( HealthStatus, Option< String > )
where
E : OpenaiEnvironment + EnvironmentInterface + Send + Sync + 'static,
{
match client.models().list().await
{
Ok( _ ) => ( HealthStatus::Healthy, None ),
Err( e ) =>
{
let error_msg = format!( "Health check failed : {e}" );
( HealthStatus::Unhealthy, Some( error_msg ) )
}
}
}
pub async fn check_multiple_endpoints< E >(
clients : &[ &Client< E > ],
config : &HealthCheckConfig
) -> Vec< HealthCheckResult >
where
E : OpenaiEnvironment + EnvironmentInterface + Send + Sync + 'static,
{
let mut results = Vec::with_capacity( clients.len() );
for client in clients
{
let result = Self::check_endpoint( client, config ).await;
results.push( result );
}
results
}
#[ must_use ]
pub fn summarize_health( results : &[ HealthCheckResult ] ) -> HashMap< String, usize >
{
let mut summary = HashMap::new();
for result in results
{
let status_str = match result.status
{
HealthStatus::Healthy => "healthy",
HealthStatus::Degraded => "degraded",
HealthStatus::Unhealthy => "unhealthy",
};
*summary.entry( status_str.to_string() ).or_insert( 0 ) += 1;
}
summary
}
}
}
crate ::mod_interface!
{
exposed use private::HealthStatus;
exposed use private::HealthCheckStrategy;
exposed use private::HealthCheckResult;
exposed use private::HealthCheckConfig;
exposed use private::HealthChecker;
}