@mrtkrcm/acp-claude-code
Version:
ACP (Agent Client Protocol) bridge for Claude Code
238 lines • 8.4 kB
JavaScript
// 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