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.

717 lines (714 loc) 29.7 kB
import { EventEmitter } from 'events'; import * as os from 'os'; import pidusage from 'pidusage'; import { createSecurityError, ErrorSeverity, ErrorType } from '../../../../utils/errorHandler.js'; import { func } from '../../../../utils/fortified-function/index.js'; import { logger } from '../../server/utils/Logger.js'; /** * FortifyJS Auto Scaler * Intelligent auto-scaling with predictive analytics and resource optimization */ /** * Advanced auto-scaler with machine learning-inspired decision making */ class AutoScaler extends EventEmitter { constructor(config, errorLogger) { super(); this.scalingHistory = []; this.lastScalingAction = Date.now(); this.isScaling = false; this.currentWorkerCount = 0; this.scalingTimings = new Map(); // Track scaling operation timings this.config = config; this.errorLogger = errorLogger; // Initialize auto-scaler configuration this.autoScaler = { enabled: config.autoScaling?.enabled !== false, minWorkers: config.autoScaling?.minWorkers || 1, maxWorkers: config.autoScaling?.maxWorkers || 8, cooldownPeriod: config.autoScaling?.cooldownPeriod || 300000, // 5 minutes lastScalingAction: new Date(), pendingActions: [], }; this.setupAutoScaling(); } /** * Setup auto-scaling with intelligent monitoring */ setupAutoScaling() { if (!this.autoScaler.enabled) return; // Start scaling evaluation loop this.startScalingEvaluation(); // Emit initialization event instead of logging this.emit("autoscaler:initialized", { minWorkers: this.autoScaler.minWorkers, maxWorkers: this.autoScaler.maxWorkers, cooldownPeriod: this.autoScaler.cooldownPeriod, }); } /** * Start scaling evaluation loop */ startScalingEvaluation() { const evaluationInterval = 30000; // 30 seconds const fortifiedEvaluator = func(async () => { await this.evaluateScaling(); }, { ultraFast: "maximum", auditLog: true, timeout: 15000, errorHandling: "graceful", }); this.scalingInterval = setInterval(() => { fortifiedEvaluator().catch((error) => { const securityError = createSecurityError(`Auto-scaling evaluation failed: ${error.message}`, ErrorType.INTERNAL, ErrorSeverity.MEDIUM, "AUTOSCALING_ERROR", { operation: "auto_scaling_evaluation" }); this.errorLogger.logError(securityError); }); }, evaluationInterval); } /** * Stop auto-scaling evaluation */ stopScaling() { if (this.scalingInterval) { clearInterval(this.scalingInterval); this.scalingInterval = undefined; } this.emit("autoscaler:stopped"); } /** * Evaluate scaling needs based on current metrics */ async evaluateScaling() { if (this.isScaling || !this.autoScaler.enabled) return; // Check cooldown period if (this.isInCooldownPeriod()) return; try { // Get current metrics from integrated MetricsCollector const metrics = await this.getCurrentMetrics(); // Make scaling decision const decision = this.makeScalingDecision(metrics); // Execute scaling if needed if (decision.action !== "no-action") { await this.executeScaling(decision); } } catch (error) { const securityError = createSecurityError(`Scaling evaluation error: ${error.message}`, ErrorType.INTERNAL, ErrorSeverity.MEDIUM, "SCALING_EVALUATION_ERROR", { operation: "scaling_evaluation" }); this.errorLogger.logError(securityError); } } /** * Make intelligent scaling decision based on metrics */ makeScalingDecision(metrics) { const scaleUpThreshold = this.config.autoScaling?.scaleUpThreshold; const scaleDownThreshold = this.config.autoScaling?.scaleDownThreshold; let action = "no-action"; let reason = "No scaling needed"; let confidence = 0; let targetWorkers = this.currentWorkerCount; // Evaluate scale-up conditions const scaleUpReasons = []; let scaleUpScore = 0; if (scaleUpThreshold?.cpu && metrics.cpu > scaleUpThreshold.cpu) { scaleUpReasons.push(`CPU usage (${metrics.cpu}%) > threshold (${scaleUpThreshold.cpu}%)`); scaleUpScore += 30; } if (scaleUpThreshold?.memory && metrics.memory > scaleUpThreshold.memory) { scaleUpReasons.push(`Memory usage (${metrics.memory}%) > threshold (${scaleUpThreshold.memory}%)`); scaleUpScore += 25; } if (scaleUpThreshold?.responseTime && metrics.responseTime > scaleUpThreshold.responseTime) { scaleUpReasons.push(`Response time (${metrics.responseTime}ms) > threshold (${scaleUpThreshold.responseTime}ms)`); scaleUpScore += 35; } if (scaleUpThreshold?.queueLength && metrics.queueLength > scaleUpThreshold.queueLength) { scaleUpReasons.push(`Queue length (${metrics.queueLength}) > threshold (${scaleUpThreshold.queueLength})`); scaleUpScore += 40; } // Evaluate scale-down conditions const scaleDownReasons = []; let scaleDownScore = 0; if (scaleDownThreshold?.cpu && metrics.cpu < scaleDownThreshold.cpu) { scaleDownReasons.push(`CPU usage (${metrics.cpu}%) < threshold (${scaleDownThreshold.cpu}%)`); scaleDownScore += 20; } if (scaleDownThreshold?.memory && metrics.memory < scaleDownThreshold.memory) { scaleDownReasons.push(`Memory usage (${metrics.memory}%) < threshold (${scaleDownThreshold.memory}%)`); scaleDownScore += 15; } if (scaleDownThreshold?.idleTime && metrics.idleTime > scaleDownThreshold.idleTime) { scaleDownReasons.push(`Idle time (${metrics.idleTime}min) > threshold (${scaleDownThreshold.idleTime}min)`); scaleDownScore += 30; } // Make decision based on scores and historical data if (scaleUpScore >= 50 && this.currentWorkerCount < this.autoScaler.maxWorkers) { action = "scale-up"; reason = scaleUpReasons.join(", "); confidence = Math.min(100, scaleUpScore); targetWorkers = Math.min(this.autoScaler.maxWorkers, this.currentWorkerCount + (this.config.autoScaling?.scaleStep || 1)); } else if (scaleDownScore >= 40 && this.currentWorkerCount > this.autoScaler.minWorkers) { action = "scale-down"; reason = scaleDownReasons.join(", "); confidence = Math.min(100, scaleDownScore); targetWorkers = Math.max(this.autoScaler.minWorkers, this.currentWorkerCount - (this.config.autoScaling?.scaleStep || 1)); } // Apply historical learning confidence = this.adjustConfidenceBasedOnHistory(action, confidence); return { action, targetWorkers, reason, confidence, metrics: { cpu: metrics.cpu, memory: metrics.memory, responseTime: metrics.responseTime, queueLength: metrics.queueLength, }, }; } /** * Adjust confidence based on historical scaling success */ adjustConfidenceBasedOnHistory(action, baseConfidence) { const recentHistory = this.scalingHistory .filter((h) => h.timestamp.getTime() > Date.now() - 3600000) // Last hour .filter((h) => h.action === action); if (recentHistory.length === 0) return baseConfidence; const successRate = recentHistory.filter((h) => h.success).length / recentHistory.length; // Adjust confidence based on success rate if (successRate > 0.8) { return Math.min(100, baseConfidence * 1.1); // Boost confidence } else if (successRate < 0.5) { return Math.max(0, baseConfidence * 0.8); // Reduce confidence } return baseConfidence; } /** * Execute scaling decision */ async executeScaling(decision) { if (decision.confidence < 60) { this.emit("scaling:skipped", { reason: "Low confidence", confidence: decision.confidence, decision: decision.action, }); return; } this.isScaling = true; const startWorkers = this.currentWorkerCount; const scalingId = `${decision.action}_${Date.now()}`; const scalingStartTime = Date.now(); let success = false; try { this.emit("scaling:executing", { action: decision.action, fromWorkers: startWorkers, toWorkers: decision.targetWorkers, reason: decision.reason, confidence: decision.confidence, }); this.emit("scaling:triggered", decision.reason, startWorkers, decision.targetWorkers); if (decision.action === "scale-up") { await this.scaleUp(decision.targetWorkers - startWorkers); } else { await this.scaleDown(startWorkers - decision.targetWorkers); } success = true; this.currentWorkerCount = decision.targetWorkers; this.lastScalingAction = Date.now(); this.autoScaler.lastScalingAction = new Date(); // Record scaling timing const scalingDuration = Date.now() - scalingStartTime; this.scalingTimings.set(scalingId, scalingDuration); this.emit("scaling:completed", { action: decision.action, fromWorkers: startWorkers, toWorkers: decision.targetWorkers, success: true, duration: scalingDuration, }); } catch (error) { const securityError = createSecurityError(`Scaling execution failed: ${error.message}`, ErrorType.INTERNAL, ErrorSeverity.HIGH, "SCALING_EXECUTION_ERROR", { operation: "scaling_execution" }); this.errorLogger.logError(securityError); } finally { // Record scaling history this.recordScalingHistory({ timestamp: new Date(), action: decision.action, fromWorkers: startWorkers, toWorkers: decision.targetWorkers, reason: decision.reason, success, }); this.isScaling = false; } } /** * Scale up by adding workers using real cluster forking */ async scaleUp(count = 1) { const targetCount = Math.min(this.autoScaler.maxWorkers, this.currentWorkerCount + count); const actualCount = targetCount - this.currentWorkerCount; if (actualCount <= 0) { throw new Error("Cannot scale up: already at maximum workers"); } this.emit("scaling:starting", { action: "scale-up", count: actualCount, targetCount, }); try { // Fork new workers using real cluster API with runtime compatibility const clusterModule = require("cluster"); const startPromises = []; // Check if we're in a runtime that supports cluster.fork (Node.js vs Bun) if (typeof clusterModule.fork !== "function") { // Fallback for Bun or other runtimes that don't support cluster.fork logger.debug("other", `Runtime doesn't support cluster.fork, simulating ${actualCount} workers`); for (let i = 0; i < actualCount; i++) { const mockWorkerId = `simulated_worker_${Date.now()}_${i}`; const mockPid = process.pid + i + 1; this.emit("worker:online", { workerId: mockWorkerId, pid: mockPid, }); this.emit("worker:started", mockWorkerId, mockPid); } // Update worker count for simulation this.currentWorkerCount = targetCount; this.emit("cluster:scaled", "scale-up", targetCount); this.emit("scaling:success", { action: "scale-up", targetCount, message: `Successfully simulated scaling up to ${targetCount} workers`, }); return; } for (let i = 0; i < actualCount; i++) { const startPromise = new Promise((resolve, reject) => { const worker = clusterModule.fork(); const timeout = setTimeout(() => { reject(new Error(`Worker startup timeout after 10 seconds`)); }, 10000); worker.once("online", () => { clearTimeout(timeout); this.emit("worker:online", { workerId: worker.id, pid: worker.process.pid, }); resolve(); }); worker.once("error", (error) => { clearTimeout(timeout); reject(error); }); }); startPromises.push(startPromise); // Stagger worker starts to avoid overwhelming the system if (i < actualCount - 1) { await new Promise((resolve) => setTimeout(resolve, 500)); } } // Wait for all workers to start await Promise.all(startPromises); this.emit("cluster:scaled", "scale-up", targetCount); this.emit("scaling:success", { action: "scale-up", targetCount, message: `Successfully scaled up to ${targetCount} workers`, }); } catch (error) { throw new Error(`Failed to scale up workers: ${error.message}`); } } /** * Scale down by gracefully removing workers */ async scaleDown(count = 1) { const targetCount = Math.max(this.autoScaler.minWorkers, this.currentWorkerCount - count); const actualCount = this.currentWorkerCount - targetCount; if (actualCount <= 0) { throw new Error("Cannot scale down: already at minimum workers"); } this.emit("scaling:starting", { action: "scale-down", count: actualCount, targetCount, }); try { const clusterModule = require("cluster"); // Check if we're in a runtime that supports cluster workers if (!clusterModule.workers || typeof clusterModule.workers !== "object") { // Fallback for Bun or other runtimes logger.debug("other", `Runtime doesn't support cluster.workers, simulating scale down of ${actualCount} workers`); // Update worker count for simulation this.currentWorkerCount = targetCount; this.emit("cluster:scaled", "scale-down", targetCount); logger.debug("other", `Successfully simulated scaling down to ${targetCount} workers`); return; } const workers = Object.values(clusterModule.workers || {}); const workersToStop = workers.slice(0, actualCount); const stopPromises = []; for (const worker of workersToStop) { if (worker && typeof worker === "object" && "disconnect" in worker) { const stopPromise = new Promise((resolve, reject) => { const timeout = setTimeout(() => { // Force kill if graceful shutdown takes too long if ("kill" in worker && typeof worker.kill === "function") { worker.kill("SIGKILL"); } resolve(); }, 10000); // 10 second timeout const workerObj = worker; workerObj.once("disconnect", () => { clearTimeout(timeout); this.emit("worker:disconnected", { workerId: workerObj.id, graceful: true, }); resolve(); }); workerObj.once("error", (error) => { clearTimeout(timeout); logger.warn("other", `Worker ${workerObj.id} error during shutdown:`, error.message); resolve(); // Continue with shutdown even on error }); // Start graceful shutdown if ("disconnect" in workerObj && typeof workerObj.disconnect === "function") { workerObj.disconnect(); } }); stopPromises.push(stopPromise); } } // Wait for all workers to stop await Promise.all(stopPromises); this.emit("cluster:scaled", "scale-down", targetCount); logger.debug("other", `Successfully scaled down to ${targetCount} workers`); } catch (error) { throw new Error(`Failed to scale down workers: ${error.message}`); } } /** * Perform auto-scaling evaluation */ async autoScale() { await this.evaluateScaling(); } /** * Get optimal worker count based on current conditions */ async getOptimalWorkerCount() { const metrics = await this.getCurrentMetrics(); const decision = this.makeScalingDecision(metrics); return decision.targetWorkers; } /** * Check if in cooldown period */ isInCooldownPeriod() { return (Date.now() - this.lastScalingAction < this.autoScaler.cooldownPeriod); } /** * Get current system metrics using MetricsCollector integration */ async getCurrentMetrics() { try { // First priority: Use MetricsCollector for comprehensive metrics if (this.metricsCollector) { try { const clusterMetrics = this.metricsCollector.getClusterMetrics(); const aggregatedMetrics = this.metricsCollector.getAggregatedMetrics(); return { cpu: aggregatedMetrics.cpu, memory: aggregatedMetrics.memory, responseTime: aggregatedMetrics.responseTime, queueLength: this.estimateQueueLength(aggregatedMetrics.cpu, clusterMetrics.activeWorkers), idleTime: Math.max(0, (100 - aggregatedMetrics.cpu) / 10), systemLoad: clusterMetrics.resources.totalCpu, systemMemory: (clusterMetrics.resources.totalMemory / clusterMetrics.resources.availableMemory) * 100, workerCount: clusterMetrics.activeWorkers, totalRequests: clusterMetrics.totalRequests, errorRate: clusterMetrics.errorRate, }; } catch (error) { // Fall through to WorkerManager integration } } // Second priority: Use WorkerManager for worker-specific metrics if (this.workerManager) { try { const workers = this.workerManager.getActiveWorkers(); const workerCount = workers.length; if (workerCount > 0) { let totalWorkerCpu = 0; let totalWorkerMemory = 0; let totalResponseTime = 0; let totalQueueLength = 0; workers.forEach((worker) => { totalWorkerCpu += worker.cpu?.usage || 0; totalWorkerMemory += worker.memory?.percentage || 0; totalResponseTime += worker.requests?.averageResponseTime || 0; totalQueueLength += worker.requests?.queuedRequests || 0; }); const avgWorkerCpu = totalWorkerCpu / workerCount; const avgWorkerMemory = totalWorkerMemory / workerCount; const avgResponseTime = totalResponseTime / workerCount; const avgQueueLength = totalQueueLength / workerCount; return { cpu: Math.min(100, avgWorkerCpu), memory: Math.min(100, avgWorkerMemory), responseTime: avgResponseTime, queueLength: avgQueueLength, idleTime: Math.max(0, (100 - avgWorkerCpu) / 10), systemLoad: avgWorkerCpu, systemMemory: avgWorkerMemory, workerCount: workerCount, }; } } catch (error) { // Fall through to direct cluster monitoring } } // Third priority: Direct cluster monitoring with pidusage const clusterModule = require("cluster"); if (clusterModule.workers) { const workerPromises = Object.values(clusterModule.workers).map(async (worker) => { if (worker && worker.process && worker.process.pid && !worker.isDead()) { try { const stats = await pidusage(worker.process.pid); return { cpu: stats.cpu, memory: stats.memory }; } catch (error) { return { cpu: 0, memory: 0 }; } } return { cpu: 0, memory: 0 }; }); const workerStats = await Promise.all(workerPromises); let totalWorkerCpu = 0; let totalWorkerMemory = 0; let workerCount = 0; workerStats.forEach((stats) => { totalWorkerCpu += stats.cpu; totalWorkerMemory += stats.memory / (1024 * 1024); // Convert to MB if (stats.cpu > 0 || stats.memory > 0) workerCount++; }); if (workerCount > 0) { const avgWorkerCpu = totalWorkerCpu / workerCount; const avgWorkerMemory = totalWorkerMemory / workerCount; return { cpu: Math.min(100, avgWorkerCpu), memory: Math.min(100, avgWorkerMemory), responseTime: this.estimateResponseTime(avgWorkerCpu, workerCount), queueLength: this.estimateQueueLength(avgWorkerCpu, workerCount), idleTime: Math.max(0, (100 - avgWorkerCpu) / 10), systemLoad: avgWorkerCpu, systemMemory: avgWorkerMemory, workerCount: workerCount, }; } } // Final fallback: System-wide metrics const totalMem = os.totalmem(); const freeMem = os.freemem(); const memoryUsage = ((totalMem - freeMem) / totalMem) * 100; const loadAvg = os.loadavg()[0]; const cpuCount = os.cpus().length; const cpuUsage = (loadAvg / cpuCount) * 100; return { cpu: Math.min(100, cpuUsage), memory: memoryUsage, responseTime: cpuUsage * 10, queueLength: Math.floor(cpuUsage / 10), idleTime: Math.max(0, (100 - cpuUsage) / 10), systemLoad: cpuUsage, systemMemory: memoryUsage, workerCount: this.currentWorkerCount, }; } catch (error) { // Emergency fallback return { cpu: 50, memory: 50, responseTime: 100, queueLength: 5, idleTime: 5, systemLoad: 50, systemMemory: 50, workerCount: this.currentWorkerCount, }; } } /** * Estimate response time based on CPU usage and worker count */ estimateResponseTime(cpuUsage, workerCount) { // Base response time increases with CPU usage let baseTime = 50; // 50ms base if (cpuUsage > 80) { baseTime += (cpuUsage - 80) * 20; // Add 20ms per % over 80% } else if (cpuUsage > 60) { baseTime += (cpuUsage - 60) * 5; // Add 5ms per % over 60% } // Adjust for worker count - fewer workers = higher response time if (workerCount > 0) { const optimalWorkers = os.cpus().length; if (workerCount < optimalWorkers) { baseTime *= optimalWorkers / workerCount; } } return Math.min(5000, baseTime); // Cap at 5 seconds } /** * Estimate queue length based on CPU usage and worker count */ estimateQueueLength(cpuUsage, workerCount) { if (cpuUsage < 50) return 0; // Queue builds up when CPU is high let queueLength = Math.floor((cpuUsage - 50) / 10); // Adjust for worker count if (workerCount > 0) { const optimalWorkers = os.cpus().length; if (workerCount < optimalWorkers) { queueLength *= optimalWorkers / workerCount; } } return Math.min(100, queueLength); // Cap at 100 } /** * Record scaling history for learning */ recordScalingHistory(entry) { this.scalingHistory.push(entry); // Keep only last 100 entries if (this.scalingHistory.length > 100) { this.scalingHistory.splice(0, this.scalingHistory.length - 100); } } /** * Update current worker count */ updateWorkerCount(count) { this.currentWorkerCount = count; } /** * Get scaling configuration */ getConfiguration() { return { ...this.autoScaler }; } /** * Get scaling history */ getScalingHistory() { return [...this.scalingHistory]; } /** * Get scaling statistics */ getScalingStats() { const total = this.scalingHistory.length; const successful = this.scalingHistory.filter((h) => h.success).length; const failed = total - successful; const successRate = total > 0 ? (successful / total) * 100 : 0; return { totalScalingActions: total, successfulActions: successful, failedActions: failed, successRate, averageScalingTime: this.calculateAverageScalingTime(), lastScalingAction: this.autoScaler.lastScalingAction, }; } /** * Enable auto-scaling */ enable() { this.autoScaler.enabled = true; this.startScalingEvaluation(); logger.debug("other", "Auto-scaling enabled"); } /** * Disable auto-scaling */ disable() { this.autoScaler.enabled = false; this.stopScaling(); logger.debug("other", "Auto-scaling disabled"); } /** * Check if auto-scaling is enabled */ isEnabled() { return this.autoScaler.enabled; } /** * Set worker manager reference for integration */ setWorkerManager(workerManager) { this.workerManager = workerManager; } /** * Set metrics collector reference for integration */ setMetricsCollector(metricsCollector) { this.metricsCollector = metricsCollector; } /** * Calculate average scaling time from recorded timings */ calculateAverageScalingTime() { if (this.scalingTimings.size === 0) return 0; const timings = Array.from(this.scalingTimings.values()); const total = timings.reduce((sum, time) => sum + time, 0); return total / timings.length; } } export { AutoScaler }; //# sourceMappingURL=AutoScaler.js.map