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.
1,124 lines (1,118 loc) • 43.9 kB
JavaScript
'use strict';
var events = require('events');
var crypto = require('crypto');
var Logger = require('../../server/utils/Logger.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var crypto__namespace = /*#__PURE__*/_interopNamespaceDefault(crypto);
/**
* FortifyJS Load Balancer
* Intelligent load balancing with adaptive strategies, performance optimization,
* and advanced health-aware routing
*/
/**
* Advanced load balancer with multiple strategies and intelligent routing
*/
class LoadBalancer extends events.EventEmitter {
constructor(config) {
super();
this.connectionCounts = new Map();
this.requestCounts = new Map();
this.responseTimesHistory = new Map();
this.performanceMetrics = new Map();
this.healthScores = new Map();
this.circuitBreakers = new Map();
this.strategies = new Map();
this.lastSelectedWorker = "";
this.roundRobinIndex = 0;
this.adaptiveWeights = new Map();
this.metricsWindow = 300000; // 5 minutes
// Performance tracking
this.requestLatencies = new Map();
this.errorCounts = new Map();
this.throughputCounters = new Map();
// Historical trends tracking
this.historicalTrends = {
requestsPerMinute: [],
averageResponseTimes: [],
errorRates: [],
};
this.config = config;
// Initialize load balancer configuration
this.loadBalancer = {
strategy: config.loadBalancing?.strategy || "adaptive",
weights: new Map(),
connections: new Map(),
lastSelected: "",
selector: this.createSelector(),
};
this.initializeStrategies();
this.setupLoadBalancing();
// Setup periodic cleanup and recalculation
this.cleanupInterval = setInterval(() => {
this.performPeriodicMaintenance();
}, 30000); // Every 30 seconds
}
/**
* Initialize all available load balancing strategies
*/
initializeStrategies() {
this.strategies.set("round-robin", {
name: "round-robin",
selector: (workers) => this.roundRobinSelection(workers),
healthAware: false,
performanceAware: false,
});
this.strategies.set("least-connections", {
name: "least-connections",
selector: (workers) => this.leastConnectionsSelection(workers),
healthAware: true,
performanceAware: false,
});
this.strategies.set("ip-hash", {
name: "ip-hash",
selector: (workers, request) => this.ipHashSelection(workers, request),
healthAware: true,
performanceAware: false,
});
this.strategies.set("weighted", {
name: "weighted",
selector: (workers) => this.weightedSelection(workers),
healthAware: true,
performanceAware: false,
});
this.strategies.set("least-response-time", {
name: "least-response-time",
selector: (workers) => this.leastResponseTimeSelection(workers),
healthAware: true,
performanceAware: true,
});
this.strategies.set("adaptive", {
name: "adaptive",
selector: (workers, request) => this.adaptiveSelection(workers, request),
healthAware: true,
performanceAware: true,
});
this.strategies.set("resource-based", {
name: "resource-based",
selector: (workers) => this.resourceBasedSelection(workers),
healthAware: true,
performanceAware: true,
});
}
/**
* Setup load balancing with intelligent defaults
*/
setupLoadBalancing() {
// Initialize weights if provided
if (this.config.loadBalancing?.weights) {
this.config.loadBalancing.weights.forEach((weight, index) => {
this.loadBalancer.weights.set(`worker_${index}`, weight);
this.adaptiveWeights.set(`worker_${index}`, weight);
});
}
this.setupConnectionTracking();
}
/**
* Setup connection tracking for load balancing decisions
*/
setupConnectionTracking() {
// More intelligent cleanup with sliding window
this.cleanupMetrics();
}
/**
* Perform periodic maintenance tasks
*/
performPeriodicMaintenance() {
this.cleanupMetrics();
this.updateHealthScores();
this.updateAdaptiveWeights();
this.checkCircuitBreakers();
this.updateHistoricalTrends();
this.emitMetricsUpdate();
}
/**
* Clean up old metrics data
*/
cleanupMetrics() {
Date.now() - this.metricsWindow;
// Clean response times with sliding window
this.responseTimesHistory.forEach((times, workerId) => {
if (times.length > 1000) {
// Increased history size
this.responseTimesHistory.set(workerId, times.slice(-500));
}
});
// Clean latency data
this.requestLatencies.forEach((latencies, workerId) => {
if (latencies.length > 1000) {
this.requestLatencies.set(workerId, latencies.slice(-500));
}
});
// Reset hourly counters
if (Date.now() % 3600000 < 30000) {
// Every hour, within 30s window
this.connectionCounts.clear();
this.requestCounts.clear();
this.errorCounts.clear();
}
}
/**
* Update health scores based on recent performance
*/
updateHealthScores() {
this.performanceMetrics.forEach((metrics, workerId) => {
const health = this.healthScores.get(workerId) || {
status: "healthy",
score: 100,
lastCheck: Date.now(),
consecutiveFailures: 0,
};
// Calculate health score based on multiple factors
let score = 100;
// Response time factor (0-40 points)
if (metrics.avgResponseTime > 0) {
score -= Math.min(40, (metrics.avgResponseTime / 1000) * 10);
}
// Error rate factor (0-30 points)
score -= metrics.errorRate * 30;
// Resource usage factor (0-20 points)
const resourcePenalty = ((metrics.cpuUsage + metrics.memoryUsage) / 200) * 20;
score -= resourcePenalty;
// Throughput bonus (0-10 points)
if (metrics.throughput > 0) {
score += Math.min(10, metrics.throughput / 10);
}
health.score = Math.max(0, Math.min(100, score));
health.lastCheck = Date.now();
// Update status based on score
if (health.score >= 80)
health.status = "healthy";
else if (health.score >= 60)
health.status = "warning";
else if (health.score >= 30)
health.status = "critical";
else
health.status = "down";
this.healthScores.set(workerId, health);
});
}
/**
* Update adaptive weights based on performance
*/
updateAdaptiveWeights() {
const totalHealthScore = Array.from(this.healthScores.values()).reduce((sum, health) => sum + health.score, 0);
if (totalHealthScore === 0)
return;
this.healthScores.forEach((health, workerId) => {
// Calculate adaptive weight based on health score and performance
const baseWeight = this.loadBalancer.weights.get(workerId) || 1;
const healthWeight = (health.score / 100) * baseWeight;
// Apply performance multiplier
const metrics = this.performanceMetrics.get(workerId);
let performanceMultiplier = 1;
if (metrics) {
// Boost weight for low response time and high throughput
performanceMultiplier = Math.max(0.1, (1 - metrics.errorRate) *
Math.min(2, 1000 / Math.max(1, metrics.avgResponseTime)) *
Math.min(1.5, metrics.throughput / 100));
}
this.adaptiveWeights.set(workerId, healthWeight * performanceMultiplier);
});
}
/**
* Check and update circuit breaker states
*/
checkCircuitBreakers() {
const now = Date.now();
this.circuitBreakers.forEach((breaker, workerId) => {
if (breaker.state === "open" && now >= breaker.nextAttempt) {
breaker.state = "half-open";
this.emit("circuit-breaker:half-open", workerId);
}
});
}
/**
* Create selector function based on strategy
*/
createSelector() {
return (workers, request) => {
return this.selectWorker(workers, request);
};
}
/**
* Select optimal worker based on configured strategy
*/
selectWorker(workers, request) {
if (workers.length === 0) {
throw new Error("No workers available for load balancing");
}
// Filter workers based on circuit breaker state
let availableWorkers = workers.filter((worker) => {
const breaker = this.circuitBreakers.get(worker.workerId);
return !breaker || breaker.state !== "open";
});
// If no workers available due to circuit breakers, allow one half-open attempt
if (availableWorkers.length === 0) {
const halfOpenWorkers = workers.filter((worker) => {
const breaker = this.circuitBreakers.get(worker.workerId);
return breaker && breaker.state === "half-open";
});
availableWorkers =
halfOpenWorkers.length > 0
? [halfOpenWorkers[0]]
: [workers[0]];
}
// Apply health filtering for health-aware strategies
const strategy = this.strategies.get(this.loadBalancer.strategy);
if (strategy?.healthAware) {
const healthyWorkers = availableWorkers.filter((worker) => {
const health = this.healthScores.get(worker.workerId);
return !health || health.status !== "down";
});
if (healthyWorkers.length > 0) {
availableWorkers = healthyWorkers;
}
}
const selectedWorkerId = strategy?.selector(availableWorkers, request) ||
this.roundRobinSelection(availableWorkers);
this.updateSelectionTracking(selectedWorkerId);
this.loadBalancer.lastSelected = selectedWorkerId;
return selectedWorkerId;
}
/**
* Least response time selection strategy
*/
leastResponseTimeSelection(workers) {
let bestWorker = workers[0];
let bestScore = Infinity;
for (const worker of workers) {
const avgResponseTime = this.getAverageResponseTime(worker.workerId);
const activeRequests = worker.requests?.activeRequests || 0;
// Score based on response time and current load
const score = avgResponseTime * (1 + activeRequests * 0.1);
if (score < bestScore) {
bestScore = score;
bestWorker = worker;
}
}
return bestWorker.workerId;
}
/**
* Adaptive selection strategy that combines multiple factors
*/
adaptiveSelection(workers, request) {
let bestWorker = workers[0];
let bestScore = -Infinity;
for (const worker of workers) {
const health = this.healthScores.get(worker.workerId);
const metrics = this.performanceMetrics.get(worker.workerId);
const activeRequests = worker.requests?.activeRequests || 0;
const adaptiveWeight = this.adaptiveWeights.get(worker.workerId) || 1;
// Calculate composite score
let score = 0;
// Health score (0-40 points)
if (health) {
score += (health.score / 100) * 40;
}
else {
score += 20; // Default moderate score for unknown health
}
// Performance score (0-30 points)
if (metrics) {
const responseTimeFactor = Math.max(0, 30 - metrics.avgResponseTime / 100);
const errorRateFactor = (1 - metrics.errorRate) * 15;
const throughputFactor = Math.min(15, metrics.throughput / 10);
score +=
responseTimeFactor + errorRateFactor + throughputFactor;
}
// Load factor (0-20 points)
const maxRequests = Math.max(1, ...workers.map((w) => w.requests?.activeRequests || 1));
const loadFactor = (1 - activeRequests / maxRequests) * 20;
score += loadFactor;
// Adaptive weight factor (0-10 points)
score +=
(adaptiveWeight /
Math.max(...Array.from(this.adaptiveWeights.values()))) *
10;
// Add some randomness to prevent thundering herd (±2 points)
score += (Math.random() - 0.5) * 4;
if (score > bestScore) {
bestScore = score;
bestWorker = worker;
}
}
return bestWorker.workerId;
}
/**
* Resource-based selection considering CPU and memory usage
*/
resourceBasedSelection(workers) {
let bestWorker = workers[0];
let bestScore = Infinity;
for (const worker of workers) {
const metrics = this.performanceMetrics.get(worker.workerId);
if (!metrics)
continue;
// Score based on resource usage (lower is better)
const resourceScore = (metrics.cpuUsage + metrics.memoryUsage) / 2;
const loadScore = (worker.requests?.activeRequests || 0) * 10;
const totalScore = resourceScore + loadScore;
if (totalScore < bestScore) {
bestScore = totalScore;
bestWorker = worker;
}
}
return bestWorker.workerId;
}
/**
* round-robin selection with health awareness
*/
roundRobinSelection(workers) {
this.roundRobinIndex;
let attempts = 0;
do {
const worker = workers[this.roundRobinIndex % workers.length];
this.roundRobinIndex = (this.roundRobinIndex + 1) % workers.length;
const health = this.healthScores.get(worker.workerId);
if (!health || health.status !== "down") {
return worker.workerId;
}
attempts++;
} while (attempts < workers.length);
// Fallback to first worker if all are down
return workers[0].workerId;
}
/**
* least connections selection with performance weighting
*/
leastConnectionsSelection(workers) {
let minScore = Infinity;
let selectedWorker = workers[0];
for (const worker of workers) {
const connections = this.connectionCounts.get(worker.workerId) || 0;
const activeRequests = worker.requests?.activeRequests || 0;
const avgResponseTime = this.getAverageResponseTime(worker.workerId);
// Weighted score considering connections, active requests, and response time
const score = connections + activeRequests + avgResponseTime / 100;
if (score < minScore) {
minScore = score;
selectedWorker = worker;
}
}
return selectedWorker.workerId;
}
/**
* IP hash selection with consistent hashing
*/
ipHashSelection(workers, request) {
if (!request?.ip) {
return this.adaptiveSelection(workers, request);
}
const sessionKey = this.config.loadBalancing?.sessionAffinityKey;
const hashInput = sessionKey && request[sessionKey]
? request[sessionKey]
: request.ip;
// Use consistent hashing with virtual nodes
const virtualNodes = 150; // Number of virtual nodes per worker
const ring = [];
workers.forEach((worker) => {
for (let i = 0; i < virtualNodes; i++) {
const virtualKey = `${worker.workerId}:${i}`;
const hash = this.hashString(virtualKey);
ring.push({ hash, workerId: worker.workerId });
}
});
ring.sort((a, b) => a.hash - b.hash);
const requestHash = this.hashString(hashInput);
const node = ring.find((n) => n.hash >= requestHash) || ring[0];
return node.workerId;
}
/**
* weighted selection with dynamic weights
*/
weightedSelection(workers) {
const useAdaptive = this.loadBalancer.strategy === "adaptive";
const weightMap = useAdaptive
? this.adaptiveWeights
: this.loadBalancer.weights;
const totalWeight = workers.reduce((sum, worker) => {
const weight = weightMap.get(worker.workerId) || 1;
return sum + Math.max(0.1, weight); // Minimum weight to prevent zero
}, 0);
if (totalWeight === 0) {
return this.roundRobinSelection(workers);
}
const random = Math.random() * totalWeight;
let currentWeight = 0;
for (const worker of workers) {
const weight = Math.max(0.1, weightMap.get(worker.workerId) || 1);
currentWeight += weight;
if (random <= currentWeight) {
return worker.workerId;
}
}
return workers[0].workerId;
}
/**
* Hash string to number for consistent hashing
*/
hashString(str) {
const hash = crypto__namespace.createHash("md5").update(str).digest("hex");
return parseInt(hash.substring(0, 8), 16);
}
/**
* Update selection tracking for analytics
*/
updateSelectionTracking(workerId) {
const currentConnections = this.connectionCounts.get(workerId) || 0;
this.connectionCounts.set(workerId, currentConnections + 1);
const currentRequests = this.requestCounts.get(workerId) || 0;
this.requestCounts.set(workerId, currentRequests + 1);
this.loadBalancer.connections.set(workerId, currentConnections + 1);
// Update throughput counter
const now = Date.now();
const throughputData = this.throughputCounters.get(workerId) || {
count: 0,
timestamp: now,
};
if (now - throughputData.timestamp > 1000) {
// Reset every second
const throughput = throughputData.count /
((now - throughputData.timestamp) / 1000);
this.updatePerformanceMetric(workerId, "throughput", throughput);
this.throughputCounters.set(workerId, { count: 1, timestamp: now });
}
else {
throughputData.count++;
}
}
/**
* Update performance metrics for a worker
*/
updatePerformanceMetric(workerId, metric, value) {
const current = this.performanceMetrics.get(workerId) || {
avgResponseTime: 0,
throughput: 0,
errorRate: 0,
cpuUsage: 0,
memoryUsage: 0,
activeConnections: 0,
};
current[metric] = value;
this.performanceMetrics.set(workerId, current);
}
/**
* Record response time with analytics
*/
recordResponseTime(workerId, responseTime) {
// Record in history
if (!this.responseTimesHistory.has(workerId)) {
this.responseTimesHistory.set(workerId, []);
}
const times = this.responseTimesHistory.get(workerId);
times.push(responseTime);
// Record in latencies for more detailed analysis
if (!this.requestLatencies.has(workerId)) {
this.requestLatencies.set(workerId, []);
}
const latencies = this.requestLatencies.get(workerId);
latencies.push(responseTime);
// Update average response time
const avgResponseTime = times.reduce((sum, time) => sum + time, 0) / times.length;
this.updatePerformanceMetric(workerId, "avgResponseTime", avgResponseTime);
// Cleanup old data
if (times.length > 1000) {
times.splice(0, times.length - 500);
}
if (latencies.length > 1000) {
latencies.splice(0, latencies.length - 500);
}
}
/**
* Record error for circuit breaker and error rate tracking
*/
recordError(workerId) {
// Update error count
const currentErrors = this.errorCounts.get(workerId) || 0;
this.errorCounts.set(workerId, currentErrors + 1);
// Update circuit breaker
let breaker = this.circuitBreakers.get(workerId);
if (!breaker) {
breaker = {
failures: 0,
lastFailure: 0,
state: "closed",
nextAttempt: 0,
};
this.circuitBreakers.set(workerId, breaker);
}
breaker.failures++;
breaker.lastFailure = Date.now();
// Open circuit breaker if failure threshold exceeded
const failureThreshold = this.config.loadBalancing?.circuitBreakerThreshold || 5;
if (breaker.failures >= failureThreshold &&
breaker.state === "closed") {
breaker.state = "open";
breaker.nextAttempt =
Date.now() +
(this.config.loadBalancing?.circuitBreakerTimeout || 60000);
this.emit("circuit-breaker:opened", workerId);
}
// Calculate error rate
const totalRequests = this.requestCounts.get(workerId) || 1;
const errorRate = Math.min(1, currentErrors / totalRequests);
this.updatePerformanceMetric(workerId, "errorRate", errorRate);
}
/**
* Record successful request (for circuit breaker recovery)
*/
recordSuccess(workerId) {
const breaker = this.circuitBreakers.get(workerId);
if (breaker) {
if (breaker.state === "half-open") {
breaker.state = "closed";
breaker.failures = 0;
this.emit("circuit-breaker:closed", workerId);
}
else if (breaker.state === "closed") {
breaker.failures = Math.max(0, breaker.failures - 1);
}
}
}
/**
* Get average response time for a worker
*/
getAverageResponseTime(workerId) {
const times = this.responseTimesHistory.get(workerId);
if (!times || times.length === 0)
return 0;
return times.reduce((sum, time) => sum + time, 0) / times.length;
}
/**
* Get response time percentiles
*/
getResponseTimePercentiles(workerId, percentiles = [50, 90, 95, 99]) {
const times = this.responseTimesHistory.get(workerId);
if (!times || times.length === 0)
return {};
const sorted = [...times].sort((a, b) => a - b);
const result = {};
percentiles.forEach((p) => {
const index = Math.ceil((p / 100) * sorted.length) - 1;
result[p] = sorted[Math.max(0, index)];
});
return result;
}
/**
* Update load balancing strategy with options
*/
async updateStrategy(strategy, options) {
if (!this.strategies.has(strategy)) {
throw new Error(`Unknown load balancing strategy: ${strategy}`);
}
this.loadBalancer.strategy = strategy;
if (options?.weights && Array.isArray(options.weights)) {
this.loadBalancer.weights.clear();
options.weights.forEach((weight, index) => {
this.loadBalancer.weights.set(`worker_${index}`, weight);
});
}
if (options?.circuitBreakerThreshold) {
this.config.loadBalancing = this.config.loadBalancing || {};
this.config.loadBalancing.circuitBreakerThreshold =
options.circuitBreakerThreshold;
}
this.loadBalancer.selector = this.createSelector();
this.emit("loadbalancer:updated", strategy, this.getWeights());
}
/**
* Get distribution statistics
*/
getDistributionStats() {
const totalRequests = Array.from(this.requestCounts.values()).reduce((sum, count) => sum + count, 0);
const distribution = {};
this.requestCounts.forEach((count, workerId) => {
distribution[workerId] = count;
});
const averageResponseTimes = {};
const healthScores = {};
const errorRates = {};
const circuitBreakerStates = {};
const responseTimePercentiles = {};
this.responseTimesHistory.forEach((_, workerId) => {
averageResponseTimes[workerId] =
this.getAverageResponseTime(workerId);
responseTimePercentiles[workerId] =
this.getResponseTimePercentiles(workerId);
});
this.healthScores.forEach((health, workerId) => {
healthScores[workerId] = health.score;
});
this.performanceMetrics.forEach((metrics, workerId) => {
errorRates[workerId] = metrics.errorRate;
});
this.circuitBreakers.forEach((breaker, workerId) => {
circuitBreakerStates[workerId] = breaker.state;
});
return {
strategy: this.loadBalancer.strategy,
totalRequests,
distribution,
efficiency: this.getLoadDistributionEfficiency(),
averageResponseTimes,
healthScores,
errorRates,
circuitBreakerStates,
responseTimePercentiles,
};
}
/**
* Get current load balance status with metrics
*/
getLoadBalanceStatus() {
const connections = {};
this.connectionCounts.forEach((count, workerId) => {
connections[workerId] = count;
});
const health = {};
this.healthScores.forEach((score, workerId) => {
health[workerId] = score;
});
const performance = {};
this.performanceMetrics.forEach((metrics, workerId) => {
performance[workerId] = metrics;
});
const adaptiveWeights = {};
this.adaptiveWeights.forEach((weight, workerId) => {
adaptiveWeights[workerId] = weight;
});
return { connections, health, performance, adaptiveWeights };
}
/**
* load distribution efficiency calculation
*/
getLoadDistributionEfficiency() {
const connections = Array.from(this.connectionCounts.values());
if (connections.length === 0)
return 100;
const total = connections.reduce((sum, count) => sum + count, 0);
const average = total / connections.length;
if (average === 0)
return 100;
// Calculate Gini coefficient for more accurate distribution measurement
const sortedConnections = connections.sort((a, b) => a - b);
let giniSum = 0;
sortedConnections.forEach((count, i) => {
giniSum += (2 * (i + 1) - connections.length - 1) * count;
});
const giniCoefficient = giniSum / (connections.length * total);
// Convert Gini coefficient to efficiency score (0-100, higher is better)
// Gini of 0 = perfect equality = 100% efficiency
// Gini of 1 = maximum inequality = 0% efficiency
return Math.max(0, 100 * (1 - Math.abs(giniCoefficient)));
}
/**
* Advanced redistribute load with strategy-specific optimizations
*/
async redistributeLoad() {
// Reset connection counts
this.connectionCounts.clear();
this.roundRobinIndex = 0;
// Reset circuit breakers for healthy redistribution
this.circuitBreakers.forEach((breaker, workerId) => {
if (breaker.state === "open") {
breaker.state = "half-open";
}
});
// Recalculate adaptive weights
this.updateAdaptiveWeights();
Logger.logger.info("cluster", `Load redistributed across workers using ${this.loadBalancer.strategy} strategy`);
this.emit("load:redistributed", {
strategy: this.loadBalancer.strategy,
timestamp: Date.now(),
efficiency: this.getLoadDistributionEfficiency(),
});
}
/**
* Get worker weights (static and adaptive)
*/
getWeights() {
const staticWeights = {};
const adaptiveWeights = {};
this.loadBalancer.weights.forEach((weight, workerId) => {
staticWeights[workerId] = weight;
});
this.adaptiveWeights.forEach((weight, workerId) => {
adaptiveWeights[workerId] = weight;
});
return { static: staticWeights, adaptive: adaptiveWeights };
}
/**
* Select worker using the configured strategy (public interface)
*/
selectWorkerForRequest(workers, request) {
return this.loadBalancer.selector(workers, request);
}
/**
* Get comprehensive load balancer configuration
*/
getConfiguration() {
const currentStrategy = this.strategies.get(this.loadBalancer.strategy);
return {
strategy: this.loadBalancer.strategy,
weights: new Map(this.loadBalancer.weights),
connections: new Map(this.loadBalancer.connections),
lastSelected: this.loadBalancer.lastSelected,
selector: this.loadBalancer.selector,
availableStrategies: Array.from(this.strategies.keys()),
healthAware: currentStrategy?.healthAware || false,
performanceAware: currentStrategy?.performanceAware || false,
circuitBreakerEnabled: this.circuitBreakers.size > 0,
metricsWindow: this.metricsWindow,
};
}
/**
* Emit periodic metrics update
*/
emitMetricsUpdate() {
this.emit("metrics:updated", {
timestamp: Date.now(),
distribution: this.getDistributionStats(),
status: this.getLoadBalanceStatus(),
efficiency: this.getLoadDistributionEfficiency(),
});
}
/**
* Get worker ranking based on current strategy
*/
getWorkerRanking(workers) {
const rankings = workers.map((worker) => {
const health = this.healthScores.get(worker.workerId) || null;
const performance = this.performanceMetrics.get(worker.workerId) || null;
let score = 0;
// Calculate score based on current strategy
switch (this.loadBalancer.strategy) {
case "adaptive":
score = this.calculateAdaptiveScore(worker, health, performance);
break;
case "least-response-time":
score = 1000 - this.getAverageResponseTime(worker.workerId);
break;
case "least-connections":
score =
1000 -
(this.connectionCounts.get(worker.workerId) || 0);
break;
case "resource-based":
if (performance) {
score =
200 -
(performance.cpuUsage + performance.memoryUsage);
}
break;
default:
score = Math.random() * 100; // Random for round-robin, etc.
}
return {
workerId: worker.workerId,
rank: 0, // Will be set after sorting
score,
health,
performance,
};
});
// Sort by score (descending) and assign ranks
rankings.sort((a, b) => b.score - a.score);
rankings.forEach((ranking, index) => {
ranking.rank = index + 1;
});
return rankings;
}
/**
* Calculate adaptive score for worker ranking
*/
calculateAdaptiveScore(worker, health, performance) {
let score = 0;
// Health contribution (40%)
if (health) {
score += health.score * 0.4;
}
else {
score += 50 * 0.4; // Default moderate score
}
// Performance contribution (40%)
if (performance) {
const responseTimeScore = Math.max(0, 100 - performance.avgResponseTime / 10);
const errorRateScore = (1 - performance.errorRate) * 100;
const throughputScore = Math.min(100, performance.throughput);
score +=
((responseTimeScore + errorRateScore + throughputScore) / 3) *
0.4;
}
// Load contribution (20%)
const activeRequests = worker.requests?.activeRequests || 0;
const loadScore = Math.max(0, 100 - activeRequests * 5);
score += loadScore * 0.2;
return Math.max(0, Math.min(100, score));
}
/**
* Enable/disable specific workers
*/
setWorkerEnabled(workerId, enabled) {
if (enabled) {
// Remove from circuit breaker or reset it
const breaker = this.circuitBreakers.get(workerId);
if (breaker) {
breaker.state = "closed";
breaker.failures = 0;
}
}
else {
// Force circuit breaker open
this.circuitBreakers.set(workerId, {
failures: 999,
lastFailure: Date.now(),
state: "open",
nextAttempt: Date.now() + 86400000, // 24 hours
});
}
this.emit("worker:toggled", { workerId, enabled });
}
/**
* Set IPC Manager reference for worker communication
*/
setIPCManager(ipcManager) {
this.ipcManager = ipcManager;
}
/**
* Perform worker health ping using IPC communication
*/
async performWorkerHealthPing(worker) {
const existingHealth = this.healthScores.get(worker.workerId) || {
status: "healthy",
score: 100,
lastCheck: Date.now(),
consecutiveFailures: 0,
};
try {
if (this.ipcManager) {
// Send health ping via IPC with timeout
const startTime = Date.now();
const response = await Promise.race([
this.ipcManager.sendRequest(worker.workerId, "health_ping", {}, 3000),
new Promise((_, reject) => setTimeout(() => reject(new Error("Health ping timeout")), 3000)),
]);
const responseTime = Date.now() - startTime;
if (response && response.status === "healthy") {
existingHealth.consecutiveFailures = 0;
existingHealth.score = Math.min(100, existingHealth.score + 2);
existingHealth.status =
responseTime < 1000 ? "healthy" : "warning";
}
else {
existingHealth.consecutiveFailures++;
existingHealth.score = Math.max(0, existingHealth.score - 10);
existingHealth.status = "warning";
}
}
else {
// Fallback to basic health check based on worker metrics
if (worker.health.status === "healthy") {
existingHealth.consecutiveFailures = 0;
existingHealth.score = Math.min(100, existingHealth.score + 1);
existingHealth.status = "healthy";
}
else {
existingHealth.consecutiveFailures++;
existingHealth.score = Math.max(0, existingHealth.score - 5);
existingHealth.status = "warning";
}
}
}
catch (error) {
// Health ping failed
existingHealth.consecutiveFailures++;
existingHealth.score = Math.max(0, existingHealth.score - 15);
existingHealth.status =
existingHealth.consecutiveFailures >= 3
? "critical"
: "warning";
}
return existingHealth;
}
/**
* Update historical trends data
*/
updateHistoricalTrends() {
const now = new Date();
// Calculate current metrics
const totalRequests = Array.from(this.requestCounts.values()).reduce((sum, count) => sum + count, 0);
const totalErrors = Array.from(this.errorCounts.values()).reduce((sum, count) => sum + count, 0);
const allResponseTimes = Array.from(this.responseTimesHistory.values()).flat();
const avgResponseTime = allResponseTimes.length > 0
? allResponseTimes.reduce((sum, time) => sum + time, 0) /
allResponseTimes.length
: 0;
const errorRate = totalRequests > 0 ? totalErrors / totalRequests : 0;
// Add to historical trends
this.historicalTrends.requestsPerMinute.push({
timestamp: now,
value: totalRequests,
});
this.historicalTrends.averageResponseTimes.push({
timestamp: now,
value: avgResponseTime,
});
this.historicalTrends.errorRates.push({
timestamp: now,
value: errorRate,
});
// Keep only last 1000 entries for each trend
const maxEntries = 1000;
if (this.historicalTrends.requestsPerMinute.length > maxEntries) {
this.historicalTrends.requestsPerMinute =
this.historicalTrends.requestsPerMinute.slice(-maxEntries);
}
if (this.historicalTrends.averageResponseTimes.length > maxEntries) {
this.historicalTrends.averageResponseTimes =
this.historicalTrends.averageResponseTimes.slice(-maxEntries);
}
if (this.historicalTrends.errorRates.length > maxEntries) {
this.historicalTrends.errorRates =
this.historicalTrends.errorRates.slice(-maxEntries);
}
}
/**
* Perform health check on all workers
*/
async performHealthCheck(workers) {
const healthResults = {};
for (const worker of workers) {
try {
// worker health ping implementation using IPC
const health = await this.performWorkerHealthPing(worker);
health.lastCheck = Date.now();
// Update health based on recent metrics
const performance = this.performanceMetrics.get(worker.workerId);
if (performance) {
if (performance.errorRate > 0.1) {
// 10% error rate threshold
health.consecutiveFailures++;
health.score = Math.max(0, health.score - 20);
}
else {
health.consecutiveFailures = 0;
health.score = Math.min(100, health.score + 5);
}
}
// Update status based on consecutive failures
if (health.consecutiveFailures >= 3) {
health.status = "critical";
}
else if (health.consecutiveFailures >= 1) {
health.status = "warning";
}
else {
health.status = "healthy";
}
this.healthScores.set(worker.workerId, health);
healthResults[worker.workerId] = health;
}
catch (error) {
const health = {
status: "down",
score: 0,
lastCheck: Date.now(),
consecutiveFailures: 999,
};
this.healthScores.set(worker.workerId, health);
healthResults[worker.workerId] = health;
}
}
this.emit("health-check:completed", healthResults);
return healthResults;
}
/**
* Get detailed analytics for monitoring dashboards
*/
getAnalytics() {
const totalRequests = Array.from(this.requestCounts.values()).reduce((sum, count) => sum + count, 0);
const totalErrors = Array.from(this.errorCounts.values()).reduce((sum, count) => sum + count, 0);
const allResponseTimes = Array.from(this.responseTimesHistory.values())
.flat()
.filter((time) => time > 0);
const averageResponseTime = allResponseTimes.length > 0
? allResponseTimes.reduce((sum, time) => sum + time, 0) /
allResponseTimes.length
: 0;
const errorRate = totalRequests > 0 ? totalErrors / totalRequests : 0;
const workers = Array.from(this.requestCounts.keys()).map((workerId) => {
const requests = this.requestCounts.get(workerId) || 0;
const responseTime = this.getAverageResponseTime(workerId);
const errors = this.errorCounts.get(workerId) || 0;
const workerErrorRate = requests > 0 ? errors / requests : 0;
const health = this.healthScores.get(workerId);
const performance = this.performanceMetrics.get(workerId);
return {
workerId,
requests,
responseTime,
errorRate: workerErrorRate,
healthScore: health?.score || 0,
status: health?.status || "unknown",
throughput: performance?.throughput || 0,
};
});
return {
overview: {
totalRequests,
averageResponseTime,
errorRate,
efficiency: this.getLoadDistributionEfficiency(),
},
workers,
trends: {
requestsPerMinute: this.historicalTrends.requestsPerMinute.map((t) => t.value),
averageResponseTimes: this.historicalTrends.averageResponseTimes.map((t) => t.value),
errorRates: this.historicalTrends.errorRates.map((t) => t.value),
},
};
}
/**
* Cleanup resources when shutting down
*/
destroy() {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
}
this.connectionCounts.clear();
this.requestCounts.clear();
this.responseTimesHistory.clear();
this.performanceMetrics.clear();
this.healthScores.clear();
this.circuitBreakers.clear();
this.requestLatencies.clear();
this.errorCounts.clear();
this.throughputCounters.clear();
this.adaptiveWeights.clear();
this.removeAllListeners();
Logger.logger.info("cluster", " Load Balancer destroyed");
}
}
exports.LoadBalancer = LoadBalancer;
//# sourceMappingURL=LoadBalancer.js.map