UNPKG

@simonecoelhosfo/optimizely-mcp-server

Version:

Optimizely MCP Server for AI assistants with integrated CLI tools

379 lines 11.8 kB
/** * Execution Monitor * @description Monitors and tracks orchestration template execution with performance metrics * @author Optimizely MCP Server * @version 1.0.0 */ import { getLogger } from '../../logging/Logger.js'; import { EventEmitter } from 'events'; export class ExecutionMonitor extends EventEmitter { logger = getLogger(); executions; timeouts; memoryIntervals; constructor() { super(); this.executions = new Map(); this.timeouts = new Map(); this.memoryIntervals = new Map(); } /** * Start monitoring an execution */ startMonitoring(executionId, config) { this.logger.info({ executionId, maxExecutionTime: config?.max_execution_time }, 'Starting execution monitoring'); // Initialize metrics const metrics = { executionId, startTime: Date.now(), stepMetrics: new Map(), memoryUsage: [], errors: [], status: 'running' }; this.executions.set(executionId, metrics); // Emit start event this.emitEvent({ type: 'start', executionId, timestamp: metrics.startTime }); // Set up timeout if configured if (config?.max_execution_time) { const timeout = setTimeout(() => { this.handleTimeout(executionId); }, config.max_execution_time); this.timeouts.set(executionId, timeout); } // Start memory monitoring this.startMemoryMonitoring(executionId); } /** * Stop monitoring an execution */ stopMonitoring(executionId) { this.logger.info({ executionId }, 'Stopping execution monitoring'); // Clear timeout const timeout = this.timeouts.get(executionId); if (timeout) { clearTimeout(timeout); this.timeouts.delete(executionId); } // Stop memory monitoring this.stopMemoryMonitoring(executionId); // Get final metrics const metrics = this.executions.get(executionId); if (metrics && !metrics.endTime) { metrics.endTime = Date.now(); metrics.duration = metrics.endTime - metrics.startTime; if (metrics.status === 'running') { metrics.status = 'completed'; } } return metrics; } /** * Record step start */ recordStepStart(executionId, stepId, stepName) { const metrics = this.executions.get(executionId); if (!metrics) return; const stepMetrics = { stepId, stepName, startTime: Date.now(), status: 'running', retryCount: 0 }; metrics.stepMetrics.set(stepId, stepMetrics); this.emitEvent({ type: 'step_start', executionId, timestamp: stepMetrics.startTime, data: { stepId, stepName } }); this.logger.debug({ executionId, stepId, stepName }, 'Step started'); } /** * Record step completion */ recordStepComplete(executionId, stepId) { const metrics = this.executions.get(executionId); if (!metrics) return; const stepMetrics = metrics.stepMetrics.get(stepId); if (!stepMetrics) return; stepMetrics.endTime = Date.now(); stepMetrics.duration = stepMetrics.endTime - stepMetrics.startTime; stepMetrics.status = 'completed'; this.emitEvent({ type: 'step_complete', executionId, timestamp: stepMetrics.endTime, data: { stepId, duration: stepMetrics.duration } }); this.logger.debug({ executionId, stepId, duration: stepMetrics.duration }, 'Step completed'); } /** * Record step error */ recordStepError(executionId, stepId, error) { const metrics = this.executions.get(executionId); if (!metrics) return; const stepMetrics = metrics.stepMetrics.get(stepId); if (!stepMetrics) return; stepMetrics.endTime = Date.now(); stepMetrics.duration = stepMetrics.endTime - stepMetrics.startTime; stepMetrics.status = 'failed'; stepMetrics.errorMessage = error.message; // Add to errors const errorMetric = { timestamp: Date.now(), stepId, message: error.message, stack: error.stack, recoverable: false }; metrics.errors.push(errorMetric); this.emitEvent({ type: 'step_error', executionId, timestamp: errorMetric.timestamp, data: { stepId, error: error.message } }); this.logger.error({ executionId, stepId, error: error.message }, 'Step failed'); } /** * Record step retry */ recordStepRetry(executionId, stepId) { const metrics = this.executions.get(executionId); if (!metrics) return; const stepMetrics = metrics.stepMetrics.get(stepId); if (!stepMetrics) return; stepMetrics.retryCount++; this.logger.debug({ executionId, stepId, retryCount: stepMetrics.retryCount }, 'Step retry'); } /** * Record step skipped */ recordStepSkipped(executionId, stepId) { const metrics = this.executions.get(executionId); if (!metrics) return; const stepMetrics = metrics.stepMetrics.get(stepId); if (stepMetrics) { stepMetrics.status = 'skipped'; } else { metrics.stepMetrics.set(stepId, { stepId, stepName: 'Unknown', startTime: Date.now(), status: 'skipped', retryCount: 0 }); } } /** * Mark execution as completed */ markCompleted(executionId) { const metrics = this.executions.get(executionId); if (!metrics) return; metrics.endTime = Date.now(); metrics.duration = metrics.endTime - metrics.startTime; metrics.status = 'completed'; this.emitEvent({ type: 'complete', executionId, timestamp: metrics.endTime, data: { duration: metrics.duration, stepCount: metrics.stepMetrics.size, errorCount: metrics.errors.length } }); this.stopMonitoring(executionId); } /** * Mark execution as failed */ markFailed(executionId, error) { const metrics = this.executions.get(executionId); if (!metrics) return; metrics.endTime = Date.now(); metrics.duration = metrics.endTime - metrics.startTime; metrics.status = 'failed'; // Add error metrics.errors.push({ timestamp: Date.now(), message: error.message, stack: error.stack, recoverable: false }); this.emitEvent({ type: 'error', executionId, timestamp: metrics.endTime, data: { error: error.message } }); this.stopMonitoring(executionId); } /** * Get execution metrics */ getMetrics(executionId) { return this.executions.get(executionId); } /** * Get all active executions */ getActiveExecutions() { return Array.from(this.executions.keys()).filter(id => { const metrics = this.executions.get(id); return metrics && metrics.status === 'running'; }); } /** * Get execution summary */ getExecutionSummary(executionId) { const metrics = this.executions.get(executionId); if (!metrics) return null; const completedSteps = Array.from(metrics.stepMetrics.values()) .filter(s => s.status === 'completed').length; const failedSteps = Array.from(metrics.stepMetrics.values()) .filter(s => s.status === 'failed').length; const skippedSteps = Array.from(metrics.stepMetrics.values()) .filter(s => s.status === 'skipped').length; const avgStepDuration = Array.from(metrics.stepMetrics.values()) .filter(s => s.duration) .reduce((sum, s) => sum + s.duration, 0) / completedSteps || 0; return { executionId, status: metrics.status, duration: metrics.duration, totalSteps: metrics.stepMetrics.size, completedSteps, failedSteps, skippedSteps, avgStepDuration, errorCount: metrics.errors.length, memoryPeakMB: Math.max(...metrics.memoryUsage.map(m => m.heapUsed)) / 1024 / 1024 }; } /** * Start memory monitoring */ startMemoryMonitoring(executionId) { // Capture memory usage every 5 seconds const interval = setInterval(() => { const metrics = this.executions.get(executionId); if (!metrics) { clearInterval(interval); return; } const memUsage = process.memoryUsage(); metrics.memoryUsage.push({ timestamp: Date.now(), heapUsed: memUsage.heapUsed, heapTotal: memUsage.heapTotal, external: memUsage.external, rss: memUsage.rss }); // Keep only last 100 samples if (metrics.memoryUsage.length > 100) { metrics.memoryUsage.shift(); } }, 5000); this.memoryIntervals.set(executionId, interval); } /** * Stop memory monitoring */ stopMemoryMonitoring(executionId) { const interval = this.memoryIntervals.get(executionId); if (interval) { clearInterval(interval); this.memoryIntervals.delete(executionId); } } /** * Handle execution timeout */ handleTimeout(executionId) { const metrics = this.executions.get(executionId); if (!metrics || metrics.status !== 'running') return; metrics.status = 'timeout'; metrics.endTime = Date.now(); metrics.duration = metrics.endTime - metrics.startTime; this.logger.error({ executionId, duration: metrics.duration }, 'Execution timeout'); this.emitEvent({ type: 'timeout', executionId, timestamp: metrics.endTime }); this.stopMonitoring(executionId); } /** * Emit execution event */ emitEvent(event) { this.emit('execution-event', event); } /** * Clean up old metrics */ cleanupOldMetrics(olderThanMs = 24 * 60 * 60 * 1000) { const cutoffTime = Date.now() - olderThanMs; for (const [executionId, metrics] of this.executions.entries()) { if (metrics.endTime && metrics.endTime < cutoffTime) { this.executions.delete(executionId); this.logger.debug({ executionId, age: Date.now() - metrics.endTime }, 'Cleaned up old execution metrics'); } } } } //# sourceMappingURL=ExecutionMonitor.js.map