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
JavaScript
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;
}
}