monitor.js•12.3 kB
#!/usr/bin/env node
// CodeCompass MCP Monitoring Dashboard
// Based on patterns from OpenRouter MCP repository
import { monitoring } from '../build/utils/monitoring.js';
import { log } from '../build/utils/logger.js';
// Colors for terminal output
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
dim: '\x1b[2m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
cyan: '\x1b[36m',
white: '\x1b[37m',
gray: '\x1b[90m',
};
// Status symbols
const symbols = {
healthy: '✅',
degraded: '⚠️',
unhealthy: '❌',
info: 'ℹ️',
warning: '⚠️',
error: '❌',
success: '✅',
};
// Format bytes to human readable
function formatBytes(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// Format duration to human readable
function formatDuration(ms) {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (days > 0) return `${days}d ${hours % 24}h ${minutes % 60}m`;
if (hours > 0) return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
return `${seconds}s`;
}
// Format percentage with color
function formatPercentage(value, threshold = 80) {
const color = value >= threshold ? colors.red : value >= threshold * 0.8 ? colors.yellow : colors.green;
return `${color}${value}%${colors.reset}`;
}
// Format status with color and symbol
function formatStatus(status) {
const color = status === 'healthy' ? colors.green : status === 'degraded' ? colors.yellow : colors.red;
const symbol = symbols[status];
return `${color}${symbol} ${status.toUpperCase()}${colors.reset}`;
}
// Clear screen
function clearScreen() {
process.stdout.write('\x1b[2J\x1b[0f');
}
// Display header
function displayHeader() {
console.log(colors.bright + colors.blue + '╔══════════════════════════════════════════════════════════════════════════════╗' + colors.reset);
console.log(colors.bright + colors.blue + '║ CodeCompass MCP Monitor ║' + colors.reset);
console.log(colors.bright + colors.blue + '╚══════════════════════════════════════════════════════════════════════════════╝' + colors.reset);
console.log();
}
// Display system metrics
function displaySystemMetrics(metrics) {
console.log(colors.bright + colors.cyan + '📊 System Metrics' + colors.reset);
console.log(colors.gray + '─'.repeat(80) + colors.reset);
console.log(`${colors.bright}Uptime:${colors.reset} ${formatDuration(metrics.uptime)}`);
console.log(`${colors.bright}Memory:${colors.reset} ${formatBytes(metrics.memory.heapUsed)} / ${formatBytes(metrics.memory.heapTotal)} (${formatPercentage(Math.round((metrics.memory.heapUsed / metrics.memory.heapTotal) * 100), 80)})`);
console.log(`${colors.bright}RSS:${colors.reset} ${formatBytes(metrics.memory.rss)}`);
console.log(`${colors.bright}External:${colors.reset} ${formatBytes(metrics.memory.external)}`);
console.log();
}
// Display request metrics
function displayRequestMetrics(metrics) {
console.log(colors.bright + colors.cyan + '📈 Request Metrics' + colors.reset);
console.log(colors.gray + '─'.repeat(80) + colors.reset);
console.log(`${colors.bright}Total Requests:${colors.reset} ${metrics.requestCount.toLocaleString()}`);
console.log(`${colors.bright}Error Count:${colors.reset} ${metrics.errorCount.toLocaleString()}`);
console.log(`${colors.bright}Error Rate:${colors.reset} ${formatPercentage(metrics.requestCount > 0 ? Math.round((metrics.errorCount / metrics.requestCount) * 100) : 0, 10)}`);
console.log(`${colors.bright}Average Response Time:${colors.reset} ${metrics.responseTime.average}ms`);
console.log(`${colors.bright}Min Response Time:${colors.reset} ${metrics.responseTime.min}ms`);
console.log(`${colors.bright}Max Response Time:${colors.reset} ${metrics.responseTime.max}ms`);
console.log(`${colors.bright}95th Percentile:${colors.reset} ${metrics.responseTime.percentile95}ms`);
console.log();
}
// Display health status
function displayHealthStatus(health) {
console.log(colors.bright + colors.cyan + '🏥 Health Status' + colors.reset);
console.log(colors.gray + '─'.repeat(80) + colors.reset);
console.log(`${colors.bright}Overall Status:${colors.reset} ${formatStatus(health.status)}`);
console.log();
Object.entries(health.checks).forEach(([check, data]) => {
console.log(`${colors.bright}${check}:${colors.reset} ${formatStatus(data.status)}`);
if (check === 'memory') {
console.log(` Usage: ${formatPercentage(data.usage)} (limit: ${data.limit}%)`);
} else if (check === 'errorRate') {
console.log(` Rate: ${formatPercentage(data.rate)} (limit: ${data.limit}%)`);
} else if (check === 'recentErrors') {
console.log(` Count: ${data.count} (limit: ${data.limit})`);
} else if (check === 'responseTime') {
console.log(` Average: ${data.average}ms (limit: ${data.limit}ms)`);
}
});
console.log();
}
// Display tool usage
function displayToolUsage(toolStats) {
console.log(colors.bright + colors.cyan + '🔧 Tool Usage' + colors.reset);
console.log(colors.gray + '─'.repeat(80) + colors.reset);
if (toolStats.length === 0) {
console.log(colors.gray + 'No tool usage data available' + colors.reset);
console.log();
return;
}
toolStats.slice(0, 10).forEach(tool => {
const errorColor = tool.errorRate > 10 ? colors.red : tool.errorRate > 5 ? colors.yellow : colors.green;
console.log(`${colors.bright}${tool.tool}:${colors.reset} ${tool.count} calls (${tool.percentage}%)`);
console.log(` Avg Response: ${tool.averageResponseTime}ms | Error Rate: ${errorColor}${tool.errorRate}%${colors.reset}`);
});
console.log();
}
// Display recent requests
function displayRecentRequests(requests) {
console.log(colors.bright + colors.cyan + '📋 Recent Requests (Last 10)' + colors.reset);
console.log(colors.gray + '─'.repeat(80) + colors.reset);
if (requests.length === 0) {
console.log(colors.gray + 'No recent requests' + colors.reset);
console.log();
return;
}
requests.slice(0, 10).forEach(req => {
const timestamp = new Date(req.timestamp).toLocaleTimeString();
const status = req.success ? colors.green + '✓' : colors.red + '✗';
const duration = req.duration < 1000 ? colors.green : req.duration < 5000 ? colors.yellow : colors.red;
console.log(`${timestamp} ${status} ${colors.bright}${req.tool}${colors.reset} ${duration}${req.duration}ms${colors.reset}`);
if (!req.success && req.error) {
console.log(` ${colors.red}Error: ${req.error}${colors.reset}`);
}
});
console.log();
}
// Display performance insights
function displayInsights(insights) {
console.log(colors.bright + colors.cyan + '💡 Performance Insights' + colors.reset);
console.log(colors.gray + '─'.repeat(80) + colors.reset);
if (insights.slowestTools.length > 0) {
console.log(`${colors.bright}Slowest Tools:${colors.reset}`);
insights.slowestTools.slice(0, 3).forEach(tool => {
console.log(` ${tool.tool}: ${tool.avgTime}ms`);
});
console.log();
}
if (insights.mostErrorProneTools.length > 0) {
console.log(`${colors.bright}Most Error-Prone Tools:${colors.reset}`);
insights.mostErrorProneTools.slice(0, 3).forEach(tool => {
console.log(` ${tool.tool}: ${tool.errorRate}%`);
});
console.log();
}
if (insights.peakUsageHours.length > 0) {
console.log(`${colors.bright}Peak Usage Hours:${colors.reset}`);
insights.peakUsageHours.slice(0, 3).forEach(hour => {
console.log(` ${hour.hour}:00 - ${hour.requestCount} requests`);
});
console.log();
}
if (insights.recommendations.length > 0) {
console.log(`${colors.bright}Recommendations:${colors.reset}`);
insights.recommendations.forEach(rec => {
console.log(` ${symbols.info} ${rec}`);
});
}
console.log();
}
// Display logs
function displayLogs() {
console.log(colors.bright + colors.cyan + '📜 Recent Logs' + colors.reset);
console.log(colors.gray + '─'.repeat(80) + colors.reset);
const logBuffer = log.getLogBuffer();
const recentLogs = logBuffer.slice(-10);
if (recentLogs.length === 0) {
console.log(colors.gray + 'No logs available' + colors.reset);
console.log();
return;
}
recentLogs.forEach(logEntry => {
const timestamp = new Date(logEntry.timestamp).toLocaleTimeString();
const level = logEntry.level === 0 ? colors.gray + 'DEBUG' :
logEntry.level === 1 ? colors.blue + 'INFO' :
logEntry.level === 2 ? colors.yellow + 'WARN' :
colors.red + 'ERROR';
console.log(`${timestamp} ${level}${colors.reset} ${logEntry.message}`);
if (logEntry.error) {
console.log(` ${colors.red}${logEntry.error.message}${colors.reset}`);
}
});
console.log();
}
// Main dashboard function
function displayDashboard() {
clearScreen();
displayHeader();
try {
const metrics = monitoring.getMetrics();
const health = monitoring.getHealthStatus();
const toolStats = monitoring.getToolUsageStats();
const recentRequests = monitoring.getRecentRequests(10);
const insights = monitoring.getPerformanceInsights();
displaySystemMetrics(metrics);
displayRequestMetrics(metrics);
displayHealthStatus(health);
displayToolUsage(toolStats);
displayRecentRequests(recentRequests);
displayInsights(insights);
displayLogs();
console.log(colors.gray + `Last updated: ${new Date().toLocaleString()}` + colors.reset);
console.log(colors.gray + 'Press Ctrl+C to exit' + colors.reset);
} catch (error) {
console.error(colors.red + 'Error displaying dashboard:', error.message + colors.reset);
}
}
// Command line interface
function main() {
const args = process.argv.slice(2);
if (args.includes('--help') || args.includes('-h')) {
console.log('CodeCompass MCP Monitor');
console.log('');
console.log('Usage: node monitor.js [options]');
console.log('');
console.log('Options:');
console.log(' --watch, -w Watch mode (refresh every 5 seconds)');
console.log(' --export, -e Export metrics to JSON');
console.log(' --reset, -r Reset metrics');
console.log(' --help, -h Show this help message');
return;
}
if (args.includes('--export') || args.includes('-e')) {
console.log('Exporting metrics...');
console.log(monitoring.exportMetrics());
return;
}
if (args.includes('--reset') || args.includes('-r')) {
console.log('Resetting metrics...');
monitoring.resetMetrics();
console.log('Metrics reset successfully');
return;
}
if (args.includes('--watch') || args.includes('-w')) {
console.log('Starting monitor in watch mode...');
displayDashboard();
// Refresh every 5 seconds
const interval = setInterval(() => {
displayDashboard();
}, 5000);
// Handle Ctrl+C
process.on('SIGINT', () => {
clearInterval(interval);
console.log(colors.bright + colors.green + '\n\nMonitor stopped' + colors.reset);
process.exit(0);
});
} else {
// Single run
displayDashboard();
}
}
// Handle uncaught exceptions
process.on('uncaughtException', (error) => {
console.error(colors.red + 'Uncaught Exception:', error.message + colors.reset);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error(colors.red + 'Unhandled Rejection at:', promise, 'reason:', reason + colors.reset);
process.exit(1);
});
// Run the monitor
main();