@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
379 lines • 11.8 kB
JavaScript
/**
* 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