UNPKG

fortify2-js

Version:

MOST POWERFUL JavaScript Security Library! Military-grade cryptography + 19 enhanced object methods + quantum-resistant algorithms + perfect TypeScript support. More powerful than Lodash with built-in security.

539 lines (536 loc) 20.3 kB
import { EventEmitter } from 'events'; import * as os from 'os'; import { func } from '../../../../utils/fortified-function/index.js'; import { logger } from '../../server/utils/Logger.js'; /** * FortifyJS Metrics Collector * Advanced performance metrics collection and analysis for cluster monitoring */ /** * Comprehensive metrics collector with real-time monitoring and analytics */ class MetricsCollector extends EventEmitter { constructor(config) { super(); this.workerMetrics = new Map(); this.metricsHistory = new Map(); this.isCollecting = false; this.startTime = Date.now(); this.config = config; // Initialize cluster metrics this.clusterMetrics = this.initializeClusterMetrics(); // Setup metrics collection this.setupMetricsCollection(); } /** * Initialize cluster metrics structure */ initializeClusterMetrics() { return { totalWorkers: 0, activeWorkers: 0, deadWorkers: 0, restarting: 0, totalRequests: 0, requestsPerSecond: 0, averageResponseTime: 0, totalErrors: 0, errorRate: 0, resources: { totalCpu: 0, totalMemory: 0, peakCpu: 0, peakMemory: 0, availableMemory: os.totalmem(), totalConnections: 0, }, workers: [], uptime: 0, loadBalance: { strategy: this.config.loadBalancing?.strategy || "round-robin", distribution: {}, efficiency: 100, }, healthOverall: { status: "healthy", healthyWorkers: 0, unhealthyWorkers: 0, averageHealthScore: 100, }, }; } /** * Setup metrics collection with intelligent scheduling */ setupMetricsCollection() { const enabled = this.config.monitoring?.enabled !== false; const interval = this.config.monitoring?.metricsInterval || 60000; // 1 minute if (enabled) { this.startCollection(interval); } } /** * Start metrics collection */ startCollection(interval = 60000) { if (this.isCollecting) return; this.isCollecting = true; const fortifiedCollector = func(async () => { await this.collectMetrics(); }, { ultraFast: "maximum", auditLog: false, // High frequency operation timeout: 30000, errorHandling: "graceful", }); this.collectionInterval = setInterval(() => { fortifiedCollector().catch((error) => { console.error("Metrics collection error:", error); }); }, interval); logger.info("cluster", `Metrics collection started (interval: ${interval}ms)`); } /** * Stop metrics collection */ stopCollection() { if (this.collectionInterval) { clearInterval(this.collectionInterval); this.collectionInterval = undefined; } this.isCollecting = false; logger.info("cluster", "Metrics collection stopped"); } /** * Collect comprehensive metrics from all workers */ async collectMetrics() { try { // Update cluster uptime this.clusterMetrics.uptime = Date.now() - this.startTime; // Collect system metrics await this.collectSystemMetrics(); // Collect worker metrics await this.collectWorkerMetrics(); // Calculate aggregated metrics this.calculateAggregatedMetrics(); // Update health status this.updateHealthStatus(); // Store metrics snapshot this.storeMetricsSnapshot(); // Emit metrics update event this.emit("metrics:updated", this.clusterMetrics); } catch (error) { console.error("Error collecting metrics:", error); } } /** * Collect system-level metrics */ async collectSystemMetrics() { // CPU metrics os.cpus(); const totalCpu = this.calculateCpuUsage(); this.clusterMetrics.resources.totalCpu = totalCpu; if (totalCpu > this.clusterMetrics.resources.peakCpu) { this.clusterMetrics.resources.peakCpu = totalCpu; } // Memory metrics const totalMemory = os.totalmem(); const freeMemory = os.freemem(); const usedMemory = totalMemory - freeMemory; this.clusterMetrics.resources.totalMemory = usedMemory; this.clusterMetrics.resources.availableMemory = freeMemory; if (usedMemory > this.clusterMetrics.resources.peakMemory) { this.clusterMetrics.resources.peakMemory = usedMemory; } } /** * Collect metrics from individual workers */ async collectWorkerMetrics() { const workers = Array.from(this.workerMetrics.values()); // Update worker counts this.clusterMetrics.totalWorkers = workers.length; this.clusterMetrics.activeWorkers = workers.filter((w) => w.health.status === "healthy" || w.health.status === "warning").length; this.clusterMetrics.deadWorkers = workers.filter((w) => w.health.status === "dead").length; // Update workers array in cluster metrics this.clusterMetrics.workers = [...workers]; // Collect custom metrics if configured if (this.config.monitoring?.customMetrics) { await this.collectCustomMetrics(workers); } } /** * Collect custom metrics defined in configuration */ async collectCustomMetrics(workers) { const customMetrics = this.config.monitoring?.customMetrics; if (!customMetrics) return; for (const worker of workers) { for (const [metricName, metricFunction] of Object.entries(customMetrics)) { try { const value = metricFunction(worker.workerId); // Store custom metric (could be extended to store in worker metrics) logger.info("cluster", `Custom metric ${metricName} for ${worker.workerId}: ${value}`); } catch (error) { console.error(`Error collecting custom metric ${metricName}:`, error); } } } } /** * Calculate aggregated metrics across all workers */ calculateAggregatedMetrics() { const workers = this.clusterMetrics.workers; if (workers.length === 0) return; // Calculate totals this.clusterMetrics.totalRequests = workers.reduce((sum, w) => sum + w.requests.total, 0); this.clusterMetrics.totalErrors = workers.reduce((sum, w) => sum + w.requests.errors, 0); this.clusterMetrics.resources.totalConnections = workers.reduce((sum, w) => sum + w.network.connections, 0); // Calculate averages const totalResponseTime = workers.reduce((sum, w) => sum + w.requests.averageResponseTime, 0); this.clusterMetrics.averageResponseTime = totalResponseTime / workers.length; const totalRequestsPerSecond = workers.reduce((sum, w) => sum + w.requests.perSecond, 0); this.clusterMetrics.requestsPerSecond = totalRequestsPerSecond; // Calculate error rate this.clusterMetrics.errorRate = this.clusterMetrics.totalRequests > 0 ? (this.clusterMetrics.totalErrors / this.clusterMetrics.totalRequests) * 100 : 0; // Update load balance distribution this.updateLoadBalanceMetrics(workers); } /** * Update load balance metrics */ updateLoadBalanceMetrics(workers) { const distribution = {}; workers.forEach((worker) => { distribution[worker.workerId] = worker.requests.total; }); this.clusterMetrics.loadBalance.distribution = distribution; // Calculate load balance efficiency const requestCounts = Object.values(distribution); if (requestCounts.length > 0) { const average = requestCounts.reduce((sum, count) => sum + count, 0) / requestCounts.length; const variance = requestCounts.reduce((sum, count) => sum + Math.pow(count - average, 2), 0) / requestCounts.length; const standardDeviation = Math.sqrt(variance); const coefficientOfVariation = average > 0 ? standardDeviation / average : 0; this.clusterMetrics.loadBalance.efficiency = Math.max(0, 100 - coefficientOfVariation * 100); } } /** * Update overall health status */ updateHealthStatus() { const workers = this.clusterMetrics.workers; if (workers.length === 0) { this.clusterMetrics.healthOverall.status = "critical"; return; } const healthyWorkers = workers.filter((w) => w.health.status === "healthy").length; const unhealthyWorkers = workers.length - healthyWorkers; this.clusterMetrics.healthOverall.healthyWorkers = healthyWorkers; this.clusterMetrics.healthOverall.unhealthyWorkers = unhealthyWorkers; // Calculate average health score const totalHealthScore = workers.reduce((sum, w) => sum + w.health.healthScore, 0); this.clusterMetrics.healthOverall.averageHealthScore = totalHealthScore / workers.length; // Determine overall status const healthyPercentage = (healthyWorkers / workers.length) * 100; if (healthyPercentage >= 80) { this.clusterMetrics.healthOverall.status = "healthy"; } else if (healthyPercentage >= 50) { this.clusterMetrics.healthOverall.status = "degraded"; } else { this.clusterMetrics.healthOverall.status = "critical"; } } /** * Store metrics snapshot for historical analysis */ storeMetricsSnapshot() { const snapshot = { timestamp: new Date(), cpu: this.clusterMetrics.resources.totalCpu, memory: this.clusterMetrics.resources.totalMemory, requests: this.clusterMetrics.totalRequests, errors: this.clusterMetrics.totalErrors, responseTime: this.clusterMetrics.averageResponseTime, }; // Store cluster-level snapshot if (!this.metricsHistory.has("cluster")) { this.metricsHistory.set("cluster", []); } const clusterHistory = this.metricsHistory.get("cluster"); clusterHistory.push(snapshot); // Keep only last 1000 snapshots if (clusterHistory.length > 1000) { clusterHistory.splice(0, clusterHistory.length - 1000); } } /** * Calculate CPU usage percentage */ calculateCpuUsage() { const cpus = os.cpus(); let totalIdle = 0; let totalTick = 0; cpus.forEach((cpu) => { for (const type in cpu.times) { totalTick += cpu.times[type]; } totalIdle += cpu.times.idle; }); const idle = totalIdle / cpus.length; const total = totalTick / cpus.length; return 100 - ~~((100 * idle) / total); } /** * Update worker metrics */ updateWorkerMetrics(workerId, metrics) { const existing = this.workerMetrics.get(workerId); if (existing) { // Merge with existing metrics const updated = { ...existing, ...metrics }; this.workerMetrics.set(workerId, updated); } else { // Create new metrics entry const newMetrics = { workerId, pid: 0, uptime: 0, restarts: 0, cpu: { usage: 0, average: 0, peak: 0 }, memory: { usage: 0, peak: 0, percentage: 0, heapUsed: 0, heapTotal: 0, external: 0, }, network: { connections: 0, bytesReceived: 0, bytesSent: 0, connectionsPerSecond: 0, }, requests: { total: 0, perSecond: 0, errors: 0, averageResponseTime: 0, p95ResponseTime: 0, p99ResponseTime: 0, activeRequests: 0, queuedRequests: 0, }, health: { status: "healthy", lastCheck: new Date(), consecutiveFailures: 0, healthScore: 100, }, gc: { collections: 0, timeSpent: 0, averageTime: 0 }, eventLoop: { delay: 0, utilization: 0 }, ...metrics, }; this.workerMetrics.set(workerId, newMetrics); } } /** * Remove worker metrics */ removeWorkerMetrics(workerId) { this.workerMetrics.delete(workerId); this.metricsHistory.delete(workerId); } /** * Get current cluster metrics */ getClusterMetrics() { return { ...this.clusterMetrics }; } /** * Get worker metrics by ID */ getWorkerMetrics(workerId) { return this.workerMetrics.get(workerId) || null; } /** * Get aggregated metrics summary */ getAggregatedMetrics() { return { cpu: this.clusterMetrics.resources.totalCpu, memory: this.clusterMetrics.resources.totalMemory, requests: this.clusterMetrics.totalRequests, errors: this.clusterMetrics.totalErrors, responseTime: this.clusterMetrics.averageResponseTime, }; } /** * Export metrics in different formats */ async exportMetrics(format = "json") { switch (format) { case "json": return JSON.stringify(this.clusterMetrics, null, 2); case "prometheus": return this.exportPrometheusFormat(); case "csv": return this.exportCSVFormat(); default: return JSON.stringify(this.clusterMetrics, null, 2); } } /** * Export metrics in Prometheus format */ exportPrometheusFormat() { const metrics = this.clusterMetrics; const lines = []; lines.push(`# HELP cluster_workers_total Total number of workers`); lines.push(`# TYPE cluster_workers_total gauge`); lines.push(`cluster_workers_total ${metrics.totalWorkers}`); lines.push(`# HELP cluster_requests_total Total number of requests`); lines.push(`# TYPE cluster_requests_total counter`); lines.push(`cluster_requests_total ${metrics.totalRequests}`); lines.push(`# HELP cluster_errors_total Total number of errors`); lines.push(`# TYPE cluster_errors_total counter`); lines.push(`cluster_errors_total ${metrics.totalErrors}`); lines.push(`# HELP cluster_response_time_avg Average response time in milliseconds`); lines.push(`# TYPE cluster_response_time_avg gauge`); lines.push(`cluster_response_time_avg ${metrics.averageResponseTime}`); return lines.join("\n"); } /** * Export metrics in CSV format */ exportCSVFormat() { const metrics = this.clusterMetrics; const headers = [ "timestamp", "total_workers", "active_workers", "total_requests", "total_errors", "error_rate", "avg_response_time", "cpu_usage", "memory_usage", ]; const row = [ new Date().toISOString(), metrics.totalWorkers, metrics.activeWorkers, metrics.totalRequests, metrics.totalErrors, metrics.errorRate.toFixed(2), metrics.averageResponseTime.toFixed(2), metrics.resources.totalCpu.toFixed(2), metrics.resources.totalMemory, ]; return headers.join(",") + "\n" + row.join(","); } /** * Get metrics history */ getMetricsHistory(workerId) { const key = workerId || "cluster"; return this.metricsHistory.get(key) || []; } /** * Set worker manager reference for integration */ setWorkerManager(workerManager) { this.workerManager = workerManager; } /** * Restore historical metrics data from persistent storage */ restoreHistoricalData(historicalData) { // Restore cluster-level historical data if (!this.metricsHistory.has("cluster")) { this.metricsHistory.set("cluster", []); } const clusterHistory = this.metricsHistory.get("cluster"); // Convert historical data to MetricsSnapshot format const snapshots = historicalData.map((data) => ({ timestamp: new Date(data.timestamp), cpu: data.cpu, memory: data.memory, requests: data.requests, errors: data.errors, responseTime: data.responseTime, })); // Merge with existing data, avoiding duplicates const existingTimestamps = new Set(clusterHistory.map((s) => s.timestamp.getTime())); const newSnapshots = snapshots.filter((s) => !existingTimestamps.has(s.timestamp.getTime())); clusterHistory.push(...newSnapshots); // Sort by timestamp and keep only last 1000 entries clusterHistory.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); if (clusterHistory.length > 1000) { clusterHistory.splice(0, clusterHistory.length - 1000); } logger.info("cluster", `Restored ${newSnapshots.length} historical metrics snapshots`); } /** * Restore worker-specific historical data */ restoreWorkerHistoricalData(workerHistory) { for (const [workerId, workerData] of workerHistory.entries()) { if (!this.metricsHistory.has(workerId)) { this.metricsHistory.set(workerId, []); } const workerHistoryArray = this.metricsHistory.get(workerId); // Convert to MetricsSnapshot format const snapshots = workerData.map((data) => ({ timestamp: new Date(data.timestamp), cpu: data.cpu, memory: data.memory, requests: data.requests, errors: data.errors, responseTime: data.responseTime, })); // Merge with existing data, avoiding duplicates const existingTimestamps = new Set(workerHistoryArray.map((s) => s.timestamp.getTime())); const newSnapshots = snapshots.filter((s) => !existingTimestamps.has(s.timestamp.getTime())); workerHistoryArray.push(...newSnapshots); // Sort by timestamp and keep only last 1000 entries workerHistoryArray.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); if (workerHistoryArray.length > 1000) { workerHistoryArray.splice(0, workerHistoryArray.length - 1000); } } logger.info("cluster", `Restored historical data for ${workerHistory.size} workers`); } /** * Export current metrics for persistence */ exportMetricsForPersistence() { return { clusterMetrics: this.clusterMetrics, historicalData: this.metricsHistory.get("cluster") || [], workerHistory: new Map(Array.from(this.metricsHistory.entries()).filter(([key]) => key !== "cluster")), }; } } export { MetricsCollector }; //# sourceMappingURL=MetricsCollector.js.map