UNPKG

@mrtkrcm/acp-claude-code

Version:

ACP (Agent Client Protocol) bridge for Claude Code

238 lines 8.4 kB
// Performance monitoring and metrics collection inspired by Gemini CLI import { createLogger } from './logger.js'; export class PerformanceMonitor { logger; metrics = []; activeOperations = new Map(); MAX_METRICS_HISTORY = 1000; processStartTime = Date.now(); totalOperations = 0; totalErrors = 0; constructor() { this.logger = createLogger('PerformanceMonitor'); this.startPeriodicReporting(); } /** * Start tracking an operation */ startOperation(operationId, operation, sessionId) { this.activeOperations.set(operationId, { start: performance.now(), operation, sessionId }); this.logger.debug(`Started operation: ${operation}`, { operationId, sessionId }); } /** * End tracking an operation and record metrics */ endOperation(operationId, success = true, metadata) { const activeOp = this.activeOperations.get(operationId); if (!activeOp) { this.logger.warn(`Attempted to end unknown operation: ${operationId}`); return null; } const duration = performance.now() - activeOp.start; const memoryUsage = process.memoryUsage(); const metric = { operation: activeOp.operation, duration, timestamp: new Date(), sessionId: activeOp.sessionId, success, memoryUsage, metadata }; // Add to metrics history this.metrics.push(metric); if (this.metrics.length > this.MAX_METRICS_HISTORY) { this.metrics.shift(); } // Update counters this.totalOperations++; if (!success) { this.totalErrors++; } // Clean up this.activeOperations.delete(operationId); // Log slow operations if (duration > 5000) { // 5 seconds this.logger.warn(`Slow operation detected: ${activeOp.operation}`, { duration: `${duration.toFixed(2)}ms`, sessionId: activeOp.sessionId, success, metadata }); } this.logger.debug(`Completed operation: ${activeOp.operation}`, { operationId, duration: `${duration.toFixed(2)}ms`, success, sessionId: activeOp.sessionId }); return metric; } /** * Record a one-shot metric without active tracking */ recordMetric(operation, duration, success = true, sessionId, metadata) { const metric = { operation, duration, timestamp: new Date(), sessionId, success, memoryUsage: process.memoryUsage(), metadata }; this.metrics.push(metric); if (this.metrics.length > this.MAX_METRICS_HISTORY) { this.metrics.shift(); } this.totalOperations++; if (!success) { this.totalErrors++; } } /** * Get system-wide metrics */ getSystemMetrics() { const now = Date.now(); const uptime = now - this.processStartTime; const cpuUsage = process.cpuUsage(); const memoryUsage = process.memoryUsage(); // Calculate average response time from recent metrics const recentMetrics = this.metrics.slice(-100); // Last 100 operations const averageResponseTime = recentMetrics.length > 0 ? recentMetrics.reduce((sum, m) => sum + m.duration, 0) / recentMetrics.length : 0; // Calculate error rate const errorRate = this.totalOperations > 0 ? (this.totalErrors / this.totalOperations) * 100 : 0; return { uptime, cpuUsage, memoryUsage, activeOperations: this.activeOperations.size, totalOperations: this.totalOperations, averageResponseTime, errorRate }; } /** * Get metrics for a specific operation type */ getOperationMetrics(operation, timeRangeMs) { let filtered = this.metrics.filter(m => m.operation === operation); if (timeRangeMs) { const cutoff = Date.now() - timeRangeMs; filtered = filtered.filter(m => m.timestamp.getTime() > cutoff); } return filtered; } /** * Get metrics for a specific session */ getSessionMetrics(sessionId, timeRangeMs) { let filtered = this.metrics.filter(m => m.sessionId === sessionId); if (timeRangeMs) { const cutoff = Date.now() - timeRangeMs; filtered = filtered.filter(m => m.timestamp.getTime() > cutoff); } return filtered; } /** * Get basic operation statistics */ getOperationStats(operation) { const metrics = this.getOperationMetrics(operation); if (metrics.length === 0) { return { count: 0, averageDuration: 0, successRate: 100 }; } const durations = metrics.map(m => m.duration); const successCount = metrics.filter(m => m.success).length; return { count: metrics.length, averageDuration: durations.reduce((sum, d) => sum + d, 0) / durations.length, successRate: (successCount / metrics.length) * 100 }; } /** * Clear metrics history (useful for testing) */ clearMetrics() { this.metrics.length = 0; this.activeOperations.clear(); this.totalOperations = 0; this.totalErrors = 0; this.processStartTime = Date.now(); } /** * Get basic health status */ getHealthStatus() { const memory = process.memoryUsage(); const memoryUsageMB = memory.heapUsed / 1024 / 1024; const errorRate = this.totalOperations > 0 ? (this.totalErrors / this.totalOperations) * 100 : 0; const activeOps = this.activeOperations.size; if (memoryUsageMB > 1000 || errorRate > 10 || activeOps > 50) return 'critical'; if (memoryUsageMB > 500 || errorRate > 5 || activeOps > 20) return 'warning'; return 'healthy'; } startPeriodicReporting() { // Simple periodic cleanup every 5 minutes setInterval(() => { this.cleanupOldMetrics(); if (process.env.ACP_DEBUG === 'true') { const metrics = this.getSystemMetrics(); this.logger.info('Performance metrics', { totalOperations: metrics.totalOperations, errorRate: `${metrics.errorRate.toFixed(2)}%`, memoryMB: Math.round(metrics.memoryUsage.heapUsed / 1024 / 1024) }); } }, 5 * 60 * 1000); } cleanupOldMetrics() { // Keep only last 100 metrics for memory efficiency if (this.metrics.length > 100) { this.metrics.splice(0, this.metrics.length - 100); } // Cleanup stale operations (> 10 minutes) const staleThreshold = Date.now() - (10 * 60 * 1000); for (const [operationId, operation] of this.activeOperations.entries()) { if (operation.start < staleThreshold) { this.activeOperations.delete(operationId); } } } } // Global performance monitor instance let globalPerformanceMonitor = null; export function getGlobalPerformanceMonitor() { if (!globalPerformanceMonitor) { globalPerformanceMonitor = new PerformanceMonitor(); } return globalPerformanceMonitor; } export function resetGlobalPerformanceMonitor() { globalPerformanceMonitor = null; } // Convenience functions for common monitoring patterns export function withPerformanceTracking(operation, fn, sessionId, metadata) { const monitor = getGlobalPerformanceMonitor(); const operationId = `${operation}-${Date.now()}-${Math.random()}`; monitor.startOperation(operationId, operation, sessionId); return fn().then((result) => { monitor.endOperation(operationId, true, metadata); return result; }, (error) => { monitor.endOperation(operationId, false, { ...metadata, error: error.message }); throw error; }); } //# sourceMappingURL=performance-monitor.js.map