@sailboat-computer/resilience
Version:
Enhanced resilience patterns for sailboat computer v3 with marine-specific adaptations
613 lines • 23.9 kB
JavaScript
"use strict";
/**
* Graceful degradation strategies with marine-specific adaptations
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.defaultDegradationManager = exports.DegradationManager = exports.FeaturePriority = exports.ServiceQualityLevel = void 0;
const marine_constants_1 = require("../types/marine-constants");
/**
* Service quality level
*/
var ServiceQualityLevel;
(function (ServiceQualityLevel) {
/**
* Full functionality with optimal performance
*/
ServiceQualityLevel["OPTIMAL"] = "optimal";
/**
* Full functionality with reduced performance
*/
ServiceQualityLevel["REDUCED"] = "reduced";
/**
* Core functionality only
*/
ServiceQualityLevel["MINIMAL"] = "minimal";
/**
* Critical functionality only
*/
ServiceQualityLevel["CRITICAL"] = "critical";
/**
* Emergency functionality only
*/
ServiceQualityLevel["EMERGENCY"] = "emergency";
})(ServiceQualityLevel || (exports.ServiceQualityLevel = ServiceQualityLevel = {}));
/**
* Feature priority for degradation decisions
*/
var FeaturePriority;
(function (FeaturePriority) {
/**
* Critical for vessel safety
*/
FeaturePriority["CRITICAL"] = "critical";
/**
* Essential for operation
*/
FeaturePriority["ESSENTIAL"] = "essential";
/**
* Important but can be degraded
*/
FeaturePriority["IMPORTANT"] = "important";
/**
* Useful but non-essential
*/
FeaturePriority["USEFUL"] = "useful";
/**
* Comfort or convenience only
*/
FeaturePriority["COMFORT"] = "comfort";
})(FeaturePriority || (exports.FeaturePriority = FeaturePriority = {}));
/**
* Degradation manager for graceful service degradation
*/
class DegradationManager {
constructor(config) {
this.featureToggles = new Map();
this.eventListeners = [];
this.qualityLevelTimestamps = new Map();
this.lastQualityUpdateTime = Date.now();
this.config = {
defaultQualityLevel: ServiceQualityLevel.OPTIMAL,
adaptToMarineConditions: true,
powerAware: true,
connectivityAware: true,
qualityThresholds: {
seaState: {
rough: ServiceQualityLevel.REDUCED,
veryRough: ServiceQualityLevel.MINIMAL
},
powerStatus: {
conservation: ServiceQualityLevel.REDUCED,
critical: ServiceQualityLevel.CRITICAL
},
connectivityQuality: {
low: 0.3,
lowQualityLevel: ServiceQualityLevel.REDUCED,
veryLow: 0.1,
veryLowQualityLevel: ServiceQualityLevel.MINIMAL
}
},
featureToggles: {
autoToggleFeatures: true,
qualityLevelMinPriority: {
[ServiceQualityLevel.OPTIMAL]: FeaturePriority.COMFORT,
[ServiceQualityLevel.REDUCED]: FeaturePriority.USEFUL,
[ServiceQualityLevel.MINIMAL]: FeaturePriority.IMPORTANT,
[ServiceQualityLevel.CRITICAL]: FeaturePriority.ESSENTIAL,
[ServiceQualityLevel.EMERGENCY]: FeaturePriority.CRITICAL
}
},
resourceLimits: {
cpu: {
[ServiceQualityLevel.OPTIMAL]: 80,
[ServiceQualityLevel.REDUCED]: 60,
[ServiceQualityLevel.MINIMAL]: 40,
[ServiceQualityLevel.CRITICAL]: 30,
[ServiceQualityLevel.EMERGENCY]: 20
},
memory: {
[ServiceQualityLevel.OPTIMAL]: 80,
[ServiceQualityLevel.REDUCED]: 60,
[ServiceQualityLevel.MINIMAL]: 40,
[ServiceQualityLevel.CRITICAL]: 30,
[ServiceQualityLevel.EMERGENCY]: 20
},
bandwidth: {
[ServiceQualityLevel.OPTIMAL]: 1000,
[ServiceQualityLevel.REDUCED]: 500,
[ServiceQualityLevel.MINIMAL]: 200,
[ServiceQualityLevel.CRITICAL]: 50,
[ServiceQualityLevel.EMERGENCY]: 10
},
storageIops: {
[ServiceQualityLevel.OPTIMAL]: 1000,
[ServiceQualityLevel.REDUCED]: 500,
[ServiceQualityLevel.MINIMAL]: 200,
[ServiceQualityLevel.CRITICAL]: 50,
[ServiceQualityLevel.EMERGENCY]: 10
}
},
...config
};
this.currentQualityLevel = this.config.defaultQualityLevel;
this.metrics = this.initializeMetrics();
// Initialize quality level timestamps
Object.values(ServiceQualityLevel).forEach(level => {
this.qualityLevelTimestamps.set(level, 0);
});
this.qualityLevelTimestamps.set(this.currentQualityLevel, Date.now());
}
/**
* Register a feature toggle
*/
registerFeature(feature) {
this.featureToggles.set(feature.id, feature);
this.updateMetrics();
this.emitEvent('recovery', `feature-${feature.id}`, {
action: 'feature_registered',
featureId: feature.id,
featureName: feature.name,
priority: feature.priority,
enabled: feature.enabled
}, 'info');
// Apply current quality level to the feature
this.applyQualityLevelToFeature(feature);
}
/**
* Unregister a feature toggle
*/
unregisterFeature(featureId) {
const feature = this.featureToggles.get(featureId);
if (!feature) {
return false;
}
this.featureToggles.delete(featureId);
this.updateMetrics();
this.emitEvent('recovery', `feature-${featureId}`, {
action: 'feature_unregistered',
featureId
}, 'info');
return true;
}
/**
* Check if a feature is enabled
*/
isFeatureEnabled(featureId) {
const feature = this.featureToggles.get(featureId);
return feature ? feature.enabled : false;
}
/**
* Manually enable a feature
*/
enableFeature(featureId) {
const feature = this.featureToggles.get(featureId);
if (!feature || !feature.manualToggleAllowed) {
return false;
}
if (!feature.enabled) {
feature.enabled = true;
this.updateMetrics();
this.emitEvent('recovery', `feature-${featureId}`, {
action: 'feature_enabled',
featureId,
featureName: feature.name,
manual: true
}, 'info');
}
return true;
}
/**
* Manually disable a feature
*/
disableFeature(featureId) {
const feature = this.featureToggles.get(featureId);
if (!feature || !feature.manualToggleAllowed) {
return false;
}
// Don't disable critical features in emergency mode
if (this.currentQualityLevel === ServiceQualityLevel.EMERGENCY &&
feature.priority === FeaturePriority.CRITICAL) {
return false;
}
if (feature.enabled) {
feature.enabled = false;
this.updateMetrics();
this.emitEvent('recovery', `feature-${featureId}`, {
action: 'feature_disabled',
featureId,
featureName: feature.name,
manual: true
}, 'info');
}
return true;
}
/**
* Get current service quality level
*/
getQualityLevel() {
return this.currentQualityLevel;
}
/**
* Set service quality level manually
*/
setQualityLevel(level) {
if (level === this.currentQualityLevel) {
return;
}
const previousLevel = this.currentQualityLevel;
this.currentQualityLevel = level;
// Update quality level timestamps
const now = Date.now();
const timeInPreviousLevel = now - this.lastQualityUpdateTime;
const previousTime = this.qualityLevelTimestamps.get(previousLevel) || 0;
this.qualityLevelTimestamps.set(previousLevel, previousTime + timeInPreviousLevel);
this.lastQualityUpdateTime = now;
// Apply quality level to all features
this.applyQualityLevelToAllFeatures();
// Update metrics
this.metrics.previousQualityLevel = previousLevel;
this.metrics.currentQualityLevel = level;
this.metrics.qualityLevelChanges++;
this.updateMetrics();
this.emitEvent('recovery', 'degradation-manager', {
action: 'quality_level_changed',
previousLevel,
newLevel: level,
manual: true
}, 'warning');
}
/**
* Update marine environment for adaptive degradation
*/
updateMarineEnvironment(environment) {
this.marineEnvironment = environment;
// Update marine-specific metrics
if (environment.seaState === 'rough' || environment.seaState === 'very_rough') {
this.metrics.marineMetrics.timeInRoughSeas += Date.now() - this.lastQualityUpdateTime;
}
if (environment.powerStatus === 'conservation' || environment.powerStatus === 'critical') {
this.metrics.marineMetrics.timeInPowerConservation += Date.now() - this.lastQualityUpdateTime;
}
if (environment.connectivityQuality < this.config.qualityThresholds.connectivityQuality.low) {
this.metrics.marineMetrics.timeWithPoorConnectivity += Date.now() - this.lastQualityUpdateTime;
}
if (environment.criticalOperationsOnly) {
this.metrics.marineMetrics.timeInEmergencyMode += Date.now() - this.lastQualityUpdateTime;
}
this.lastQualityUpdateTime = Date.now();
// Determine appropriate quality level based on marine conditions
const newQualityLevel = this.determineQualityLevelFromEnvironment(environment);
if (newQualityLevel !== this.currentQualityLevel) {
const previousLevel = this.currentQualityLevel;
this.currentQualityLevel = newQualityLevel;
// Update quality level timestamps
const now = Date.now();
const timeInPreviousLevel = now - this.lastQualityUpdateTime;
const previousTime = this.qualityLevelTimestamps.get(previousLevel) || 0;
this.qualityLevelTimestamps.set(previousLevel, previousTime + timeInPreviousLevel);
this.lastQualityUpdateTime = now;
// Apply quality level to all features
this.applyQualityLevelToAllFeatures();
// Update metrics
this.metrics.previousQualityLevel = previousLevel;
this.metrics.currentQualityLevel = newQualityLevel;
this.metrics.qualityLevelChanges++;
this.emitEvent('recovery', 'degradation-manager', {
action: 'quality_level_changed',
previousLevel,
newLevel: newQualityLevel,
manual: false,
environmentalCause: true,
seaState: environment.seaState,
powerStatus: environment.powerStatus,
connectivityQuality: environment.connectivityQuality,
criticalOperationsOnly: environment.criticalOperationsOnly
}, 'warning');
}
this.emitEvent('recovery', 'degradation-manager', {
action: 'marine_environment_updated',
seaState: environment.seaState,
powerStatus: environment.powerStatus,
connectivityQuality: environment.connectivityQuality,
criticalOperationsOnly: environment.criticalOperationsOnly
}, 'info');
}
/**
* Get current degradation metrics
*/
getMetrics() {
this.updateMetrics();
return { ...this.metrics };
}
/**
* Get all registered features
*/
getAllFeatures() {
return Array.from(this.featureToggles.values());
}
/**
* Get enabled features
*/
getEnabledFeatures() {
return Array.from(this.featureToggles.values()).filter(feature => feature.enabled);
}
/**
* Get disabled features
*/
getDisabledFeatures() {
return Array.from(this.featureToggles.values()).filter(feature => !feature.enabled);
}
/**
* 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);
}
}
/**
* Determine quality level from marine environment
*/
determineQualityLevelFromEnvironment(environment) {
// Start with default quality level
let qualityLevel = this.config.defaultQualityLevel;
// Emergency mode takes precedence
if (environment.criticalOperationsOnly) {
return ServiceQualityLevel.EMERGENCY;
}
// Check power status if configured
if (this.config.powerAware) {
if (environment.powerStatus === 'critical') {
qualityLevel = this.getMoreRestrictiveLevel(qualityLevel, this.config.qualityThresholds.powerStatus.critical);
}
else if (environment.powerStatus === 'conservation') {
qualityLevel = this.getMoreRestrictiveLevel(qualityLevel, this.config.qualityThresholds.powerStatus.conservation);
}
}
// Check sea state if configured
if (this.config.adaptToMarineConditions) {
if (environment.seaState === 'very_rough') {
qualityLevel = this.getMoreRestrictiveLevel(qualityLevel, this.config.qualityThresholds.seaState.veryRough);
}
else if (environment.seaState === 'rough') {
qualityLevel = this.getMoreRestrictiveLevel(qualityLevel, this.config.qualityThresholds.seaState.rough);
}
}
// Check connectivity quality if configured
if (this.config.connectivityAware) {
if (environment.connectivityQuality < this.config.qualityThresholds.connectivityQuality.veryLow) {
qualityLevel = this.getMoreRestrictiveLevel(qualityLevel, this.config.qualityThresholds.connectivityQuality.veryLowQualityLevel);
}
else if (environment.connectivityQuality < this.config.qualityThresholds.connectivityQuality.low) {
qualityLevel = this.getMoreRestrictiveLevel(qualityLevel, this.config.qualityThresholds.connectivityQuality.lowQualityLevel);
}
}
return qualityLevel;
}
/**
* Get the more restrictive of two quality levels
*/
getMoreRestrictiveLevel(level1, level2) {
const levelOrder = [
ServiceQualityLevel.OPTIMAL,
ServiceQualityLevel.REDUCED,
ServiceQualityLevel.MINIMAL,
ServiceQualityLevel.CRITICAL,
ServiceQualityLevel.EMERGENCY
];
const index1 = levelOrder.indexOf(level1);
const index2 = levelOrder.indexOf(level2);
// Ensure we don't return undefined
return levelOrder[Math.max(index1, index2)] || ServiceQualityLevel.OPTIMAL;
}
/**
* Apply quality level to all features
*/
applyQualityLevelToAllFeatures() {
if (!this.config.featureToggles.autoToggleFeatures) {
return;
}
for (const feature of this.featureToggles.values()) {
this.applyQualityLevelToFeature(feature);
}
}
/**
* Apply quality level to a specific feature
*/
applyQualityLevelToFeature(feature) {
if (!this.config.featureToggles.autoToggleFeatures || !feature.manualToggleAllowed) {
return;
}
const minPriority = this.config.featureToggles.qualityLevelMinPriority[this.currentQualityLevel];
const priorityOrder = [
FeaturePriority.COMFORT,
FeaturePriority.USEFUL,
FeaturePriority.IMPORTANT,
FeaturePriority.ESSENTIAL,
FeaturePriority.CRITICAL
];
const featurePriorityIndex = priorityOrder.indexOf(feature.priority);
const minPriorityIndex = priorityOrder.indexOf(minPriority);
const shouldBeEnabled = featurePriorityIndex >= minPriorityIndex;
// Special case for marine-specific features
if (this.marineEnvironment) {
// Always enable safety features in rough seas
if (feature.marineSettings.requiredForSafety &&
(this.marineEnvironment.seaState === 'rough' ||
this.marineEnvironment.seaState === 'very_rough')) {
if (!feature.enabled) {
feature.enabled = true;
this.emitEvent('recovery', `feature-${feature.id}`, {
action: 'feature_enabled',
featureId: feature.id,
featureName: feature.name,
reason: 'safety_required_in_rough_seas',
manual: false
}, 'info');
}
return;
}
// Always enable navigation features when sailing or motoring
if (feature.marineSettings.requiredForNavigation &&
(this.marineEnvironment.seaState === 'rough' ||
this.marineEnvironment.seaState === 'very_rough')) {
if (!feature.enabled) {
feature.enabled = true;
this.emitEvent('recovery', `feature-${feature.id}`, {
action: 'feature_enabled',
featureId: feature.id,
featureName: feature.name,
reason: 'navigation_required_in_rough_seas',
manual: false
}, 'info');
}
return;
}
// Disable connectivity-dependent features when connectivity is poor
if (feature.marineSettings.requiresGoodConnectivity &&
this.marineEnvironment.connectivityQuality < this.config.qualityThresholds.connectivityQuality.low) {
if (feature.enabled) {
feature.enabled = false;
this.emitEvent('recovery', `feature-${feature.id}`, {
action: 'feature_disabled',
featureId: feature.id,
featureName: feature.name,
reason: 'poor_connectivity',
manual: false
}, 'info');
}
return;
}
}
// Apply standard priority-based rules
if (feature.enabled !== shouldBeEnabled) {
feature.enabled = shouldBeEnabled;
this.emitEvent('recovery', `feature-${feature.id}`, {
action: shouldBeEnabled ? 'feature_enabled' : 'feature_disabled',
featureId: feature.id,
featureName: feature.name,
reason: 'quality_level_change',
qualityLevel: this.currentQualityLevel,
featurePriority: feature.priority,
minPriority,
manual: false
}, 'info');
}
}
/**
* Update metrics
*/
updateMetrics() {
// Update time at quality level
const now = Date.now();
const timeInCurrentLevel = now - this.lastQualityUpdateTime;
const currentTime = this.qualityLevelTimestamps.get(this.currentQualityLevel) || 0;
this.qualityLevelTimestamps.set(this.currentQualityLevel, currentTime + timeInCurrentLevel);
this.lastQualityUpdateTime = now;
// Update time at quality level metrics
Object.values(ServiceQualityLevel).forEach(level => {
this.metrics.timeAtQualityLevel[level] = this.qualityLevelTimestamps.get(level) || 0;
});
// Update feature toggle metrics
const features = Array.from(this.featureToggles.values());
this.metrics.featureToggles.total = features.length;
this.metrics.featureToggles.enabled = features.filter(f => f.enabled).length;
this.metrics.featureToggles.disabled = features.filter(f => !f.enabled).length;
// Update resource usage metrics
let totalCpu = 0;
let totalMemory = 0;
let totalBandwidth = 0;
let totalStorageIops = 0;
let totalPower = 0;
for (const feature of features) {
if (feature.enabled) {
totalCpu += feature.cpuUsage;
totalMemory += feature.memoryUsage;
totalBandwidth += feature.bandwidthUsage;
totalStorageIops += feature.storageIops;
totalPower += feature.powerConsumption;
}
}
this.metrics.resourceUsage.cpu = totalCpu;
this.metrics.resourceUsage.memory = totalMemory;
this.metrics.resourceUsage.bandwidth = totalBandwidth;
this.metrics.resourceUsage.storageIops = totalStorageIops;
this.metrics.resourceUsage.power = totalPower;
}
/**
* Initialize metrics
*/
initializeMetrics() {
return {
currentQualityLevel: ServiceQualityLevel.OPTIMAL,
previousQualityLevel: ServiceQualityLevel.OPTIMAL,
timeAtQualityLevel: {
[ServiceQualityLevel.OPTIMAL]: 0,
[ServiceQualityLevel.REDUCED]: 0,
[ServiceQualityLevel.MINIMAL]: 0,
[ServiceQualityLevel.CRITICAL]: 0,
[ServiceQualityLevel.EMERGENCY]: 0
},
qualityLevelChanges: 0,
featureToggles: {
total: 0,
enabled: 0,
disabled: 0,
autoToggled: 0,
manuallyToggled: 0
},
resourceUsage: {
cpu: 0,
memory: 0,
bandwidth: 0,
storageIops: 0,
power: 0
},
marineMetrics: {
timeInRoughSeas: 0,
timeInPowerConservation: 0,
timeWithPoorConnectivity: 0,
timeInEmergencyMode: 0
}
};
}
/**
* Emit resilience event
*/
emitEvent(eventType, component, details, severity) {
const event = {
timestamp: new Date(),
eventType,
component,
details,
severity,
marineContext: {
operationalContext: this.marineEnvironment?.powerStatus === 'critical' ?
marine_constants_1.OperationalContext.EMERGENCY : marine_constants_1.OperationalContext.SAILING,
environmentalImpact: this.marineEnvironment?.seaState === 'very_rough',
safetyImpact: false
}
};
this.eventListeners.forEach(listener => {
try {
listener(event);
}
catch (error) {
console.error('Error in degradation manager event listener:', error);
}
});
}
}
exports.DegradationManager = DegradationManager;
/**
* Default degradation manager instance
*/
exports.defaultDegradationManager = new DegradationManager();
//# sourceMappingURL=DegradationManager.js.map