UNPKG

stellar-cyber-mcp-agents

Version:

Model Context Protocol (MCP) server for Stellar Cyber security operations with specialized multi-agent analysis capabilities

481 lines 18.9 kB
import { EventEmitter } from 'events'; export class PerformanceMonitor extends EventEmitter { metrics = new Map(); agentLoads = new Map(); alerts = new Map(); thresholds = { cpu: 80, // Percentage memory: 85, // Percentage errorRate: 10, // Percentage responseTime: 5000, // ms queueDepth: 100, healthCheckInterval: 30000 // ms }; retentionPeriod = 24 * 60 * 60 * 1000; // 24 hours monitoringInterval; scalingCooldown = 300000; // 5 minutes lastScalingDecision = new Map(); constructor() { super(); // Start monitoring loop this.monitoringInterval = setInterval(() => { this.performMonitoringCycle(); }, 10000); // Every 10 seconds } recordMetrics(metrics) { const agentKey = this.getAgentKey(metrics.agentId); // Store metrics const agentMetrics = this.metrics.get(agentKey) || []; agentMetrics.push(metrics); // Keep only recent metrics const cutoff = Date.now() - this.retentionPeriod; const filteredMetrics = agentMetrics.filter(m => new Date(m.timestamp).getTime() > cutoff); this.metrics.set(agentKey, filteredMetrics); // Update agent load this.updateAgentLoad(metrics); // Check for performance issues this.checkThresholds(metrics); // Emit metrics event this.emit('metrics:recorded', { agentId: metrics.agentId, timestamp: metrics.timestamp, summary: { cpu: metrics.cpu.usage, memory: metrics.memory.percentage, operations: metrics.operations.completed, health: metrics.health } }); } updateAgentLoad(metrics) { const agentKey = this.getAgentKey(metrics.agentId); // Calculate current load (weighted combination of factors) const cpuLoad = metrics.cpu.usage / 100; const memoryLoad = metrics.memory.percentage / 100; const queueLoad = Math.min(metrics.operations.pending / 50, 1); // Normalize to queue of 50 const errorLoad = metrics.operations.failed / (metrics.operations.completed + metrics.operations.failed + 1); const currentLoad = (cpuLoad * 0.3) + (memoryLoad * 0.2) + (queueLoad * 0.3) + (errorLoad * 0.2); const agentLoad = { agentId: metrics.agentId, currentLoad: Math.min(1, currentLoad), capacity: 1, // Assuming normalized capacity of 1 queueDepth: metrics.operations.pending, averageResponseTime: metrics.network.avgResponseTime, errorRate: (metrics.operations.failed / (metrics.operations.completed + metrics.operations.failed + 1)) * 100, lastUpdate: metrics.timestamp }; this.agentLoads.set(agentKey, agentLoad); } checkThresholds(metrics) { const alerts = []; // CPU threshold if (metrics.cpu.usage > this.thresholds.cpu) { alerts.push(this.createAlert('cpu_high', metrics, this.thresholds.cpu, metrics.cpu.usage)); } // Memory threshold if (metrics.memory.percentage > this.thresholds.memory) { alerts.push(this.createAlert('memory_high', metrics, this.thresholds.memory, metrics.memory.percentage)); } // Error rate threshold const errorRate = (metrics.operations.failed / (metrics.operations.completed + metrics.operations.failed + 1)) * 100; if (errorRate > this.thresholds.errorRate) { alerts.push(this.createAlert('error_rate_high', metrics, this.thresholds.errorRate, errorRate)); } // Response time threshold if (metrics.network.avgResponseTime > this.thresholds.responseTime) { alerts.push(this.createAlert('response_time_high', metrics, this.thresholds.responseTime, metrics.network.avgResponseTime)); } // Process alerts for (const alert of alerts) { this.processAlert(alert); } } createAlert(type, metrics, threshold, actualValue) { return { id: crypto.randomUUID(), type, severity: actualValue > threshold * 1.5 ? 'CRITICAL' : 'WARNING', agentId: metrics.agentId, message: this.generateAlertMessage(type, metrics.agentId, actualValue, threshold), metrics, threshold, actualValue, timestamp: new Date().toISOString(), acknowledged: false }; } generateAlertMessage(type, agentId, value, threshold) { const agentDesc = `${agentId.type}:${agentId.instance}`; switch (type) { case 'cpu_high': return `High CPU usage on ${agentDesc}: ${value.toFixed(1)}% (threshold: ${threshold}%)`; case 'memory_high': return `High memory usage on ${agentDesc}: ${value.toFixed(1)}% (threshold: ${threshold}%)`; case 'error_rate_high': return `High error rate on ${agentDesc}: ${value.toFixed(1)}% (threshold: ${threshold}%)`; case 'response_time_high': return `High response time on ${agentDesc}: ${value.toFixed(0)}ms (threshold: ${threshold}ms)`; default: return `Performance issue on ${agentDesc}`; } } processAlert(alert) { this.alerts.set(alert.id, alert); this.emit('alert:triggered', alert); // Auto-scaling consideration if (alert.severity === 'CRITICAL') { this.considerScaling(alert.agentId.type); } } performMonitoringCycle() { // Check for unresponsive agents this.checkUnresponsiveAgents(); // Perform auto-scaling analysis this.performScalingAnalysis(); // Cleanup old data this.cleanupOldData(); } checkUnresponsiveAgents() { const now = Date.now(); const threshold = this.thresholds.healthCheckInterval * 2; for (const [agentKey, load] of this.agentLoads.entries()) { const lastUpdate = new Date(load.lastUpdate).getTime(); if (now - lastUpdate > threshold) { const alert = { id: crypto.randomUUID(), type: 'agent_unresponsive', severity: 'CRITICAL', agentId: load.agentId, message: `Agent ${load.agentId.type}:${load.agentId.instance} is unresponsive`, metrics: {}, // Placeholder threshold: threshold, actualValue: now - lastUpdate, timestamp: new Date().toISOString(), acknowledged: false }; this.processAlert(alert); } } } performScalingAnalysis() { const agentsByType = this.groupAgentsByType(); for (const [agentType, agents] of agentsByType.entries()) { const decision = this.analyzeScalingNeed(agentType, agents); if (decision.action !== 'none') { this.processScalingDecision(decision); } } } groupAgentsByType() { const groups = new Map(); for (const load of this.agentLoads.values()) { const group = groups.get(load.agentId.type) || []; group.push(load); groups.set(load.agentId.type, group); } return groups; } analyzeScalingNeed(agentType, agents) { if (agents.length === 0) { return { agentType, action: 'none', reason: 'No agents found', targetInstances: 0, currentInstances: 0, confidence: 0, timestamp: new Date().toISOString() }; } // Check scaling cooldown const lastScaling = this.lastScalingDecision.get(agentType) || 0; const now = Date.now(); if (now - lastScaling < this.scalingCooldown) { return { agentType, action: 'none', reason: 'Scaling cooldown active', targetInstances: agents.length, currentInstances: agents.length, confidence: 0, timestamp: new Date().toISOString() }; } // Calculate aggregate metrics const avgLoad = agents.reduce((sum, agent) => sum + agent.currentLoad, 0) / agents.length; const maxLoad = Math.max(...agents.map(agent => agent.currentLoad)); const avgQueueDepth = agents.reduce((sum, agent) => sum + agent.queueDepth, 0) / agents.length; const avgErrorRate = agents.reduce((sum, agent) => sum + agent.errorRate, 0) / agents.length; // Scale up conditions if (avgLoad > 0.8 || maxLoad > 0.9 || avgQueueDepth > 50) { return { agentType, action: 'scale_up', reason: `High load detected: avg=${avgLoad.toFixed(2)}, max=${maxLoad.toFixed(2)}, queue=${avgQueueDepth.toFixed(0)}`, targetInstances: Math.min(agents.length + 1, 10), // Max 10 instances currentInstances: agents.length, confidence: 0.8, timestamp: new Date().toISOString() }; } // Scale down conditions if (agents.length > 1 && avgLoad < 0.3 && maxLoad < 0.5 && avgQueueDepth < 5) { return { agentType, action: 'scale_down', reason: `Low load detected: avg=${avgLoad.toFixed(2)}, max=${maxLoad.toFixed(2)}, queue=${avgQueueDepth.toFixed(0)}`, targetInstances: Math.max(agents.length - 1, 1), // Min 1 instance currentInstances: agents.length, confidence: 0.7, timestamp: new Date().toISOString() }; } // Redistribution conditions if (agents.length > 1 && (maxLoad - Math.min(...agents.map(a => a.currentLoad))) > 0.4) { return { agentType, action: 'redistribute', reason: 'Load imbalance detected between agent instances', targetInstances: agents.length, currentInstances: agents.length, confidence: 0.6, timestamp: new Date().toISOString() }; } return { agentType, action: 'none', reason: 'No scaling action needed', targetInstances: agents.length, currentInstances: agents.length, confidence: 0.5, timestamp: new Date().toISOString() }; } processScalingDecision(decision) { this.lastScalingDecision.set(decision.agentType, Date.now()); this.emit('scaling:decision', decision); // In a real implementation, this would trigger actual scaling actions console.log(`Scaling decision for ${decision.agentType}: ${decision.action} (${decision.reason})`); } considerScaling(agentType) { const agents = Array.from(this.agentLoads.values()) .filter(load => load.agentId.type === agentType); const decision = this.analyzeScalingNeed(agentType, agents); if (decision.action === 'scale_up' && decision.confidence > 0.7) { this.processScalingDecision(decision); } } cleanupOldData() { const cutoff = Date.now() - this.retentionPeriod; // Cleanup metrics for (const [agentKey, metrics] of this.metrics.entries()) { const filteredMetrics = metrics.filter(m => new Date(m.timestamp).getTime() > cutoff); if (filteredMetrics.length === 0) { this.metrics.delete(agentKey); } else { this.metrics.set(agentKey, filteredMetrics); } } // Cleanup alerts (keep for 7 days) const alertCutoff = Date.now() - (7 * 24 * 60 * 60 * 1000); for (const [alertId, alert] of this.alerts.entries()) { if (new Date(alert.timestamp).getTime() < alertCutoff) { this.alerts.delete(alertId); } } } getAgentMetrics(agentId, timeRange) { const agentKey = this.getAgentKey(agentId); const metrics = this.metrics.get(agentKey) || []; if (!timeRange) { return metrics; } const start = new Date(timeRange.start).getTime(); const end = new Date(timeRange.end).getTime(); return metrics.filter(m => { const timestamp = new Date(m.timestamp).getTime(); return timestamp >= start && timestamp <= end; }); } getSystemOverview() { const agentsByType = this.groupAgentsByType(); const overview = { timestamp: new Date().toISOString(), totalAgents: this.agentLoads.size, agentTypes: agentsByType.size, systemLoad: { avgCpuUsage: 0, avgMemoryUsage: 0, totalOperations: 0, totalErrors: 0 }, alerts: { total: this.alerts.size, critical: 0, warnings: 0, unacknowledged: 0 }, byType: new Map() }; // Calculate system-wide metrics let totalCpu = 0; let totalMemory = 0; let totalOperations = 0; let totalErrors = 0; let agentCount = 0; for (const [agentType, agents] of agentsByType.entries()) { let typeCpu = 0; let typeMemory = 0; let typeOperations = 0; let typeErrors = 0; for (const agent of agents) { const recentMetrics = this.getRecentMetrics(agent.agentId); if (recentMetrics) { typeCpu += recentMetrics.cpu.usage; typeMemory += recentMetrics.memory.percentage; typeOperations += recentMetrics.operations.completed; typeErrors += recentMetrics.operations.failed; agentCount++; } } overview.byType.set(agentType, { instanceCount: agents.length, avgLoad: agents.reduce((sum, a) => sum + a.currentLoad, 0) / agents.length, avgCpuUsage: typeCpu / agents.length, avgMemoryUsage: typeMemory / agents.length, totalOperations: typeOperations, totalErrors: typeErrors, errorRate: typeErrors / (typeOperations + typeErrors + 1) * 100 }); totalCpu += typeCpu; totalMemory += typeMemory; totalOperations += typeOperations; totalErrors += typeErrors; } if (agentCount > 0) { overview.systemLoad.avgCpuUsage = totalCpu / agentCount; overview.systemLoad.avgMemoryUsage = totalMemory / agentCount; } overview.systemLoad.totalOperations = totalOperations; overview.systemLoad.totalErrors = totalErrors; // Alert statistics for (const alert of this.alerts.values()) { if (alert.severity === 'CRITICAL') { overview.alerts.critical++; } else { overview.alerts.warnings++; } if (!alert.acknowledged) { overview.alerts.unacknowledged++; } } return overview; } getRecentMetrics(agentId) { const agentKey = this.getAgentKey(agentId); const metrics = this.metrics.get(agentKey) || []; if (metrics.length === 0) return null; // Return the most recent metrics return metrics[metrics.length - 1]; } acknowledgeAlert(alertId) { const alert = this.alerts.get(alertId); if (!alert) return false; alert.acknowledged = true; this.emit('alert:acknowledged', { alertId, timestamp: new Date().toISOString() }); return true; } getActiveAlerts() { return Array.from(this.alerts.values()) .filter(alert => !alert.acknowledged) .sort((a, b) => { // Sort by severity, then by timestamp if (a.severity !== b.severity) { return a.severity === 'CRITICAL' ? -1 : 1; } return new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(); }); } getAgentKey(agentId) { return `${agentId.type}:${agentId.instance}:${agentId.uuid}`; } updateThresholds(newThresholds) { Object.assign(this.thresholds, newThresholds); this.emit('thresholds:updated', { newThresholds: this.thresholds, timestamp: new Date().toISOString() }); } getThresholds() { return { ...this.thresholds }; } destroy() { clearInterval(this.monitoringInterval); this.metrics.clear(); this.agentLoads.clear(); this.alerts.clear(); this.removeAllListeners(); } /** * Get performance monitor metrics */ getMetrics() { const overview = this.getSystemOverview(); return { totalMetrics: Array.from(this.metrics.values()).reduce((sum, metrics) => sum + metrics.length, 0), activeAgents: this.agentLoads.size, alertsCount: this.alerts.size, systemLoad: overview.systemLoad }; } } /** * Basic AgentMetrics implementation */ export class SimpleAgentMetrics { counters = new Map(); gauges = new Map(); histograms = new Map(); timers = new Map(); incrementCounter(name, value = 1) { const current = this.counters.get(name) || 0; this.counters.set(name, current + value); } recordGauge(name, value) { this.gauges.set(name, value); } recordHistogram(name, value) { const values = this.histograms.get(name) || []; values.push(value); this.histograms.set(name, values); } recordTimer(name, duration) { const values = this.timers.get(name) || []; values.push(duration); this.timers.set(name, values); } getCounters() { return new Map(this.counters); } getGauges() { return new Map(this.gauges); } getHistograms() { return new Map(this.histograms); } getTimers() { return new Map(this.timers); } clear() { this.counters.clear(); this.gauges.clear(); this.histograms.clear(); this.timers.clear(); } } //# sourceMappingURL=performance-monitor.js.map