things3_core/database/
health.rs1use crate::{
4 database::{
5 pool::{ComprehensiveHealthStatus, PoolHealthStatus, PoolMetrics},
6 stats::DatabaseStats,
7 ThingsDatabase,
8 },
9 error::{Result as ThingsResult, ThingsError},
10};
11use chrono::Utc;
12use tracing::{debug, error, instrument};
13
14impl ThingsDatabase {
15 #[instrument]
17 pub async fn is_connected(&self) -> bool {
18 match sqlx::query("SELECT 1").fetch_one(&self.pool).await {
19 Ok(_) => {
20 debug!("Database connection is healthy");
21 true
22 }
23 Err(e) => {
24 error!("Database connection check failed: {}", e);
25 false
26 }
27 }
28 }
29
30 #[instrument]
36 pub async fn get_pool_health(&self) -> ThingsResult<PoolHealthStatus> {
37 let pool_size = self.pool.size();
38 let idle_connections = self.pool.num_idle();
39 let active_connections = pool_size - u32::try_from(idle_connections).unwrap_or(0);
40
41 let is_healthy = self.is_connected().await;
42
43 Ok(PoolHealthStatus {
44 is_healthy,
45 pool_size,
46 active_connections,
47 idle_connections: u32::try_from(idle_connections).unwrap_or(0),
48 max_connections: self.config.max_connections,
49 min_connections: self.config.min_connections,
50 connection_timeout: self.config.connect_timeout,
51 idle_timeout: Some(self.config.idle_timeout),
52 max_lifetime: Some(self.config.max_lifetime),
53 })
54 }
55
56 #[instrument]
62 pub async fn get_pool_metrics(&self) -> ThingsResult<PoolMetrics> {
63 let pool_size = self.pool.size();
64 let idle_connections = self.pool.num_idle();
65 let active_connections = pool_size - u32::try_from(idle_connections).unwrap_or(0);
66
67 let max_connections = self.config.max_connections;
68 let utilization_percentage = if max_connections > 0 {
69 (f64::from(active_connections) / f64::from(max_connections)) * 100.0
70 } else {
71 0.0
72 };
73
74 let start_time = std::time::Instant::now();
75 let is_connected = self.is_connected().await;
76 let response_time_ms = u64::try_from(start_time.elapsed().as_millis()).unwrap_or(0);
77
78 Ok(PoolMetrics {
79 pool_size,
80 active_connections,
81 idle_connections: u32::try_from(idle_connections).unwrap_or(0),
82 max_connections,
83 min_connections: self.config.min_connections,
84 utilization_percentage,
85 is_healthy: is_connected,
86 response_time_ms,
87 connection_timeout: self.config.connect_timeout,
88 idle_timeout: Some(self.config.idle_timeout),
89 max_lifetime: Some(self.config.max_lifetime),
90 })
91 }
92
93 #[instrument]
99 pub async fn comprehensive_health_check(&self) -> ThingsResult<ComprehensiveHealthStatus> {
100 let pool_health = self.get_pool_health().await?;
101 let pool_metrics = self.get_pool_metrics().await?;
102 let db_stats = self.get_stats().await?;
103
104 let overall_healthy = pool_health.is_healthy && pool_metrics.is_healthy;
105
106 Ok(ComprehensiveHealthStatus {
107 overall_healthy,
108 pool_health,
109 pool_metrics,
110 database_stats: db_stats,
111 timestamp: Utc::now(),
112 })
113 }
114
115 #[instrument]
121 pub async fn get_stats(&self) -> ThingsResult<DatabaseStats> {
122 let task_count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM TMTask")
123 .fetch_one(&self.pool)
124 .await
125 .map_err(|e| ThingsError::unknown(format!("Failed to get task count: {e}")))?;
126
127 let project_count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM TMTask WHERE type = 1")
128 .fetch_one(&self.pool)
129 .await
130 .map_err(|e| ThingsError::unknown(format!("Failed to get project count: {e}")))?;
131
132 let area_count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM TMArea")
133 .fetch_one(&self.pool)
134 .await
135 .map_err(|e| ThingsError::unknown(format!("Failed to get area count: {e}")))?;
136
137 Ok(DatabaseStats {
138 task_count: task_count.try_into().unwrap_or(0),
139 project_count: project_count.try_into().unwrap_or(0),
140 area_count: area_count.try_into().unwrap_or(0),
141 })
142 }
143}