- Deleted event-utils.ts which contained deprecated Port80Handler and its subscribers. - Updated index.ts to remove the export of event-utils. - Refactored ConnectionManager to extend LifecycleComponent for better resource management. - Added BinaryHeap implementation for efficient priority queue operations. - Introduced EnhancedConnectionPool for managing pooled connections with lifecycle management. - Implemented LifecycleComponent to manage timers and event listeners automatically. - Added comprehensive tests for BinaryHeap and LifecycleComponent to ensure functionality.
35 KiB
SmartProxy Performance Optimization Plan
Executive Summary
This plan addresses critical performance issues in SmartProxy that impact scalability, responsiveness, and stability. The approach is phased, starting with critical event loop blockers and progressing to long-term architectural improvements.
Phase 1: Critical Issues (Week 1) ✅ COMPLETE
1.1 Eliminate Busy Wait Loop ✅
Issue: ts/proxies/nftables-proxy/nftables-proxy.ts:235-238
blocks the entire event loop
Solution:
// Create utility function in ts/core/utils/async-utils.ts
export async function delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Replace busy wait in nftables-proxy.ts
// OLD:
const waitUntil = Date.now() + retryDelayMs;
while (Date.now() < waitUntil) { }
// NEW:
await delay(retryDelayMs);
Implementation:
- Create
async-utils.ts
with common async utilities - Replace all synchronous sleeps with async delay
- Ensure all calling functions are async
1.2 Async Filesystem Operations ✅
Issue: Multiple synchronous filesystem operations blocking the event loop
Solution Architecture:
// Create ts/core/utils/fs-utils.ts
import * as plugins from '../../plugins.js';
export class AsyncFileSystem {
static async exists(path: string): Promise<boolean> {
try {
await plugins.fs.promises.access(path);
return true;
} catch {
return false;
}
}
static async ensureDir(path: string): Promise<void> {
await plugins.fs.promises.mkdir(path, { recursive: true });
}
static async readFile(path: string): Promise<string> {
return plugins.fs.promises.readFile(path, 'utf8');
}
static async writeFile(path: string, data: string): Promise<void> {
// Ensure directory exists
const dir = plugins.path.dirname(path);
await this.ensureDir(dir);
await plugins.fs.promises.writeFile(path, data);
}
static async remove(path: string): Promise<void> {
try {
await plugins.fs.promises.unlink(path);
} catch (error: any) {
if (error.code !== 'ENOENT') throw error;
}
}
static async readJSON<T>(path: string): Promise<T> {
const content = await this.readFile(path);
return JSON.parse(content);
}
static async writeJSON(path: string, data: any): Promise<void> {
await this.writeFile(path, JSON.stringify(data, null, 2));
}
}
Migration Strategy:
-
Certificate Manager (
ts/proxies/http-proxy/certificate-manager.ts
)// OLD: constructor(private options: IHttpProxyOptions) { if (!fs.existsSync(this.certDir)) { fs.mkdirSync(this.certDir, { recursive: true }); } } // NEW: private initialized = false; constructor(private options: IHttpProxyOptions) {} async initialize(): Promise<void> { if (this.initialized) return; await AsyncFileSystem.ensureDir(this.certDir); this.initialized = true; } async getCertificate(domain: string): Promise<{ cert: string; key: string } | null> { await this.initialize(); const certPath = path.join(this.certDir, `${domain}.crt`); const keyPath = path.join(this.certDir, `${domain}.key`); if (await AsyncFileSystem.exists(certPath) && await AsyncFileSystem.exists(keyPath)) { const [cert, key] = await Promise.all([ AsyncFileSystem.readFile(certPath), AsyncFileSystem.readFile(keyPath) ]); return { cert, key }; } return null; }
-
Certificate Store (
ts/proxies/smart-proxy/cert-store.ts
)// Convert all methods to async export class CertStore { constructor(private storePath: string) {} async init(): Promise<void> { await AsyncFileSystem.ensureDir(this.storePath); } async hasCertificate(domain: string): Promise<boolean> { const certPath = this.getCertificatePath(domain); return AsyncFileSystem.exists(certPath); } async getCertificate(domain: string): Promise<ICertificateInfo | null> { if (!await this.hasCertificate(domain)) return null; const metaPath = path.join(this.getCertificatePath(domain), 'meta.json'); return AsyncFileSystem.readJSON<ICertificateInfo>(metaPath); } }
-
NFTables Proxy (
ts/proxies/nftables-proxy/nftables-proxy.ts
)// Replace execSync with execAsync private async execNftCommand(command: string): Promise<string> { const maxRetries = 3; let lastError: Error | null = null; for (let i = 0; i < maxRetries; i++) { try { const { stdout } = await this.execAsync(command); return stdout; } catch (err: any) { lastError = err; if (i < maxRetries - 1) { await delay(this.retryDelayMs); } } } throw new NftExecutionError(`Failed after ${maxRetries} attempts: ${lastError?.message}`); }
Dependencies Between Phases
Critical Path
Phase 1.1 (Busy Wait) ─┐
├─> Phase 2.1 (Timer Management) ─> Phase 3.2 (Worker Threads)
Phase 1.2 (Async FS) ──┘ │
├─> Phase 4.1 (Monitoring)
Phase 2.2 (Connection Pool) ────────────────────────────┘
Phase Dependencies
- Phase 1 must complete before Phase 2 (foundation for async operations)
- Phase 2.1 enables proper cleanup for Phase 3.2 worker threads
- Phase 3 optimizations depend on stable async foundation
- Phase 4 monitoring requires all components to be instrumented
Phase 2: Resource Management (Week 2) 🔨 IN PROGRESS
2.1 Timer Lifecycle Management
Issue: Timers created without cleanup references causing memory leaks
Solution Pattern:
// Create base class in ts/core/utils/lifecycle-component.ts
export abstract class LifecycleComponent {
private timers: Set<NodeJS.Timeout> = new Set();
private listeners: Array<{ target: any, event: string, handler: Function }> = [];
protected isShuttingDown = false;
protected setInterval(handler: Function, timeout: number): NodeJS.Timeout {
const timer = setInterval(() => {
if (!this.isShuttingDown) {
handler();
}
}, timeout);
this.timers.add(timer);
return timer;
}
protected setTimeout(handler: Function, timeout: number): NodeJS.Timeout {
const timer = setTimeout(() => {
this.timers.delete(timer);
if (!this.isShuttingDown) {
handler();
}
}, timeout);
this.timers.add(timer);
return timer;
}
protected addEventListener(target: any, event: string, handler: Function): void {
target.on(event, handler);
this.listeners.push({ target, event, handler });
}
protected async cleanup(): Promise<void> {
this.isShuttingDown = true;
// Clear all timers
for (const timer of this.timers) {
clearInterval(timer);
clearTimeout(timer);
}
this.timers.clear();
// Remove all listeners
for (const { target, event, handler } of this.listeners) {
target.removeListener(event, handler);
}
this.listeners = [];
}
}
Implementation:
-
Extend LifecycleComponent in:
HttpProxy
SmartProxy
ConnectionManager
RequestHandler
SharedSecurityManager
-
Replace direct timer/listener usage with lifecycle methods
2.2 Connection Pool Enhancement
Issue: No backpressure mechanism and synchronous operations
Solution:
// First, implement efficient BinaryHeap for O(log n) operations
// Create ts/core/utils/binary-heap.ts
export class BinaryHeap<T> {
private heap: T[] = [];
constructor(
private compareFn: (a: T, b: T) => number,
private extractKey?: (item: T) => string
) {}
insert(item: T): void {
this.heap.push(item);
this.bubbleUp(this.heap.length - 1);
}
extract(): T | undefined {
if (this.heap.length === 0) return undefined;
if (this.heap.length === 1) return this.heap.pop();
const result = this.heap[0];
this.heap[0] = this.heap.pop()!;
this.bubbleDown(0);
return result;
}
extractIf(predicate: (item: T) => boolean): T | undefined {
const index = this.heap.findIndex(predicate);
if (index === -1) return undefined;
if (index === this.heap.length - 1) return this.heap.pop();
const result = this.heap[index];
this.heap[index] = this.heap.pop()!;
// Restore heap property
this.bubbleUp(index);
this.bubbleDown(index);
return result;
}
sizeFor(key: string): number {
if (!this.extractKey) return this.heap.length;
return this.heap.filter(item => this.extractKey!(item) === key).length;
}
private bubbleUp(index: number): void {
while (index > 0) {
const parentIndex = Math.floor((index - 1) / 2);
if (this.compareFn(this.heap[index], this.heap[parentIndex]) >= 0) break;
[this.heap[index], this.heap[parentIndex]] =
[this.heap[parentIndex], this.heap[index]];
index = parentIndex;
}
}
private bubbleDown(index: number): void {
while (true) {
let minIndex = index;
const leftChild = 2 * index + 1;
const rightChild = 2 * index + 2;
if (leftChild < this.heap.length &&
this.compareFn(this.heap[leftChild], this.heap[minIndex]) < 0) {
minIndex = leftChild;
}
if (rightChild < this.heap.length &&
this.compareFn(this.heap[rightChild], this.heap[minIndex]) < 0) {
minIndex = rightChild;
}
if (minIndex === index) break;
[this.heap[index], this.heap[minIndex]] =
[this.heap[minIndex], this.heap[index]];
index = minIndex;
}
}
}
// Enhanced connection pool with queue and heap
export class EnhancedConnectionPool extends LifecycleComponent {
private connectionQueue: Array<{
resolve: (socket: net.Socket) => void;
reject: (error: Error) => void;
host: string;
port: number;
timestamp: number;
}> = [];
private connectionHeap: BinaryHeap<IConnectionEntry>;
private metricsCollector: ConnectionMetrics;
constructor(options: IConnectionPoolOptions) {
super();
// Priority: least recently used connections first
this.connectionHeap = new BinaryHeap(
(a, b) => a.lastUsed - b.lastUsed,
(item) => item.poolKey
);
this.metricsCollector = new ConnectionMetrics();
this.startQueueProcessor();
}
private startQueueProcessor(): void {
// Process queue periodically to handle timeouts and retries
this.setInterval(() => {
const now = Date.now();
const timeout = this.options.connectionQueueTimeout || 30000;
// Remove timed out requests
this.connectionQueue = this.connectionQueue.filter(item => {
if (now - item.timestamp > timeout) {
item.reject(new Error(`Connection pool timeout for ${item.host}:${item.port}`));
this.metricsCollector.recordTimeout();
return false;
}
return true;
});
// Try to fulfill queued requests
this.processQueue();
}, 1000);
}
private processQueue(): void {
if (this.connectionQueue.length === 0) return;
// Group by destination
const grouped = new Map<string, typeof this.connectionQueue>();
for (const item of this.connectionQueue) {
const key = `${item.host}:${item.port}`;
if (!grouped.has(key)) grouped.set(key, []);
grouped.get(key)!.push(item);
}
// Try to fulfill requests for each destination
for (const [poolKey, requests] of grouped) {
const available = this.connectionHeap.extractIf(
conn => conn.poolKey === poolKey && conn.isIdle && !conn.socket.destroyed
);
if (available) {
const request = requests.shift()!;
this.connectionQueue = this.connectionQueue.filter(item => item !== request);
available.isIdle = false;
available.lastUsed = Date.now();
request.resolve(available.socket);
this.metricsCollector.recordReuse();
}
}
}
async getConnection(host: string, port: number): Promise<net.Socket> {
const poolKey = `${host}:${port}`;
// Try to get existing connection
let connection = this.connectionHeap.extractIf(
conn => conn.poolKey === poolKey && conn.isIdle && !conn.socket.destroyed
);
if (connection) {
connection.isIdle = false;
connection.lastUsed = Date.now();
this.metricsCollector.recordReuse();
// Validate connection is still alive
if (await this.validateConnection(connection.socket)) {
return connection.socket;
}
// Connection is dead, try another
connection.socket.destroy();
return this.getConnection(host, port);
}
// Check pool size
const poolSize = this.connectionHeap.sizeFor(poolKey);
if (poolSize < this.options.connectionPoolSize) {
return this.createConnection(host, port);
}
// Queue the request
return this.queueConnectionRequest(host, port);
}
private async validateConnection(socket: net.Socket): Promise<boolean> {
return new Promise((resolve) => {
if (socket.destroyed || !socket.readable || !socket.writable) {
resolve(false);
return;
}
// Try to write a TCP keepalive probe
const originalWrite = socket.write;
let writeError = false;
socket.write = function(data: any, encoding?: any, cb?: any) {
writeError = true;
return false;
};
socket.setNoDelay(true);
socket.setNoDelay(false);
socket.write = originalWrite;
resolve(!writeError);
});
}
returnConnection(socket: net.Socket, host: string, port: number): void {
const poolKey = `${host}:${port}`;
// Check for queued requests first
const queuedIndex = this.connectionQueue.findIndex(
item => item.host === host && item.port === port
);
if (queuedIndex >= 0) {
const queued = this.connectionQueue.splice(queuedIndex, 1)[0];
queued.resolve(socket);
this.metricsCollector.recordDirectHandoff();
return;
}
// Return to pool
this.connectionHeap.insert({
socket,
poolKey,
lastUsed: Date.now(),
isIdle: true,
created: Date.now()
});
}
getMetrics(): IConnectionPoolMetrics {
return {
...this.metricsCollector.getMetrics(),
poolSize: this.connectionHeap.size(),
queueLength: this.connectionQueue.length
};
}
}
Phase 3: Performance Optimizations (Week 3)
3.1 JSON Operations Optimization
Issue: Frequent JSON.stringify for cache keys
Solution:
// Create ts/core/utils/hash-utils.ts
import * as crypto from 'crypto';
export class HashUtils {
private static readonly objectCache = new WeakMap<object, string>();
static hashObject(obj: any): string {
// Check cache first
if (typeof obj === 'object' && obj !== null) {
const cached = this.objectCache.get(obj);
if (cached) return cached;
}
// Create stable string representation
const str = this.stableStringify(obj);
const hash = crypto.createHash('sha256').update(str).digest('hex').slice(0, 16);
// Cache if object
if (typeof obj === 'object' && obj !== null) {
this.objectCache.set(obj, hash);
}
return hash;
}
private static stableStringify(obj: any): string {
if (obj === null || typeof obj !== 'object') {
return JSON.stringify(obj);
}
if (Array.isArray(obj)) {
return '[' + obj.map(item => this.stableStringify(item)).join(',') + ']';
}
const keys = Object.keys(obj).sort();
const pairs = keys.map(key => `"${key}":${this.stableStringify(obj[key])}`);
return '{' + pairs.join(',') + '}';
}
}
// Update function-cache.ts
private computeContextHash(context: IRouteContext): string {
return HashUtils.hashObject({
domain: context.domain,
path: context.path,
clientIp: context.clientIp
});
}
3.2 Worker Thread Integration
Issue: CPU-intensive operations blocking event loop
Solution Architecture:
// Create ts/core/workers/worker-pool.ts
import { Worker } from 'worker_threads';
export class WorkerPool {
private workers: Worker[] = [];
private queue: Array<{
task: any;
resolve: Function;
reject: Function;
}> = [];
private busyWorkers = new Set<Worker>();
constructor(
private workerScript: string,
private poolSize: number = 4
) {
this.initializeWorkers();
}
async execute<T>(task: any): Promise<T> {
const worker = await this.getAvailableWorker();
return new Promise((resolve, reject) => {
const messageHandler = (result: any) => {
worker.off('message', messageHandler);
worker.off('error', errorHandler);
this.releaseWorker(worker);
resolve(result);
};
const errorHandler = (error: Error) => {
worker.off('message', messageHandler);
worker.off('error', errorHandler);
this.releaseWorker(worker);
reject(error);
};
worker.on('message', messageHandler);
worker.on('error', errorHandler);
worker.postMessage(task);
});
}
}
// Create ts/core/workers/nftables-worker.ts
import { parentPort } from 'worker_threads';
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
parentPort?.on('message', async (task) => {
try {
const result = await execAsync(task.command);
parentPort?.postMessage({ success: true, result });
} catch (error) {
parentPort?.postMessage({ success: false, error: error.message });
}
});
Phase 4: Monitoring & Metrics (Week 4)
4.1 Event Loop Monitoring
// Create ts/core/monitoring/performance-monitor.ts
export class PerformanceMonitor extends LifecycleComponent {
private metrics = {
eventLoopLag: [] as number[],
activeConnections: 0,
memoryUsage: {} as NodeJS.MemoryUsage,
cpuUsage: {} as NodeJS.CpuUsage
};
start() {
// Monitor event loop lag
let lastCheck = process.hrtime.bigint();
this.setInterval(() => {
const now = process.hrtime.bigint();
const expectedInterval = 100n * 1000000n; // 100ms in nanoseconds
const actualInterval = now - lastCheck;
const lag = Number(actualInterval - expectedInterval) / 1000000; // Convert to ms
this.metrics.eventLoopLag.push(lag);
if (this.metrics.eventLoopLag.length > 100) {
this.metrics.eventLoopLag.shift();
}
lastCheck = now;
}, 100);
// Monitor system resources
this.setInterval(() => {
this.metrics.memoryUsage = process.memoryUsage();
this.metrics.cpuUsage = process.cpuUsage();
}, 5000);
}
getMetrics() {
const avgLag = this.metrics.eventLoopLag.reduce((a, b) => a + b, 0)
/ this.metrics.eventLoopLag.length;
return {
eventLoopLag: {
current: this.metrics.eventLoopLag[this.metrics.eventLoopLag.length - 1],
average: avgLag,
max: Math.max(...this.metrics.eventLoopLag)
},
memory: this.metrics.memoryUsage,
cpu: this.metrics.cpuUsage,
activeConnections: this.metrics.activeConnections
};
}
}
Testing Strategy
Unit Tests
- Create tests for each new utility class
- Mock filesystem and network operations
- Test error scenarios and edge cases
Integration Tests
- Test async migration with real filesystem
- Verify timer cleanup on shutdown
- Test connection pool under load
Performance Tests
// Create test/performance/event-loop-test.ts
import { tap, expect } from '@git.zone/tstest/tapbundle';
tap.test('should not block event loop', async () => {
const intervals: number[] = [];
let lastTime = Date.now();
const timer = setInterval(() => {
const now = Date.now();
intervals.push(now - lastTime);
lastTime = now;
}, 10);
// Run operations that might block
await runPotentiallyBlockingOperation();
clearInterval(timer);
// Check that no interval exceeded 50ms (allowing some tolerance)
const maxInterval = Math.max(...intervals);
expect(maxInterval).toBeLessThan(50);
});
Migration Timeline
Week 1: Critical Fixes
- Day 1-2: Fix busy wait loop
- Day 3-4: Convert critical sync operations
- Day 5: Testing and validation
Week 2: Resource Management
- Day 1-2: Implement LifecycleComponent
- Day 3-4: Migrate components
- Day 5: Connection pool enhancement
Week 3: Optimizations
- Day 1-2: JSON operation optimization
- Day 3-4: Worker thread integration
- Day 5: Performance testing
Week 4: Monitoring & Polish
- Day 1-2: Performance monitoring
- Day 3-4: Load testing
- Day 5: Documentation and release
Error Handling Strategy
Graceful Degradation
// Create ts/core/utils/error-handler.ts
export class ErrorHandler {
private static errorCounts = new Map<string, number>();
private static circuitBreakers = new Map<string, CircuitBreaker>();
static async withFallback<T>(
operation: () => Promise<T>,
fallback: () => Promise<T>,
context: string
): Promise<T> {
const breaker = this.getCircuitBreaker(context);
if (breaker.isOpen()) {
return fallback();
}
try {
const result = await operation();
breaker.recordSuccess();
return result;
} catch (error) {
breaker.recordFailure();
this.recordError(context, error);
if (breaker.isOpen()) {
logger.warn(`Circuit breaker opened for ${context}`);
}
return fallback();
}
}
private static getCircuitBreaker(context: string): CircuitBreaker {
if (!this.circuitBreakers.has(context)) {
this.circuitBreakers.set(context, new CircuitBreaker({
failureThreshold: 5,
resetTimeout: 60000
}));
}
return this.circuitBreakers.get(context)!;
}
}
// Usage example in Certificate Manager
async getCertificate(domain: string): Promise<CertificateInfo | null> {
return ErrorHandler.withFallback(
// Try async operation
async () => {
await this.initialize();
return this.loadCertificateAsync(domain);
},
// Fallback to sync if needed
async () => {
logger.warn(`Falling back to sync certificate load for ${domain}`);
return this.loadCertificateSync(domain);
},
'certificate-load'
);
}
Backward Compatibility
API Preservation
- Maintain existing interfaces - All public APIs remain unchanged
- Progressive enhancement - New features are opt-in via configuration
- Sync method wrappers - Provide sync-looking APIs that use async internally
// Example: Maintaining backward compatibility
export class CertStore {
// Old sync API (deprecated but maintained)
getCertificateSync(domain: string): ICertificateInfo | null {
console.warn('getCertificateSync is deprecated, use getCertificate');
return this.syncFallbackGetCertificate(domain);
}
// New async API
async getCertificate(domain: string): Promise<ICertificateInfo | null> {
return this.asyncGetCertificate(domain);
}
// Smart detection for gradual migration
getCertificateAuto(domain: string, callback?: (err: Error | null, cert: ICertificateInfo | null) => void) {
if (callback) {
// Callback style for compatibility
this.getCertificate(domain)
.then(cert => callback(null, cert))
.catch(err => callback(err, null));
} else {
// Return promise for modern usage
return this.getCertificate(domain);
}
}
}
Configuration Compatibility
// Support both old and new configuration formats
interface SmartProxyOptions {
// Old options (maintained)
preserveSourceIP?: boolean;
defaultAllowedIPs?: string[];
// New performance options (added)
performance?: {
asyncFilesystem?: boolean;
enhancedConnectionPool?: boolean;
workerThreads?: boolean;
};
}
Monitoring Dashboard
Real-time Metrics Visualization
// Create ts/core/monitoring/dashboard-server.ts
export class MonitoringDashboard {
private httpServer: http.Server;
private wsServer: WebSocket.Server;
private metricsHistory: MetricsHistory;
async start(port: number = 9090): Promise<void> {
this.httpServer = http.createServer(this.handleRequest.bind(this));
this.wsServer = new WebSocket.Server({ server: this.httpServer });
this.wsServer.on('connection', (ws) => {
// Send current metrics
ws.send(JSON.stringify({
type: 'initial',
data: this.metricsHistory.getLast(100)
}));
// Subscribe to updates
const interval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({
type: 'update',
data: this.performanceMonitor.getMetrics()
}));
}
}, 1000);
ws.on('close', () => clearInterval(interval));
});
this.httpServer.listen(port);
logger.info(`Monitoring dashboard available at http://localhost:${port}`);
}
private handleRequest(req: http.IncomingMessage, res: http.ServerResponse) {
if (req.url === '/') {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(this.getDashboardHTML());
} else if (req.url === '/metrics') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(this.performanceMonitor.getMetrics()));
}
}
private getDashboardHTML(): string {
return `
<!DOCTYPE html>
<html>
<head>
<title>SmartProxy Performance Monitor</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
.metric { display: inline-block; margin: 10px; padding: 15px; background: #f0f0f0; }
.chart-container { width: 45%; display: inline-block; margin: 2%; }
</style>
</head>
<body>
<h1>SmartProxy Performance Monitor</h1>
<div id="metrics">
<div class="metric">
<h3>Event Loop Lag</h3>
<div id="eventLoopLag">--</div>
</div>
<div class="metric">
<h3>Active Connections</h3>
<div id="activeConnections">--</div>
</div>
<div class="metric">
<h3>Memory Usage</h3>
<div id="memoryUsage">--</div>
</div>
<div class="metric">
<h3>Connection Pool</h3>
<div id="connectionPool">--</div>
</div>
</div>
<div class="chart-container">
<canvas id="eventLoopChart"></canvas>
</div>
<div class="chart-container">
<canvas id="connectionChart"></canvas>
</div>
<script>
const ws = new WebSocket('ws://localhost:9090');
const eventLoopData = [];
const connectionData = [];
// Initialize charts
const eventLoopChart = new Chart(document.getElementById('eventLoopChart'), {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Event Loop Lag (ms)',
data: eventLoopData,
borderColor: 'rgb(255, 99, 132)',
tension: 0.1
}]
},
options: {
responsive: true,
scales: { y: { beginAtZero: true } }
}
});
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
updateMetrics(message.data);
};
function updateMetrics(metrics) {
document.getElementById('eventLoopLag').textContent =
metrics.eventLoopLag.current.toFixed(2) + ' ms';
document.getElementById('activeConnections').textContent =
metrics.activeConnections;
document.getElementById('memoryUsage').textContent =
(metrics.memory.heapUsed / 1024 / 1024).toFixed(2) + ' MB';
// Update charts
const now = new Date().toLocaleTimeString();
eventLoopData.push(metrics.eventLoopLag.current);
eventLoopChart.data.labels.push(now);
if (eventLoopData.length > 60) {
eventLoopData.shift();
eventLoopChart.data.labels.shift();
}
eventLoopChart.update();
}
</script>
</body>
</html>
`;
}
}
Performance Benchmarking
Benchmark Suite
// Create test/performance/benchmark.ts
import { SmartProxy } from '../../ts/index.js';
export class PerformanceBenchmark {
async runConnectionStresTest(): Promise<BenchmarkResult> {
const proxy = new SmartProxy({ /* config */ });
await proxy.start();
const results = {
connectionRate: 0,
avgLatency: 0,
maxConnections: 0,
eventLoopLag: []
};
// Monitor event loop during test
const lagSamples: number[] = [];
let lastCheck = process.hrtime.bigint();
const monitor = setInterval(() => {
const now = process.hrtime.bigint();
const lag = Number(now - lastCheck - 100_000_000n) / 1_000_000;
lagSamples.push(lag);
lastCheck = now;
}, 100);
// Create connections with increasing rate
const startTime = Date.now();
let connectionCount = 0;
for (let rate = 100; rate <= 10000; rate += 100) {
const connections = await this.createConnections(rate);
connectionCount += connections.length;
// Check if performance degrades
const avgLag = lagSamples.slice(-10).reduce((a, b) => a + b) / 10;
if (avgLag > 50) {
results.maxConnections = connectionCount;
break;
}
await this.delay(1000);
}
clearInterval(monitor);
await proxy.stop();
results.connectionRate = connectionCount / ((Date.now() - startTime) / 1000);
results.avgLatency = this.calculateAvgLatency();
results.eventLoopLag = lagSamples;
return results;
}
}
Documentation Updates
API Documentation
- Update JSDoc comments for all modified methods
- Add migration guide for async transitions
- Performance tuning guide with recommended settings
Example Updates
/**
* Gets a certificate for the specified domain
* @param domain - The domain to get certificate for
* @returns Promise resolving to certificate info or null
* @since v20.0.0 - Now returns Promise (breaking change)
* @example
* // Old way (deprecated)
* const cert = certStore.getCertificateSync('example.com');
*
* // New way
* const cert = await certStore.getCertificate('example.com');
*
* // Compatibility mode
* certStore.getCertificateAuto('example.com', (err, cert) => {
* if (err) console.error(err);
* else console.log(cert);
* });
*/
async getCertificate(domain: string): Promise<ICertificateInfo | null> {
// Implementation
}
Rollback Strategy
Each phase is designed to be independently deployable with feature flags:
export const PerformanceFlags = {
useAsyncFilesystem: process.env.SMARTPROXY_ASYNC_FS !== 'false',
useEnhancedPool: process.env.SMARTPROXY_ENHANCED_POOL === 'true',
useWorkerThreads: process.env.SMARTPROXY_WORKERS === 'true',
enableMonitoring: process.env.SMARTPROXY_MONITORING === 'true'
};
Gradual Rollout Plan
- Development: All flags enabled
- Staging: Monitor metrics for 1 week
- Production:
- 10% traffic → 25% → 50% → 100%
- Monitor key metrics at each stage
- Rollback if metrics degrade
Success Metrics
- Event Loop Lag: < 10ms average, < 50ms max
- Connection Handling: Support 10k+ concurrent connections
- Memory Usage: Stable under sustained load
- CPU Usage: Efficient utilization across cores
- Response Time: < 5ms overhead for proxy operations
Risk Mitigation
- Backward Compatibility: Maintain existing APIs
- Gradual Rollout: Use feature flags
- Monitoring: Track metrics before/after changes
- Testing: Comprehensive test coverage
- Documentation: Update all API docs
Summary of Key Optimizations
Immediate Impact (Phase 1)
- Eliminate busy wait loop - Unblocks event loop immediately
- Async filesystem operations - Prevents I/O blocking
- Proper error handling - Graceful degradation with fallbacks
Performance Enhancements (Phase 2-3)
- Enhanced connection pooling - O(log n) operations with BinaryHeap
- Resource lifecycle management - Prevents memory leaks
- Worker threads - Offloads CPU-intensive operations
- Optimized JSON operations - Reduces parsing overhead
Monitoring & Validation (Phase 4)
- Real-time dashboard - Visual performance monitoring
- Event loop lag tracking - Early warning system
- Automated benchmarking - Regression prevention
Implementation Checklist
Phase 1: Critical Fixes (Priority: URGENT)
- Create
ts/core/utils/async-utils.ts
with delay function - Fix busy wait loop in
nftables-proxy.ts
- Create
ts/core/utils/fs-utils.ts
with AsyncFileSystem class - Migrate
certificate-manager.ts
to async operations - Migrate
cert-store.ts
to async operations - Replace
execSync
withexecAsync
innftables-proxy.ts
- Add comprehensive unit tests for async operations
- Performance test to verify event loop improvements
Phase 2: Resource Management
- Implement LifecycleComponent base class
- Migrate components to extend LifecycleComponent
- Implement BinaryHeap data structure
- Create EnhancedConnectionPool with queue support
- Add connection validation and health checks
- Implement proper timer cleanup in all components
- Add integration tests for resource management
- Clean up legacy code (removed ts/common/, event-utils.ts, event-system.ts)
Phase 3: Performance Optimizations
- Implement HashUtils for efficient object hashing
- Create WorkerPool for CPU-intensive operations
- Migrate NFTables operations to worker threads
- Optimize JSON operations with caching
- Add performance benchmarks
Phase 4: Monitoring & Polish
- Implement PerformanceMonitor class
- Create monitoring dashboard with WebSocket updates
- Add comprehensive metrics collection
- Document all API changes
- Create migration guide
- Update examples and tutorials
Next Steps
- Immediate Action: Fix the busy wait loop (blocks entire event loop)
- Code Review: Review this plan with the team
- Feature Branch: Create
feature/performance-optimization
- Phase 1 Implementation: Complete within 1 week
- Staging Deployment: Test with real traffic patterns
- Gradual Rollout: Use feature flags for production
- Monitor & Iterate: Track metrics and adjust as needed
Expected Outcomes
After implementing all phases:
- 10x improvement in concurrent connection handling
- 90% reduction in event loop blocking
- 50% reduction in memory usage under load
- Zero memory leaks with proper resource cleanup
- Real-time visibility into performance metrics
- Graceful degradation under extreme load
Version Plan
- v19.6.0: Phase 1 (Critical fixes) - Backward compatible
- v19.7.0: Phase 2 (Resource management) - Backward compatible
- v19.8.0: Phase 3 (Optimizations) - Backward compatible
- v20.0.0: Phase 4 (Full async) - Breaking changes with migration path