@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
JavaScript
/**
* 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