UNPKG

@nanocollective/nanocoder

Version:

A local-first CLI coding agent that brings the power of agentic coding tools like Claude Code and Gemini CLI to local models or controlled APIs like OpenRouter

297 lines 11.3 kB
/** * Main health monitoring class */ import { loadavg } from 'os'; import { COOLDOWN_ALERT_MS, DURATION_AVERAGE_CRITICAL_MS, DURATION_AVERAGE_WARNING_MS, DURATION_REQUEST_CRITICAL_MS, DURATION_REQUEST_WARNING_MS, HEAP_USAGE_CRITICAL_THRESHOLD, HEAP_USAGE_WARNING_THRESHOLD, INTERVAL_HEALTH_CHECK_MS, TIMEOUT_HEALTH_CHECK_MS, } from '../../../../constants.js'; import { generateCorrelationId } from '../../correlation.js'; import { globalLogStorage } from '../../log-query/index.js'; import { loggerProvider } from '../../logger-provider.js'; import { globalPerformanceMonitor } from '../../performance.js'; import { globalRequestTracker } from '../../request-tracker.js'; const getLogger = () => loggerProvider.getLogger(); import { sendAlert } from '../alerts/alert-manager.js'; import { runHealthCheck } from './health-check-runner.js'; // Get logger instance directly to avoid circular dependencies const logger = getLogger(); /** * Health monitoring system */ export class HealthMonitor { config; isRunning = false; intervalId; lastCheck; lastAlert; correlationId; constructor(config) { this.correlationId = generateCorrelationId(); this.config = { enabled: true, interval: INTERVAL_HEALTH_CHECK_MS, timeout: TIMEOUT_HEALTH_CHECK_MS, thresholds: { memory: { heapUsageWarning: HEAP_USAGE_WARNING_THRESHOLD, heapUsageCritical: HEAP_USAGE_CRITICAL_THRESHOLD, externalWarning: 256, externalCritical: 512, }, performance: { averageDurationWarning: DURATION_AVERAGE_WARNING_MS, averageDurationCritical: DURATION_AVERAGE_CRITICAL_MS, errorRateWarning: 0.05, errorRateCritical: 0.1, }, logging: { logRateWarning: 100, logRateCritical: 500, errorRateWarning: 0.02, errorRateCritical: 0.05, }, requests: { durationWarning: DURATION_REQUEST_WARNING_MS, durationCritical: DURATION_REQUEST_CRITICAL_MS, errorRateWarning: 0.05, errorRateCritical: 0.1, }, }, alerts: { enabled: true, channels: ['console'], cooldown: COOLDOWN_ALERT_MS, }, ...config, }; } /** * Start health monitoring */ start() { try { if (this.isRunning) { logger.warn('Health monitoring already running', { correlationId: this.correlationId, source: 'health-monitor', }); return; } this.isRunning = true; // Run initial check try { void this.runHealthCheck(); } catch (error) { logger.error('Failed to run initial health check', { error: error instanceof Error ? error.message : error, correlationId: this.correlationId, source: 'health-monitor', }); // Continue starting monitoring even if initial check fails } // Schedule regular checks try { this.intervalId = setInterval(() => { try { void this.runHealthCheck(); } catch (error) { logger.error('Failed to run scheduled health check', { error: error instanceof Error ? error.message : error, correlationId: this.correlationId, source: 'health-monitor', }); } }, this.config.interval); } catch (error) { logger.error('Failed to schedule health monitoring', { error: error instanceof Error ? error.message : error, correlationId: this.correlationId, source: 'health-monitor', }); this.isRunning = false; return; } logger.info('Health monitoring started', { interval: `${this.config.interval}ms`, timeout: `${this.config.timeout}ms`, correlationId: this.correlationId, source: 'health-monitor', }); } catch (error) { logger.error('Critical error starting health monitoring', { error: error instanceof Error ? error.message : error, correlationId: this.correlationId, source: 'health-monitor', }); this.isRunning = false; } } /** * Stop health monitoring */ stop() { try { if (!this.isRunning) { logger.debug('Health monitoring not running', { correlationId: this.correlationId, source: 'health-monitor', }); return; } this.isRunning = false; try { if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = undefined; } } catch (error) { logger.error('Failed to clear health monitoring interval', { error: error instanceof Error ? error.message : error, correlationId: this.correlationId, source: 'health-monitor', }); // Continue with cleanup even if interval clearing fails } logger.info('Health monitoring stopped', { correlationId: this.correlationId, source: 'health-monitor', }); } catch (error) { logger.error('Critical error stopping health monitoring', { error: error instanceof Error ? error.message : error, correlationId: this.correlationId, source: 'health-monitor', }); // Ensure we don't leave the system in a bad state this.isRunning = false; if (this.intervalId) { try { clearInterval(this.intervalId); this.intervalId = undefined; } catch (cleanupError) { // Final fallback - log but don't rethrow logger.error('Failed to cleanup health monitoring interval during error recovery', { error: cleanupError instanceof Error ? cleanupError.message : cleanupError, correlationId: this.correlationId, source: 'health-monitor', }); } } } } /** * Run a comprehensive health check */ async runHealthCheck() { const result = await runHealthCheck(this.config, this.correlationId); this.lastCheck = result; // Check if alerts should be sent if (this.config.alerts.enabled && (result.status === 'unhealthy' || result.status === 'degraded')) { await sendAlert(result, this.config, this.lastAlert, this.correlationId); this.lastAlert = Date.now(); } return result; } /** * Get current system metrics */ getSystemMetrics() { const _now = Date.now(); const memory = process.memoryUsage(); const cpuUsage = process.cpuUsage(); const requestStats = globalRequestTracker.getStats(); const logStats = globalLogStorage.getEntryCount(); const perfStats = globalPerformanceMonitor.getAllStats(); // Calculate rates (approximate based on current state) const logRate = logStats / (process.uptime() / 60); // logs per minute const errorRate = requestStats.errorRate; const requestsPerSecond = requestStats.totalRequests / process.uptime(); // Calculate average operation duration from performance stats const allPerfStats = Object.values(perfStats).filter(Boolean); const averageDuration = allPerfStats.length > 0 ? allPerfStats.reduce((sum, stat) => sum + (stat?.avgDuration || 0), 0) / allPerfStats.length : 0; return { timestamp: new Date().toISOString(), memory: { heapUsed: memory.heapUsed, heapTotal: memory.heapTotal, external: memory.external, rss: memory.rss, heapUsagePercent: memory.heapUsed / memory.heapTotal, }, cpu: { usage: (cpuUsage.user + cpuUsage.system) / 1000000, // Convert to milliseconds loadAverage: loadavg(), }, process: { uptime: process.uptime(), pid: process.pid, nodeVersion: process.version, platform: process.platform, arch: process.arch, }, logging: { totalLogEntries: logStats, logRate, errorRate, averageLogSize: 256, // Estimated }, requests: { totalRequests: requestStats.totalRequests, activeRequests: globalRequestTracker.getActiveRequests().length, averageDuration: requestStats.averageDuration, errorRate: requestStats.errorRate, requestsPerSecond, }, performance: { operationCount: allPerfStats.reduce((sum, stat) => sum + (stat?.count || 0), 0), averageDuration, slowOperations: 0, // Would need tracking over time memoryIntensiveOps: 0, // Would need tracking over time }, }; } /** * Get last health check result */ getLastHealthCheck() { return this.lastCheck; } /** * Check if monitoring is running */ isActive() { return this.isRunning; } /** * Update health check configuration */ updateConfig(config) { this.config = { ...this.config, ...config }; logger.info('Health monitor configuration updated', { enabled: this.config.enabled, interval: `${this.config.interval}ms`, correlationId: this.correlationId, source: 'health-monitor', }); // Restart monitoring if running if (this.isRunning) { this.stop(); this.start(); } } } /** * Global health monitor instance */ export const globalHealthMonitor = new HealthMonitor(); //# sourceMappingURL=health-monitor.js.map