@sailboat-computer/health-monitoring
Version:
Comprehensive health monitoring system for sailboat computer v3 with marine-specific health checks
445 lines • 15.6 kB
JavaScript
"use strict";
/**
* Core health check engine for marine systems
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.defaultHealthCheckEngine = exports.HealthCheckEngine = exports.BaseHealthCheck = void 0;
const types_1 = require("../types");
const resilience_1 = require("@sailboat-computer/resilience");
/**
* Abstract base class for health checks
*/
class BaseHealthCheck {
constructor(config) {
this.consecutiveFailures = 0;
this.lastExecutionTime = 0;
this.config = config;
}
/**
* Execute the health check
*/
async execute(operationalContext, marineEnvironment) {
const startTime = Date.now();
try {
// Check if this health check should run in current operational context
if (!this.shouldExecute(operationalContext, marineEnvironment)) {
return this.createSkippedResult(operationalContext, 'Skipped due to operational context');
}
// Execute the actual health check with timeout protection
const timeoutContext = this.getTimeoutContext(operationalContext);
const result = await (0, resilience_1.executeWithTimeout)(() => this.performCheck(operationalContext, marineEnvironment), timeoutContext);
if (result.success && result.data) {
this.consecutiveFailures = 0;
this.lastResult = result.data;
this.lastExecutionTime = Date.now() - startTime;
return result.data;
}
else {
throw result.error || new Error('Health check failed');
}
}
catch (error) {
this.consecutiveFailures++;
const failureResult = this.createFailureResult(operationalContext, error, Date.now() - startTime);
this.lastResult = failureResult;
this.lastExecutionTime = Date.now() - startTime;
return failureResult;
}
}
/**
* Get the last health check result
*/
getLastResult() {
return this.lastResult;
}
/**
* Get health check configuration
*/
getConfig() {
return { ...this.config };
}
/**
* Check if health check should execute in current context
*/
shouldExecute(operationalContext, marineEnvironment) {
// Check operational context
if (!this.config.marineSettings.operationalContexts.includes(operationalContext)) {
return false;
}
// Check power awareness
if (this.config.marineSettings.powerAware && marineEnvironment?.powerStatus === 'critical') {
// Only run critical health checks when power is critical
return this.config.marineSystemType === types_1.MarineSystemType.SAFETY ||
this.config.marineSystemType === types_1.MarineSystemType.NAVIGATION;
}
// Check environmental conditions
if (marineEnvironment?.criticalOperationsOnly && !this.config.marineSettings.safetyImpact) {
return false;
}
return true;
}
/**
* Get timeout context for this health check
*/
getTimeoutContext(operationalContext) {
switch (this.config.marineSystemType) {
case types_1.MarineSystemType.SAFETY:
return resilience_1.TimeoutContexts.safetyOperation(this.config.name, operationalContext);
case types_1.MarineSystemType.NAVIGATION:
return resilience_1.TimeoutContexts.navigationSensor(this.config.name, operationalContext);
case types_1.MarineSystemType.COMMUNICATION:
return resilience_1.TimeoutContexts.networkOperation(this.config.name, operationalContext);
case types_1.MarineSystemType.MAINTENANCE:
return resilience_1.TimeoutContexts.maintenanceOperation(this.config.name);
default:
return resilience_1.TimeoutContexts.comfortOperation(this.config.name, operationalContext);
}
}
/**
* Create a skipped result
*/
createSkippedResult(operationalContext, reason) {
return {
checkId: this.config.checkId,
name: this.config.name,
type: this.config.type,
marineSystemType: this.config.marineSystemType,
status: types_1.HealthStatus.UNKNOWN,
score: 0.5, // Neutral score for skipped checks
message: reason,
timestamp: new Date(),
executionTime: 0,
marineContext: {
operationalContext,
environmentalImpact: false,
safetyImpact: this.config.marineSettings.safetyImpact,
powerImpact: 0
}
};
}
/**
* Create a failure result
*/
createFailureResult(operationalContext, error, executionTime) {
// Determine severity based on consecutive failures
let status;
let score;
if (this.consecutiveFailures >= 3) {
status = types_1.HealthStatus.CRITICAL;
score = 0.0;
}
else if (this.consecutiveFailures >= 2) {
status = types_1.HealthStatus.UNHEALTHY;
score = 0.2;
}
else {
status = types_1.HealthStatus.DEGRADED;
score = 0.4;
}
return {
checkId: this.config.checkId,
name: this.config.name,
type: this.config.type,
marineSystemType: this.config.marineSystemType,
status,
score,
message: `Health check failed: ${error.message}`,
details: {
error: error.message,
consecutiveFailures: this.consecutiveFailures,
stack: error.stack
},
timestamp: new Date(),
executionTime,
marineContext: {
operationalContext,
environmentalImpact: false,
safetyImpact: this.config.marineSettings.safetyImpact,
powerImpact: 0
}
};
}
}
exports.BaseHealthCheck = BaseHealthCheck;
/**
* Health check scheduler and executor
*/
class HealthCheckEngine {
constructor() {
this.healthChecks = new Map();
this.scheduledChecks = new Map();
this.eventListeners = [];
this.isRunning = false;
this.currentOperationalContext = types_1.OperationalContext.DOCKED;
}
/**
* Register a health check
*/
registerHealthCheck(healthCheck) {
const config = healthCheck.getConfig();
this.healthChecks.set(config.checkId, healthCheck);
// Schedule the health check if engine is running
if (this.isRunning) {
this.scheduleHealthCheck(config.checkId);
}
this.emitEvent('health_check_completed', config.checkId, {
message: `Health check '${config.name}' registered`,
details: { action: 'registered' }
});
}
/**
* Unregister a health check
*/
unregisterHealthCheck(checkId) {
const healthCheck = this.healthChecks.get(checkId);
if (!healthCheck) {
return;
}
// Cancel scheduled execution
const timer = this.scheduledChecks.get(checkId);
if (timer) {
clearTimeout(timer);
this.scheduledChecks.delete(checkId);
}
this.healthChecks.delete(checkId);
this.emitEvent('health_check_completed', checkId, {
message: `Health check unregistered`,
details: { action: 'unregistered' }
});
}
/**
* Start the health check engine
*/
start() {
if (this.isRunning) {
return;
}
this.isRunning = true;
// Schedule all registered health checks
for (const checkId of this.healthChecks.keys()) {
this.scheduleHealthCheck(checkId);
}
this.emitEvent('system_recovery', 'health-engine', {
message: 'Health check engine started',
details: {
registeredChecks: this.healthChecks.size,
operationalContext: this.currentOperationalContext
}
});
}
/**
* Stop the health check engine
*/
stop() {
if (!this.isRunning) {
return;
}
this.isRunning = false;
// Cancel all scheduled checks
for (const [checkId, timer] of this.scheduledChecks) {
clearTimeout(timer);
}
this.scheduledChecks.clear();
this.emitEvent('system_recovery', 'health-engine', {
message: 'Health check engine stopped',
details: { cancelledChecks: this.scheduledChecks.size }
});
}
/**
* Update operational context
*/
updateOperationalContext(context) {
const previousContext = this.currentOperationalContext;
this.currentOperationalContext = context;
this.emitEvent('system_recovery', 'health-engine', {
message: 'Operational context updated',
details: {
previousContext,
newContext: context,
affectedChecks: this.getAffectedChecks(context)
}
});
// Reschedule health checks if context change affects them
this.rescheduleAffectedChecks(context);
}
/**
* Update marine environment status
*/
updateMarineEnvironment(environment) {
this.currentMarineEnvironment = environment;
this.emitEvent('system_recovery', 'health-engine', {
message: 'Marine environment updated',
details: {
seaState: environment.seaState,
weather: environment.weather,
powerStatus: environment.powerStatus,
criticalOperationsOnly: environment.criticalOperationsOnly
}
});
// Adjust health check scheduling based on environment
this.adjustForEnvironment(environment);
}
/**
* Execute a specific health check immediately
*/
async executeHealthCheck(checkId) {
const healthCheck = this.healthChecks.get(checkId);
if (!healthCheck) {
return null;
}
try {
const result = await healthCheck.execute(this.currentOperationalContext, this.currentMarineEnvironment);
this.emitEvent('health_check_completed', checkId, {
message: `Health check completed: ${result.status}`,
score: result.score,
details: {
status: result.status,
executionTime: result.executionTime,
manual: true
}
});
return result;
}
catch (error) {
this.emitEvent('alert_triggered', checkId, {
message: `Health check execution failed: ${error.message}`,
details: { error: error.message }
});
return null;
}
}
/**
* Get all health check results
*/
getAllResults() {
const results = new Map();
for (const [checkId, healthCheck] of this.healthChecks) {
const result = healthCheck.getLastResult();
if (result) {
results.set(checkId, result);
}
}
return results;
}
/**
* Get health checks by system type
*/
getHealthChecksBySystem(systemType) {
return Array.from(this.healthChecks.values()).filter(check => check.getConfig().marineSystemType === systemType);
}
/**
* Add event listener
*/
onEvent(listener) {
this.eventListeners.push(listener);
}
/**
* Remove event listener
*/
removeEventListener(listener) {
const index = this.eventListeners.indexOf(listener);
if (index > -1) {
this.eventListeners.splice(index, 1);
}
}
/**
* Schedule a health check
*/
scheduleHealthCheck(checkId) {
const healthCheck = this.healthChecks.get(checkId);
if (!healthCheck) {
return;
}
const config = healthCheck.getConfig();
// Cancel existing timer
const existingTimer = this.scheduledChecks.get(checkId);
if (existingTimer) {
clearTimeout(existingTimer);
}
// Calculate interval based on marine environment
let interval = config.interval;
if (this.currentMarineEnvironment) {
// Adjust interval based on environmental conditions
if (this.currentMarineEnvironment.criticalOperationsOnly) {
interval *= 0.5; // More frequent checks in critical conditions
}
else if (this.currentMarineEnvironment.powerStatus === 'conservation') {
interval *= 2.0; // Less frequent checks to save power
}
}
// Schedule next execution
const timer = setTimeout(async () => {
await this.executeHealthCheck(checkId);
// Reschedule if still running
if (this.isRunning) {
this.scheduleHealthCheck(checkId);
}
}, interval);
this.scheduledChecks.set(checkId, timer);
}
/**
* Get health checks affected by operational context change
*/
getAffectedChecks(context) {
const affected = [];
for (const [checkId, healthCheck] of this.healthChecks) {
const config = healthCheck.getConfig();
if (!config.marineSettings.operationalContexts.includes(context)) {
affected.push(checkId);
}
}
return affected;
}
/**
* Reschedule health checks affected by context change
*/
rescheduleAffectedChecks(context) {
for (const checkId of this.healthChecks.keys()) {
this.scheduleHealthCheck(checkId);
}
}
/**
* Adjust health check scheduling for marine environment
*/
adjustForEnvironment(environment) {
// Reschedule all checks with new environmental considerations
for (const checkId of this.healthChecks.keys()) {
this.scheduleHealthCheck(checkId);
}
}
/**
* Emit health monitoring event
*/
emitEvent(eventType, checkId, data) {
const event = {
eventId: `${eventType}-${checkId}-${Date.now()}`,
timestamp: new Date(),
eventType,
systemId: 'health-engine',
checkId,
currentStatus: types_1.HealthStatus.UNKNOWN, // Will be updated by specific implementations
data: {
...data,
marineContext: {
operationalContext: this.currentOperationalContext,
environmentalConditions: this.currentMarineEnvironment ? { ...this.currentMarineEnvironment } : {},
safetyImpact: false
}
}
};
this.eventListeners.forEach(listener => {
try {
listener(event);
}
catch (error) {
console.error('Error in health monitoring event listener:', error);
}
});
}
}
exports.HealthCheckEngine = HealthCheckEngine;
/**
* Default health check engine instance
*/
exports.defaultHealthCheckEngine = new HealthCheckEngine();
//# sourceMappingURL=HealthCheckEngine.js.map