dcrouter/test/suite/smtpserver_performance/test.perf-06.message-processing-time.ts
2025-05-25 19:05:43 +00:00

252 lines
7.8 KiB
TypeScript

import * as plugins from '@git.zone/tstest/tapbundle';
import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 2525;
let testServer;
// Helper function to wait for SMTP response
const waitForResponse = (socket: net.Socket, expectedCode: string, timeout = 5000): Promise<string> => {
return new Promise((resolve, reject) => {
let buffer = '';
const timer = setTimeout(() => {
socket.removeListener('data', handler);
reject(new Error(`Timeout waiting for ${expectedCode} response`));
}, timeout);
const handler = (data: Buffer) => {
buffer += data.toString();
const lines = buffer.split('\r\n');
// Check if we have a complete response
for (const line of lines) {
if (line.startsWith(expectedCode + ' ')) {
clearTimeout(timer);
socket.removeListener('data', handler);
resolve(buffer);
return;
}
}
};
socket.on('data', handler);
});
};
tap.test('prepare server', async () => {
testServer = await startTestServer({ port: TEST_PORT });
await new Promise(resolve => setTimeout(resolve, 100));
});
tap.test('PERF-06: Message processing time - Various message sizes', async (tools) => {
const done = tools.defer();
const messageSizes = [1000, 5000, 10000, 25000, 50000]; // bytes
const messageProcessingTimes: number[] = [];
const processingRates: number[] = [];
try {
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 waitForResponse(socket, '220');
// Send EHLO
socket.write('EHLO testhost\r\n');
await waitForResponse(socket, '250');
console.log('Testing message processing times for various sizes...\n');
for (let i = 0; i < messageSizes.length; i++) {
const messageSize = messageSizes[i];
const messageContent = 'A'.repeat(messageSize);
const messageStart = Date.now();
// Send MAIL FROM
socket.write(`MAIL FROM:<sender${i}@example.com>\r\n`);
await waitForResponse(socket, '250');
// Send RCPT TO
socket.write(`RCPT TO:<recipient${i}@example.com>\r\n`);
await waitForResponse(socket, '250');
// Send DATA
socket.write('DATA\r\n');
await waitForResponse(socket, '354');
// Send email content
const emailContent = [
`From: sender${i}@example.com`,
`To: recipient${i}@example.com`,
`Subject: Message Processing Test ${i} (${messageSize} bytes)`,
'',
messageContent,
'.',
''
].join('\r\n');
socket.write(emailContent);
await waitForResponse(socket, '250');
const messageProcessingTime = Date.now() - messageStart;
messageProcessingTimes.push(messageProcessingTime);
const processingRateKBps = (messageSize / 1024) / (messageProcessingTime / 1000);
processingRates.push(processingRateKBps);
console.log(`${messageSize} bytes: ${messageProcessingTime}ms (${processingRateKBps.toFixed(1)} KB/s)`);
// Send RSET
socket.write('RSET\r\n');
await new Promise<void>((resolve) => {
socket.once('data', (chunk) => {
const response = chunk.toString();
expect(response).toInclude('250');
resolve();
});
});
// Small delay between tests
await new Promise(resolve => setTimeout(resolve, 100));
}
// Calculate statistics
const avgProcessingTime = messageProcessingTimes.reduce((a, b) => a + b, 0) / messageProcessingTimes.length;
const avgProcessingRate = processingRates.reduce((a, b) => a + b, 0) / processingRates.length;
const minProcessingTime = Math.min(...messageProcessingTimes);
const maxProcessingTime = Math.max(...messageProcessingTimes);
console.log(`\nMessage Processing Results:`);
console.log(`Average processing time: ${avgProcessingTime.toFixed(0)}ms`);
console.log(`Min/Max processing time: ${minProcessingTime}ms / ${maxProcessingTime}ms`);
console.log(`Average processing rate: ${avgProcessingRate.toFixed(1)} KB/s`);
socket.write('QUIT\r\n');
socket.end();
// Test passes if average processing time is less than 3000ms and rate > 10KB/s
expect(avgProcessingTime).toBeLessThan(3000);
expect(avgProcessingRate).toBeGreaterThan(10);
done.resolve();
} catch (error) {
done.reject(error);
}
});
tap.test('PERF-06: Message processing time - Large message handling', async (tools) => {
const done = tools.defer();
const largeSizes = [100000, 250000, 500000]; // 100KB, 250KB, 500KB
const results: Array<{ size: number; time: number; rate: number }> = [];
try {
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: 60000 // Longer timeout for large messages
});
await new Promise<void>((resolve, reject) => {
socket.once('connect', resolve);
socket.once('error', reject);
});
// Read greeting
await waitForResponse(socket, '220');
// Send EHLO
socket.write('EHLO testhost-large\r\n');
await waitForResponse(socket, '250');
console.log('\nTesting large message processing...\n');
for (let i = 0; i < largeSizes.length; i++) {
const messageSize = largeSizes[i];
const messageStart = Date.now();
// Send MAIL FROM
socket.write(`MAIL FROM:<largesender${i}@example.com>\r\n`);
await waitForResponse(socket, '250');
// Send RCPT TO
socket.write(`RCPT TO:<largerecipient${i}@example.com>\r\n`);
await waitForResponse(socket, '250');
// Send DATA
socket.write('DATA\r\n');
await waitForResponse(socket, '354');
// Send large email content in chunks to avoid buffer issues
socket.write(`From: largesender${i}@example.com\r\n`);
socket.write(`To: largerecipient${i}@example.com\r\n`);
socket.write(`Subject: Large Message Test ${i} (${messageSize} bytes)\r\n\r\n`);
// Send content in 10KB chunks
const chunkSize = 10000;
let remaining = messageSize;
while (remaining > 0) {
const currentChunk = Math.min(remaining, chunkSize);
socket.write('B'.repeat(currentChunk));
remaining -= currentChunk;
// Small delay to avoid overwhelming buffers
if (remaining > 0) {
await new Promise(resolve => setTimeout(resolve, 10));
}
}
socket.write('\r\n.\r\n');
const response = await waitForResponse(socket, '250', 30000);
expect(response).toInclude('250');
const messageProcessingTime = Date.now() - messageStart;
const processingRateMBps = (messageSize / (1024 * 1024)) / (messageProcessingTime / 1000);
results.push({
size: messageSize,
time: messageProcessingTime,
rate: processingRateMBps
});
console.log(`${(messageSize/1024).toFixed(0)}KB: ${messageProcessingTime}ms (${processingRateMBps.toFixed(2)} MB/s)`);
// Send RSET
socket.write('RSET\r\n');
await waitForResponse(socket, '250');
// Delay between large tests
await new Promise(resolve => setTimeout(resolve, 500));
}
const avgRate = results.reduce((sum, r) => sum + r.rate, 0) / results.length;
console.log(`\nAverage large message rate: ${avgRate.toFixed(2)} MB/s`);
socket.write('QUIT\r\n');
socket.end();
// Test passes if we can process at least 0.5 MB/s
expect(avgRate).toBeGreaterThan(0.5);
done.resolve();
} catch (error) {
done.reject(error);
}
});
tap.test('cleanup server', async () => {
await stopTestServer(testServer);
});
export default tap.start();