UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

607 lines (606 loc) 24.7 kB
import { TaskManagerMemoryManager } from './memory-manager-integration.js'; import { AppError } from '../../../utils/errors.js'; import logger from '../../../logger.js'; export class PerformanceMonitor { static instance = null; config; metrics = new Map(); alerts = new Map(); bottlenecks = new Map(); regressions = new Map(); monitoringInterval = null; analysisInterval = null; memoryManager = null; metricCounter = 0; realTimeMetrics = []; operationTimings = new Map(); activeOperations = new Set(); optimizationSuggestions = []; constructor(config) { this.config = config; this.memoryManager = TaskManagerMemoryManager.getInstance(); if (config.enabled) { this.startMonitoring(); } if (config.bottleneckDetection.enabled) { this.startBottleneckAnalysis(); } logger.info({ config }, 'Performance Monitor initialized'); } static getInstance(config) { if (!PerformanceMonitor.instance) { if (!config) { throw new AppError('Performance monitor configuration required for first initialization'); } PerformanceMonitor.instance = new PerformanceMonitor(config); } return PerformanceMonitor.instance; } startMonitoring() { if (this.monitoringInterval) { return; } this.monitoringInterval = setInterval(() => { this.collectSystemMetrics(); this.checkThresholds(); this.detectRegressions(); }, this.config.metricsInterval); logger.debug('Performance monitoring started'); } stopMonitoring() { if (this.monitoringInterval) { clearInterval(this.monitoringInterval); this.monitoringInterval = null; logger.debug('Performance monitoring stopped'); } } startBottleneckAnalysis() { if (this.analysisInterval) { return; } this.analysisInterval = setInterval(() => { this.analyzeBottlenecks(); }, this.config.bottleneckDetection.analysisInterval); logger.debug('Bottleneck analysis started'); } stopBottleneckAnalysis() { if (this.analysisInterval) { clearInterval(this.analysisInterval); this.analysisInterval = null; logger.debug('Bottleneck analysis stopped'); } } collectSystemMetrics() { const timestamp = new Date(); const memoryUsage = process.memoryUsage(); this.recordMetric({ type: 'memory_usage', name: 'heap_used', value: memoryUsage.heapUsed, unit: 'bytes', timestamp, tags: { component: 'system' }, threshold: { warning: this.config.performanceThresholds.maxMemoryUsage * 0.8 * 1024 * 1024, critical: this.config.performanceThresholds.maxMemoryUsage * 1024 * 1024 } }); const startTime = process.hrtime.bigint(); setImmediate(() => { const delay = Number(process.hrtime.bigint() - startTime) / 1000000; this.recordMetric({ type: 'cpu_usage', name: 'event_loop_delay', value: delay, unit: 'ms', timestamp, tags: { component: 'system' }, threshold: { warning: 10, critical: 50 } }); }); if (this.memoryManager) { const memStats = this.memoryManager.getCurrentMemoryStats(); if (memStats) { this.recordMetric({ type: 'memory_usage', name: 'task_manager_memory', value: memStats.totalMemoryUsage, unit: 'bytes', timestamp, tags: { component: 'task_manager' } }); this.recordMetric({ type: 'cache_hit_rate', name: 'cache_memory_usage', value: memStats.cacheMemoryUsage, unit: 'bytes', timestamp, tags: { component: 'cache' } }); } } } recordMetric(metric) { const fullMetric = { id: `metric_${++this.metricCounter}_${Date.now()}`, ...metric }; const key = `${metric.type}_${metric.name}`; if (!this.metrics.has(key)) { this.metrics.set(key, []); } const metricArray = this.metrics.get(key); metricArray.push(fullMetric); if (metricArray.length > 1000) { metricArray.splice(0, metricArray.length - 1000); } logger.debug({ type: metric.type, name: metric.name, value: metric.value, unit: metric.unit }, 'Performance metric recorded'); } checkThresholds() { for (const metricArray of this.metrics.values()) { const latestMetric = metricArray[metricArray.length - 1]; if (!latestMetric?.threshold) continue; const { warning, critical } = latestMetric.threshold; if (latestMetric.value >= critical) { this.generateAlert(latestMetric, 'critical', critical); } else if (latestMetric.value >= warning) { this.generateAlert(latestMetric, 'warning', warning); } } } generateAlert(metric, type, threshold) { const alertId = `alert_${metric.type}_${metric.name}_${Date.now()}`; const existingAlert = Array.from(this.alerts.values()).find(alert => alert.metricId === metric.id && !alert.resolved); if (existingAlert) { return; } const alert = { id: alertId, metricId: metric.id, type, message: `${metric.name} ${type}: ${metric.value}${metric.unit} exceeds threshold of ${threshold}${metric.unit}`, value: metric.value, threshold, timestamp: new Date(), resolved: false }; this.alerts.set(alertId, alert); logger[type === 'critical' ? 'error' : 'warn']({ alertId, metric: metric.name, value: metric.value, threshold, unit: metric.unit }, `Performance ${type} alert`); } analyzeBottlenecks() { const now = Date.now(); const analysisWindow = 5 * 60 * 1000; for (const [key, metricArray] of this.metrics) { const recentMetrics = metricArray.filter(metric => now - metric.timestamp.getTime() < analysisWindow); if (recentMetrics.length < this.config.bottleneckDetection.minSampleSize) { continue; } const bottleneck = this.detectBottleneck(key, recentMetrics); if (bottleneck) { this.bottlenecks.set(bottleneck.id, bottleneck); logger.warn({ bottleneck }, 'Performance bottleneck detected'); } } } detectBottleneck(key, metrics) { const avgValue = metrics.reduce((sum, m) => sum + m.value, 0) / metrics.length; const latestMetric = metrics[metrics.length - 1]; if (latestMetric.threshold) { const { warning, critical } = latestMetric.threshold; if (avgValue > critical * 0.9) { return { id: `bottleneck_${key}_${Date.now()}`, component: latestMetric.tags.component || 'unknown', type: this.getBottleneckType(latestMetric.type), severity: 'critical', description: `${latestMetric.name} consistently high: avg ${avgValue.toFixed(2)}${latestMetric.unit}`, metrics: metrics.slice(-10), suggestions: this.getBottleneckSuggestions(latestMetric.type), detectedAt: new Date() }; } else if (avgValue > warning * 0.9) { return { id: `bottleneck_${key}_${Date.now()}`, component: latestMetric.tags.component || 'unknown', type: this.getBottleneckType(latestMetric.type), severity: 'medium', description: `${latestMetric.name} elevated: avg ${avgValue.toFixed(2)}${latestMetric.unit}`, metrics: metrics.slice(-10), suggestions: this.getBottleneckSuggestions(latestMetric.type), detectedAt: new Date() }; } } return null; } getBottleneckType(metricType) { switch (metricType) { case 'memory_usage': return 'memory'; case 'cpu_usage': return 'cpu'; case 'disk_io': return 'io'; case 'cache_hit_rate': return 'cache'; default: return 'cpu'; } } getBottleneckSuggestions(metricType) { switch (metricType) { case 'memory_usage': return [ 'Enable aggressive memory cleanup', 'Reduce cache sizes', 'Implement lazy loading', 'Check for memory leaks' ]; case 'cpu_usage': return [ 'Reduce concurrent operations', 'Optimize algorithms', 'Implement batching', 'Use worker threads for heavy tasks' ]; case 'cache_hit_rate': return [ 'Increase cache size', 'Optimize cache warming', 'Review eviction policies', 'Implement better caching strategies' ]; default: return ['Monitor and analyze further']; } } detectRegressions() { if (!this.config.regressionDetection.enabled) { return; } const now = Date.now(); const baselineWindow = this.config.regressionDetection.baselineWindow * 60 * 60 * 1000; const comparisonWindow = this.config.regressionDetection.comparisonWindow * 60 * 60 * 1000; for (const [key, metricArray] of this.metrics) { const baselineMetrics = metricArray.filter(metric => now - metric.timestamp.getTime() >= comparisonWindow && now - metric.timestamp.getTime() < baselineWindow + comparisonWindow); const recentMetrics = metricArray.filter(metric => now - metric.timestamp.getTime() < comparisonWindow); if (baselineMetrics.length < 10 || recentMetrics.length < 10) { continue; } const baselineAvg = baselineMetrics.reduce((sum, m) => sum + m.value, 0) / baselineMetrics.length; const recentAvg = recentMetrics.reduce((sum, m) => sum + m.value, 0) / recentMetrics.length; const degradationPercentage = ((recentAvg - baselineAvg) / baselineAvg) * 100; if (degradationPercentage > this.config.regressionDetection.significanceThreshold) { const regression = { id: `regression_${key}_${Date.now()}`, metricType: recentMetrics[0].type, baselineValue: baselineAvg, currentValue: recentAvg, degradationPercentage, detectedAt: new Date(), timeWindow: `${this.config.regressionDetection.comparisonWindow}h` }; this.regressions.set(regression.id, regression); logger.warn({ regression }, 'Performance regression detected'); } } } getComprehensivePerformanceSummary() { const latestMetrics = {}; for (const [key, metricArray] of this.metrics) { if (metricArray.length > 0) { latestMetrics[key] = metricArray[metricArray.length - 1]; } } const activeAlerts = Array.from(this.alerts.values()).filter(alert => !alert.resolved); const bottlenecks = Array.from(this.bottlenecks.values()); const regressions = Array.from(this.regressions.values()); let overallHealth = 'good'; if (activeAlerts.some(alert => alert.type === 'critical') || bottlenecks.some(b => b.severity === 'critical')) { overallHealth = 'critical'; } else if (activeAlerts.length > 0 || bottlenecks.length > 0 || regressions.length > 0) { overallHealth = 'warning'; } return { metrics: latestMetrics, activeAlerts, bottlenecks, regressions, overallHealth }; } resolveAlert(alertId) { const alert = this.alerts.get(alertId); if (alert && !alert.resolved) { alert.resolved = true; alert.resolvedAt = new Date(); logger.info({ alertId }, 'Performance alert resolved'); return true; } return false; } getMetrics(type, name, timeRange) { const results = []; for (const [key, metricArray] of this.metrics) { const [metricType, metricName] = key.split('_', 2); if (metricType === type && (!name || metricName === name)) { let filteredMetrics = metricArray; if (timeRange) { filteredMetrics = metricArray.filter(metric => metric.timestamp >= timeRange.start && metric.timestamp <= timeRange.end); } results.push(...filteredMetrics); } } return results.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); } startOperation(operationId) { this.operationTimings.set(operationId, performance.now()); this.activeOperations.add(operationId); } endOperation(operationId, metadata) { const startTime = this.operationTimings.get(operationId); if (!startTime) { logger.warn({ operationId }, 'Operation timing not found'); return 0; } const duration = performance.now() - startTime; this.operationTimings.delete(operationId); this.activeOperations.delete(operationId); this.recordMetric({ type: 'response_time', name: operationId, value: duration, unit: 'ms', timestamp: new Date(), tags: { operation: operationId, ...metadata }, threshold: { warning: this.config.performanceThresholds.maxResponseTime * 0.8, critical: this.config.performanceThresholds.maxResponseTime } }); if (duration > this.config.performanceThresholds.maxResponseTime) { this.generateOptimizationSuggestion({ category: 'cpu', priority: duration > this.config.performanceThresholds.maxResponseTime * 2 ? 'critical' : 'high', description: `Operation ${operationId} took ${duration.toFixed(2)}ms, exceeding target of ${this.config.performanceThresholds.maxResponseTime}ms`, implementation: 'Consider caching, batching, or algorithm optimization', estimatedImpact: `Potential ${((duration - this.config.performanceThresholds.maxResponseTime) / duration * 100).toFixed(1)}% improvement` }); } return duration; } getCurrentRealTimeMetrics() { const memoryUsage = process.memoryUsage(); const now = performance.now(); const metrics = { responseTime: this.getAverageResponseTime(), memoryUsage: memoryUsage.heapUsed / 1024 / 1024, cpuUsage: this.getEstimatedCpuUsage(), cacheHitRate: this.getCacheHitRate(), activeConnections: this.getActiveConnectionCount(), queueLength: this.activeOperations.size, timestamp: now }; this.realTimeMetrics.push(metrics); if (this.realTimeMetrics.length > 100) { this.realTimeMetrics.shift(); } return metrics; } getAverageResponseTime() { const recentMetrics = this.getMetrics('response_time', undefined, { start: new Date(Date.now() - 60000), end: new Date() }); if (recentMetrics.length === 0) return 0; const total = recentMetrics.reduce((sum, metric) => sum + metric.value, 0); return total / recentMetrics.length; } getEstimatedCpuUsage() { const recentCpuMetrics = this.getMetrics('cpu_usage', 'event_loop_delay', { start: new Date(Date.now() - 10000), end: new Date() }); if (recentCpuMetrics.length === 0) return 0; const avgDelay = recentCpuMetrics.reduce((sum, metric) => sum + metric.value, 0) / recentCpuMetrics.length; return Math.min(100, (avgDelay / 100) * 100); } getCacheHitRate() { return 0; } getActiveConnectionCount() { return 0; } generateOptimizationSuggestion(suggestion) { this.optimizationSuggestions.push(suggestion); if (this.optimizationSuggestions.length > 50) { this.optimizationSuggestions.shift(); } logger.info({ suggestion }, 'Performance optimization suggestion generated'); } async autoOptimize() { const applied = []; const skipped = []; const errors = []; try { const metrics = this.getCurrentRealTimeMetrics(); if (metrics.memoryUsage > this.config.performanceThresholds.maxMemoryUsage * 0.8) { try { await this.optimizeMemoryUsage(); applied.push('memory-optimization'); } catch (error) { errors.push(`Memory optimization failed: ${error instanceof Error ? error.message : String(error)}`); } } if (metrics.cacheHitRate < 0.7) { try { await this.optimizeCacheStrategy(); applied.push('cache-optimization'); } catch (error) { errors.push(`Cache optimization failed: ${error instanceof Error ? error.message : String(error)}`); } } if (metrics.queueLength > 10) { try { await this.optimizeConcurrentProcessing(); applied.push('concurrency-optimization'); } catch (error) { errors.push(`Concurrency optimization failed: ${error instanceof Error ? error.message : String(error)}`); } } if (metrics.responseTime > this.config.performanceThresholds.maxResponseTime) { try { await this.optimizeResponseTime(); applied.push('response-time-optimization'); } catch (error) { errors.push(`Response time optimization failed: ${error instanceof Error ? error.message : String(error)}`); } } logger.info({ applied, skipped, errors }, 'Auto-optimization completed'); return { applied, skipped, errors }; } catch (error) { logger.error({ err: error }, 'Auto-optimization failed'); errors.push(`Auto-optimization failed: ${error instanceof Error ? error.message : String(error)}`); return { applied, skipped, errors }; } } async optimizeMemoryUsage() { logger.info('Starting memory optimization'); if (this.memoryManager) { await this.memoryManager.performAggressiveCleanup(); } if (this.realTimeMetrics.length > 50) { this.realTimeMetrics.splice(0, this.realTimeMetrics.length - 50); } const cutoffTime = Date.now() - (60 * 60 * 1000); for (const [operationId, timestamp] of this.operationTimings.entries()) { if (timestamp < cutoffTime) { this.operationTimings.delete(operationId); } } if (global.gc) { global.gc(); } logger.info('Memory optimization completed'); } async optimizeCacheStrategy() { logger.info('Starting cache optimization'); try { const { ConfigLoader } = await import('./config-loader.js'); const configLoader = ConfigLoader.getInstance(); configLoader.resetCacheStats(); await configLoader.warmupCache(); logger.info('Cache optimization completed'); } catch (error) { logger.warn({ err: error }, 'Cache optimization partially failed'); } } async optimizeConcurrentProcessing() { logger.info('Starting concurrency optimization'); try { const { ExecutionCoordinator } = await import('../services/execution-coordinator.js'); const coordinator = await ExecutionCoordinator.getInstance(); await coordinator.optimizeBatchProcessing(); logger.info('Concurrency optimization completed'); } catch (error) { logger.warn({ err: error }, 'Concurrency optimization failed'); throw error; } } async optimizeResponseTime() { logger.info('Starting response time optimization'); const originalInterval = this.config.metricsInterval; this.config.metricsInterval = Math.max(originalInterval * 2, 5000); const stuckOperations = Array.from(this.activeOperations).filter(op => { const startTime = this.operationTimings.get(op); return startTime && (Date.now() - startTime) > 30000; }); for (const operationId of stuckOperations) { this.activeOperations.delete(operationId); this.operationTimings.delete(operationId); } setTimeout(() => { this.config.metricsInterval = originalInterval; }, 60000); logger.info({ clearedOperations: stuckOperations.length }, 'Response time optimization completed'); } getOptimizationSuggestions(category) { if (category) { return this.optimizationSuggestions.filter(s => s.category === category); } return [...this.optimizationSuggestions]; } getPerformanceSummary(periodMinutes = 5) { const since = new Date(Date.now() - periodMinutes * 60 * 1000); const responseMetrics = this.getMetrics('response_time', undefined, { start: since, end: new Date() }); const averageResponseTime = responseMetrics.length > 0 ? responseMetrics.reduce((sum, m) => sum + m.value, 0) / responseMetrics.length : 0; const maxResponseTime = responseMetrics.length > 0 ? Math.max(...responseMetrics.map(m => m.value)) : 0; const recentAlerts = Array.from(this.alerts.values()) .filter(alert => alert.timestamp.getTime() > since.getTime()); const recentBottlenecks = Array.from(this.bottlenecks.values()) .filter(bottleneck => bottleneck.detectedAt.getTime() > since.getTime()); return { averageResponseTime, maxResponseTime, memoryUsage: process.memoryUsage().heapUsed / 1024 / 1024, alertCount: recentAlerts.length, bottleneckCount: recentBottlenecks.length, targetsMet: averageResponseTime <= this.config.performanceThresholds.maxResponseTime }; } shutdown() { this.stopMonitoring(); this.stopBottleneckAnalysis(); this.metrics.clear(); this.alerts.clear(); this.bottlenecks.clear(); this.regressions.clear(); this.realTimeMetrics.length = 0; this.operationTimings.clear(); this.activeOperations.clear(); this.optimizationSuggestions.length = 0; logger.info('Performance Monitor shutdown'); } } export function getPerformanceMonitor() { try { return PerformanceMonitor.getInstance(); } catch { return null; } }