use crate::error::ApiResult;
use crate::models::{
CircuitBreakerStatus, ComponentHealth, HealthResponse, MetricsResponse, ServiceStatus,
};
use crate::state::AppState;
use axum::{Json, extract::State};
use clmm_lp_execution::prelude::CircuitState;
use std::time::Instant;
static START_TIME: std::sync::OnceLock<Instant> = std::sync::OnceLock::new();
static REQUEST_COUNT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
static ERROR_COUNT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
pub fn init_start_time() {
START_TIME.get_or_init(Instant::now);
}
pub fn increment_request_count() {
REQUEST_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
pub fn increment_error_count() {
ERROR_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
#[utoipa::path(
get,
path = "/health",
tag = "Health",
responses(
(status = 200, description = "Service health status", body = HealthResponse)
)
)]
pub async fn health_check(State(state): State<AppState>) -> ApiResult<Json<HealthResponse>> {
let uptime = START_TIME.get().map(|t| t.elapsed().as_secs()).unwrap_or(0);
let circuit_state = state.circuit_breaker.state().await;
let circuit_status = match circuit_state {
CircuitState::Closed => CircuitBreakerStatus::Closed,
CircuitState::Open => CircuitBreakerStatus::Open,
CircuitState::HalfOpen => CircuitBreakerStatus::HalfOpen,
};
let rpc_healthy = state.provider.get_slot().await.is_ok();
let status = if rpc_healthy && circuit_state == CircuitState::Closed {
ServiceStatus::Healthy
} else if rpc_healthy {
ServiceStatus::Degraded
} else {
ServiceStatus::Unhealthy
};
let response = HealthResponse {
status,
version: env!("CARGO_PKG_VERSION").to_string(),
uptime_secs: uptime,
components: ComponentHealth {
rpc: rpc_healthy,
database: true, circuit_breaker: circuit_status,
},
};
Ok(Json(response))
}
#[utoipa::path(
get,
path = "/health/live",
tag = "Health",
responses(
(status = 200, description = "Service is alive")
)
)]
pub async fn liveness() -> &'static str {
"OK"
}
#[utoipa::path(
get,
path = "/health/ready",
tag = "Health",
responses(
(status = 200, description = "Service is ready"),
(status = 503, description = "Service is not ready")
)
)]
pub async fn readiness(State(state): State<AppState>) -> Result<&'static str, &'static str> {
if state.provider.get_slot().await.is_ok() {
Ok("OK")
} else {
Err("NOT READY")
}
}
#[utoipa::path(
get,
path = "/metrics",
tag = "Health",
responses(
(status = 200, description = "Service metrics", body = MetricsResponse)
)
)]
pub async fn metrics(State(state): State<AppState>) -> ApiResult<Json<MetricsResponse>> {
let positions = state.monitor.get_positions().await;
let strategies = state.strategies.read().await;
let response = MetricsResponse {
request_count: REQUEST_COUNT.load(std::sync::atomic::Ordering::Relaxed),
error_count: ERROR_COUNT.load(std::sync::atomic::Ordering::Relaxed),
avg_response_time_ms: 0.0, active_ws_connections: 0, positions_monitored: positions.len() as u32,
strategies_running: strategies.values().filter(|s| s.running).count() as u32,
};
Ok(Json(response))
}