UNPKG

@sethdouglasford/claude-flow

Version:

Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology

483 lines 18.1 kB
/** * MCP Performance Monitoring and Optimization */ import { EventEmitter } from "node:events"; import { performance } from "node:perf_hooks"; /** * MCP Performance Monitor * Provides comprehensive performance monitoring, alerting, and optimization suggestions */ export class MCPPerformanceMonitor extends EventEmitter { logger; requestMetrics = new Map(); historicalMetrics = []; responseTimes = []; alertRules = new Map(); activeAlerts = new Map(); optimizationSuggestions = []; metricsTimer; alertCheckTimer; cleanupTimer; config = { metricsInterval: 10000, // 10 seconds alertCheckInterval: 5000, // 5 seconds maxHistorySize: 1000, maxResponseTimeHistory: 10000, cleanupInterval: 300000, // 5 minutes requestTimeout: 30000, // 30 seconds }; constructor(logger) { super(); this.logger = logger; this.setupDefaultAlertRules(); this.startMonitoring(); } /** * Record the start of a request */ recordRequestStart(request, session) { const requestId = `${request.id}_${Date.now()}`; const metrics = { id: requestId, method: request.method, sessionId: session.id, startTime: performance.now(), requestSize: this.calculateRequestSize(request), }; this.requestMetrics.set(requestId, metrics); this.logger.debug("Request started", { requestId, method: request.method, sessionId: session.id, }); return requestId; } /** * Record the completion of a request */ recordRequestEnd(requestId, response, error) { const metrics = this.requestMetrics.get(requestId); if (!metrics) { this.logger.warn("Request metrics not found", { requestId }); return; } const endTime = performance.now(); const duration = endTime - metrics.startTime; metrics.endTime = endTime; metrics.duration = duration; metrics.success = !error; metrics.error = error?.message; metrics.responseSize = response ? this.calculateResponseSize(response) : 0; // Add to response time history this.responseTimes.push(duration); if (this.responseTimes.length > this.config.maxResponseTimeHistory) { this.responseTimes.shift(); } this.logger.debug("Request completed", { requestId, duration, success: metrics.success, error: metrics.error, }); this.emit("requestCompleted", metrics); // Remove from active metrics after some time setTimeout(() => { this.requestMetrics.delete(requestId); }, 60000); // Keep for 1 minute } /** * Get current performance metrics */ getCurrentMetrics() { const now = Date.now(); const completedRequests = Array.from(this.requestMetrics.values()) .filter(m => m.endTime !== undefined); const successfulRequests = completedRequests.filter(m => m.success); const errorRate = completedRequests.length > 0 ? (completedRequests.length - successfulRequests.length) / completedRequests.length * 100 : 0; // Calculate response time percentiles const sortedTimes = [...this.responseTimes].sort((a, b) => a - b); const p50 = this.getPercentile(sortedTimes, 0.5); const p95 = this.getPercentile(sortedTimes, 0.95); const p99 = this.getPercentile(sortedTimes, 0.99); // Calculate throughput (requests per second over last minute) const oneMinuteAgo = now - 60000; const recentRequests = completedRequests.filter(m => m.endTime && (m.startTime + oneMinuteAgo) > 0); const throughput = recentRequests.length / 60; const memUsage = process.memoryUsage(); const cpuUsage = process.cpuUsage(); const metrics = { requestCount: completedRequests.length, averageResponseTime: this.responseTimes.length > 0 ? this.responseTimes.reduce((a, b) => a + b, 0) / this.responseTimes.length : 0, minResponseTime: sortedTimes.length > 0 ? sortedTimes[0] : 0, maxResponseTime: sortedTimes.length > 0 ? sortedTimes[sortedTimes.length - 1] : 0, p50ResponseTime: p50, p95ResponseTime: p95, p99ResponseTime: p99, errorRate, throughput, activeConnections: this.requestMetrics.size, memoryUsage: { heapUsed: memUsage.heapUsed, heapTotal: memUsage.heapTotal, external: memUsage.external, rss: memUsage.rss, }, cpuUsage: { user: cpuUsage.user / 1000000, // Convert to seconds system: cpuUsage.system / 1000000, }, timestamp: new Date(), }; return metrics; } /** * Get historical metrics */ getHistoricalMetrics(limit) { return limit ? this.historicalMetrics.slice(-limit) : [...this.historicalMetrics]; } /** * Add custom alert rule */ addAlertRule(rule) { this.alertRules.set(rule.id, rule); this.logger.info("Alert rule added", { id: rule.id, name: rule.name, metric: rule.metric, threshold: rule.threshold, }); } /** * Remove alert rule */ removeAlertRule(ruleId) { this.alertRules.delete(ruleId); // Resolve any active alerts for this rule for (const [alertId, alert] of this.activeAlerts.entries()) { if (alert.ruleId === ruleId) { this.resolveAlert(alertId); } } this.logger.info("Alert rule removed", { ruleId }); } /** * Get active alerts */ getActiveAlerts() { return Array.from(this.activeAlerts.values()); } /** * Get optimization suggestions */ getOptimizationSuggestions() { return [...this.optimizationSuggestions]; } /** * Get performance summary */ getPerformanceSummary() { const current = this.getCurrentMetrics(); const trends = this.calculateTrends(); return { current, trends, alerts: this.activeAlerts.size, suggestions: this.optimizationSuggestions.length, }; } /** * Resolve an alert */ resolveAlert(alertId) { const alert = this.activeAlerts.get(alertId); if (alert) { alert.resolvedAt = new Date(); this.activeAlerts.delete(alertId); this.logger.info("Alert resolved", { alertId, ruleName: alert.ruleName, duration: alert.resolvedAt.getTime() - alert.triggeredAt.getTime(), }); this.emit("alertResolved", alert); } } /** * Clear all optimization suggestions */ clearOptimizationSuggestions() { this.optimizationSuggestions = []; this.logger.info("Optimization suggestions cleared"); } /** * Stop monitoring */ stop() { if (this.metricsTimer) { clearInterval(this.metricsTimer); this.metricsTimer = undefined; } if (this.alertCheckTimer) { clearInterval(this.alertCheckTimer); this.alertCheckTimer = undefined; } if (this.cleanupTimer) { clearInterval(this.cleanupTimer); this.cleanupTimer = undefined; } this.logger.info("Performance monitoring stopped"); } startMonitoring() { // Collect metrics periodically this.metricsTimer = setInterval(() => { const metrics = this.getCurrentMetrics(); this.historicalMetrics.push(metrics); // Keep only recent history if (this.historicalMetrics.length > this.config.maxHistorySize) { this.historicalMetrics.shift(); } this.emit("metricsCollected", metrics); }, this.config.metricsInterval); // Check alerts periodically this.alertCheckTimer = setInterval(() => { this.checkAlerts(); }, this.config.alertCheckInterval); // Cleanup old data this.cleanupTimer = setInterval(() => { this.cleanup(); this.generateOptimizationSuggestions(); }, this.config.cleanupInterval); this.logger.info("Performance monitoring started"); } setupDefaultAlertRules() { const defaultRules = [ { id: "high_response_time", name: "High Response Time", metric: "averageResponseTime", operator: "gt", threshold: 5000, // 5 seconds duration: 30000, // 30 seconds enabled: true, severity: "medium", actions: ["log", "notify"], }, { id: "high_error_rate", name: "High Error Rate", metric: "errorRate", operator: "gt", threshold: 10, // 10% duration: 60000, // 1 minute enabled: true, severity: "high", actions: ["log", "notify", "alert"], }, { id: "low_throughput", name: "Low Throughput", metric: "throughput", operator: "lt", threshold: 1, // 1 request per second duration: 120000, // 2 minutes enabled: true, severity: "medium", actions: ["log", "notify"], }, { id: "high_memory_usage", name: "High Memory Usage", metric: "memoryUsage.heapUsed", operator: "gt", threshold: 1024 * 1024 * 1024, // 1GB duration: 300000, // 5 minutes enabled: true, severity: "high", actions: ["log", "notify", "alert"], }, ]; for (const rule of defaultRules) { this.alertRules.set(rule.id, rule); } } checkAlerts() { const metrics = this.getCurrentMetrics(); for (const rule of this.alertRules.values()) { if (!rule.enabled) continue; const value = this.getMetricValue(metrics, rule.metric); const triggered = this.evaluateCondition(value, rule.operator, rule.threshold); const existingAlert = Array.from(this.activeAlerts.values()) .find(a => a.ruleId === rule.id && !a.resolvedAt); if (triggered && !existingAlert) { // Create new alert const alert = { id: `alert_${rule.id}_${Date.now()}`, ruleId: rule.id, ruleName: rule.name, severity: rule.severity, message: `${rule.name}: ${rule.metric} is ${value} (threshold: ${rule.threshold})`, triggeredAt: new Date(), currentValue: value, threshold: rule.threshold, }; this.activeAlerts.set(alert.id, alert); this.logger.warn("Alert triggered", { alertId: alert.id, ruleName: rule.name, metric: rule.metric, value, threshold: rule.threshold, }); this.emit("alertTriggered", alert); } else if (!triggered && existingAlert) { // Resolve existing alert this.resolveAlert(existingAlert.id); } } } getMetricValue(metrics, path) { const parts = path.split("."); let value = metrics; for (const part of parts) { if (typeof value === "object" && value !== null) { value = value?.[part]; } else { value = undefined; } if (value === undefined) break; } return typeof value === "number" ? value : 0; } evaluateCondition(value, operator, threshold) { switch (operator) { case "gt": return value > threshold; case "gte": return value >= threshold; case "lt": return value < threshold; case "lte": return value <= threshold; case "eq": return value === threshold; default: return false; } } getPercentile(sortedArray, percentile) { if (sortedArray.length === 0) return 0; const index = Math.ceil(sortedArray.length * percentile) - 1; return sortedArray[Math.max(0, Math.min(index, sortedArray.length - 1))]; } calculateTrends() { const recentMetrics = this.historicalMetrics.slice(-10); // Last 10 data points if (recentMetrics.length < 2) { return { responseTime: "stable", throughput: "stable", errorRate: "stable", }; } const first = recentMetrics[0]; const last = recentMetrics[recentMetrics.length - 1]; return { responseTime: this.getTrend(first.averageResponseTime, last.averageResponseTime, true), throughput: this.getTrend(first.throughput, last.throughput, false), errorRate: this.getTrend(first.errorRate, last.errorRate, true), }; } getTrend(oldValue, newValue, lowerIsBetter) { const change = (newValue - oldValue) / oldValue; const threshold = 0.1; // 10% change threshold if (Math.abs(change) < threshold) { return "stable"; } const improving = lowerIsBetter ? change < 0 : change > 0; return improving ? "improving" : "degrading"; } generateOptimizationSuggestions() { const metrics = this.getCurrentMetrics(); const suggestions = []; // High response time suggestion if (metrics.averageResponseTime > 2000) { suggestions.push({ id: `opt_response_time_${Date.now()}`, type: "performance", priority: "high", title: "Optimize Response Time", description: "Average response time is above 2 seconds", impact: "Improve user experience and system throughput", implementation: "Consider implementing caching, optimizing database queries, or adding connection pooling", estimatedImprovement: "30-50% response time reduction", detectedAt: new Date(), metrics: { averageResponseTime: metrics.averageResponseTime }, }); } // High memory usage suggestion if (metrics.memoryUsage.heapUsed > 512 * 1024 * 1024) { // 512MB suggestions.push({ id: `opt_memory_${Date.now()}`, type: "memory", priority: "medium", title: "Optimize Memory Usage", description: "Heap memory usage is high", impact: "Prevent memory leaks and improve stability", implementation: "Review memory usage patterns, implement object pooling, or add garbage collection tuning", estimatedImprovement: "20-30% memory reduction", detectedAt: new Date(), metrics: { heapUsed: metrics.memoryUsage.heapUsed }, }); } // Low throughput suggestion if (metrics.throughput < 5 && metrics.requestCount > 100) { suggestions.push({ id: `opt_throughput_${Date.now()}`, type: "throughput", priority: "medium", title: "Improve Throughput", description: "Request throughput is below optimal levels", impact: "Handle more concurrent requests efficiently", implementation: "Consider horizontal scaling, load balancing, or request batching", estimatedImprovement: "2-3x throughput increase", detectedAt: new Date(), metrics: { throughput: metrics.throughput }, }); } // Add only new suggestions for (const suggestion of suggestions) { const exists = this.optimizationSuggestions.some(s => s.type === suggestion.type && s.title === suggestion.title); if (!exists) { this.optimizationSuggestions.push(suggestion); this.emit("optimizationSuggestion", suggestion); } } // Keep only recent suggestions (last 24 hours) const dayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000); this.optimizationSuggestions = this.optimizationSuggestions.filter(s => s.detectedAt > dayAgo); } cleanup() { const now = Date.now(); // Clean up old request metrics for (const [id, metrics] of this.requestMetrics.entries()) { if (now - metrics.startTime > this.config.requestTimeout) { this.requestMetrics.delete(id); } } // Clean up old response times if (this.responseTimes.length > this.config.maxResponseTimeHistory) { this.responseTimes = this.responseTimes.slice(-this.config.maxResponseTimeHistory); } } calculateRequestSize(request) { return JSON.stringify(request).length; } calculateResponseSize(response) { return JSON.stringify(response).length; } } //# sourceMappingURL=performance-monitor.js.map