dcrouter/test/suite/smtpclient_performance/test.cperf-05.network-efficiency.ts

686 lines
23 KiB
TypeScript
Raw Normal View History

2025-05-24 18:12:08 +00:00
import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as plugins from './plugins.js';
import { createTestServer } from '../../helpers/server.loader.js';
import { createSmtpClient } from '../../helpers/smtp.client.js';
tap.test('CPERF-05: should optimize network efficiency', async (tools) => {
const testId = 'CPERF-05-network-efficiency';
console.log(`\n${testId}: Testing network efficiency optimization...`);
let scenarioCount = 0;
// Helper to track network activity
class NetworkTracker {
private startTime: number;
private bytesSent: number = 0;
private bytesReceived: number = 0;
private connections: number = 0;
private roundTrips: number = 0;
constructor() {
this.startTime = Date.now();
}
addConnection() {
this.connections++;
}
addBytesSent(bytes: number) {
this.bytesSent += bytes;
}
addBytesReceived(bytes: number) {
this.bytesReceived += bytes;
}
addRoundTrip() {
this.roundTrips++;
}
getStats() {
const elapsed = Date.now() - this.startTime;
return {
elapsed,
bytesSent: this.bytesSent,
bytesReceived: this.bytesReceived,
totalBytes: this.bytesSent + this.bytesReceived,
connections: this.connections,
roundTrips: this.roundTrips,
bytesPerSecond: ((this.bytesSent + this.bytesReceived) / elapsed) * 1000,
efficiency: this.bytesSent / (this.bytesSent + this.bytesReceived)
};
}
}
// Scenario 1: Connection reuse efficiency
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing connection reuse efficiency`);
const tracker = new NetworkTracker();
let connectionCount = 0;
let totalCommandBytes = 0;
let totalResponseBytes = 0;
const testServer = await createTestServer({
onConnection: async (socket) => {
connectionCount++;
tracker.addConnection();
console.log(` [Server] Connection ${connectionCount} established`);
const greeting = '220 reuse.example.com ESMTP\r\n';
socket.write(greeting);
tracker.addBytesSent(greeting.length);
socket.on('close', () => {
console.log(` [Server] Connection ${connectionCount} closed`);
});
socket.on('data', (data) => {
totalCommandBytes += data.length;
tracker.addBytesReceived(data.length);
tracker.addRoundTrip();
const command = data.toString().trim();
let response = '';
if (command.startsWith('EHLO')) {
response = '250-reuse.example.com\r\n250 OK\r\n';
} else if (command.startsWith('MAIL FROM:')) {
response = '250 OK\r\n';
} else if (command.startsWith('RCPT TO:')) {
response = '250 OK\r\n';
} else if (command === 'DATA') {
response = '354 Start mail input\r\n';
} else if (command === '.') {
response = '250 OK\r\n';
} else if (command === 'RSET') {
response = '250 OK\r\n';
} else if (command === 'QUIT') {
response = '221 Bye\r\n';
}
if (response) {
socket.write(response);
totalResponseBytes += response.length;
tracker.addBytesSent(response.length);
}
if (command === 'QUIT') {
socket.end();
}
});
}
});
// Test individual connections vs reused connection
const messageCount = 10;
// Individual connections approach
console.log(` Testing ${messageCount} individual connections...`);
const individualStart = Date.now();
connectionCount = 0;
totalCommandBytes = 0;
totalResponseBytes = 0;
for (let i = 0; i < messageCount; i++) {
const client = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: [`individual${i + 1}@example.com`],
subject: `Individual connection test ${i + 1}`,
text: `Testing individual connections - message ${i + 1}`
});
await client.sendMail(email);
if (client.close) {
await client.close();
}
}
const individualTime = Date.now() - individualStart;
const individualStats = {
connections: connectionCount,
commandBytes: totalCommandBytes,
responseBytes: totalResponseBytes,
totalBytes: totalCommandBytes + totalResponseBytes,
time: individualTime
};
console.log(` Individual connections: ${individualStats.connections} connections, ${individualStats.totalBytes} bytes`);
// Connection reuse approach
console.log(` Testing connection reuse...`);
const reuseStart = Date.now();
connectionCount = 0;
totalCommandBytes = 0;
totalResponseBytes = 0;
const reuseClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pool: true,
maxConnections: 1,
maxMessages: messageCount
});
const reuseEmails = Array(messageCount).fill(null).map((_, i) =>
new plugins.smartmail.Email({
from: 'sender@example.com',
to: [`reuse${i + 1}@example.com`],
subject: `Connection reuse test ${i + 1}`,
text: `Testing connection reuse - message ${i + 1}`
})
);
for (const email of reuseEmails) {
await reuseClient.sendMail(email);
}
await reuseClient.close();
const reuseTime = Date.now() - reuseStart;
const reuseStats = {
connections: connectionCount,
commandBytes: totalCommandBytes,
responseBytes: totalResponseBytes,
totalBytes: totalCommandBytes + totalResponseBytes,
time: reuseTime
};
console.log(` Connection reuse: ${reuseStats.connections} connections, ${reuseStats.totalBytes} bytes`);
// Calculate efficiency
const connectionEfficiency = individualStats.connections / reuseStats.connections;
const byteEfficiency = individualStats.totalBytes / reuseStats.totalBytes;
const timeEfficiency = individualTime / reuseTime;
console.log(` Connection efficiency: ${connectionEfficiency.toFixed(1)}x`);
console.log(` Byte efficiency: ${byteEfficiency.toFixed(1)}x`);
console.log(` Time efficiency: ${timeEfficiency.toFixed(1)}x`);
// Connection reuse should be more efficient
expect(reuseStats.connections).toBeLessThan(individualStats.connections);
expect(reuseStats.totalBytes).toBeLessThan(individualStats.totalBytes);
expect(connectionEfficiency).toBeGreaterThan(5); // At least 5x fewer connections
await testServer.server.close();
})();
// Scenario 2: Command pipelining efficiency
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing command pipelining efficiency`);
let totalCommands = 0;
let pipelinedCommands = 0;
let maxPipelineDepth = 0;
const testServer = await createTestServer({
onConnection: async (socket) => {
console.log(' [Server] Client connected for pipelining test');
socket.write('220 pipeline.example.com ESMTP\r\n');
socket.on('data', (data) => {
const commands = data.toString().split('\r\n').filter(cmd => cmd.length > 0);
totalCommands += commands.length;
if (commands.length > 1) {
pipelinedCommands += commands.length;
maxPipelineDepth = Math.max(maxPipelineDepth, commands.length);
console.log(` [Server] Received ${commands.length} pipelined commands`);
}
commands.forEach(command => {
if (command.startsWith('EHLO')) {
socket.write('250-pipeline.example.com\r\n250-PIPELINING\r\n250 OK\r\n');
} else if (command.startsWith('MAIL FROM:')) {
socket.write('250 OK\r\n');
} else if (command.startsWith('RCPT TO:')) {
socket.write('250 OK\r\n');
} else if (command === 'DATA') {
socket.write('354 Start mail input\r\n');
} else if (command === '.') {
socket.write('250 OK\r\n');
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
});
}
});
const pipelineClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pipelining: true
});
// Send emails with multiple recipients (triggers pipelining)
const emails = [
new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient1@example.com', 'recipient2@example.com', 'recipient3@example.com'],
subject: 'Pipelining test 1',
text: 'Testing command pipelining efficiency'
}),
new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient4@example.com', 'recipient5@example.com'],
subject: 'Pipelining test 2',
text: 'Testing command pipelining efficiency'
})
];
console.log(' Sending emails with pipelining support...');
for (const email of emails) {
await pipelineClient.sendMail(email);
}
console.log(` Total commands sent: ${totalCommands}`);
console.log(` Pipelined commands: ${pipelinedCommands}`);
console.log(` Max pipeline depth: ${maxPipelineDepth}`);
console.log(` Pipelining efficiency: ${(pipelinedCommands / totalCommands * 100).toFixed(1)}%`);
// Should use pipelining for efficiency
expect(pipelinedCommands).toBeGreaterThan(0);
expect(maxPipelineDepth).toBeGreaterThan(1);
await testServer.server.close();
})();
// Scenario 3: Message size optimization
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing message size optimization`);
let totalMessageBytes = 0;
let messageCount = 0;
const testServer = await createTestServer({
onConnection: async (socket) => {
console.log(' [Server] Client connected for size optimization test');
socket.write('220 size.example.com ESMTP\r\n');
let inData = false;
let messageBytes = 0;
socket.on('data', (data) => {
if (inData) {
messageBytes += data.length;
if (data.toString().includes('\r\n.\r\n')) {
inData = false;
messageCount++;
totalMessageBytes += messageBytes;
console.log(` [Server] Message ${messageCount}: ${messageBytes} bytes`);
socket.write('250 OK\r\n');
messageBytes = 0;
}
return;
}
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-size.example.com\r\n250-SIZE 52428800\r\n250 OK\r\n');
} else if (command.startsWith('MAIL FROM:')) {
socket.write('250 OK\r\n');
} else if (command.startsWith('RCPT TO:')) {
socket.write('250 OK\r\n');
} else if (command === 'DATA') {
socket.write('354 Start mail input\r\n');
inData = true;
messageBytes = 0;
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
}
});
const sizeClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
// Test different message sizes and encoding efficiency
const sizeTests = [
{
name: 'Plain text',
email: new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'Plain text efficiency test',
text: 'This is a plain text message for testing size efficiency.'
})
},
{
name: 'HTML message',
email: new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'HTML efficiency test',
html: '<html><body><h1>HTML Message</h1><p>This is an HTML message.</p></body></html>'
})
},
{
name: 'Multipart message',
email: new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'Multipart efficiency test',
text: 'Plain text version of the message.',
html: '<p>HTML version of the message.</p>'
})
},
{
name: 'Message with small attachment',
email: new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'Attachment efficiency test',
text: 'Message with attachment.',
attachments: [{
filename: 'test.txt',
content: 'Small test attachment content for efficiency testing.'
}]
})
}
];
for (const test of sizeTests) {
console.log(` Testing ${test.name}...`);
const beforeBytes = totalMessageBytes;
const beforeCount = messageCount;
await sizeClient.sendMail(test.email);
const messageSize = totalMessageBytes - beforeBytes;
const overhead = messageSize / (test.email.text?.length || test.email.html?.length || 100);
console.log(` ${test.name}: ${messageSize} bytes (overhead: ${overhead.toFixed(1)}x)`);
// Message overhead should be reasonable
expect(overhead).toBeLessThan(10); // Less than 10x overhead
}
const avgMessageSize = totalMessageBytes / messageCount;
console.log(` Average message size: ${avgMessageSize.toFixed(0)} bytes`);
await testServer.server.close();
})();
// Scenario 4: Bandwidth utilization
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing bandwidth utilization`);
let totalBytes = 0;
let dataTransferTime = 0;
const testServer = await createTestServer({
onConnection: async (socket) => {
socket.write('220 bandwidth.example.com ESMTP\r\n');
let transferStart = 0;
let inData = false;
socket.on('data', (data) => {
totalBytes += data.length;
if (inData) {
if (data.toString().includes('\r\n.\r\n')) {
inData = false;
dataTransferTime += Date.now() - transferStart;
socket.write('250 OK\r\n');
}
return;
}
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-bandwidth.example.com\r\n250 OK\r\n');
} else if (command.startsWith('MAIL FROM:')) {
socket.write('250 OK\r\n');
} else if (command.startsWith('RCPT TO:')) {
socket.write('250 OK\r\n');
} else if (command === 'DATA') {
socket.write('354 Start mail input\r\n');
inData = true;
transferStart = Date.now();
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
}
});
const bandwidthClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
// Test bandwidth efficiency with varying message sizes
const messageSizes = [1024, 10240, 102400]; // 1KB, 10KB, 100KB
console.log(' Testing bandwidth utilization with different message sizes...');
const bandwidthStart = Date.now();
for (const size of messageSizes) {
const content = 'x'.repeat(size);
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: `Bandwidth test ${size} bytes`,
text: content
});
await bandwidthClient.sendMail(email);
}
const bandwidthElapsed = Date.now() - bandwidthStart;
const throughput = (totalBytes / bandwidthElapsed) * 1000; // bytes per second
const dataEfficiency = (messageSizes.reduce((a, b) => a + b, 0) / totalBytes) * 100;
console.log(` Total bytes transferred: ${totalBytes}`);
console.log(` Data transfer time: ${dataTransferTime}ms`);
console.log(` Overall throughput: ${(throughput / 1024).toFixed(1)} KB/s`);
console.log(` Data efficiency: ${dataEfficiency.toFixed(1)}% (payload vs total)`);
// Bandwidth utilization should be efficient
expect(throughput).toBeGreaterThan(1024); // At least 1KB/s
expect(dataEfficiency).toBeGreaterThan(20); // At least 20% payload efficiency
await testServer.server.close();
})();
// Scenario 5: Network round-trip optimization
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing network round-trip optimization`);
let roundTrips = 0;
let commandCount = 0;
const testServer = await createTestServer({
onConnection: async (socket) => {
socket.write('220 roundtrip.example.com ESMTP\r\n');
socket.on('data', (data) => {
const commands = data.toString().split('\r\n').filter(cmd => cmd.length > 0);
roundTrips++;
commandCount += commands.length;
console.log(` [Server] Round-trip ${roundTrips}: ${commands.length} commands`);
commands.forEach(command => {
if (command.startsWith('EHLO')) {
socket.write('250-roundtrip.example.com\r\n250-PIPELINING\r\n250 OK\r\n');
} else if (command.startsWith('MAIL FROM:')) {
socket.write('250 OK\r\n');
} else if (command.startsWith('RCPT TO:')) {
socket.write('250 OK\r\n');
} else if (command === 'DATA') {
socket.write('354 Start mail input\r\n');
} else if (command === '.') {
socket.write('250 OK\r\n');
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
});
}
});
const roundtripClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pipelining: true
});
// Send email with multiple recipients to test round-trip efficiency
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['rcpt1@example.com', 'rcpt2@example.com', 'rcpt3@example.com', 'rcpt4@example.com'],
subject: 'Round-trip optimization test',
text: 'Testing network round-trip optimization with multiple recipients'
});
console.log(' Sending email with multiple recipients...');
await roundtripClient.sendMail(email);
const commandsPerRoundTrip = commandCount / roundTrips;
const efficiency = commandsPerRoundTrip;
console.log(` Total round-trips: ${roundTrips}`);
console.log(` Total commands: ${commandCount}`);
console.log(` Commands per round-trip: ${commandsPerRoundTrip.toFixed(1)}`);
console.log(` Round-trip efficiency: ${efficiency.toFixed(1)}`);
// Should minimize round-trips through pipelining
expect(roundTrips).toBeLessThan(commandCount); // Fewer round-trips than commands
expect(commandsPerRoundTrip).toBeGreaterThan(1.5); // At least 1.5 commands per round-trip
await testServer.server.close();
})();
// Scenario 6: Connection pooling network efficiency
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing connection pooling network efficiency`);
let totalConnections = 0;
let totalBytes = 0;
let connectionSetupBytes = 0;
const testServer = await createTestServer({
onConnection: async (socket) => {
totalConnections++;
const greeting = '220 pool.example.com ESMTP\r\n';
socket.write(greeting);
connectionSetupBytes += greeting.length;
console.log(` [Server] Pool connection ${totalConnections} established`);
socket.on('data', (data) => {
totalBytes += data.length;
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
const response = '250-pool.example.com\r\n250 OK\r\n';
socket.write(response);
connectionSetupBytes += response.length;
totalBytes += response.length;
} else if (command.startsWith('MAIL FROM:')) {
const response = '250 OK\r\n';
socket.write(response);
totalBytes += response.length;
} else if (command.startsWith('RCPT TO:')) {
const response = '250 OK\r\n';
socket.write(response);
totalBytes += response.length;
} else if (command === 'DATA') {
const response = '354 Start mail input\r\n';
socket.write(response);
totalBytes += response.length;
} else if (command === '.') {
const response = '250 OK\r\n';
socket.write(response);
totalBytes += response.length;
} else if (command === 'QUIT') {
const response = '221 Bye\r\n';
socket.write(response);
totalBytes += response.length;
socket.end();
}
});
}
});
const poolClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pool: true,
maxConnections: 3,
maxMessages: 100
});
// Send multiple emails through pool
const emailCount = 15;
const emails = Array(emailCount).fill(null).map((_, i) =>
new plugins.smartmail.Email({
from: 'sender@example.com',
to: [`pooled${i + 1}@example.com`],
subject: `Pool efficiency test ${i + 1}`,
text: `Testing pooled connection network efficiency - message ${i + 1}`
})
);
console.log(` Sending ${emailCount} emails through connection pool...`);
const poolStart = Date.now();
await Promise.all(emails.map(email => poolClient.sendMail(email)));
await poolClient.close();
const poolTime = Date.now() - poolStart;
const setupOverhead = (connectionSetupBytes / totalBytes) * 100;
const messagesPerConnection = emailCount / totalConnections;
const bytesPerMessage = totalBytes / emailCount;
console.log(` Emails sent: ${emailCount}`);
console.log(` Connections used: ${totalConnections}`);
console.log(` Messages per connection: ${messagesPerConnection.toFixed(1)}`);
console.log(` Total bytes: ${totalBytes}`);
console.log(` Setup overhead: ${setupOverhead.toFixed(1)}%`);
console.log(` Bytes per message: ${bytesPerMessage.toFixed(0)}`);
console.log(` Network efficiency: ${(emailCount / totalConnections).toFixed(1)} msg/conn`);
// Connection pooling should be network efficient
expect(totalConnections).toBeLessThan(emailCount); // Fewer connections than messages
expect(messagesPerConnection).toBeGreaterThan(3); // At least 3 messages per connection
expect(setupOverhead).toBeLessThan(20); // Less than 20% setup overhead
await testServer.server.close();
})();
console.log(`\n${testId}: All ${scenarioCount} network efficiency scenarios tested ✓`);
});