feat(metrics): add comprehensive metrics collection system
Implement real-time stats tracking including connection counts, request metrics, bandwidth usage, and route-specific monitoring. Adds MetricsCollector with observable streams for reactive monitoring integration.
This commit is contained in:
250
test/test.keepalive-support.node.ts
Normal file
250
test/test.keepalive-support.node.ts
Normal file
@ -0,0 +1,250 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import * as net from 'net';
|
||||
import { SmartProxy } from '../ts/index.js';
|
||||
import * as plugins from '../ts/plugins.js';
|
||||
|
||||
tap.test('keepalive support - verify keepalive connections are properly handled', async (tools) => {
|
||||
console.log('\n=== KeepAlive Support Test ===');
|
||||
console.log('Purpose: Verify that keepalive connections are not prematurely cleaned up');
|
||||
|
||||
// Create a simple echo backend
|
||||
const echoBackend = net.createServer((socket) => {
|
||||
socket.on('data', (data) => {
|
||||
// Echo back received data
|
||||
try {
|
||||
socket.write(data);
|
||||
} catch (err) {
|
||||
// Ignore write errors during shutdown
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('error', (err) => {
|
||||
// Ignore errors from backend sockets
|
||||
console.log(`Backend socket error (expected during cleanup): ${err.code}`);
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
echoBackend.listen(9998, () => {
|
||||
console.log('✓ Echo backend started on port 9998');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// Test 1: Standard keepalive treatment
|
||||
console.log('\n--- Test 1: Standard KeepAlive Treatment ---');
|
||||
|
||||
const proxy1 = new SmartProxy({
|
||||
routes: [{
|
||||
name: 'keepalive-route',
|
||||
match: { ports: 8590 },
|
||||
action: {
|
||||
type: 'forward',
|
||||
target: { host: 'localhost', port: 9998 }
|
||||
}
|
||||
}],
|
||||
keepAlive: true,
|
||||
keepAliveTreatment: 'standard',
|
||||
inactivityTimeout: 5000, // 5 seconds for faster testing
|
||||
enableDetailedLogging: false,
|
||||
});
|
||||
|
||||
await proxy1.start();
|
||||
console.log('✓ Proxy with standard keepalive started on port 8590');
|
||||
|
||||
// Create a keepalive connection
|
||||
const client1 = net.connect(8590, 'localhost');
|
||||
|
||||
// Add error handler to prevent unhandled errors
|
||||
client1.on('error', (err) => {
|
||||
console.log(`Client1 error (expected during cleanup): ${err.code}`);
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
client1.on('connect', () => {
|
||||
console.log('Client connected');
|
||||
client1.setKeepAlive(true, 1000);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// Send initial data
|
||||
client1.write('Hello keepalive\n');
|
||||
|
||||
// Wait for echo
|
||||
await new Promise<void>((resolve) => {
|
||||
client1.once('data', (data) => {
|
||||
console.log(`Received echo: ${data.toString().trim()}`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// Check connection is marked as keepalive
|
||||
const cm1 = (proxy1 as any).connectionManager;
|
||||
const connections1 = cm1.getConnections();
|
||||
let keepAliveCount = 0;
|
||||
|
||||
for (const [id, record] of connections1) {
|
||||
if (record.hasKeepAlive) {
|
||||
keepAliveCount++;
|
||||
console.log(`KeepAlive connection ${id}: hasKeepAlive=${record.hasKeepAlive}`);
|
||||
}
|
||||
}
|
||||
|
||||
expect(keepAliveCount).toEqual(1);
|
||||
|
||||
// Wait to ensure it's not cleaned up prematurely
|
||||
await plugins.smartdelay.delayFor(6000);
|
||||
|
||||
const afterWaitCount1 = cm1.getConnectionCount();
|
||||
console.log(`Connections after 6s wait: ${afterWaitCount1}`);
|
||||
expect(afterWaitCount1).toEqual(1); // Should still be connected
|
||||
|
||||
// Send more data to keep it alive
|
||||
client1.write('Still alive\n');
|
||||
|
||||
// Clean up test 1
|
||||
client1.destroy();
|
||||
await proxy1.stop();
|
||||
await plugins.smartdelay.delayFor(500); // Wait for port to be released
|
||||
|
||||
// Test 2: Extended keepalive treatment
|
||||
console.log('\n--- Test 2: Extended KeepAlive Treatment ---');
|
||||
|
||||
const proxy2 = new SmartProxy({
|
||||
routes: [{
|
||||
name: 'keepalive-extended',
|
||||
match: { ports: 8591 },
|
||||
action: {
|
||||
type: 'forward',
|
||||
target: { host: 'localhost', port: 9998 }
|
||||
}
|
||||
}],
|
||||
keepAlive: true,
|
||||
keepAliveTreatment: 'extended',
|
||||
keepAliveInactivityMultiplier: 6,
|
||||
inactivityTimeout: 2000, // 2 seconds base, 12 seconds with multiplier
|
||||
enableDetailedLogging: false,
|
||||
});
|
||||
|
||||
await proxy2.start();
|
||||
console.log('✓ Proxy with extended keepalive started on port 8591');
|
||||
|
||||
const client2 = net.connect(8591, 'localhost');
|
||||
|
||||
// Add error handler to prevent unhandled errors
|
||||
client2.on('error', (err) => {
|
||||
console.log(`Client2 error (expected during cleanup): ${err.code}`);
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
client2.on('connect', () => {
|
||||
console.log('Client connected with extended timeout');
|
||||
client2.setKeepAlive(true, 1000);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// Send initial data
|
||||
client2.write('Extended keepalive\n');
|
||||
|
||||
// Check connection
|
||||
const cm2 = (proxy2 as any).connectionManager;
|
||||
await plugins.smartdelay.delayFor(1000);
|
||||
|
||||
const connections2 = cm2.getConnections();
|
||||
for (const [id, record] of connections2) {
|
||||
console.log(`Extended connection ${id}: hasKeepAlive=${record.hasKeepAlive}, treatment=extended`);
|
||||
}
|
||||
|
||||
// Wait 3 seconds (would timeout with standard treatment)
|
||||
await plugins.smartdelay.delayFor(3000);
|
||||
|
||||
const midWaitCount = cm2.getConnectionCount();
|
||||
console.log(`Connections after 3s (base timeout exceeded): ${midWaitCount}`);
|
||||
expect(midWaitCount).toEqual(1); // Should still be connected due to extended treatment
|
||||
|
||||
// Clean up test 2
|
||||
client2.destroy();
|
||||
await proxy2.stop();
|
||||
await plugins.smartdelay.delayFor(500); // Wait for port to be released
|
||||
|
||||
// Test 3: Immortal keepalive treatment
|
||||
console.log('\n--- Test 3: Immortal KeepAlive Treatment ---');
|
||||
|
||||
const proxy3 = new SmartProxy({
|
||||
routes: [{
|
||||
name: 'keepalive-immortal',
|
||||
match: { ports: 8592 },
|
||||
action: {
|
||||
type: 'forward',
|
||||
target: { host: 'localhost', port: 9998 }
|
||||
}
|
||||
}],
|
||||
keepAlive: true,
|
||||
keepAliveTreatment: 'immortal',
|
||||
inactivityTimeout: 1000, // 1 second - should be ignored for immortal
|
||||
enableDetailedLogging: false,
|
||||
});
|
||||
|
||||
await proxy3.start();
|
||||
console.log('✓ Proxy with immortal keepalive started on port 8592');
|
||||
|
||||
const client3 = net.connect(8592, 'localhost');
|
||||
|
||||
// Add error handler to prevent unhandled errors
|
||||
client3.on('error', (err) => {
|
||||
console.log(`Client3 error (expected during cleanup): ${err.code}`);
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
client3.on('connect', () => {
|
||||
console.log('Client connected with immortal treatment');
|
||||
client3.setKeepAlive(true, 1000);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// Send initial data
|
||||
client3.write('Immortal connection\n');
|
||||
|
||||
// Wait well beyond normal timeout
|
||||
await plugins.smartdelay.delayFor(5000);
|
||||
|
||||
const cm3 = (proxy3 as any).connectionManager;
|
||||
const immortalCount = cm3.getConnectionCount();
|
||||
console.log(`Immortal connections after 5s inactivity: ${immortalCount}`);
|
||||
expect(immortalCount).toEqual(1); // Should never timeout
|
||||
|
||||
// Verify zombie detection doesn't affect immortal connections
|
||||
console.log('\n--- Verifying zombie detection respects keepalive ---');
|
||||
|
||||
// Manually trigger inactivity check
|
||||
cm3.performOptimizedInactivityCheck();
|
||||
|
||||
await plugins.smartdelay.delayFor(1000);
|
||||
|
||||
const afterCheckCount = cm3.getConnectionCount();
|
||||
console.log(`Connections after manual inactivity check: ${afterCheckCount}`);
|
||||
expect(afterCheckCount).toEqual(1); // Should still be alive
|
||||
|
||||
// Clean up
|
||||
client3.destroy();
|
||||
await proxy3.stop();
|
||||
|
||||
// Close backend and wait for it to fully close
|
||||
await new Promise<void>((resolve) => {
|
||||
echoBackend.close(() => {
|
||||
console.log('Echo backend closed');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
console.log('\n✓ All keepalive tests passed:');
|
||||
console.log(' - Standard treatment works correctly');
|
||||
console.log(' - Extended treatment applies multiplier');
|
||||
console.log(' - Immortal treatment never times out');
|
||||
console.log(' - Zombie detection respects keepalive settings');
|
||||
});
|
||||
|
||||
tap.start();
|
Reference in New Issue
Block a user