use std::sync::Arc;
use crate::database::pool::DbPool;
pub type ReplicaHealthProvider = Arc<dyn Fn() -> Vec<serde_json::Value> + Send + Sync>;
impl DbPool {
#[cfg(feature = "health-check")]
pub async fn set_replica_health_provider(&self, provider: Option<ReplicaHealthProvider>) {
*self
.inner
.replica_health_provider
.write()
.expect("replica_health_provider lock") = provider;
}
#[cfg(feature = "health-check")]
pub async fn health_snapshot(&self) -> serde_json::Value {
let st = self.status();
let saturation = if st.total == 0 {
1.0
} else {
(st.active as f64 / st.total as f64).clamp(0.0, 1.0)
};
let status = if st.total == 0 {
"unhealthy"
} else if st.wait_count > 0 || st.active >= st.total {
"degraded"
} else {
"healthy"
};
#[cfg(feature = "metrics")]
let slow_queries = {
let collector = self
.inner
.metrics_collector
.read()
.expect("metrics_collector lock")
.clone();
if let Some(collector) = collector {
let cfg = collector.slow_query_config_snapshot();
serde_json::json!({
"count": collector.slow_queries().len(),
"threshold_ms": cfg.threshold_ms,
"enabled": cfg.enabled,
})
} else {
serde_json::json!({ "count": 0 })
}
};
#[cfg(not(feature = "metrics"))]
let slow_queries = serde_json::json!({ "count": 0 });
let replicas = {
let provider = self
.inner
.replica_health_provider
.read()
.expect("replica_health_provider lock");
match provider.as_ref() {
Some(p) => serde_json::Value::Array(p()),
None => serde_json::Value::Array(Vec::new()),
}
};
serde_json::json!({
"status": status,
"pool": {
"total": st.total,
"active": st.active,
"idle": st.idle,
"wait_count": st.wait_count,
"max_waiters": st.max_waiters,
"borrow_count": st.borrow_count,
"max_active": st.max_active,
"max_connections": self.inner.config.pool_config.max_connections,
"saturation": saturation,
},
"slow_queries": slow_queries,
"replicas": replicas,
})
}
#[cfg(all(feature = "health-check", feature = "metrics"))]
pub async fn set_metrics_collector(
&self,
collector: Option<Arc<crate::observability::MetricsCollector>>,
) {
*self
.inner
.metrics_collector
.write()
.expect("metrics_collector lock") = collector;
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_saturation_semantics_reference() {
let total = 0u32;
let saturation = if total == 0 { 1.0 } else { 0.0 };
assert_eq!(saturation, 1.0);
let (total, active) = (5u32, 2u32);
let saturation = (active as f64 / total as f64).clamp(0.0, 1.0);
assert!((saturation - 0.4).abs() < f64::EPSILON);
}
}