UNPKG

@sailboat-computer/resilience

Version:

Enhanced resilience patterns for sailboat computer v3 with marine-specific adaptations

1,050 lines (913 loc) 27.8 kB
/** * Graceful degradation strategies with marine-specific adaptations */ import { MarineEnvironmentStatus, ResilienceEvent, ResilienceResult } from '../types'; import { OperationalContext } from '../types/marine-constants'; /** * Service quality level */ export enum ServiceQualityLevel { /** * Full functionality with optimal performance */ OPTIMAL = 'optimal', /** * Full functionality with reduced performance */ REDUCED = 'reduced', /** * Core functionality only */ MINIMAL = 'minimal', /** * Critical functionality only */ CRITICAL = 'critical', /** * Emergency functionality only */ EMERGENCY = 'emergency' } /** * Feature priority for degradation decisions */ export enum FeaturePriority { /** * Critical for vessel safety */ CRITICAL = 'critical', /** * Essential for operation */ ESSENTIAL = 'essential', /** * Important but can be degraded */ IMPORTANT = 'important', /** * Useful but non-essential */ USEFUL = 'useful', /** * Comfort or convenience only */ COMFORT = 'comfort' } /** * Degradation strategy configuration */ export interface DegradationConfig { /** * Default service quality level */ defaultQualityLevel: ServiceQualityLevel; /** * Whether to adapt to marine conditions */ adaptToMarineConditions: boolean; /** * Whether to adapt to power status */ powerAware: boolean; /** * Whether to adapt to connectivity quality */ connectivityAware: boolean; /** * Quality level thresholds for different conditions */ qualityThresholds: { /** * Sea state thresholds */ seaState: { rough: ServiceQualityLevel; veryRough: ServiceQualityLevel; }; /** * Power status thresholds */ powerStatus: { conservation: ServiceQualityLevel; critical: ServiceQualityLevel; }; /** * Connectivity quality thresholds (0-1) */ connectivityQuality: { low: number; lowQualityLevel: ServiceQualityLevel; veryLow: number; veryLowQualityLevel: ServiceQualityLevel; }; }; /** * Feature toggles configuration */ featureToggles: { /** * Whether to enable automatic feature toggling */ autoToggleFeatures: boolean; /** * Minimum priority level for each quality level */ qualityLevelMinPriority: { [ServiceQualityLevel.OPTIMAL]: FeaturePriority; [ServiceQualityLevel.REDUCED]: FeaturePriority; [ServiceQualityLevel.MINIMAL]: FeaturePriority; [ServiceQualityLevel.CRITICAL]: FeaturePriority; [ServiceQualityLevel.EMERGENCY]: FeaturePriority; }; }; /** * Resource limits for different quality levels */ resourceLimits: { /** * CPU usage percentage (0-100) */ cpu: { [ServiceQualityLevel.OPTIMAL]: number; [ServiceQualityLevel.REDUCED]: number; [ServiceQualityLevel.MINIMAL]: number; [ServiceQualityLevel.CRITICAL]: number; [ServiceQualityLevel.EMERGENCY]: number; }; /** * Memory usage percentage (0-100) */ memory: { [ServiceQualityLevel.OPTIMAL]: number; [ServiceQualityLevel.REDUCED]: number; [ServiceQualityLevel.MINIMAL]: number; [ServiceQualityLevel.CRITICAL]: number; [ServiceQualityLevel.EMERGENCY]: number; }; /** * Network bandwidth usage (KB/s) */ bandwidth: { [ServiceQualityLevel.OPTIMAL]: number; [ServiceQualityLevel.REDUCED]: number; [ServiceQualityLevel.MINIMAL]: number; [ServiceQualityLevel.CRITICAL]: number; [ServiceQualityLevel.EMERGENCY]: number; }; /** * Storage I/O operations per second */ storageIops: { [ServiceQualityLevel.OPTIMAL]: number; [ServiceQualityLevel.REDUCED]: number; [ServiceQualityLevel.MINIMAL]: number; [ServiceQualityLevel.CRITICAL]: number; [ServiceQualityLevel.EMERGENCY]: number; }; }; } /** * Feature toggle definition */ export interface FeatureToggle { /** * Feature ID */ id: string; /** * Feature name */ name: string; /** * Feature description */ description: string; /** * Feature priority */ priority: FeaturePriority; /** * Whether the feature is currently enabled */ enabled: boolean; /** * Whether the feature can be manually toggled */ manualToggleAllowed: boolean; /** * Power consumption in watts when enabled */ powerConsumption: number; /** * CPU usage percentage when enabled */ cpuUsage: number; /** * Memory usage in MB when enabled */ memoryUsage: number; /** * Network bandwidth usage in KB/s when enabled */ bandwidthUsage: number; /** * Storage I/O operations per second when enabled */ storageIops: number; /** * Marine-specific settings */ marineSettings: { /** * Whether this feature is required in rough seas */ requiredInRoughSeas: boolean; /** * Whether this feature is required for navigation */ requiredForNavigation: boolean; /** * Whether this feature is required for safety */ requiredForSafety: boolean; /** * Whether this feature requires good connectivity */ requiresGoodConnectivity: boolean; }; } /** * Degradation metrics */ export interface DegradationMetrics { /** * Current service quality level */ currentQualityLevel: ServiceQualityLevel; /** * Previous service quality level */ previousQualityLevel: ServiceQualityLevel; /** * Time spent at each quality level (ms) */ timeAtQualityLevel: { [key in ServiceQualityLevel]: number; }; /** * Number of quality level changes */ qualityLevelChanges: number; /** * Feature toggle metrics */ featureToggles: { /** * Total number of features */ total: number; /** * Number of enabled features */ enabled: number; /** * Number of disabled features */ disabled: number; /** * Number of features toggled automatically */ autoToggled: number; /** * Number of features toggled manually */ manuallyToggled: number; }; /** * Resource usage metrics */ resourceUsage: { /** * CPU usage percentage */ cpu: number; /** * Memory usage percentage */ memory: number; /** * Network bandwidth usage (KB/s) */ bandwidth: number; /** * Storage I/O operations per second */ storageIops: number; /** * Power consumption in watts */ power: number; }; /** * Marine-specific metrics */ marineMetrics: { /** * Time spent in rough seas (ms) */ timeInRoughSeas: number; /** * Time spent in power conservation mode (ms) */ timeInPowerConservation: number; /** * Time spent with poor connectivity (ms) */ timeWithPoorConnectivity: number; /** * Time spent in emergency mode (ms) */ timeInEmergencyMode: number; }; } /** * Degradation manager for graceful service degradation */ export class DegradationManager { private config: DegradationConfig; private currentQualityLevel: ServiceQualityLevel; private featureToggles: Map<string, FeatureToggle> = new Map(); private metrics: DegradationMetrics; private eventListeners: ((event: ResilienceEvent) => void)[] = []; private marineEnvironment?: MarineEnvironmentStatus; private qualityLevelTimestamps: Map<ServiceQualityLevel, number> = new Map(); private lastQualityUpdateTime: number = Date.now(); constructor(config?: Partial<DegradationConfig>) { 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: FeatureToggle): void { 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: string): boolean { 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: string): boolean { const feature = this.featureToggles.get(featureId); return feature ? feature.enabled : false; } /** * Manually enable a feature */ enableFeature(featureId: string): boolean { 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: string): boolean { 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(): ServiceQualityLevel { return this.currentQualityLevel; } /** * Set service quality level manually */ setQualityLevel(level: ServiceQualityLevel): void { 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: MarineEnvironmentStatus): void { 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(): DegradationMetrics { this.updateMetrics(); return { ...this.metrics }; } /** * Get all registered features */ getAllFeatures(): FeatureToggle[] { return Array.from(this.featureToggles.values()); } /** * Get enabled features */ getEnabledFeatures(): FeatureToggle[] { return Array.from(this.featureToggles.values()).filter(feature => feature.enabled); } /** * Get disabled features */ getDisabledFeatures(): FeatureToggle[] { return Array.from(this.featureToggles.values()).filter(feature => !feature.enabled); } /** * Add event listener */ onEvent(listener: (event: ResilienceEvent) => void): void { this.eventListeners.push(listener); } /** * Remove event listener */ removeEventListener(listener: (event: ResilienceEvent) => void): void { const index = this.eventListeners.indexOf(listener); if (index > -1) { this.eventListeners.splice(index, 1); } } /** * Determine quality level from marine environment */ private determineQualityLevelFromEnvironment(environment: MarineEnvironmentStatus): ServiceQualityLevel { // 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 */ private getMoreRestrictiveLevel(level1: ServiceQualityLevel, level2: ServiceQualityLevel): ServiceQualityLevel { 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 */ private applyQualityLevelToAllFeatures(): void { if (!this.config.featureToggles.autoToggleFeatures) { return; } for (const feature of this.featureToggles.values()) { this.applyQualityLevelToFeature(feature); } } /** * Apply quality level to a specific feature */ private applyQualityLevelToFeature(feature: FeatureToggle): void { 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 */ private updateMetrics(): void { // 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 */ private initializeMetrics(): DegradationMetrics { 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 */ private emitEvent( eventType: ResilienceEvent['eventType'], component: string, details: Record<string, any>, severity: ResilienceEvent['severity'] ): void { const event: ResilienceEvent = { timestamp: new Date(), eventType, component, details, severity, marineContext: { operationalContext: this.marineEnvironment?.powerStatus === 'critical' ? OperationalContext.EMERGENCY : 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); } }); } } /** * Default degradation manager instance */ export const defaultDegradationManager = new DegradationManager();