UNPKG

claude-flow

Version:

Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)

909 lines (830 loc) 33.2 kB
/** * Analysis & Monitoring Tools for Claude Flow Web UI * Agent 2 - Analysis & Monitoring Tools Developer * * Features: * - 13 analysis and monitoring tools * - Real-time dashboards and visualizations * - WebSocket integration for live data * - Export functionality for reports * - 4 main tabs: Metrics, Reports, Analysis, Health */ class AnalysisTools { constructor() { this.ws = null; this.charts = {}; this.currentTab = 'metrics'; this.isConnected = false; this.metricsCache = new Map(); this.updateInterval = null; this.init(); } init() { this.setupWebSocket(); this.setupEventListeners(); this.initializeCharts(); this.startRealTimeUpdates(); } setupWebSocket() { try { this.ws = new WebSocket('ws://localhost:3000/analysis'); this.ws.onopen = () => { this.isConnected = true; console.log('Analysis WebSocket connected'); this.updateConnectionStatus('connected'); }; this.ws.onmessage = (event) => { const data = JSON.parse(event.data); this.handleWebSocketData(data); }; this.ws.onclose = () => { this.isConnected = false; console.log('Analysis WebSocket disconnected'); this.updateConnectionStatus('disconnected'); setTimeout(() => this.setupWebSocket(), 5000); }; this.ws.onerror = (error) => { console.error('Analysis WebSocket error:', error); this.updateConnectionStatus('error'); }; } catch (error) { console.error('Failed to setup WebSocket:', error); this.updateConnectionStatus('error'); } } setupEventListeners() { // Tab switching document.querySelectorAll('.analysis-tab').forEach(tab => { tab.addEventListener('click', (e) => { this.switchTab(e.target.dataset.tab); }); }); // Export buttons document.querySelectorAll('.export-btn').forEach(btn => { btn.addEventListener('click', (e) => { const format = e.target.dataset.format; const type = e.target.dataset.type; this.exportData(type, format); }); }); // Tool buttons document.querySelectorAll('.tool-btn').forEach(btn => { btn.addEventListener('click', (e) => { const tool = e.target.dataset.tool; this.executeTool(tool); }); }); // Refresh buttons document.querySelectorAll('.refresh-btn').forEach(btn => { btn.addEventListener('click', (e) => { const section = e.target.dataset.section; this.refreshSection(section); }); }); } initializeCharts() { // Performance metrics chart this.charts.performance = new Chart(document.getElementById('performance-chart'), { type: 'line', data: { labels: [], datasets: [{ label: 'Response Time (ms)', data: [], borderColor: '#3b82f6', backgroundColor: 'rgba(59, 130, 246, 0.1)', tension: 0.4 }, { label: 'Throughput (req/s)', data: [], borderColor: '#10b981', backgroundColor: 'rgba(16, 185, 129, 0.1)', tension: 0.4 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'top' } }, scales: { x: { display: true, title: { display: true, text: 'Time' } }, y: { display: true, title: { display: true, text: 'Value' } } } } }); // Token usage chart this.charts.tokenUsage = new Chart(document.getElementById('token-usage-chart'), { type: 'doughnut', data: { labels: ['Input Tokens', 'Output Tokens', 'Cached Tokens'], datasets: [{ data: [0, 0, 0], backgroundColor: ['#3b82f6', '#10b981', '#f59e0b'], borderWidth: 2 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom' } } } }); // System health chart this.charts.systemHealth = new Chart(document.getElementById('system-health-chart'), { type: 'radar', data: { labels: ['CPU', 'Memory', 'Disk', 'Network', 'API', 'Database'], datasets: [{ label: 'Health Score', data: [100, 100, 100, 100, 100, 100], borderColor: '#10b981', backgroundColor: 'rgba(16, 185, 129, 0.2)', pointBackgroundColor: '#10b981', pointBorderColor: '#fff', pointHoverBackgroundColor: '#fff', pointHoverBorderColor: '#10b981' }] }, options: { responsive: true, maintainAspectRatio: false, scales: { r: { beginAtZero: true, max: 100 } } } }); // Load monitoring chart this.charts.loadMonitor = new Chart(document.getElementById('load-monitor-chart'), { type: 'bar', data: { labels: ['1m', '5m', '15m', '30m', '1h', '24h'], datasets: [{ label: 'Average Load', data: [0, 0, 0, 0, 0, 0], backgroundColor: 'rgba(59, 130, 246, 0.7)', borderColor: '#3b82f6', borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'top' } }, scales: { y: { beginAtZero: true } } } }); } startRealTimeUpdates() { this.updateInterval = setInterval(() => { if (this.isConnected) { this.requestMetricsUpdate(); } }, 5000); } requestMetricsUpdate() { if (this.ws && this.ws.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify({ type: 'request_metrics', timestamp: Date.now() })); } } handleWebSocketData(data) { switch (data.type) { case 'metrics_update': this.updateMetrics(data.payload); break; case 'alert': this.handleAlert(data.payload); break; case 'health_status': this.updateHealthStatus(data.payload); break; default: console.log('Unknown WebSocket message type:', data.type); } } updateMetrics(metrics) { this.metricsCache.set('latest', metrics); // Update performance chart if (this.charts.performance && metrics.performance) { const chart = this.charts.performance; const now = new Date().toLocaleTimeString(); chart.data.labels.push(now); chart.data.datasets[0].data.push(metrics.performance.responseTime); chart.data.datasets[1].data.push(metrics.performance.throughput); // Keep only last 20 data points if (chart.data.labels.length > 20) { chart.data.labels.shift(); chart.data.datasets[0].data.shift(); chart.data.datasets[1].data.shift(); } chart.update('none'); } // Update token usage chart if (this.charts.tokenUsage && metrics.tokens) { const chart = this.charts.tokenUsage; chart.data.datasets[0].data = [ metrics.tokens.input, metrics.tokens.output, metrics.tokens.cached ]; chart.update('none'); } // Update system health chart if (this.charts.systemHealth && metrics.health) { const chart = this.charts.systemHealth; chart.data.datasets[0].data = [ metrics.health.cpu, metrics.health.memory, metrics.health.disk, metrics.health.network, metrics.health.api, metrics.health.database ]; chart.update('none'); } // Update load monitor chart if (this.charts.loadMonitor && metrics.load) { const chart = this.charts.loadMonitor; chart.data.datasets[0].data = [ metrics.load.oneMin, metrics.load.fiveMin, metrics.load.fifteenMin, metrics.load.thirtyMin, metrics.load.oneHour, metrics.load.twentyFourHour ]; chart.update('none'); } // Update metric displays this.updateMetricDisplays(metrics); } updateMetricDisplays(metrics) { // Performance metrics const perfSection = document.getElementById('performance-metrics'); if (perfSection && metrics.performance) { perfSection.innerHTML = ` <div class="metric-card"> <div class="metric-label">Response Time</div> <div class="metric-value">${metrics.performance.responseTime}ms</div> </div> <div class="metric-card"> <div class="metric-label">Throughput</div> <div class="metric-value">${metrics.performance.throughput} req/s</div> </div> <div class="metric-card"> <div class="metric-label">Error Rate</div> <div class="metric-value">${metrics.performance.errorRate}%</div> </div> <div class="metric-card"> <div class="metric-label">Uptime</div> <div class="metric-value">${metrics.performance.uptime}</div> </div> `; } // Token usage metrics const tokenSection = document.getElementById('token-metrics'); if (tokenSection && metrics.tokens) { const total = metrics.tokens.input + metrics.tokens.output + metrics.tokens.cached; tokenSection.innerHTML = ` <div class="metric-card"> <div class="metric-label">Total Tokens</div> <div class="metric-value">${total.toLocaleString()}</div> </div> <div class="metric-card"> <div class="metric-label">Input Tokens</div> <div class="metric-value">${metrics.tokens.input.toLocaleString()}</div> </div> <div class="metric-card"> <div class="metric-label">Output Tokens</div> <div class="metric-value">${metrics.tokens.output.toLocaleString()}</div> </div> <div class="metric-card"> <div class="metric-label">Cache Hit Rate</div> <div class="metric-value">${((metrics.tokens.cached / total) * 100).toFixed(1)}%</div> </div> `; } // System health status const healthSection = document.getElementById('health-status'); if (healthSection && metrics.health) { const overallHealth = Math.round( (metrics.health.cpu + metrics.health.memory + metrics.health.disk + metrics.health.network + metrics.health.api + metrics.health.database) / 6 ); healthSection.innerHTML = ` <div class="health-overview"> <div class="health-score ${this.getHealthClass(overallHealth)}"> ${overallHealth}% </div> <div class="health-label">Overall Health</div> </div> <div class="health-components"> <div class="health-component"> <span class="component-name">CPU</span> <span class="component-value ${this.getHealthClass(metrics.health.cpu)}">${metrics.health.cpu}%</span> </div> <div class="health-component"> <span class="component-name">Memory</span> <span class="component-value ${this.getHealthClass(metrics.health.memory)}">${metrics.health.memory}%</span> </div> <div class="health-component"> <span class="component-name">Disk</span> <span class="component-value ${this.getHealthClass(metrics.health.disk)}">${metrics.health.disk}%</span> </div> <div class="health-component"> <span class="component-name">Network</span> <span class="component-value ${this.getHealthClass(metrics.health.network)}">${metrics.health.network}%</span> </div> <div class="health-component"> <span class="component-name">API</span> <span class="component-value ${this.getHealthClass(metrics.health.api)}">${metrics.health.api}%</span> </div> <div class="health-component"> <span class="component-name">Database</span> <span class="component-value ${this.getHealthClass(metrics.health.database)}">${metrics.health.database}%</span> </div> </div> `; } } getHealthClass(score) { if (score >= 90) return 'health-excellent'; if (score >= 70) return 'health-good'; if (score >= 50) return 'health-warning'; return 'health-critical'; } handleAlert(alert) { const alertsContainer = document.getElementById('alerts-container'); if (alertsContainer) { const alertElement = document.createElement('div'); alertElement.className = `alert alert-${alert.severity}`; alertElement.innerHTML = ` <div class="alert-header"> <span class="alert-title">${alert.title}</span> <span class="alert-timestamp">${new Date(alert.timestamp).toLocaleTimeString()}</span> </div> <div class="alert-message">${alert.message}</div> <button class="alert-dismiss" onclick="this.parentElement.remove()">×</button> `; alertsContainer.insertBefore(alertElement, alertsContainer.firstChild); // Auto-dismiss after 10 seconds for info alerts if (alert.severity === 'info') { setTimeout(() => { if (alertElement.parentElement) { alertElement.remove(); } }, 10000); } } } switchTab(tabName) { this.currentTab = tabName; // Update tab buttons document.querySelectorAll('.analysis-tab').forEach(tab => { tab.classList.remove('active'); }); document.querySelector(`[data-tab="${tabName}"]`).classList.add('active'); // Update content panels document.querySelectorAll('.analysis-panel').forEach(panel => { panel.classList.remove('active'); }); document.getElementById(`${tabName}-panel`).classList.add('active'); // Refresh charts when switching tabs setTimeout(() => { Object.values(this.charts).forEach(chart => { if (chart && chart.resize) { chart.resize(); } }); }, 100); } // Tool execution methods async executeTool(toolName) { const button = document.querySelector(`[data-tool="${toolName}"]`); if (button) { button.classList.add('loading'); button.disabled = true; } try { switch (toolName) { case 'performance_report': await this.performanceReport(); break; case 'bottleneck_analyze': await this.bottleneckAnalyze(); break; case 'token_usage': await this.tokenUsage(); break; case 'benchmark_run': await this.benchmarkRun(); break; case 'metrics_collect': await this.metricsCollect(); break; case 'trend_analysis': await this.trendAnalysis(); break; case 'cost_analysis': await this.costAnalysis(); break; case 'quality_assess': await this.qualityAssess(); break; case 'error_analysis': await this.errorAnalysis(); break; case 'usage_stats': await this.usageStats(); break; case 'health_check': await this.healthCheck(); break; case 'load_monitor': await this.loadMonitor(); break; case 'capacity_plan': await this.capacityPlan(); break; default: console.warn('Unknown tool:', toolName); } } catch (error) { console.error(`Error executing tool ${toolName}:`, error); this.showError(`Failed to execute ${toolName}: ${error.message}`); } finally { if (button) { button.classList.remove('loading'); button.disabled = false; } } } // Tool implementations async performanceReport() { const report = await this.fetchAnalysisData('/api/analysis/performance-report'); this.displayReport('performance-report-output', report); await this.notifyToolCompletion('performance_report'); } async bottleneckAnalyze() { const analysis = await this.fetchAnalysisData('/api/analysis/bottleneck-analyze'); this.displayAnalysis('bottleneck-analysis-output', analysis); await this.notifyToolCompletion('bottleneck_analyze'); } async tokenUsage() { const usage = await this.fetchAnalysisData('/api/analysis/token-usage'); this.displayUsage('token-usage-output', usage); await this.notifyToolCompletion('token_usage'); } async benchmarkRun() { const benchmark = await this.fetchAnalysisData('/api/analysis/benchmark-run'); this.displayBenchmark('benchmark-output', benchmark); await this.notifyToolCompletion('benchmark_run'); } async metricsCollect() { const metrics = await this.fetchAnalysisData('/api/analysis/metrics-collect'); this.displayMetrics('metrics-output', metrics); await this.notifyToolCompletion('metrics_collect'); } async trendAnalysis() { const trends = await this.fetchAnalysisData('/api/analysis/trend-analysis'); this.displayTrends('trends-output', trends); await this.notifyToolCompletion('trend_analysis'); } async costAnalysis() { const costs = await this.fetchAnalysisData('/api/analysis/cost-analysis'); this.displayCosts('costs-output', costs); await this.notifyToolCompletion('cost_analysis'); } async qualityAssess() { const quality = await this.fetchAnalysisData('/api/analysis/quality-assess'); this.displayQuality('quality-output', quality); await this.notifyToolCompletion('quality_assess'); } async errorAnalysis() { const errors = await this.fetchAnalysisData('/api/analysis/error-analysis'); this.displayErrors('errors-output', errors); await this.notifyToolCompletion('error_analysis'); } async usageStats() { const stats = await this.fetchAnalysisData('/api/analysis/usage-stats'); this.displayStats('stats-output', stats); await this.notifyToolCompletion('usage_stats'); } async healthCheck() { const health = await this.fetchAnalysisData('/api/analysis/health-check'); this.displayHealth('health-output', health); await this.notifyToolCompletion('health_check'); } async loadMonitor() { const load = await this.fetchAnalysisData('/api/analysis/load-monitor'); this.displayLoad('load-output', load); await this.notifyToolCompletion('load_monitor'); } async capacityPlan() { const capacity = await this.fetchAnalysisData('/api/analysis/capacity-plan'); this.displayCapacity('capacity-output', capacity); await this.notifyToolCompletion('capacity_plan'); } async fetchAnalysisData(endpoint) { try { const response = await fetch(endpoint); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { console.error('Fetch error:', error); // Return mock data for development return this.getMockData(endpoint); } } getMockData(endpoint) { const mockData = { '/api/analysis/performance-report': { summary: 'System performance is within acceptable ranges', metrics: { averageResponseTime: 245, throughput: 1250, errorRate: 0.02, uptime: '99.8%' }, recommendations: [ 'Consider caching frequently accessed data', 'Optimize database queries for better performance', 'Monitor memory usage during peak hours' ] }, '/api/analysis/bottleneck-analyze': { bottlenecks: [ { component: 'Database', severity: 'medium', impact: 'Response time +15%' }, { component: 'API Gateway', severity: 'low', impact: 'Throughput -5%' } ], recommendations: [ 'Add database read replicas', 'Implement connection pooling', 'Optimize slow queries' ] }, '/api/analysis/token-usage': { totalTokens: 2450000, inputTokens: 1200000, outputTokens: 950000, cachedTokens: 300000, cost: 245.50, efficiency: 85.2 } }; return mockData[endpoint] || { message: 'No data available' }; } displayReport(containerId, report) { const container = document.getElementById(containerId); if (!container) return; container.innerHTML = ` <div class="report-summary"> <h3>Performance Report</h3> <p>${report.summary}</p> </div> <div class="report-metrics"> <div class="metric-grid"> <div class="metric-item"> <label>Average Response Time</label> <span>${report.metrics.averageResponseTime}ms</span> </div> <div class="metric-item"> <label>Throughput</label> <span>${report.metrics.throughput} req/s</span> </div> <div class="metric-item"> <label>Error Rate</label> <span>${(report.metrics.errorRate * 100).toFixed(2)}%</span> </div> <div class="metric-item"> <label>Uptime</label> <span>${report.metrics.uptime}</span> </div> </div> </div> <div class="report-recommendations"> <h4>Recommendations</h4> <ul> ${report.recommendations.map(rec => `<li>${rec}</li>`).join('')} </ul> </div> `; } displayAnalysis(containerId, analysis) { const container = document.getElementById(containerId); if (!container) return; container.innerHTML = ` <div class="analysis-results"> <h3>Bottleneck Analysis</h3> <div class="bottleneck-list"> ${analysis.bottlenecks.map(bottleneck => ` <div class="bottleneck-item severity-${bottleneck.severity}"> <div class="bottleneck-component">${bottleneck.component}</div> <div class="bottleneck-severity">${bottleneck.severity}</div> <div class="bottleneck-impact">${bottleneck.impact}</div> </div> `).join('')} </div> <div class="analysis-recommendations"> <h4>Recommendations</h4> <ul> ${analysis.recommendations.map(rec => `<li>${rec}</li>`).join('')} </ul> </div> </div> `; } displayUsage(containerId, usage) { const container = document.getElementById(containerId); if (!container) return; container.innerHTML = ` <div class="usage-overview"> <h3>Token Usage Analysis</h3> <div class="usage-stats"> <div class="usage-stat"> <label>Total Tokens</label> <span>${usage.totalTokens.toLocaleString()}</span> </div> <div class="usage-stat"> <label>Input Tokens</label> <span>${usage.inputTokens.toLocaleString()}</span> </div> <div class="usage-stat"> <label>Output Tokens</label> <span>${usage.outputTokens.toLocaleString()}</span> </div> <div class="usage-stat"> <label>Cached Tokens</label> <span>${usage.cachedTokens.toLocaleString()}</span> </div> <div class="usage-stat"> <label>Total Cost</label> <span>$${usage.cost.toFixed(2)}</span> </div> <div class="usage-stat"> <label>Efficiency</label> <span>${usage.efficiency.toFixed(1)}%</span> </div> </div> </div> `; } exportData(type, format) { const data = this.metricsCache.get('latest') || {}; const timestamp = new Date().toISOString().split('T')[0]; const filename = `${type}_${timestamp}.${format}`; if (format === 'json') { this.downloadJSON(data, filename); } else if (format === 'csv') { this.downloadCSV(data, filename); } } downloadJSON(data, filename) { const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url); } downloadCSV(data, filename) { const csv = this.jsonToCSV(data); const blob = new Blob([csv], { type: 'text/csv' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url); } jsonToCSV(json) { const flatten = (obj, prefix = '') => { const flattened = {}; for (const key in obj) { if (typeof obj[key] === 'object' && obj[key] !== null) { Object.assign(flattened, flatten(obj[key], prefix + key + '.')); } else { flattened[prefix + key] = obj[key]; } } return flattened; }; const flattened = flatten(json); const headers = Object.keys(flattened); const values = Object.values(flattened); return [headers.join(','), values.join(',')].join('\n'); } refreshSection(section) { switch (section) { case 'metrics': this.requestMetricsUpdate(); break; case 'charts': Object.values(this.charts).forEach(chart => { if (chart && chart.update) { chart.update(); } }); break; case 'alerts': document.getElementById('alerts-container').innerHTML = ''; break; default: console.warn('Unknown section:', section); } } updateConnectionStatus(status) { const statusElement = document.getElementById('connection-status'); if (statusElement) { statusElement.className = `connection-status ${status}`; statusElement.textContent = status.charAt(0).toUpperCase() + status.slice(1); } } showError(message) { const errorElement = document.createElement('div'); errorElement.className = 'error-message'; errorElement.textContent = message; document.body.appendChild(errorElement); setTimeout(() => { errorElement.remove(); }, 5000); } async notifyToolCompletion(toolName) { try { // Notify swarm of tool completion const response = await fetch('/api/swarm/notify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: `${toolName} completed`, timestamp: Date.now(), agent: 'analysis-tools' }) }); if (!response.ok) { console.warn('Failed to notify swarm'); } } catch (error) { console.error('Error notifying swarm:', error); } } destroy() { if (this.updateInterval) { clearInterval(this.updateInterval); } if (this.ws) { this.ws.close(); } Object.values(this.charts).forEach(chart => { if (chart && chart.destroy) { chart.destroy(); } }); } } // Initialize when DOM is ready document.addEventListener('DOMContentLoaded', () => { window.analysisTools = new AnalysisTools(); }); // Export for module use if (typeof module !== 'undefined' && module.exports) { module.exports = AnalysisTools; }