dcrouter/test/suite/performance/test.connection-processing-time.ts

350 lines
9.9 KiB
TypeScript
Raw Normal View History

2025-05-23 19:09:30 +00:00
import * as plugins from '@git.zone/tstest/tapbundle';
import { expect, tap } from '@git.zone/tstest/tapbundle';
2025-05-23 19:03:44 +00:00
import * as net from 'net';
2025-05-23 21:20:39 +00:00
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js';
2025-05-23 19:03:44 +00:00
const TEST_PORT = 2525;
tap.test('prepare server', async () => {
await startTestServer();
await new Promise(resolve => setTimeout(resolve, 100));
});
tap.test('PERF-05: Connection processing time - Connection establishment', async (tools) => {
const done = tools.defer();
const testConnections = 10;
const connectionTimes: number[] = [];
try {
console.log(`Testing connection establishment time for ${testConnections} connections...`);
for (let i = 0; i < testConnections; i++) {
const connectionStart = Date.now();
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: 30000
});
await new Promise<void>((resolve, reject) => {
socket.once('connect', () => {
const connectionTime = Date.now() - connectionStart;
connectionTimes.push(connectionTime);
resolve();
});
socket.once('error', reject);
});
// Read greeting
await new Promise<void>((resolve) => {
socket.once('data', () => resolve());
});
// Clean close
socket.write('QUIT\r\n');
await new Promise<void>((resolve) => {
socket.once('data', () => resolve());
});
socket.end();
// Small delay between connections
await new Promise(resolve => setTimeout(resolve, 50));
}
// Calculate statistics
const avgConnectionTime = connectionTimes.reduce((a, b) => a + b, 0) / connectionTimes.length;
const minConnectionTime = Math.min(...connectionTimes);
const maxConnectionTime = Math.max(...connectionTimes);
console.log(`\nConnection Establishment Results:`);
console.log(`Average: ${avgConnectionTime.toFixed(0)}ms`);
console.log(`Min: ${minConnectionTime}ms`);
console.log(`Max: ${maxConnectionTime}ms`);
console.log(`All times: ${connectionTimes.join(', ')}ms`);
// Test passes if average connection time is less than 1000ms
expect(avgConnectionTime).toBeLessThan(1000);
done.resolve();
} catch (error) {
done.reject(error);
}
});
tap.test('PERF-05: Connection processing time - Transaction processing', async (tools) => {
const done = tools.defer();
const testTransactions = 10;
const processingTimes: number[] = [];
const fullTransactionTimes: number[] = [];
try {
console.log(`\nTesting transaction processing time for ${testTransactions} transactions...`);
for (let i = 0; i < testTransactions; i++) {
const fullTransactionStart = Date.now();
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: 30000
});
await new Promise<void>((resolve, reject) => {
socket.once('connect', resolve);
socket.once('error', reject);
});
// Read greeting
await new Promise<void>((resolve) => {
socket.once('data', () => resolve());
});
const processingStart = Date.now();
// Send EHLO
socket.write(`EHLO testhost-perf-${i}\r\n`);
await new Promise<void>((resolve) => {
let data = '';
const handleData = (chunk: Buffer) => {
data += chunk.toString();
if (data.includes('250 ') && !data.includes('250-')) {
socket.removeListener('data', handleData);
resolve();
}
};
socket.on('data', handleData);
});
// Send MAIL FROM
socket.write(`MAIL FROM:<sender${i}@example.com>\r\n`);
await new Promise<void>((resolve) => {
socket.once('data', (chunk) => {
const response = chunk.toString();
expect(response).toInclude('250');
resolve();
});
});
// Send RCPT TO
socket.write(`RCPT TO:<recipient${i}@example.com>\r\n`);
await new Promise<void>((resolve) => {
socket.once('data', (chunk) => {
const response = chunk.toString();
expect(response).toInclude('250');
resolve();
});
});
// Send DATA
socket.write('DATA\r\n');
await new Promise<void>((resolve) => {
socket.once('data', (chunk) => {
const response = chunk.toString();
expect(response).toInclude('354');
resolve();
});
});
// Send email content
const emailContent = [
`From: sender${i}@example.com`,
`To: recipient${i}@example.com`,
`Subject: Connection Processing Test ${i}`,
'',
'Connection processing time test.',
'.',
''
].join('\r\n');
socket.write(emailContent);
await new Promise<void>((resolve) => {
socket.once('data', (chunk) => {
const response = chunk.toString();
expect(response).toInclude('250');
resolve();
});
});
const processingTime = Date.now() - processingStart;
processingTimes.push(processingTime);
// Send QUIT
socket.write('QUIT\r\n');
await new Promise<void>((resolve) => {
socket.once('data', () => resolve());
});
socket.end();
const fullTransactionTime = Date.now() - fullTransactionStart;
fullTransactionTimes.push(fullTransactionTime);
// Small delay between transactions
await new Promise(resolve => setTimeout(resolve, 50));
}
// Calculate statistics
const avgProcessingTime = processingTimes.reduce((a, b) => a + b, 0) / processingTimes.length;
const minProcessingTime = Math.min(...processingTimes);
const maxProcessingTime = Math.max(...processingTimes);
const avgFullTime = fullTransactionTimes.reduce((a, b) => a + b, 0) / fullTransactionTimes.length;
console.log(`\nTransaction Processing Results:`);
console.log(`Average processing: ${avgProcessingTime.toFixed(0)}ms`);
console.log(`Min processing: ${minProcessingTime}ms`);
console.log(`Max processing: ${maxProcessingTime}ms`);
console.log(`Average full transaction: ${avgFullTime.toFixed(0)}ms`);
// Test passes if average processing time is less than 2000ms
expect(avgProcessingTime).toBeLessThan(2000);
done.resolve();
} catch (error) {
done.reject(error);
}
});
tap.test('PERF-05: Connection processing time - Command response times', async (tools) => {
const done = tools.defer();
const commandTimings: { [key: string]: number[] } = {
EHLO: [],
MAIL: [],
RCPT: [],
DATA: [],
NOOP: [],
RSET: []
};
try {
console.log(`\nMeasuring individual command response times...`);
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: 30000
});
await new Promise<void>((resolve, reject) => {
socket.once('connect', resolve);
socket.once('error', reject);
});
// Read greeting
await new Promise<void>((resolve) => {
socket.once('data', () => resolve());
});
// Measure EHLO response times
for (let i = 0; i < 5; i++) {
const start = Date.now();
socket.write('EHLO testhost\r\n');
await new Promise<void>((resolve) => {
let data = '';
const handleData = (chunk: Buffer) => {
data += chunk.toString();
if (data.includes('250 ') && !data.includes('250-')) {
socket.removeListener('data', handleData);
commandTimings.EHLO.push(Date.now() - start);
resolve();
}
};
socket.on('data', handleData);
});
}
// Measure NOOP response times
for (let i = 0; i < 5; i++) {
const start = Date.now();
socket.write('NOOP\r\n');
await new Promise<void>((resolve) => {
socket.once('data', () => {
commandTimings.NOOP.push(Date.now() - start);
resolve();
});
});
}
// Measure full transaction commands
for (let i = 0; i < 3; i++) {
// MAIL FROM
let start = Date.now();
socket.write(`MAIL FROM:<test${i}@example.com>\r\n`);
await new Promise<void>((resolve) => {
socket.once('data', () => {
commandTimings.MAIL.push(Date.now() - start);
resolve();
});
});
// RCPT TO
start = Date.now();
socket.write(`RCPT TO:<recipient${i}@example.com>\r\n`);
await new Promise<void>((resolve) => {
socket.once('data', () => {
commandTimings.RCPT.push(Date.now() - start);
resolve();
});
});
// DATA
start = Date.now();
socket.write('DATA\r\n');
await new Promise<void>((resolve) => {
socket.once('data', () => {
commandTimings.DATA.push(Date.now() - start);
resolve();
});
});
// Send simple message
socket.write('Subject: Test\r\n\r\nTest\r\n.\r\n');
await new Promise<void>((resolve) => {
socket.once('data', () => resolve());
});
// RSET
start = Date.now();
socket.write('RSET\r\n');
await new Promise<void>((resolve) => {
socket.once('data', () => {
commandTimings.RSET.push(Date.now() - start);
resolve();
});
});
}
socket.write('QUIT\r\n');
socket.end();
// Calculate and display results
console.log(`\nCommand Response Times (ms):`);
for (const [command, times] of Object.entries(commandTimings)) {
if (times.length > 0) {
const avg = times.reduce((a, b) => a + b, 0) / times.length;
console.log(`${command}: avg=${avg.toFixed(0)}, samples=[${times.join(', ')}]`);
// All commands should respond in less than 500ms on average
expect(avg).toBeLessThan(500);
}
}
done.resolve();
} catch (error) {
done.reject(error);
}
});
tap.test('cleanup server', async () => {
await stopTestServer();
});
tap.start();