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
JavaScript
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