UNPKG

signalk-mosquitto

Version:

SignalK plugin for managing Mosquitto MQTT broker with bridge connections and security

265 lines 10.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProcessMonitorImpl = void 0; const file_utils_1 = require("../utils/file-utils"); class ProcessMonitorImpl { constructor(app, mosquittoManager) { this.monitorInterval = null; this.healthCheckInterval = null; this.isMonitoring = false; this.healthCheckIntervalMs = 30000; // 30 seconds this.statusCheckIntervalMs = 5000; // 5 seconds this.maxRestartAttempts = 3; this.restartAttempts = 0; this.lastHealthCheck = null; this.consecutiveFailures = 0; this.app = app; this.mosquittoManager = mosquittoManager; } start() { if (this.isMonitoring) { console.log('Process monitor is already running'); return; } this.isMonitoring = true; this.restartAttempts = 0; this.consecutiveFailures = 0; this.lastHealthCheck = new Date(); console.log('Starting Mosquitto process monitor'); this.monitorInterval = setInterval(async () => { await this.performStatusCheck(); }, this.statusCheckIntervalMs); this.healthCheckInterval = setInterval(async () => { await this.performHealthCheck(); }, this.healthCheckIntervalMs); } stop() { if (!this.isMonitoring) { return; } console.log('Stopping Mosquitto process monitor'); this.isMonitoring = false; if (this.monitorInterval) { clearInterval(this.monitorInterval); this.monitorInterval = null; } if (this.healthCheckInterval) { clearInterval(this.healthCheckInterval); this.healthCheckInterval = null; } } async isHealthy() { try { const status = await this.mosquittoManager.getStatus(); if (!status.running) { return false; } if (status.pid) { const isProcessAlive = await this.isProcessAlive(status.pid); if (!isProcessAlive) { return false; } } return true; } catch (error) { console.log(`Health check error: ${error.message}`); return false; } } async getMetrics() { try { return await this.mosquittoManager.getStatus(); } catch (error) { console.error(`Failed to get metrics: ${error.message}`); return { running: false, connectedClients: 0, totalConnections: 0, messagesReceived: 0, messagesPublished: 0, bytesReceived: 0, bytesPublished: 0, }; } } async performStatusCheck() { if (!this.isMonitoring) { return; } try { const status = await this.mosquittoManager.getStatus(); if (!status.running) { this.consecutiveFailures++; console.log(`Mosquitto process not running (failure ${this.consecutiveFailures})`); if (this.consecutiveFailures >= 2 && this.restartAttempts < this.maxRestartAttempts) { await this.attemptRestart(); } } else { if (this.consecutiveFailures > 0) { console.log('Mosquitto process recovered'); this.consecutiveFailures = 0; this.restartAttempts = 0; } } } catch (error) { this.consecutiveFailures++; console.log(`Status check failed: ${error.message} (failure ${this.consecutiveFailures})`); if (this.consecutiveFailures >= 3 && this.restartAttempts < this.maxRestartAttempts) { await this.attemptRestart(); } } } async performHealthCheck() { if (!this.isMonitoring) { return; } try { this.lastHealthCheck = new Date(); const isHealthy = await this.isHealthy(); if (!isHealthy) { console.log('Health check failed - Mosquitto is not healthy'); if (this.restartAttempts < this.maxRestartAttempts) { await this.attemptRestart(); } else { console.error(`Mosquitto health check failed after ${this.maxRestartAttempts} restart attempts`); } } else { if (this.restartAttempts > 0) { console.log('Mosquitto health restored'); this.restartAttempts = 0; this.consecutiveFailures = 0; } } } catch (error) { console.error(`Health check error: ${error.message}`); } } async attemptRestart() { if (this.restartAttempts >= this.maxRestartAttempts) { console.error(`Maximum restart attempts (${this.maxRestartAttempts}) reached`); return; } this.restartAttempts++; console.log(`Attempting to restart Mosquitto (attempt ${this.restartAttempts}/${this.maxRestartAttempts})`); try { await this.mosquittoManager.restart(); // Wait a bit for the process to stabilize await new Promise(resolve => setTimeout(resolve, 2000)); const isHealthy = await this.isHealthy(); if (isHealthy) { console.log('Mosquitto restart successful'); this.consecutiveFailures = 0; } else { console.log('Mosquitto restart failed - process is not healthy'); } } catch (error) { console.error(`Failed to restart Mosquitto: ${error.message}`); } } async isProcessAlive(pid) { try { // Use kill -0 to check if process exists without actually killing it await file_utils_1.FileUtils.executeCommand('kill', ['-0', pid.toString()]); return true; } catch { return false; } } getMonitorStatus() { return { isMonitoring: this.isMonitoring, lastHealthCheck: this.lastHealthCheck, consecutiveFailures: this.consecutiveFailures, restartAttempts: this.restartAttempts, maxRestartAttempts: this.maxRestartAttempts, }; } updateConfiguration(options) { if (options.healthCheckIntervalMs) { this.healthCheckIntervalMs = Math.max(options.healthCheckIntervalMs, 5000); // Minimum 5 seconds } if (options.statusCheckIntervalMs) { this.statusCheckIntervalMs = Math.max(options.statusCheckIntervalMs, 1000); // Minimum 1 second } if (options.maxRestartAttempts !== undefined) { this.maxRestartAttempts = Math.max(options.maxRestartAttempts, 0); } // Restart monitoring with new intervals if currently running if (this.isMonitoring) { this.stop(); this.start(); } console.log('Process monitor configuration updated'); } async forceRestart() { console.log('Force restart requested'); this.restartAttempts = 0; // Reset attempts for manual restart await this.attemptRestart(); } async getDetailedStatus() { const mosquittoStatus = await this.getMetrics(); const monitorStatus = this.getMonitorStatus(); const cpuUsage = process.cpuUsage(); return { monitor: monitorStatus, mosquitto: mosquittoStatus, system: { uptime: process.uptime(), memoryUsage: process.memoryUsage(), cpuUsage: cpuUsage, }, }; } async performMaintenanceTasks() { console.log('Performing maintenance tasks'); try { // Clean up old log files if they get too large const dataDir = file_utils_1.FileUtils.getDataDir('signalk-mosquitto'); const logFile = path.join(dataDir, 'mosquitto.log'); if (await file_utils_1.FileUtils.fileExists(logFile)) { const { stdout } = await file_utils_1.FileUtils.executeCommand('stat', ['-f%z', logFile]); const fileSize = parseInt(stdout.trim()); // If log file is larger than 10MB, rotate it if (fileSize > 10 * 1024 * 1024) { const backupFile = `${logFile}.${Date.now()}.bak`; await file_utils_1.FileUtils.copyFile(logFile, backupFile); await file_utils_1.FileUtils.writeFile(logFile, ''); console.log(`Log file rotated to ${backupFile}`); } } // Clean up old backup files (keep only last 5) const backupPattern = /mosquitto\.log\.\d+\.bak$/; const files = await file_utils_1.FileUtils.executeCommand('ls', [dataDir]); const backupFiles = files.stdout .split('\n') .filter(file => backupPattern.test(file)) .sort() .reverse(); if (backupFiles.length > 5) { const filesToDelete = backupFiles.slice(5); for (const file of filesToDelete) { await file_utils_1.FileUtils.deleteFile(path.join(dataDir, file)); console.log(`Deleted old backup file: ${file}`); } } console.log('Maintenance tasks completed'); } catch (error) { console.error(`Maintenance task error: ${error.message}`); } } } exports.ProcessMonitorImpl = ProcessMonitorImpl; // eslint-disable-next-line @typescript-eslint/no-require-imports const path = require('path'); //# sourceMappingURL=process-monitor.js.map