ruv-swarm
Version:
High-performance neural network swarm orchestration in WebAssembly
364 lines (292 loc) ⢠11.5 kB
JavaScript
/**
* Diagnostic CLI for ruv-swarm
* Usage: npx ruv-swarm diagnose [options]
*/
import { diagnostics } from './diagnostics.js';
import { loggingConfig } from './logging-config.js';
import fs from 'fs';
import path from 'path';
async function main() {
const args = process.argv.slice(2);
const command = args[0] || 'help';
// Initialize diagnostics logger
const logger = loggingConfig.getLogger('cli-diagnostics', { level: 'INFO' });
try {
switch (command) {
case 'test':
await runDiagnosticTests(logger);
break;
case 'report':
await generateReport(args.slice(1), logger);
break;
case 'monitor':
await startMonitoring(args.slice(1), logger);
break;
case 'logs':
await analyzeLogs(args.slice(1), logger);
break;
case 'config':
showLoggingConfig(logger);
break;
case 'help':
default:
showHelp();
break;
}
} catch (error) {
logger.error('Diagnostic command failed', { error, command });
process.exit(1);
}
}
async function runDiagnosticTests(logger) {
logger.info('Running diagnostic tests...');
const results = await diagnostics.runDiagnosticTests();
console.log('\nš Diagnostic Test Results:');
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāā\n');
results.tests.forEach(test => {
const icon = test.success ? 'ā
' : 'ā';
console.log(`${icon} ${test.name}`);
if (!test.success) {
console.log(` Error: ${test.error}`);
} else if (test.allocated) {
console.log(` Allocated: ${test.allocated}`);
} else if (test.path) {
console.log(` Path: ${test.path}`);
}
});
console.log('\nš Summary:');
console.log(` Total Tests: ${results.summary.total}`);
console.log(` ā
Passed: ${results.summary.passed}`);
console.log(` ā Failed: ${results.summary.failed}`);
if (results.summary.failed > 0) {
process.exit(1);
}
}
async function generateReport(args, logger) {
const outputPath = args.find(arg => arg.startsWith('--output='))?.split('=')[1];
const format = args.find(arg => arg.startsWith('--format='))?.split('=')[1] || 'json';
logger.info('Generating diagnostic report...');
// Enable diagnostics temporarily
diagnostics.enableAll();
// Wait a bit to collect some samples
await new Promise(resolve => setTimeout(resolve, 5000));
const report = await diagnostics.generateFullReport();
if (outputPath) {
const reportPath = path.resolve(outputPath);
if (format === 'json') {
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
} else if (format === 'markdown') {
fs.writeFileSync(reportPath, formatReportAsMarkdown(report));
}
console.log(`\nš Report saved to: ${reportPath}`);
} else {
console.log('\nš Diagnostic Report:');
console.log('āāāāāāāāāāāāāāāāāāāāāāā');
console.log(formatReportForConsole(report));
}
// Disable diagnostics
diagnostics.disableAll();
}
async function startMonitoring(args, logger) {
const duration = parseInt(args.find(arg => arg.startsWith('--duration='))?.split('=')[1] || '60', 10);
const interval = parseInt(args.find(arg => arg.startsWith('--interval='))?.split('=')[1] || '1000', 10);
logger.info('Starting system monitoring...', { duration, interval });
console.log('\nš System Monitoring');
console.log('āāāāāāāāāāāāāāāāāāāāāāā');
console.log(`Duration: ${duration} seconds`);
console.log(`Interval: ${interval}ms`);
console.log('\nPress Ctrl+C to stop\n');
diagnostics.enableAll();
diagnostics.system.startMonitoring(interval);
// Update display periodically
const displayInterval = setInterval(() => {
const health = diagnostics.system.getSystemHealth();
const connection = diagnostics.connection.getConnectionSummary();
console.clear();
console.log('š System Monitoring');
console.log('āāāāāāāāāāāāāāāāāāāāāāā');
console.log(`\nš System Health: ${health.status.toUpperCase()}`);
if (health.issues.length > 0) {
console.log('\nā ļø Issues:');
health.issues.forEach(issue => console.log(` - ${issue}`));
}
console.log('\nš¾ Metrics:');
Object.entries(health.metrics).forEach(([key, value]) => {
console.log(` ${key}: ${value}`);
});
console.log('\nš Connections:');
console.log(` Active: ${connection.activeConnections}`);
console.log(` Total Events: ${connection.totalEvents}`);
console.log(` Failure Rate: ${(connection.failureRate * 100).toFixed(1)}%`);
console.log('\n\nPress Ctrl+C to stop');
}, 2000);
// Set up timeout
setTimeout(() => {
clearInterval(displayInterval);
diagnostics.disableAll();
console.log('\nā
Monitoring completed');
process.exit(0);
}, duration * 1000);
// Handle Ctrl+C
process.on('SIGINT', () => {
clearInterval(displayInterval);
diagnostics.disableAll();
console.log('\n\nā
Monitoring stopped');
process.exit(0);
});
}
async function analyzeLogs(args, logger) {
const logDir = args.find(arg => arg.startsWith('--dir='))?.split('=')[1] || './logs';
const pattern = args.find(arg => arg.startsWith('--pattern='))?.split('=')[1] || 'error';
logger.info('Analyzing logs...', { logDir, pattern });
if (!fs.existsSync(logDir)) {
console.error(`ā Log directory not found: ${logDir}`);
process.exit(1);
}
const logFiles = fs.readdirSync(logDir).filter(f => f.endsWith('.log'));
console.log(`\nš Found ${logFiles.length} log files in ${logDir}`);
const results = {
totalLines: 0,
matches: 0,
files: {},
};
const regex = new RegExp(pattern, 'i');
logFiles.forEach(file => {
const content = fs.readFileSync(path.join(logDir, file), 'utf8');
const lines = content.split('\n');
const matches = lines.filter(line => regex.test(line));
results.totalLines += lines.length;
results.matches += matches.length;
if (matches.length > 0) {
results.files[file] = {
matches: matches.length,
samples: matches.slice(0, 3),
};
}
});
console.log('\nš Analysis Results:');
console.log(` Total Lines: ${results.totalLines}`);
console.log(` Matches: ${results.matches}`);
console.log(` Pattern: ${pattern}`);
if (results.matches > 0) {
console.log('\nš Files with matches:');
Object.entries(results.files).forEach(([file, data]) => {
console.log(`\n ${file} (${data.matches} matches):`);
data.samples.forEach(sample => {
console.log(` ${sample.substring(0, 100)}...`);
});
});
}
}
function showLoggingConfig(logger) {
console.log('\nāļø Logging Configuration');
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāā\n');
const config = loggingConfig.logConfiguration();
// Configuration is already logged to stderr by logConfiguration()
// Just add usage instructions
console.log('\nš Environment Variables:');
console.log(' LOG_LEVEL - Global log level (TRACE|DEBUG|INFO|WARN|ERROR|FATAL)');
console.log(' MCP_LOG_LEVEL - MCP server log level');
console.log(' TOOLS_LOG_LEVEL - MCP tools log level');
console.log(' SWARM_LOG_LEVEL - Swarm core log level');
console.log(' AGENT_LOG_LEVEL - Agent log level');
console.log(' NEURAL_LOG_LEVEL - Neural network log level');
console.log(' LOG_TO_FILE - Enable file logging (true|false)');
console.log(' LOG_FORMAT - Log format (text|json)');
console.log(' LOG_DIR - Log directory path');
console.log('\nš” Examples:');
console.log(' LOG_LEVEL=DEBUG npx ruv-swarm mcp start');
console.log(' MCP_LOG_LEVEL=TRACE TOOLS_LOG_LEVEL=DEBUG npx ruv-swarm mcp start');
console.log(' LOG_TO_FILE=true LOG_DIR=./mylogs npx ruv-swarm mcp start');
}
function formatReportForConsole(report) {
const output = [];
// Connection section
output.push('š Connection Diagnostics:');
output.push(` Active Connections: ${report.connection.connections.activeConnections}`);
output.push(` Failure Rate: ${(report.connection.connections.failureRate * 100).toFixed(1)}%`);
output.push(` Total Events: ${report.connection.connections.totalEvents}`);
if (report.connection.patterns.recommendations.length > 0) {
output.push('\nā ļø Recommendations:');
report.connection.patterns.recommendations.forEach(rec => {
output.push(` [${rec.severity.toUpperCase()}] ${rec.issue}`);
output.push(` ā ${rec.suggestion}`);
});
}
// System section
output.push('\nš» System Health:');
output.push(` Status: ${report.system.status.toUpperCase()}`);
if (report.system.metrics) {
Object.entries(report.system.metrics).forEach(([key, value]) => {
output.push(` ${key}: ${value}`);
});
}
return output.join('\n');
}
function formatReportAsMarkdown(report) {
const lines = [
'# ruv-swarm Diagnostic Report',
'',
`Generated: ${report.timestamp}`,
'',
'## Connection Diagnostics',
'',
`- **Active Connections**: ${report.connection.connections.activeConnections}`,
`- **Failure Rate**: ${(report.connection.connections.failureRate * 100).toFixed(1)}%`,
`- **Total Events**: ${report.connection.connections.totalEvents}`,
'',
];
if (report.connection.patterns.recommendations.length > 0) {
lines.push('### Recommendations');
lines.push('');
report.connection.patterns.recommendations.forEach(rec => {
lines.push(`- **${rec.severity.toUpperCase()}**: ${rec.issue}`);
lines.push(` - ${rec.suggestion}`);
});
lines.push('');
}
lines.push('## System Health');
lines.push('');
lines.push(`- **Status**: ${report.system.status.toUpperCase()}`);
if (report.system.metrics) {
lines.push('');
lines.push('### Metrics');
lines.push('');
Object.entries(report.system.metrics).forEach(([key, value]) => {
lines.push(`- **${key}**: ${value}`);
});
}
return lines.join('\n');
}
function showHelp() {
console.log(`
š ruv-swarm Diagnostics
Usage: npx ruv-swarm diagnose <command> [options]
Commands:
test Run diagnostic tests
report [options] Generate diagnostic report
--output=<path> Save report to file
--format=<json|md> Output format (default: json)
monitor [options] Start system monitoring
--duration=<seconds> Monitoring duration (default: 60)
--interval=<ms> Sample interval (default: 1000)
logs [options] Analyze log files
--dir=<path> Log directory (default: ./logs)
--pattern=<regex> Search pattern (default: error)
config Show logging configuration
help Show this help message
Examples:
npx ruv-swarm diagnose test
npx ruv-swarm diagnose report --output=report.json
npx ruv-swarm diagnose monitor --duration=120
npx ruv-swarm diagnose logs --pattern="connection.*failed"
npx ruv-swarm diagnose config
`);
}
// Export for use in main CLI
export { main as diagnosticsCLI };
// Run if called directly
if (import.meta.url === `file://${process.argv[1]}`) {
main().catch(console.error);
}