Skip to main content

things3_core/database/
health.rs

1//! Health check and metrics methods for `ThingsDatabase`.
2#![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    /// Check if the database is connected
17    #[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    /// Get connection pool health status
32    ///
33    /// # Errors
34    ///
35    /// Returns an error if the health check fails
36    #[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    /// Get detailed connection pool metrics
58    ///
59    /// # Errors
60    ///
61    /// Returns an error if the metrics collection fails
62    #[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    /// Perform a comprehensive health check including pool and database
95    ///
96    /// # Errors
97    ///
98    /// Returns an error if the health check fails
99    #[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    /// Get database statistics
117    ///
118    /// # Errors
119    ///
120    /// Returns an error if the database query fails
121    #[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}