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