signalk-mosquitto
Version:
SignalK plugin for managing Mosquitto MQTT broker with bridge connections and security
265 lines • 10.1 kB
JavaScript
"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