import { getLogger } from '@stock-bot/logger'; import type { PostgreSQLClient } from './client'; import type { PostgreSQLHealthCheck, PostgreSQLHealthStatus, PostgreSQLMetrics } from './types'; /** * PostgreSQL Health Monitor * * Monitors PostgreSQL connection health and provides metrics */ export class PostgreSQLHealthMonitor { private readonly client: PostgreSQLClient; private readonly logger: ReturnType; private healthCheckInterval: NodeJS.Timeout | null = null; private metrics: PostgreSQLMetrics; private lastHealthCheck: PostgreSQLHealthCheck | null = null; constructor(client: PostgreSQLClient) { this.client = client; this.logger = getLogger('postgres-health-monitor'); this.metrics = { queriesPerSecond: 0, averageQueryTime: 0, errorRate: 0, connectionPoolUtilization: 0, slowQueries: 0, }; } /** * Start health monitoring */ start(intervalMs: number = 30000): void { if (this.healthCheckInterval) { this.stop(); } this.logger.info(`Starting PostgreSQL health monitoring (interval: ${intervalMs}ms)`); this.healthCheckInterval = setInterval(async () => { try { await this.performHealthCheck(); } catch (error) { this.logger.error('Health check failed:', error); } }, intervalMs); // Perform initial health check this.performHealthCheck().catch(error => { this.logger.error('Initial health check failed:', error); }); } /** * Stop health monitoring */ stop(): void { if (this.healthCheckInterval) { clearInterval(this.healthCheckInterval); this.healthCheckInterval = null; this.logger.info('Stopped PostgreSQL health monitoring'); } } /** * Get current health status */ async getHealth(): Promise { if (!this.lastHealthCheck) { await this.performHealthCheck(); } if (!this.lastHealthCheck) { throw new Error('Health check failed to produce results'); } return this.lastHealthCheck; } /** * Get current metrics */ getMetrics(): PostgreSQLMetrics { return { ...this.metrics }; } /** * Perform a health check */ private async performHealthCheck(): Promise { const startTime = Date.now(); const errors: string[] = []; let status: PostgreSQLHealthStatus = 'healthy'; try { if (!this.client.connected) { errors.push('PostgreSQL client not connected'); status = 'unhealthy'; } else { // Test basic connectivity await this.client.query('SELECT 1'); // Get connection stats const stats = await this.client.getStats(); // Check connection pool utilization const utilization = parseInt(stats.active_connections) / parseInt(stats.max_connections); if (utilization > 0.8) { errors.push('High connection pool utilization'); status = status === 'healthy' ? 'degraded' : status; } // Check for high latency const latency = Date.now() - startTime; if (latency > 1000) { errors.push(`High latency: ${latency}ms`); status = status === 'healthy' ? 'degraded' : status; } this.metrics.connectionPoolUtilization = utilization; } } catch (error) { errors.push(`Health check failed: ${(error as Error).message}`); status = 'unhealthy'; } const latency = Date.now() - startTime; this.lastHealthCheck = { status, timestamp: new Date(), latency, connections: { active: 1, idle: 9, total: 10, }, errors: errors.length > 0 ? errors : undefined, }; // Log health status changes if (status !== 'healthy') { this.logger.warn(`PostgreSQL health status: ${status}`, { errors, latency }); } else { this.logger.debug(`PostgreSQL health check passed (${latency}ms)`); } } }