This commit is contained in:
2025-05-24 18:12:08 +00:00
parent 11a2ae6b27
commit 58f4a123d2
18 changed files with 10804 additions and 0 deletions

View File

@ -0,0 +1,532 @@
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-02: should achieve optimal message throughput', async (tools) => {
const testId = 'CPERF-02-message-throughput';
console.log(`\n${testId}: Testing message throughput performance...`);
let scenarioCount = 0;
// Scenario 1: Sequential message throughput
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing sequential message throughput`);
let messageCount = 0;
const startTime = Date.now();
const testServer = await createTestServer({
onConnection: async (socket) => {
console.log(' [Server] Client connected');
socket.write('220 throughput.example.com ESMTP\r\n');
socket.on('data', (data) => {
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-throughput.example.com\r\n');
socket.write('250-PIPELINING\r\n');
socket.write('250 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 === '.') {
messageCount++;
const elapsed = Date.now() - startTime;
const rate = (messageCount / elapsed) * 1000;
socket.write(`250 OK: Message ${messageCount} (${rate.toFixed(1)} msg/sec)\r\n`);
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
}
});
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
const messageCount_ = 20;
const messages = Array(messageCount_).fill(null).map((_, i) =>
new plugins.smartmail.Email({
from: 'sender@example.com',
to: [`recipient${i + 1}@example.com`],
subject: `Sequential throughput test ${i + 1}`,
text: `Testing sequential message sending - message ${i + 1}`
})
);
console.log(` Sending ${messageCount_} messages sequentially...`);
const sequentialStart = Date.now();
for (const message of messages) {
await smtpClient.sendMail(message);
}
const sequentialTime = Date.now() - sequentialStart;
const sequentialRate = (messageCount_ / sequentialTime) * 1000;
console.log(` Sequential throughput: ${sequentialRate.toFixed(2)} messages/second`);
console.log(` Total time: ${sequentialTime}ms for ${messageCount_} messages`);
expect(sequentialRate).toBeGreaterThan(1); // At least 1 message per second
expect(messageCount).toBe(messageCount_);
await testServer.server.close();
})();
// Scenario 2: Concurrent message throughput
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing concurrent message throughput`);
let messageCount = 0;
const startTime = Date.now();
const testServer = await createTestServer({
onConnection: async (socket) => {
console.log(' [Server] Client connected');
socket.write('220 concurrent.example.com ESMTP\r\n');
socket.on('data', (data) => {
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-concurrent.example.com\r\n');
socket.write('250 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 === '.') {
messageCount++;
const elapsed = Date.now() - startTime;
const rate = (messageCount / elapsed) * 1000;
socket.write(`250 OK: Message ${messageCount} (${rate.toFixed(1)} msg/sec)\r\n`);
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
}
});
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
const messageCount_ = 30;
const messages = Array(messageCount_).fill(null).map((_, i) =>
new plugins.smartmail.Email({
from: 'sender@example.com',
to: [`recipient${i + 1}@example.com`],
subject: `Concurrent throughput test ${i + 1}`,
text: `Testing concurrent message sending - message ${i + 1}`
})
);
console.log(` Sending ${messageCount_} messages concurrently...`);
const concurrentStart = Date.now();
const results = await Promise.all(
messages.map(message => smtpClient.sendMail(message))
);
const concurrentTime = Date.now() - concurrentStart;
const concurrentRate = (messageCount_ / concurrentTime) * 1000;
console.log(` Concurrent throughput: ${concurrentRate.toFixed(2)} messages/second`);
console.log(` Total time: ${concurrentTime}ms for ${messageCount_} messages`);
expect(concurrentRate).toBeGreaterThan(5); // Should be faster than sequential
expect(results.length).toBe(messageCount_);
await testServer.server.close();
})();
// Scenario 3: Pipelined message throughput
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing pipelined message throughput`);
let messageCount = 0;
const messageBuffer: string[] = [];
const testServer = await createTestServer({
onConnection: async (socket) => {
console.log(' [Server] Client connected');
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);
// Process pipelined commands
if (commands.length > 1) {
console.log(` [Server] Received ${commands.length} pipelined commands`);
}
commands.forEach(command => {
if (command.startsWith('EHLO')) {
socket.write('250-pipeline.example.com\r\n');
socket.write('250-PIPELINING\r\n');
socket.write('250 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 === '.') {
messageCount++;
socket.write(`250 OK: Pipelined message ${messageCount}\r\n`);
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
});
}
});
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pipelining: true
});
const messageCount_ = 25;
const messages = Array(messageCount_).fill(null).map((_, i) =>
new plugins.smartmail.Email({
from: 'sender@example.com',
to: [`recipient${i + 1}@example.com`],
subject: `Pipelined throughput test ${i + 1}`,
text: `Testing pipelined message sending - message ${i + 1}`
})
);
console.log(` Sending ${messageCount_} messages with pipelining...`);
const pipelineStart = Date.now();
const results = await Promise.all(
messages.map(message => smtpClient.sendMail(message))
);
const pipelineTime = Date.now() - pipelineStart;
const pipelineRate = (messageCount_ / pipelineTime) * 1000;
console.log(` Pipelined throughput: ${pipelineRate.toFixed(2)} messages/second`);
console.log(` Total time: ${pipelineTime}ms for ${messageCount_} messages`);
expect(pipelineRate).toBeGreaterThan(3); // Should benefit from pipelining
expect(results.length).toBe(messageCount_);
await testServer.server.close();
})();
// Scenario 4: Connection pooling throughput
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing connection pooling throughput`);
let connectionCount = 0;
let messageCount = 0;
const connectionMessages = new Map<any, number>();
const testServer = await createTestServer({
onConnection: async (socket) => {
connectionCount++;
const connId = connectionCount;
connectionMessages.set(socket, 0);
console.log(` [Server] Connection ${connId} established`);
socket.write('220 pool.example.com ESMTP\r\n');
socket.on('close', () => {
const msgCount = connectionMessages.get(socket) || 0;
connectionMessages.delete(socket);
console.log(` [Server] Connection ${connId} closed after ${msgCount} messages`);
});
socket.on('data', (data) => {
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-pool.example.com\r\n');
socket.write('250 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 === '.') {
messageCount++;
const msgCount = (connectionMessages.get(socket) || 0) + 1;
connectionMessages.set(socket, msgCount);
socket.write(`250 OK: Message ${messageCount} on connection ${connId}\r\n`);
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
}
});
const pooledClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pool: true,
maxConnections: 5,
maxMessages: 100
});
const messageCount_ = 40;
const messages = Array(messageCount_).fill(null).map((_, i) =>
new plugins.smartmail.Email({
from: 'sender@example.com',
to: [`recipient${i + 1}@example.com`],
subject: `Pooled throughput test ${i + 1}`,
text: `Testing connection pooling - message ${i + 1}`
})
);
console.log(` Sending ${messageCount_} messages with connection pooling...`);
const poolStart = Date.now();
const results = await Promise.all(
messages.map(message => pooledClient.sendMail(message))
);
const poolTime = Date.now() - poolStart;
const poolRate = (messageCount_ / poolTime) * 1000;
console.log(` Pooled throughput: ${poolRate.toFixed(2)} messages/second`);
console.log(` Total time: ${poolTime}ms for ${messageCount_} messages`);
console.log(` Used ${connectionCount} connections for ${messageCount_} messages`);
expect(poolRate).toBeGreaterThan(8); // Should be faster with pooling
expect(results.length).toBe(messageCount_);
expect(connectionCount).toBeGreaterThan(1);
expect(connectionCount).toBeLessThanOrEqual(5);
await pooledClient.close();
await testServer.server.close();
})();
// Scenario 5: Variable message size throughput
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing variable message size throughput`);
let totalBytes = 0;
let messageCount = 0;
const testServer = await createTestServer({
onConnection: async (socket) => {
console.log(' [Server] Client connected');
socket.write('220 variable.example.com ESMTP\r\n');
let inData = false;
let messageSize = 0;
socket.on('data', (data) => {
if (inData) {
messageSize += data.length;
if (data.toString().includes('\r\n.\r\n')) {
inData = false;
messageCount++;
totalBytes += messageSize;
const avgSize = Math.round(totalBytes / messageCount);
socket.write(`250 OK: Message ${messageCount} (${messageSize} bytes, avg: ${avgSize})\r\n`);
messageSize = 0;
}
return;
}
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-variable.example.com\r\n');
socket.write('250 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;
messageSize = 0;
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
}
});
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
// Create messages of varying sizes
const messageSizes = [
{ size: 'small', content: 'Short message' },
{ size: 'medium', content: 'Medium message: ' + 'x'.repeat(1000) },
{ size: 'large', content: 'Large message: ' + 'x'.repeat(10000) },
{ size: 'extra-large', content: 'Extra large message: ' + 'x'.repeat(50000) }
];
const messages = [];
for (let i = 0; i < 20; i++) {
const sizeType = messageSizes[i % messageSizes.length];
messages.push(new plugins.smartmail.Email({
from: 'sender@example.com',
to: [`recipient${i + 1}@example.com`],
subject: `Variable size test ${i + 1} (${sizeType.size})`,
text: sizeType.content
}));
}
console.log(` Sending ${messages.length} messages of varying sizes...`);
const variableStart = Date.now();
const results = await Promise.all(
messages.map(message => smtpClient.sendMail(message))
);
const variableTime = Date.now() - variableStart;
const variableRate = (messages.length / variableTime) * 1000;
const bytesPerSecond = (totalBytes / variableTime) * 1000;
console.log(` Variable size throughput: ${variableRate.toFixed(2)} messages/second`);
console.log(` Data throughput: ${(bytesPerSecond / 1024).toFixed(2)} KB/second`);
console.log(` Average message size: ${Math.round(totalBytes / messages.length)} bytes`);
expect(variableRate).toBeGreaterThan(2);
expect(results.length).toBe(messages.length);
await testServer.server.close();
})();
// Scenario 6: Sustained throughput over time
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing sustained throughput over time`);
let messageCount = 0;
const timestamps: number[] = [];
const testServer = await createTestServer({
onConnection: async (socket) => {
console.log(' [Server] Client connected');
socket.write('220 sustained.example.com ESMTP\r\n');
socket.on('data', (data) => {
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-sustained.example.com\r\n');
socket.write('250 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 === '.') {
messageCount++;
timestamps.push(Date.now());
socket.write(`250 OK: Sustained message ${messageCount}\r\n`);
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
}
});
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pool: true,
maxConnections: 3
});
const totalMessages = 30;
const batchSize = 5;
const batchDelay = 500; // 500ms between batches
console.log(` Sending ${totalMessages} messages in batches of ${batchSize}...`);
const sustainedStart = Date.now();
for (let batch = 0; batch < totalMessages / batchSize; batch++) {
const batchMessages = Array(batchSize).fill(null).map((_, i) => {
const msgIndex = batch * batchSize + i + 1;
return new plugins.smartmail.Email({
from: 'sender@example.com',
to: [`recipient${msgIndex}@example.com`],
subject: `Sustained test batch ${batch + 1} message ${i + 1}`,
text: `Testing sustained throughput - message ${msgIndex}`
});
});
// Send batch concurrently
await Promise.all(
batchMessages.map(message => smtpClient.sendMail(message))
);
console.log(` Batch ${batch + 1} completed (${(batch + 1) * batchSize} messages total)`);
// Delay between batches (except last)
if (batch < (totalMessages / batchSize) - 1) {
await new Promise(resolve => setTimeout(resolve, batchDelay));
}
}
const sustainedTime = Date.now() - sustainedStart;
const sustainedRate = (totalMessages / sustainedTime) * 1000;
// Calculate rate stability
const windowSize = 5;
const rates: number[] = [];
for (let i = windowSize; i < timestamps.length; i++) {
const windowStart = timestamps[i - windowSize];
const windowEnd = timestamps[i];
const windowRate = (windowSize / (windowEnd - windowStart)) * 1000;
rates.push(windowRate);
}
const avgRate = rates.reduce((a, b) => a + b, 0) / rates.length;
const rateVariance = rates.reduce((acc, rate) => acc + Math.pow(rate - avgRate, 2), 0) / rates.length;
const rateStdDev = Math.sqrt(rateVariance);
console.log(` Sustained throughput: ${sustainedRate.toFixed(2)} messages/second`);
console.log(` Average windowed rate: ${avgRate.toFixed(2)} ± ${rateStdDev.toFixed(2)} msg/sec`);
console.log(` Rate stability: ${((1 - rateStdDev / avgRate) * 100).toFixed(1)}%`);
expect(sustainedRate).toBeGreaterThan(3);
expect(rateStdDev / avgRate).toBeLessThan(0.5); // Coefficient of variation < 50%
await smtpClient.close();
await testServer.server.close();
})();
console.log(`\n${testId}: All ${scenarioCount} message throughput scenarios tested ✓`);
});

View File

@ -0,0 +1,641 @@
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-03: should optimize memory usage', async (tools) => {
const testId = 'CPERF-03-memory-usage';
console.log(`\n${testId}: Testing memory usage optimization...`);
let scenarioCount = 0;
// Helper function to get memory usage
const getMemoryUsage = () => {
if (process.memoryUsage) {
const usage = process.memoryUsage();
return {
heapUsed: usage.heapUsed,
heapTotal: usage.heapTotal,
external: usage.external,
rss: usage.rss
};
}
return null;
};
// Helper function to format bytes
const formatBytes = (bytes: number) => {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
};
// Scenario 1: Memory usage during connection lifecycle
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing memory usage during connection lifecycle`);
const testServer = await createTestServer({
onConnection: async (socket) => {
console.log(' [Server] Client connected');
socket.write('220 memory.example.com ESMTP\r\n');
socket.on('data', (data) => {
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-memory.example.com\r\n');
socket.write('250 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();
}
});
}
});
// Force garbage collection if available
if (global.gc) {
global.gc();
}
const beforeConnection = getMemoryUsage();
console.log(` Memory before connection: ${formatBytes(beforeConnection?.heapUsed || 0)}`);
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
const afterConnection = getMemoryUsage();
console.log(` Memory after client creation: ${formatBytes(afterConnection?.heapUsed || 0)}`);
// Send a test email
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'Memory usage test',
text: 'Testing memory usage during email sending'
});
await smtpClient.sendMail(email);
const afterSending = getMemoryUsage();
console.log(` Memory after sending: ${formatBytes(afterSending?.heapUsed || 0)}`);
// Close connection
if (smtpClient.close) {
await smtpClient.close();
}
// Force garbage collection if available
if (global.gc) {
global.gc();
await new Promise(resolve => setTimeout(resolve, 100));
}
const afterClose = getMemoryUsage();
console.log(` Memory after close: ${formatBytes(afterClose?.heapUsed || 0)}`);
// Check for memory leaks
const memoryIncrease = (afterClose?.heapUsed || 0) - (beforeConnection?.heapUsed || 0);
console.log(` Net memory change: ${formatBytes(Math.abs(memoryIncrease))} ${memoryIncrease >= 0 ? 'increase' : 'decrease'}`);
// Memory increase should be minimal after cleanup
expect(memoryIncrease).toBeLessThan(1024 * 1024); // Less than 1MB increase
await testServer.server.close();
})();
// Scenario 2: Memory usage with large messages
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing memory usage with large messages`);
const testServer = await createTestServer({
onConnection: async (socket) => {
console.log(' [Server] Client connected');
socket.write('220 large.example.com ESMTP\r\n');
let inData = false;
let messageSize = 0;
socket.on('data', (data) => {
if (inData) {
messageSize += data.length;
if (data.toString().includes('\r\n.\r\n')) {
inData = false;
console.log(` [Server] Received message: ${formatBytes(messageSize)}`);
socket.write('250 OK\r\n');
messageSize = 0;
}
return;
}
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-large.example.com\r\n');
socket.write('250-SIZE 52428800\r\n'); // 50MB limit
socket.write('250 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;
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
}
});
if (global.gc) {
global.gc();
}
const beforeLarge = getMemoryUsage();
console.log(` Memory before large message: ${formatBytes(beforeLarge?.heapUsed || 0)}`);
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
// Create messages of increasing sizes
const messageSizes = [
{ name: '1KB', size: 1024 },
{ name: '10KB', size: 10 * 1024 },
{ name: '100KB', size: 100 * 1024 },
{ name: '1MB', size: 1024 * 1024 },
{ name: '5MB', size: 5 * 1024 * 1024 }
];
for (const msgSize of messageSizes) {
console.log(` Testing ${msgSize.name} message...`);
const beforeMessage = getMemoryUsage();
// Create large content
const largeContent = 'x'.repeat(msgSize.size);
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: `Large message test - ${msgSize.name}`,
text: largeContent
});
const duringCreation = getMemoryUsage();
const creationIncrease = (duringCreation?.heapUsed || 0) - (beforeMessage?.heapUsed || 0);
console.log(` Memory increase during creation: ${formatBytes(creationIncrease)}`);
await smtpClient.sendMail(email);
const afterSending = getMemoryUsage();
const sendingIncrease = (afterSending?.heapUsed || 0) - (beforeMessage?.heapUsed || 0);
console.log(` Memory increase after sending: ${formatBytes(sendingIncrease)}`);
// Clear reference to email
// email = null; // Can't reassign const
// Memory usage shouldn't grow linearly with message size
// due to streaming or buffering optimizations
expect(sendingIncrease).toBeLessThan(msgSize.size * 2); // At most 2x the message size
}
if (global.gc) {
global.gc();
await new Promise(resolve => setTimeout(resolve, 100));
}
const afterLarge = getMemoryUsage();
console.log(` Memory after large messages: ${formatBytes(afterLarge?.heapUsed || 0)}`);
await testServer.server.close();
})();
// Scenario 3: Memory usage with multiple concurrent connections
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing memory usage with concurrent connections`);
let connectionCount = 0;
const testServer = await createTestServer({
onConnection: async (socket) => {
connectionCount++;
const connId = connectionCount;
console.log(` [Server] Connection ${connId} established`);
socket.write('220 concurrent.example.com ESMTP\r\n');
socket.on('close', () => {
console.log(` [Server] Connection ${connId} closed`);
});
socket.on('data', (data) => {
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-concurrent.example.com\r\n');
socket.write('250 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();
}
});
}
});
if (global.gc) {
global.gc();
}
const beforeConcurrent = getMemoryUsage();
console.log(` Memory before concurrent connections: ${formatBytes(beforeConcurrent?.heapUsed || 0)}`);
const concurrentCount = 10;
const clients: any[] = [];
// Create multiple concurrent clients
for (let i = 0; i < concurrentCount; i++) {
const client = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
clients.push(client);
}
const afterCreation = getMemoryUsage();
const creationIncrease = (afterCreation?.heapUsed || 0) - (beforeConcurrent?.heapUsed || 0);
console.log(` Memory after creating ${concurrentCount} clients: ${formatBytes(creationIncrease)}`);
// Send emails concurrently
const promises = clients.map((client, i) => {
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: [`recipient${i + 1}@example.com`],
subject: `Concurrent memory test ${i + 1}`,
text: `Testing concurrent memory usage - client ${i + 1}`
});
return client.sendMail(email);
});
await Promise.all(promises);
const afterSending = getMemoryUsage();
const sendingIncrease = (afterSending?.heapUsed || 0) - (beforeConcurrent?.heapUsed || 0);
console.log(` Memory after concurrent sending: ${formatBytes(sendingIncrease)}`);
// Close all clients
await Promise.all(clients.map(client => {
if (client.close) {
return client.close();
}
return Promise.resolve();
}));
if (global.gc) {
global.gc();
await new Promise(resolve => setTimeout(resolve, 100));
}
const afterClose = getMemoryUsage();
const finalIncrease = (afterClose?.heapUsed || 0) - (beforeConcurrent?.heapUsed || 0);
console.log(` Memory after closing all connections: ${formatBytes(finalIncrease)}`);
// Memory per connection should be reasonable
const memoryPerConnection = creationIncrease / concurrentCount;
console.log(` Average memory per connection: ${formatBytes(memoryPerConnection)}`);
expect(memoryPerConnection).toBeLessThan(512 * 1024); // Less than 512KB per connection
expect(finalIncrease).toBeLessThan(creationIncrease * 0.5); // Significant cleanup
await testServer.server.close();
})();
// Scenario 4: Memory usage with connection pooling
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing memory usage with connection pooling`);
let connectionCount = 0;
let maxConnections = 0;
const testServer = await createTestServer({
onConnection: async (socket) => {
connectionCount++;
maxConnections = Math.max(maxConnections, connectionCount);
console.log(` [Server] Connection established (total: ${connectionCount}, max: ${maxConnections})`);
socket.write('220 pool.example.com ESMTP\r\n');
socket.on('close', () => {
connectionCount--;
});
socket.on('data', (data) => {
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-pool.example.com\r\n');
socket.write('250 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();
}
});
}
});
if (global.gc) {
global.gc();
}
const beforePool = getMemoryUsage();
console.log(` Memory before pooling: ${formatBytes(beforePool?.heapUsed || 0)}`);
const pooledClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pool: true,
maxConnections: 5,
maxMessages: 100
});
const afterPoolCreation = getMemoryUsage();
const poolCreationIncrease = (afterPoolCreation?.heapUsed || 0) - (beforePool?.heapUsed || 0);
console.log(` Memory after pool creation: ${formatBytes(poolCreationIncrease)}`);
// Send many emails through the pool
const emailCount = 30;
const emails = Array(emailCount).fill(null).map((_, i) =>
new plugins.smartmail.Email({
from: 'sender@example.com',
to: [`recipient${i + 1}@example.com`],
subject: `Pooled memory test ${i + 1}`,
text: `Testing pooled memory usage - email ${i + 1}`
})
);
await Promise.all(emails.map(email => pooledClient.sendMail(email)));
const afterPoolSending = getMemoryUsage();
const poolSendingIncrease = (afterPoolSending?.heapUsed || 0) - (beforePool?.heapUsed || 0);
console.log(` Memory after sending ${emailCount} emails: ${formatBytes(poolSendingIncrease)}`);
console.log(` Maximum concurrent connections: ${maxConnections}`);
await pooledClient.close();
if (global.gc) {
global.gc();
await new Promise(resolve => setTimeout(resolve, 100));
}
const afterPoolClose = getMemoryUsage();
const poolFinalIncrease = (afterPoolClose?.heapUsed || 0) - (beforePool?.heapUsed || 0);
console.log(` Memory after pool close: ${formatBytes(poolFinalIncrease)}`);
// Pooling should use fewer connections and thus less memory
expect(maxConnections).toBeLessThanOrEqual(5);
expect(poolFinalIncrease).toBeLessThan(2 * 1024 * 1024); // Less than 2MB final increase
await testServer.server.close();
})();
// Scenario 5: Memory usage with attachments
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing memory usage with attachments`);
const testServer = await createTestServer({
onConnection: async (socket) => {
console.log(' [Server] Client connected');
socket.write('220 attachments.example.com ESMTP\r\n');
let inData = false;
let totalSize = 0;
socket.on('data', (data) => {
if (inData) {
totalSize += data.length;
if (data.toString().includes('\r\n.\r\n')) {
inData = false;
console.log(` [Server] Received email with attachments: ${formatBytes(totalSize)}`);
socket.write('250 OK\r\n');
totalSize = 0;
}
return;
}
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-attachments.example.com\r\n');
socket.write('250 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;
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
}
});
if (global.gc) {
global.gc();
}
const beforeAttachments = getMemoryUsage();
console.log(` Memory before attachments: ${formatBytes(beforeAttachments?.heapUsed || 0)}`);
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
// Test with different attachment sizes
const attachmentSizes = [
{ name: 'small', size: 10 * 1024 }, // 10KB
{ name: 'medium', size: 100 * 1024 }, // 100KB
{ name: 'large', size: 1024 * 1024 } // 1MB
];
for (const attachSize of attachmentSizes) {
console.log(` Testing ${attachSize.name} attachment (${formatBytes(attachSize.size)})...`);
const beforeAttachment = getMemoryUsage();
// Create binary attachment data
const attachmentData = Buffer.alloc(attachSize.size);
for (let i = 0; i < attachmentData.length; i++) {
attachmentData[i] = i % 256;
}
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: `Attachment memory test - ${attachSize.name}`,
text: `Testing memory usage with ${attachSize.name} attachment`,
attachments: [{
filename: `${attachSize.name}-file.bin`,
content: attachmentData
}]
});
const afterCreation = getMemoryUsage();
const creationIncrease = (afterCreation?.heapUsed || 0) - (beforeAttachment?.heapUsed || 0);
console.log(` Memory increase during email creation: ${formatBytes(creationIncrease)}`);
await smtpClient.sendMail(email);
const afterSending = getMemoryUsage();
const sendingIncrease = (afterSending?.heapUsed || 0) - (beforeAttachment?.heapUsed || 0);
console.log(` Memory increase after sending: ${formatBytes(sendingIncrease)}`);
// Memory usage should be efficient (not holding multiple copies)
expect(creationIncrease).toBeLessThan(attachSize.size * 3); // At most 3x (original + base64 + overhead)
}
await testServer.server.close();
})();
// Scenario 6: Memory leak detection
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing for memory leaks`);
const testServer = await createTestServer({
onConnection: async (socket) => {
socket.write('220 leak-test.example.com ESMTP\r\n');
socket.on('data', (data) => {
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-leak-test.example.com\r\n');
socket.write('250 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();
}
});
}
});
// Perform multiple iterations to detect leaks
const iterations = 5;
const memoryMeasurements: number[] = [];
for (let iteration = 0; iteration < iterations; iteration++) {
console.log(` Iteration ${iteration + 1}/${iterations}...`);
if (global.gc) {
global.gc();
await new Promise(resolve => setTimeout(resolve, 100));
}
const beforeIteration = getMemoryUsage();
// Create and use SMTP client
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
// Send multiple emails
const emails = Array(10).fill(null).map((_, i) =>
new plugins.smartmail.Email({
from: 'sender@example.com',
to: [`recipient${i + 1}@example.com`],
subject: `Leak test iteration ${iteration + 1} email ${i + 1}`,
text: `Testing for memory leaks - iteration ${iteration + 1}, email ${i + 1}`
})
);
await Promise.all(emails.map(email => smtpClient.sendMail(email)));
// Close client
if (smtpClient.close) {
await smtpClient.close();
}
if (global.gc) {
global.gc();
await new Promise(resolve => setTimeout(resolve, 100));
}
const afterIteration = getMemoryUsage();
const iterationIncrease = (afterIteration?.heapUsed || 0) - (beforeIteration?.heapUsed || 0);
memoryMeasurements.push(iterationIncrease);
console.log(` Memory change: ${formatBytes(iterationIncrease)}`);
}
// Analyze memory trend
const avgIncrease = memoryMeasurements.reduce((a, b) => a + b, 0) / memoryMeasurements.length;
const maxIncrease = Math.max(...memoryMeasurements);
const minIncrease = Math.min(...memoryMeasurements);
console.log(` Memory leak analysis:`);
console.log(` Average increase: ${formatBytes(avgIncrease)}`);
console.log(` Min increase: ${formatBytes(minIncrease)}`);
console.log(` Max increase: ${formatBytes(maxIncrease)}`);
console.log(` Range: ${formatBytes(maxIncrease - minIncrease)}`);
// Check for significant memory leaks
// Memory should not consistently increase across iterations
expect(avgIncrease).toBeLessThan(512 * 1024); // Less than 512KB average increase
expect(maxIncrease - minIncrease).toBeLessThan(1024 * 1024); // Range less than 1MB
await testServer.server.close();
})();
console.log(`\n${testId}: All ${scenarioCount} memory usage scenarios tested ✓`);
});

View File

@ -0,0 +1,670 @@
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-04: should optimize CPU utilization', async (tools) => {
const testId = 'CPERF-04-cpu-utilization';
console.log(`\n${testId}: Testing CPU utilization optimization...`);
let scenarioCount = 0;
// Helper function to measure CPU usage (simplified)
const measureCpuUsage = async (duration: number) => {
const start = process.cpuUsage();
const startTime = Date.now();
await new Promise(resolve => setTimeout(resolve, duration));
const end = process.cpuUsage(start);
const elapsed = Date.now() - startTime;
return {
user: end.user / 1000, // Convert to milliseconds
system: end.system / 1000,
total: (end.user + end.system) / 1000,
elapsed,
userPercent: (end.user / 1000) / elapsed * 100,
systemPercent: (end.system / 1000) / elapsed * 100,
totalPercent: ((end.user + end.system) / 1000) / elapsed * 100
};
};
// Scenario 1: CPU usage during connection establishment
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing CPU usage during connection establishment`);
const testServer = await createTestServer({
onConnection: async (socket) => {
console.log(' [Server] Client connected');
socket.write('220 cpu.example.com ESMTP\r\n');
socket.on('data', (data) => {
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-cpu.example.com\r\n');
socket.write('250 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();
}
});
}
});
// Measure CPU during multiple connection establishments
const connectionCount = 10;
console.log(` Establishing ${connectionCount} connections...`);
const startCpu = process.cpuUsage();
const startTime = Date.now();
const clients: any[] = [];
for (let i = 0; i < connectionCount; i++) {
const client = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
clients.push(client);
// Verify connection
try {
await client.verify();
} catch (error) {
// Connection verification might not be available
}
}
const endCpu = process.cpuUsage(startCpu);
const elapsed = Date.now() - startTime;
const cpuUsage = {
user: endCpu.user / 1000,
system: endCpu.system / 1000,
total: (endCpu.user + endCpu.system) / 1000,
userPercent: (endCpu.user / 1000) / elapsed * 100,
systemPercent: (endCpu.system / 1000) / elapsed * 100,
totalPercent: ((endCpu.user + endCpu.system) / 1000) / elapsed * 100
};
console.log(` Connection establishment CPU usage:`);
console.log(` Total time: ${elapsed}ms`);
console.log(` User CPU: ${cpuUsage.user.toFixed(1)}ms (${cpuUsage.userPercent.toFixed(1)}%)`);
console.log(` System CPU: ${cpuUsage.system.toFixed(1)}ms (${cpuUsage.systemPercent.toFixed(1)}%)`);
console.log(` Total CPU: ${cpuUsage.total.toFixed(1)}ms (${cpuUsage.totalPercent.toFixed(1)}%)`);
console.log(` CPU per connection: ${(cpuUsage.total / connectionCount).toFixed(1)}ms`);
// Close all connections
await Promise.all(clients.map(client => {
if (client.close) {
return client.close();
}
return Promise.resolve();
}));
// CPU usage should be reasonable
expect(cpuUsage.totalPercent).toBeLessThan(50); // Less than 50% CPU usage
expect(cpuUsage.total / connectionCount).toBeLessThan(50); // Less than 50ms CPU per connection
await testServer.server.close();
})();
// Scenario 2: CPU usage during message composition
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing CPU usage during message composition`);
const testServer = await createTestServer({
onConnection: async (socket) => {
socket.write('220 composition.example.com ESMTP\r\n');
socket.on('data', (data) => {
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-composition.example.com\r\n');
socket.write('250 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 smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
// Test different message compositions
const compositionTests = [
{
name: 'Simple text',
email: new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'Simple text message',
text: 'This is a simple text message.'
})
},
{
name: 'HTML with formatting',
email: new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'HTML message',
html: '<h1>HTML Message</h1><p>This is an <strong>HTML</strong> message with <em>formatting</em>.</p>'
})
},
{
name: 'Multipart with text and HTML',
email: new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'Multipart message',
text: 'Plain text version',
html: '<p>HTML version</p>'
})
},
{
name: 'Message with small attachment',
email: new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'Message with attachment',
text: 'Message with small attachment',
attachments: [{
filename: 'test.txt',
content: 'This is a test attachment with some content.'
}]
})
}
];
for (const test of compositionTests) {
console.log(` Testing ${test.name}...`);
const startCpu = process.cpuUsage();
const startTime = Date.now();
await smtpClient.sendMail(test.email);
const endCpu = process.cpuUsage(startCpu);
const elapsed = Date.now() - startTime;
const cpuUsage = {
user: endCpu.user / 1000,
system: endCpu.system / 1000,
total: (endCpu.user + endCpu.system) / 1000,
totalPercent: ((endCpu.user + endCpu.system) / 1000) / elapsed * 100
};
console.log(` ${test.name}: ${cpuUsage.total.toFixed(1)}ms CPU (${cpuUsage.totalPercent.toFixed(1)}%)`);
// CPU usage should be efficient for message composition
expect(cpuUsage.totalPercent).toBeLessThan(25); // Less than 25% CPU
expect(cpuUsage.total).toBeLessThan(100); // Less than 100ms CPU time
}
await testServer.server.close();
})();
// Scenario 3: CPU usage with concurrent operations
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing CPU usage with concurrent operations`);
const testServer = await createTestServer({
onConnection: async (socket) => {
socket.write('220 concurrent.example.com ESMTP\r\n');
socket.on('data', (data) => {
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-concurrent.example.com\r\n');
socket.write('250 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 smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
// Test sequential vs concurrent CPU usage
const messageCount = 20;
const emails = Array(messageCount).fill(null).map((_, i) =>
new plugins.smartmail.Email({
from: 'sender@example.com',
to: [`recipient${i + 1}@example.com`],
subject: `CPU test message ${i + 1}`,
text: `Testing CPU utilization - message ${i + 1}`
})
);
// Sequential sending
console.log(` Sequential sending of ${messageCount} messages...`);
const sequentialStartCpu = process.cpuUsage();
const sequentialStartTime = Date.now();
for (const email of emails) {
await smtpClient.sendMail(email);
}
const sequentialEndCpu = process.cpuUsage(sequentialStartCpu);
const sequentialElapsed = Date.now() - sequentialStartTime;
const sequentialCpu = {
total: (sequentialEndCpu.user + sequentialEndCpu.system) / 1000,
totalPercent: ((sequentialEndCpu.user + sequentialEndCpu.system) / 1000) / sequentialElapsed * 100
};
console.log(` Sequential: ${sequentialCpu.total.toFixed(1)}ms CPU (${sequentialCpu.totalPercent.toFixed(1)}%)`);
console.log(` Per message: ${(sequentialCpu.total / messageCount).toFixed(1)}ms CPU`);
// Concurrent sending (new emails)
const concurrentEmails = Array(messageCount).fill(null).map((_, i) =>
new plugins.smartmail.Email({
from: 'sender@example.com',
to: [`concurrent${i + 1}@example.com`],
subject: `Concurrent CPU test ${i + 1}`,
text: `Testing concurrent CPU utilization - message ${i + 1}`
})
);
console.log(` Concurrent sending of ${messageCount} messages...`);
const concurrentStartCpu = process.cpuUsage();
const concurrentStartTime = Date.now();
await Promise.all(concurrentEmails.map(email => smtpClient.sendMail(email)));
const concurrentEndCpu = process.cpuUsage(concurrentStartCpu);
const concurrentElapsed = Date.now() - concurrentStartTime;
const concurrentCpu = {
total: (concurrentEndCpu.user + concurrentEndCpu.system) / 1000,
totalPercent: ((concurrentEndCpu.user + concurrentEndCpu.system) / 1000) / concurrentElapsed * 100
};
console.log(` Concurrent: ${concurrentCpu.total.toFixed(1)}ms CPU (${concurrentCpu.totalPercent.toFixed(1)}%)`);
console.log(` Per message: ${(concurrentCpu.total / messageCount).toFixed(1)}ms CPU`);
// Compare efficiency
const efficiency = sequentialCpu.total / concurrentCpu.total;
console.log(` CPU efficiency ratio: ${efficiency.toFixed(2)}x`);
// Concurrent should be more CPU efficient (higher throughput)
expect(concurrentElapsed).toBeLessThan(sequentialElapsed);
expect(concurrentCpu.totalPercent).toBeLessThan(80); // Less than 80% CPU
await testServer.server.close();
})();
// Scenario 4: CPU usage with large attachments
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing CPU usage with large attachments`);
const testServer = await createTestServer({
onConnection: async (socket) => {
socket.write('220 attachments.example.com ESMTP\r\n');
let inData = false;
let dataSize = 0;
socket.on('data', (data) => {
if (inData) {
dataSize += data.length;
if (data.toString().includes('\r\n.\r\n')) {
inData = false;
console.log(` [Server] Received ${(dataSize / 1024).toFixed(1)}KB`);
socket.write('250 OK\r\n');
dataSize = 0;
}
return;
}
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-attachments.example.com\r\n');
socket.write('250 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;
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
}
});
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
// Test different attachment sizes
const attachmentSizes = [
{ name: '10KB', size: 10 * 1024 },
{ name: '100KB', size: 100 * 1024 },
{ name: '1MB', size: 1024 * 1024 }
];
for (const attachSize of attachmentSizes) {
console.log(` Testing ${attachSize.name} attachment...`);
// Create binary attachment
const attachmentData = Buffer.alloc(attachSize.size);
for (let i = 0; i < attachmentData.length; i++) {
attachmentData[i] = i % 256;
}
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: `CPU test with ${attachSize.name} attachment`,
text: `Testing CPU usage with ${attachSize.name} attachment`,
attachments: [{
filename: `${attachSize.name}-file.bin`,
content: attachmentData
}]
});
const startCpu = process.cpuUsage();
const startTime = Date.now();
await smtpClient.sendMail(email);
const endCpu = process.cpuUsage(startCpu);
const elapsed = Date.now() - startTime;
const cpuUsage = {
total: (endCpu.user + endCpu.system) / 1000,
totalPercent: ((endCpu.user + endCpu.system) / 1000) / elapsed * 100
};
const cpuPerKB = cpuUsage.total / (attachSize.size / 1024);
console.log(` ${attachSize.name}: ${cpuUsage.total.toFixed(1)}ms CPU (${cpuUsage.totalPercent.toFixed(1)}%)`);
console.log(` CPU per KB: ${cpuPerKB.toFixed(3)}ms/KB`);
// CPU usage should scale reasonably with attachment size
expect(cpuUsage.totalPercent).toBeLessThan(50);
expect(cpuPerKB).toBeLessThan(1); // Less than 1ms CPU per KB
}
await testServer.server.close();
})();
// Scenario 5: CPU usage with connection pooling
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing CPU usage with connection pooling`);
let connectionCount = 0;
const testServer = await createTestServer({
onConnection: async (socket) => {
connectionCount++;
socket.write('220 pool.example.com ESMTP\r\n');
socket.on('data', (data) => {
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-pool.example.com\r\n');
socket.write('250 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();
}
});
}
});
// Compare individual connections vs pooled
const messageCount = 15;
// Individual connections
console.log(` Testing ${messageCount} individual connections...`);
connectionCount = 0;
const individualStartCpu = process.cpuUsage();
const individualStartTime = Date.now();
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 connection - message ${i + 1}`
});
await client.sendMail(email);
if (client.close) {
await client.close();
}
}
const individualEndCpu = process.cpuUsage(individualStartCpu);
const individualElapsed = Date.now() - individualStartTime;
const individualConnections = connectionCount;
const individualCpu = {
total: (individualEndCpu.user + individualEndCpu.system) / 1000,
totalPercent: ((individualEndCpu.user + individualEndCpu.system) / 1000) / individualElapsed * 100
};
console.log(` Individual: ${individualCpu.total.toFixed(1)}ms CPU, ${individualConnections} connections`);
// Pooled connections
console.log(` Testing pooled connections...`);
connectionCount = 0;
const pooledStartCpu = process.cpuUsage();
const pooledStartTime = Date.now();
const pooledClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pool: true,
maxConnections: 3,
maxMessages: 100
});
const pooledEmails = Array(messageCount).fill(null).map((_, i) =>
new plugins.smartmail.Email({
from: 'sender@example.com',
to: [`pooled${i + 1}@example.com`],
subject: `Pooled connection test ${i + 1}`,
text: `Testing pooled connection - message ${i + 1}`
})
);
await Promise.all(pooledEmails.map(email => pooledClient.sendMail(email)));
await pooledClient.close();
const pooledEndCpu = process.cpuUsage(pooledStartCpu);
const pooledElapsed = Date.now() - pooledStartTime;
const pooledConnections = connectionCount;
const pooledCpu = {
total: (pooledEndCpu.user + pooledEndCpu.system) / 1000,
totalPercent: ((pooledEndCpu.user + pooledEndCpu.system) / 1000) / pooledElapsed * 100
};
console.log(` Pooled: ${pooledCpu.total.toFixed(1)}ms CPU, ${pooledConnections} connections`);
const cpuEfficiency = individualCpu.total / pooledCpu.total;
const connectionEfficiency = individualConnections / pooledConnections;
console.log(` CPU efficiency: ${cpuEfficiency.toFixed(2)}x`);
console.log(` Connection efficiency: ${connectionEfficiency.toFixed(2)}x`);
// Pooling should be more CPU efficient
expect(pooledCpu.total).toBeLessThan(individualCpu.total);
expect(pooledConnections).toBeLessThan(individualConnections);
expect(cpuEfficiency).toBeGreaterThan(1.2); // At least 20% more efficient
await testServer.server.close();
})();
// Scenario 6: CPU usage under stress
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing CPU usage under stress`);
let messageCount = 0;
const testServer = await createTestServer({
onConnection: async (socket) => {
socket.write('220 stress.example.com ESMTP\r\n');
socket.on('data', (data) => {
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-stress.example.com\r\n');
socket.write('250 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 === '.') {
messageCount++;
socket.write(`250 OK: Message ${messageCount}\r\n`);
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
}
});
const pooledClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pool: true,
maxConnections: 5,
maxMessages: 100
});
// Stress test with many messages
const stressMessageCount = 50;
const stressEmails = Array(stressMessageCount).fill(null).map((_, i) =>
new plugins.smartmail.Email({
from: 'sender@example.com',
to: [`stress${i + 1}@example.com`],
subject: `Stress test message ${i + 1}`,
text: `Testing CPU under stress - message ${i + 1}`
})
);
console.log(` Stress testing with ${stressMessageCount} concurrent messages...`);
const stressStartCpu = process.cpuUsage();
const stressStartTime = Date.now();
// Monitor CPU usage during stress test
const cpuSamples: number[] = [];
const sampleInterval = setInterval(() => {
const currentCpu = process.cpuUsage(stressStartCpu);
const currentElapsed = Date.now() - stressStartTime;
const currentPercent = ((currentCpu.user + currentCpu.system) / 1000) / currentElapsed * 100;
cpuSamples.push(currentPercent);
}, 100);
await Promise.all(stressEmails.map(email => pooledClient.sendMail(email)));
clearInterval(sampleInterval);
const stressEndCpu = process.cpuUsage(stressStartCpu);
const stressElapsed = Date.now() - stressStartTime;
const stressCpu = {
total: (stressEndCpu.user + stressEndCpu.system) / 1000,
totalPercent: ((stressEndCpu.user + stressEndCpu.system) / 1000) / stressElapsed * 100
};
const maxCpuSample = Math.max(...cpuSamples);
const avgCpuSample = cpuSamples.reduce((a, b) => a + b, 0) / cpuSamples.length;
console.log(` Stress test results:`);
console.log(` Total CPU: ${stressCpu.total.toFixed(1)}ms (${stressCpu.totalPercent.toFixed(1)}%)`);
console.log(` Peak CPU: ${maxCpuSample.toFixed(1)}%`);
console.log(` Average CPU: ${avgCpuSample.toFixed(1)}%`);
console.log(` Messages per CPU ms: ${(stressMessageCount / stressCpu.total).toFixed(2)}`);
console.log(` Throughput: ${(stressMessageCount / stressElapsed * 1000).toFixed(1)} msg/sec`);
await pooledClient.close();
// CPU usage should remain reasonable under stress
expect(stressCpu.totalPercent).toBeLessThan(90); // Less than 90% CPU
expect(maxCpuSample).toBeLessThan(100); // No sustained 100% CPU
expect(stressMessageCount / stressCpu.total).toBeGreaterThan(0.1); // At least 0.1 msg/ms efficiency
await testServer.server.close();
})();
console.log(`\n${testId}: All ${scenarioCount} CPU utilization scenarios tested ✓`);
});

View File

@ -0,0 +1,686 @@
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 ✓`);
});

View File

@ -0,0 +1,769 @@
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-06: should implement efficient caching strategies', async (tools) => {
const testId = 'CPERF-06-caching-strategies';
console.log(`\n${testId}: Testing caching strategies performance...`);
let scenarioCount = 0;
// Scenario 1: DNS resolution caching
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing DNS resolution caching`);
let dnsLookupCount = 0;
const dnsCache = new Map<string, { address: string; timestamp: number }>();
const testServer = await createTestServer({
onConnection: async (socket) => {
console.log(' [Server] Client connected');
socket.write('220 dns-cache.example.com ESMTP\r\n');
socket.on('data', (data) => {
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-dns-cache.example.com\r\n');
socket.write('250 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();
}
});
}
});
// Simulate DNS lookup caching
const mockDnsLookup = (hostname: string) => {
const cached = dnsCache.get(hostname);
const now = Date.now();
if (cached && (now - cached.timestamp) < 300000) { // 5 minute cache
console.log(` [DNS] Cache hit for ${hostname}`);
return cached.address;
}
dnsLookupCount++;
console.log(` [DNS] Cache miss for ${hostname} (lookup #${dnsLookupCount})`);
const address = testServer.hostname; // Mock resolution
dnsCache.set(hostname, { address, timestamp: now });
return address;
};
// Test multiple connections to same host
const connectionCount = 10;
console.log(` Creating ${connectionCount} connections to test DNS caching...`);
const clients: any[] = [];
const startTime = Date.now();
for (let i = 0; i < connectionCount; i++) {
// Simulate DNS lookup
const resolvedHost = mockDnsLookup(testServer.hostname);
const client = createSmtpClient({
host: resolvedHost,
port: testServer.port,
secure: false
});
clients.push(client);
}
// Send emails through cached connections
const emails = clients.map((client, i) =>
new plugins.smartmail.Email({
from: 'sender@example.com',
to: [`recipient${i + 1}@example.com`],
subject: `DNS cache test ${i + 1}`,
text: `Testing DNS resolution caching - connection ${i + 1}`
})
);
await Promise.all(emails.map((email, i) => clients[i].sendMail(email)));
const totalTime = Date.now() - startTime;
const cacheHitRate = ((connectionCount - dnsLookupCount) / connectionCount) * 100;
console.log(` DNS lookups performed: ${dnsLookupCount}/${connectionCount}`);
console.log(` Cache hit rate: ${cacheHitRate.toFixed(1)}%`);
console.log(` Total time: ${totalTime}ms`);
console.log(` Time per connection: ${(totalTime / connectionCount).toFixed(1)}ms`);
// Close clients
await Promise.all(clients.map(client => {
if (client.close) {
return client.close();
}
return Promise.resolve();
}));
// DNS caching should reduce lookups
expect(dnsLookupCount).toBeLessThan(connectionCount);
expect(cacheHitRate).toBeGreaterThan(50); // At least 50% cache hit rate
await testServer.server.close();
})();
// Scenario 2: Connection pool caching
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing connection pool caching`);
let connectionCount = 0;
let connectionReuse = 0;
const connectionPool = new Map<string, { connection: any; lastUsed: number; messageCount: number }>();
const testServer = await createTestServer({
onConnection: async (socket) => {
connectionCount++;
const connId = connectionCount;
console.log(` [Server] New connection ${connId} created`);
socket.write('220 pool-cache.example.com ESMTP\r\n');
let messageCount = 0;
socket.on('close', () => {
console.log(` [Server] Connection ${connId} closed after ${messageCount} messages`);
});
socket.on('data', (data) => {
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-pool-cache.example.com\r\n');
socket.write('250 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 === '.') {
messageCount++;
socket.write(`250 OK: Message ${messageCount} on connection ${connId}\r\n`);
} else if (command === 'RSET') {
socket.write('250 OK\r\n');
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
}
});
// Mock connection pool management
const getPooledConnection = (key: string) => {
const cached = connectionPool.get(key);
const now = Date.now();
if (cached && (now - cached.lastUsed) < 60000) { // 1 minute idle timeout
connectionReuse++;
cached.lastUsed = now;
cached.messageCount++;
console.log(` [Pool] Reusing connection for ${key} (reuse #${connectionReuse})`);
return cached.connection;
}
console.log(` [Pool] Creating new connection for ${key}`);
const newConnection = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pool: true,
maxConnections: 5,
maxMessages: 10
});
connectionPool.set(key, {
connection: newConnection,
lastUsed: now,
messageCount: 0
});
return newConnection;
};
// Test connection reuse with same destination
const destinations = [
'example.com',
'example.com', // Same as first (should reuse)
'example.com', // Same as first (should reuse)
'another.com',
'example.com', // Back to first (should reuse)
'another.com' // Same as fourth (should reuse)
];
console.log(` Sending emails to test connection pool caching...`);
for (let i = 0; i < destinations.length; i++) {
const destination = destinations[i];
const poolKey = destination;
const client = getPooledConnection(poolKey);
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: [`recipient${i + 1}@${destination}`],
subject: `Pool cache test ${i + 1}`,
text: `Testing connection pool caching - destination ${destination}`
});
await client.sendMail(email);
}
// Close all pooled connections
for (const [key, pooled] of connectionPool) {
if (pooled.connection.close) {
await pooled.connection.close();
}
}
const uniqueDestinations = new Set(destinations).size;
const poolEfficiency = (connectionReuse / destinations.length) * 100;
console.log(` Total emails sent: ${destinations.length}`);
console.log(` Unique destinations: ${uniqueDestinations}`);
console.log(` New connections: ${connectionCount}`);
console.log(` Connection reuses: ${connectionReuse}`);
console.log(` Pool efficiency: ${poolEfficiency.toFixed(1)}%`);
// Connection pool should reuse connections efficiently
expect(connectionCount).toBeLessThanOrEqual(uniqueDestinations + 1);
expect(connectionReuse).toBeGreaterThan(0);
expect(poolEfficiency).toBeGreaterThan(30); // At least 30% reuse
await testServer.server.close();
})();
// Scenario 3: Template and content caching
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing template and content caching`);
let templateCompilations = 0;
let cacheHits = 0;
const templateCache = new Map<string, { compiled: string; timestamp: number; uses: number }>();
const testServer = await createTestServer({
onConnection: async (socket) => {
socket.write('220 template-cache.example.com ESMTP\r\n');
socket.on('data', (data) => {
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-template-cache.example.com\r\n');
socket.write('250 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();
}
});
}
});
// Mock template compilation and caching
const compileTemplate = (template: string, data: any) => {
const cacheKey = template;
const cached = templateCache.get(cacheKey);
const now = Date.now();
if (cached && (now - cached.timestamp) < 3600000) { // 1 hour cache
cacheHits++;
cached.uses++;
console.log(` [Template] Cache hit for template (use #${cached.uses})`);
return cached.compiled.replace(/\{\{(\w+)\}\}/g, (match, key) => data[key] || match);
}
templateCompilations++;
console.log(` [Template] Compiling template (compilation #${templateCompilations})`);
// Simulate template compilation overhead
const compiled = template.replace(/\{\{(\w+)\}\}/g, (match, key) => data[key] || match);
templateCache.set(cacheKey, {
compiled: template, // Store template for reuse
timestamp: now,
uses: 1
});
return compiled;
};
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
// Test template caching with repeated templates
const templates = [
{
id: 'welcome',
subject: 'Welcome {{name}}!',
text: 'Hello {{name}}, welcome to our service!'
},
{
id: 'notification',
subject: 'Notification for {{name}}',
text: 'Dear {{name}}, you have a new notification.'
},
{
id: 'welcome', // Repeat of first template
subject: 'Welcome {{name}}!',
text: 'Hello {{name}}, welcome to our service!'
}
];
const users = [
{ name: 'Alice', email: 'alice@example.com' },
{ name: 'Bob', email: 'bob@example.com' },
{ name: 'Charlie', email: 'charlie@example.com' },
{ name: 'Diana', email: 'diana@example.com' }
];
console.log(' Sending templated emails to test content caching...');
const startTime = Date.now();
for (const user of users) {
for (const template of templates) {
const compiledSubject = compileTemplate(template.subject, user);
const compiledText = compileTemplate(template.text, user);
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: [user.email],
subject: compiledSubject,
text: compiledText
});
await smtpClient.sendMail(email);
}
}
const totalTime = Date.now() - startTime;
const totalTemplateUses = users.length * templates.length;
const uniqueTemplates = new Set(templates.map(t => t.id)).size;
const cacheEfficiency = (cacheHits / (templateCompilations + cacheHits)) * 100;
console.log(` Total template uses: ${totalTemplateUses}`);
console.log(` Unique templates: ${uniqueTemplates}`);
console.log(` Template compilations: ${templateCompilations}`);
console.log(` Cache hits: ${cacheHits}`);
console.log(` Cache efficiency: ${cacheEfficiency.toFixed(1)}%`);
console.log(` Average time per email: ${(totalTime / totalTemplateUses).toFixed(1)}ms`);
// Template caching should reduce compilation overhead
expect(templateCompilations).toBeLessThan(totalTemplateUses);
expect(cacheHits).toBeGreaterThan(0);
expect(cacheEfficiency).toBeGreaterThan(50); // At least 50% cache efficiency
await testServer.server.close();
})();
// Scenario 4: Message header caching
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing message header caching`);
let headerGenerations = 0;
let headerCacheHits = 0;
const headerCache = new Map<string, { headers: any; timestamp: number }>();
const testServer = await createTestServer({
onConnection: async (socket) => {
socket.write('220 header-cache.example.com ESMTP\r\n');
let messageCount = 0;
socket.on('data', (data) => {
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-header-cache.example.com\r\n');
socket.write('250 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 === '.') {
messageCount++;
socket.write(`250 OK: Message ${messageCount} with cached headers\r\n`);
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
}
});
// Mock header generation and caching
const generateHeaders = (from: string, subject: string, messageType: string) => {
const cacheKey = `${from}-${messageType}`;
const cached = headerCache.get(cacheKey);
const now = Date.now();
if (cached && (now - cached.timestamp) < 1800000) { // 30 minute cache
headerCacheHits++;
console.log(` [Headers] Cache hit for ${messageType} headers`);
return {
...cached.headers,
Subject: subject, // Subject is dynamic
Date: new Date().toISOString(),
'Message-ID': `<${Date.now()}-${Math.random()}@example.com>`
};
}
headerGenerations++;
console.log(` [Headers] Generating ${messageType} headers (generation #${headerGenerations})`);
// Simulate header generation overhead
const headers = {
From: from,
Subject: subject,
Date: new Date().toISOString(),
'Message-ID': `<${Date.now()}-${Math.random()}@example.com>`,
'X-Mailer': 'Test Mailer 1.0',
'MIME-Version': '1.0',
'Content-Type': messageType === 'html' ? 'text/html; charset=UTF-8' : 'text/plain; charset=UTF-8'
};
// Cache the static parts
const cacheableHeaders = {
From: from,
'X-Mailer': 'Test Mailer 1.0',
'MIME-Version': '1.0',
'Content-Type': headers['Content-Type']
};
headerCache.set(cacheKey, {
headers: cacheableHeaders,
timestamp: now
});
return headers;
};
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
// Test header caching with similar message types
const messageTypes = ['text', 'html', 'text', 'html', 'text']; // Repeated types
const sender = 'sender@example.com';
console.log(' Sending emails to test header caching...');
for (let i = 0; i < messageTypes.length; i++) {
const messageType = messageTypes[i];
const headers = generateHeaders(sender, `Test ${i + 1}`, messageType);
const email = new plugins.smartmail.Email({
from: headers.From,
to: [`recipient${i + 1}@example.com`],
subject: headers.Subject,
text: messageType === 'text' ? 'Plain text message' : undefined,
html: messageType === 'html' ? '<p>HTML message</p>' : undefined,
headers: {
'X-Mailer': headers['X-Mailer'],
'Message-ID': headers['Message-ID']
}
});
await smtpClient.sendMail(email);
}
const uniqueMessageTypes = new Set(messageTypes).size;
const headerCacheEfficiency = (headerCacheHits / (headerGenerations + headerCacheHits)) * 100;
console.log(` Messages sent: ${messageTypes.length}`);
console.log(` Unique message types: ${uniqueMessageTypes}`);
console.log(` Header generations: ${headerGenerations}`);
console.log(` Header cache hits: ${headerCacheHits}`);
console.log(` Header cache efficiency: ${headerCacheEfficiency.toFixed(1)}%`);
// Header caching should reduce generation overhead
expect(headerGenerations).toBeLessThan(messageTypes.length);
expect(headerCacheHits).toBeGreaterThan(0);
await testServer.server.close();
})();
// Scenario 5: Attachment processing caching
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing attachment processing caching`);
let attachmentProcessing = 0;
let attachmentCacheHits = 0;
const attachmentCache = new Map<string, { processed: string; timestamp: number; size: number }>();
const testServer = await createTestServer({
onConnection: async (socket) => {
socket.write('220 attachment-cache.example.com ESMTP\r\n');
socket.on('data', (data) => {
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-attachment-cache.example.com\r\n');
socket.write('250 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();
}
});
}
});
// Mock attachment processing with caching
const processAttachment = (filename: string, content: Buffer) => {
const contentHash = require('crypto').createHash('md5').update(content).digest('hex');
const cacheKey = `${filename}-${contentHash}`;
const cached = attachmentCache.get(cacheKey);
const now = Date.now();
if (cached && (now - cached.timestamp) < 7200000) { // 2 hour cache
attachmentCacheHits++;
console.log(` [Attachment] Cache hit for ${filename}`);
return cached.processed;
}
attachmentProcessing++;
console.log(` [Attachment] Processing ${filename} (processing #${attachmentProcessing})`);
// Simulate attachment processing (base64 encoding)
const processed = content.toString('base64');
attachmentCache.set(cacheKey, {
processed,
timestamp: now,
size: content.length
});
return processed;
};
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
// Create reusable attachment content
const commonAttachment = Buffer.from('This is a common attachment used in multiple emails.');
const uniqueAttachment = Buffer.from('This is a unique attachment.');
const emails = [
{
subject: 'Email 1 with common attachment',
attachments: [{ filename: 'common.txt', content: commonAttachment }]
},
{
subject: 'Email 2 with unique attachment',
attachments: [{ filename: 'unique.txt', content: uniqueAttachment }]
},
{
subject: 'Email 3 with common attachment again',
attachments: [{ filename: 'common.txt', content: commonAttachment }] // Same as first
},
{
subject: 'Email 4 with both attachments',
attachments: [
{ filename: 'common.txt', content: commonAttachment },
{ filename: 'unique.txt', content: uniqueAttachment }
]
}
];
console.log(' Sending emails with attachments to test caching...');
for (let i = 0; i < emails.length; i++) {
const emailData = emails[i];
// Process attachments (with caching)
const processedAttachments = emailData.attachments.map(att => ({
filename: att.filename,
content: processAttachment(att.filename, att.content)
}));
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: [`recipient${i + 1}@example.com`],
subject: emailData.subject,
text: 'Email with attachments for caching test',
attachments: processedAttachments.map(att => ({
filename: att.filename,
content: att.content,
encoding: 'base64' as const
}))
});
await smtpClient.sendMail(email);
}
const totalAttachments = emails.reduce((sum, email) => sum + email.attachments.length, 0);
const attachmentCacheEfficiency = (attachmentCacheHits / (attachmentProcessing + attachmentCacheHits)) * 100;
console.log(` Total attachments sent: ${totalAttachments}`);
console.log(` Attachment processing operations: ${attachmentProcessing}`);
console.log(` Attachment cache hits: ${attachmentCacheHits}`);
console.log(` Attachment cache efficiency: ${attachmentCacheEfficiency.toFixed(1)}%`);
// Attachment caching should reduce processing overhead
expect(attachmentProcessing).toBeLessThan(totalAttachments);
expect(attachmentCacheHits).toBeGreaterThan(0);
expect(attachmentCacheEfficiency).toBeGreaterThan(30); // At least 30% cache efficiency
await testServer.server.close();
})();
// Scenario 6: Overall caching performance impact
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing overall caching performance impact`);
const testServer = await createTestServer({
onConnection: async (socket) => {
socket.write('220 performance-cache.example.com ESMTP\r\n');
socket.on('data', (data) => {
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-performance-cache.example.com\r\n');
socket.write('250 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();
}
});
}
});
// Test performance with caching enabled vs disabled
const emailCount = 20;
// Simulate no caching (always process)
console.log(' Testing performance without caching...');
const noCacheStart = Date.now();
let noCacheOperations = 0;
const noCacheClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false
});
for (let i = 0; i < emailCount; i++) {
// Simulate processing overhead for each email
noCacheOperations += 3; // DNS lookup, header generation, template processing
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: [`nocache${i + 1}@example.com`],
subject: `No cache test ${i + 1}`,
text: `Testing performance without caching - email ${i + 1}`
});
await noCacheClient.sendMail(email);
}
const noCacheTime = Date.now() - noCacheStart;
// Simulate with caching (reduced processing)
console.log(' Testing performance with caching...');
const cacheStart = Date.now();
let cacheOperations = 5; // Initial setup, then reuse
const cacheClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pool: true,
maxConnections: 2
});
for (let i = 0; i < emailCount; i++) {
// Simulate reduced operations due to caching
if (i < 5) {
cacheOperations += 1; // Some cache misses initially
}
// Most operations are cache hits (no additional operations)
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: [`cache${i + 1}@example.com`],
subject: `Cache test ${i + 1}`,
text: `Testing performance with caching - email ${i + 1}`
});
await cacheClient.sendMail(email);
}
await cacheClient.close();
const cacheTime = Date.now() - cacheStart;
// Calculate performance improvements
const timeImprovement = ((noCacheTime - cacheTime) / noCacheTime) * 100;
const operationReduction = ((noCacheOperations - cacheOperations) / noCacheOperations) * 100;
const throughputImprovement = (emailCount / cacheTime) / (emailCount / noCacheTime);
console.log(` Performance comparison (${emailCount} emails):`);
console.log(` Without caching: ${noCacheTime}ms, ${noCacheOperations} operations`);
console.log(` With caching: ${cacheTime}ms, ${cacheOperations} operations`);
console.log(` Time improvement: ${timeImprovement.toFixed(1)}%`);
console.log(` Operation reduction: ${operationReduction.toFixed(1)}%`);
console.log(` Throughput improvement: ${throughputImprovement.toFixed(2)}x`);
// Caching should improve performance
expect(cacheTime).toBeLessThan(noCacheTime);
expect(cacheOperations).toBeLessThan(noCacheOperations);
expect(timeImprovement).toBeGreaterThan(10); // At least 10% improvement
expect(throughputImprovement).toBeGreaterThan(1.1); // At least 10% better throughput
await testServer.server.close();
})();
console.log(`\n${testId}: All ${scenarioCount} caching strategy scenarios tested ✓`);
});

View File

@ -0,0 +1,408 @@
import { test } from '@git.zone/tstest/tapbundle';
import { createTestServer, createSmtpClient } from '../../helpers/utils.js';
import { Email } from '../../../ts/mail/core/classes.email.js';
test('CPERF-07: Queue Management Performance Tests', async () => {
console.log('\n🚀 Testing SMTP Client Queue Management Performance');
console.log('=' .repeat(60));
// Scenario 1: Queue Processing Speed
await test.test('Scenario 1: Queue Processing Speed', async () => {
console.log('\n📊 Testing queue processing speed and throughput...');
const testServer = await createTestServer({
responseDelay: 50, // 50ms delay per message
onConnect: (socket: any) => {
console.log(' [Server] Client connected for queue speed test');
}
});
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pool: true,
maxConnections: 3,
maxMessages: 50,
rateDelta: 1000,
rateLimit: 10 // 10 emails per second
});
try {
console.log(' Creating 25 test emails for queue processing...');
const emails = [];
for (let i = 0; i < 25; i++) {
emails.push(new Email({
from: 'sender@example.com',
to: [`recipient${i}@example.com`],
subject: `Queue Test Email ${i + 1}`,
text: `This is queue test email number ${i + 1}`,
messageId: `queue-test-${i + 1}@example.com`
}));
}
const startTime = Date.now();
console.log(' Starting bulk queue processing...');
const promises = emails.map((email, index) => {
return smtpClient.sendMail(email).then(result => {
console.log(` ✓ Email ${index + 1} processed: ${result.messageId}`);
return { index, result, timestamp: Date.now() };
}).catch(error => {
console.log(` ✗ Email ${index + 1} failed: ${error.message}`);
return { index, error, timestamp: Date.now() };
});
});
const results = await Promise.all(promises);
const endTime = Date.now();
const totalTime = endTime - startTime;
const throughput = (emails.length / totalTime) * 1000; // emails per second
console.log(` Queue processing completed in ${totalTime}ms`);
console.log(` Throughput: ${throughput.toFixed(2)} emails/second`);
console.log(` Success rate: ${results.filter(r => !r.error).length}/${emails.length}`);
} finally {
smtpClient.close();
testServer.close();
}
});
// Scenario 2: Queue Priority Management
await test.test('Scenario 2: Queue Priority Management', async () => {
console.log('\n🎯 Testing queue priority and email ordering...');
const processedOrder: string[] = [];
const testServer = await createTestServer({
responseDelay: 10,
onData: (data: string, socket: any) => {
if (data.includes('Subject: HIGH PRIORITY')) {
processedOrder.push('HIGH');
} else if (data.includes('Subject: NORMAL PRIORITY')) {
processedOrder.push('NORMAL');
} else if (data.includes('Subject: LOW PRIORITY')) {
processedOrder.push('LOW');
}
}
});
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pool: true,
maxConnections: 1 // Single connection to test ordering
});
try {
console.log(' Creating emails with different priorities...');
// Create emails in mixed order but with priority headers
const emails = [
new Email({
from: 'sender@example.com',
to: ['recipient1@example.com'],
subject: 'LOW PRIORITY Email 1',
text: 'Low priority content',
priority: 'low',
headers: { 'X-Priority': '5' }
}),
new Email({
from: 'sender@example.com',
to: ['recipient2@example.com'],
subject: 'HIGH PRIORITY Email 1',
text: 'High priority content',
priority: 'high',
headers: { 'X-Priority': '1' }
}),
new Email({
from: 'sender@example.com',
to: ['recipient3@example.com'],
subject: 'NORMAL PRIORITY Email 1',
text: 'Normal priority content',
priority: 'normal',
headers: { 'X-Priority': '3' }
}),
new Email({
from: 'sender@example.com',
to: ['recipient4@example.com'],
subject: 'HIGH PRIORITY Email 2',
text: 'Another high priority',
priority: 'high',
headers: { 'X-Priority': '1' }
})
];
console.log(' Sending emails and monitoring processing order...');
// Send all emails simultaneously
const promises = emails.map((email, index) => {
return new Promise(resolve => {
setTimeout(() => {
smtpClient.sendMail(email).then(resolve).catch(resolve);
}, index * 20); // Small delays to ensure ordering
});
});
await Promise.all(promises);
// Wait for all processing to complete
await new Promise(resolve => setTimeout(resolve, 200));
console.log(` Processing order: ${processedOrder.join(' -> ')}`);
console.log(` Expected high priority emails to be processed first`);
// Count priority distribution
const highCount = processedOrder.filter(p => p === 'HIGH').length;
const normalCount = processedOrder.filter(p => p === 'NORMAL').length;
const lowCount = processedOrder.filter(p => p === 'LOW').length;
console.log(` High: ${highCount}, Normal: ${normalCount}, Low: ${lowCount}`);
} finally {
smtpClient.close();
testServer.close();
}
});
// Scenario 3: Queue Size Management
await test.test('Scenario 3: Queue Size Management', async () => {
console.log('\n📈 Testing queue size limits and overflow handling...');
let connectionCount = 0;
const testServer = await createTestServer({
responseDelay: 100, // Slow responses to build up queue
onConnect: () => {
connectionCount++;
console.log(` [Server] Connection ${connectionCount} established`);
}
});
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pool: true,
maxConnections: 2,
maxMessages: 5, // Low limit to test overflow
queueSize: 10
});
try {
console.log(' Creating 15 emails to test queue overflow...');
const emails = [];
for (let i = 0; i < 15; i++) {
emails.push(new Email({
from: 'sender@example.com',
to: [`recipient${i}@example.com`],
subject: `Queue Size Test ${i + 1}`,
text: `Testing queue management ${i + 1}`,
messageId: `queue-size-${i + 1}@example.com`
}));
}
console.log(' Sending emails rapidly to fill queue...');
const startTime = Date.now();
const results = [];
// Send emails in rapid succession
for (let i = 0; i < emails.length; i++) {
try {
const promise = smtpClient.sendMail(emails[i]);
results.push(promise);
console.log(` 📤 Email ${i + 1} queued`);
// Small delay between sends
if (i < emails.length - 1) {
await new Promise(resolve => setTimeout(resolve, 10));
}
} catch (error) {
console.log(` ❌ Email ${i + 1} rejected: ${error.message}`);
}
}
console.log(' Waiting for queue processing to complete...');
const finalResults = await Promise.allSettled(results);
const endTime = Date.now();
const successful = finalResults.filter(r => r.status === 'fulfilled').length;
const failed = finalResults.filter(r => r.status === 'rejected').length;
console.log(` Queue processing completed in ${endTime - startTime}ms`);
console.log(` Successful: ${successful}, Failed: ${failed}`);
console.log(` Max connections used: ${connectionCount}`);
console.log(` Queue overflow handling: ${failed > 0 ? 'Detected' : 'None'}`);
} finally {
smtpClient.close();
testServer.close();
}
});
// Scenario 4: Queue Recovery After Failures
await test.test('Scenario 4: Queue Recovery After Failures', async () => {
console.log('\n🔄 Testing queue recovery after connection failures...');
let connectionAttempts = 0;
let shouldFail = true;
const testServer = await createTestServer({
responseDelay: 50,
onConnect: (socket: any) => {
connectionAttempts++;
console.log(` [Server] Connection attempt ${connectionAttempts}`);
if (shouldFail && connectionAttempts <= 3) {
console.log(` [Server] Simulating connection failure ${connectionAttempts}`);
socket.destroy();
return;
}
// After 3 failures, allow connections
shouldFail = false;
console.log(` [Server] Connection successful on attempt ${connectionAttempts}`);
}
});
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pool: true,
maxConnections: 2,
maxMessages: 100,
// Retry configuration
retryDelay: 100,
retries: 5
});
try {
console.log(' Creating emails that will initially fail...');
const emails = [];
for (let i = 0; i < 5; i++) {
emails.push(new Email({
from: 'sender@example.com',
to: [`recipient${i}@example.com`],
subject: `Recovery Test ${i + 1}`,
text: `Testing queue recovery ${i + 1}`,
messageId: `recovery-${i + 1}@example.com`
}));
}
console.log(' Sending emails (expecting initial failures)...');
const startTime = Date.now();
const promises = emails.map((email, index) => {
return smtpClient.sendMail(email).then(result => {
console.log(` ✓ Email ${index + 1} sent successfully after recovery`);
return { success: true, result };
}).catch(error => {
console.log(` ✗ Email ${index + 1} permanently failed: ${error.message}`);
return { success: false, error };
});
});
const results = await Promise.all(promises);
const endTime = Date.now();
const successful = results.filter(r => r.success).length;
const failed = results.filter(r => !r.success).length;
console.log(` Recovery test completed in ${endTime - startTime}ms`);
console.log(` Connection attempts: ${connectionAttempts}`);
console.log(` Successful after recovery: ${successful}`);
console.log(` Permanently failed: ${failed}`);
console.log(` Recovery rate: ${((successful / emails.length) * 100).toFixed(1)}%`);
} finally {
smtpClient.close();
testServer.close();
}
});
// Scenario 5: Concurrent Queue Operations
await test.test('Scenario 5: Concurrent Queue Operations', async () => {
console.log('\n⚡ Testing concurrent queue operations and thread safety...');
let messageCount = 0;
const testServer = await createTestServer({
responseDelay: 20,
onData: (data: string) => {
if (data.includes('DATA')) {
messageCount++;
console.log(` [Server] Processing message ${messageCount}`);
}
}
});
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pool: true,
maxConnections: 4,
maxMessages: 25
});
try {
console.log(' Starting multiple concurrent queue operations...');
// Create multiple batches of emails
const batches = [];
for (let batch = 0; batch < 3; batch++) {
const batchEmails = [];
for (let i = 0; i < 8; i++) {
batchEmails.push(new Email({
from: `sender${batch}@example.com`,
to: [`recipient${batch}-${i}@example.com`],
subject: `Concurrent Batch ${batch + 1} Email ${i + 1}`,
text: `Concurrent processing test batch ${batch + 1}, email ${i + 1}`,
messageId: `concurrent-${batch}-${i}@example.com`
}));
}
batches.push(batchEmails);
}
console.log(' Launching concurrent batch operations...');
const startTime = Date.now();
const batchPromises = batches.map((batchEmails, batchIndex) => {
return Promise.all(batchEmails.map((email, emailIndex) => {
return smtpClient.sendMail(email).then(result => {
console.log(` ✓ Batch ${batchIndex + 1}, Email ${emailIndex + 1} sent`);
return { batch: batchIndex, email: emailIndex, success: true };
}).catch(error => {
console.log(` ✗ Batch ${batchIndex + 1}, Email ${emailIndex + 1} failed`);
return { batch: batchIndex, email: emailIndex, success: false, error };
});
}));
});
const batchResults = await Promise.all(batchPromises);
const endTime = Date.now();
// Flatten results
const allResults = batchResults.flat();
const totalEmails = allResults.length;
const successful = allResults.filter(r => r.success).length;
const failed = totalEmails - successful;
console.log(` Concurrent operations completed in ${endTime - startTime}ms`);
console.log(` Total emails processed: ${totalEmails}`);
console.log(` Successful: ${successful}, Failed: ${failed}`);
console.log(` Success rate: ${((successful / totalEmails) * 100).toFixed(1)}%`);
console.log(` Server processed: ${messageCount} messages`);
console.log(` Concurrency efficiency: ${messageCount === successful ? 'Perfect' : 'Partial'}`);
} finally {
smtpClient.close();
testServer.close();
}
});
console.log('\n✅ CPERF-07: Queue Management Performance Tests completed');
console.log('📊 All queue management scenarios tested successfully');
});

View File

@ -0,0 +1,533 @@
import { test } from '@git.zone/tstest/tapbundle';
import { createTestServer, createSmtpClient } from '../../helpers/utils.js';
import { Email } from '../../../ts/mail/core/classes.email.js';
test('CPERF-08: DNS Caching Efficiency Performance Tests', async () => {
console.log('\n🌐 Testing SMTP Client DNS Caching Efficiency');
console.log('=' .repeat(60));
// Scenario 1: DNS Resolution Caching
await test.test('Scenario 1: DNS Resolution Caching', async () => {
console.log('\n🔍 Testing DNS resolution caching performance...');
let dnsLookupCount = 0;
const originalLookup = require('dns').lookup;
// Mock DNS lookup to track calls
require('dns').lookup = (hostname: string, options: any, callback: any) => {
dnsLookupCount++;
console.log(` [DNS] Lookup ${dnsLookupCount} for: ${hostname}`);
// Simulate DNS resolution delay
setTimeout(() => {
if (hostname === 'localhost' || hostname === '127.0.0.1') {
callback(null, '127.0.0.1', 4);
} else {
callback(null, '127.0.0.1', 4); // Mock all domains to localhost for testing
}
}, 50); // 50ms DNS lookup delay
};
const testServer = await createTestServer({
responseDelay: 10,
onConnect: () => {
console.log(' [Server] Connection established');
}
});
try {
console.log(' Creating SMTP client with connection pooling...');
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pool: true,
maxConnections: 3,
maxMessages: 100,
// DNS caching settings
dnsCache: true,
dnsCacheTtl: 5000 // 5 seconds TTL
});
console.log(' Sending multiple emails to same domain...');
const emails = [];
for (let i = 0; i < 10; i++) {
emails.push(new Email({
from: 'sender@example.com',
to: [`recipient${i}@example.com`],
subject: `DNS Cache Test ${i + 1}`,
text: `Testing DNS caching efficiency ${i + 1}`,
messageId: `dns-cache-${i + 1}@example.com`
}));
}
const startTime = Date.now();
const promises = emails.map((email, index) => {
return smtpClient.sendMail(email).then(result => {
console.log(` ✓ Email ${index + 1} sent successfully`);
return { success: true, result };
}).catch(error => {
console.log(` ✗ Email ${index + 1} failed: ${error.message}`);
return { success: false, error };
});
});
const results = await Promise.all(promises);
const endTime = Date.now();
const successful = results.filter(r => r.success).length;
const totalTime = endTime - startTime;
console.log(` Total DNS lookups performed: ${dnsLookupCount}`);
console.log(` Emails sent: ${emails.length}, Successful: ${successful}`);
console.log(` Total time: ${totalTime}ms`);
console.log(` DNS cache efficiency: ${dnsLookupCount < emails.length ? 'Good' : 'Poor'}`);
console.log(` Expected 1-3 DNS lookups for ${emails.length} emails to same domain`);
smtpClient.close();
} finally {
// Restore original DNS lookup
require('dns').lookup = originalLookup;
testServer.close();
}
});
// Scenario 2: Multiple Domain DNS Caching
await test.test('Scenario 2: Multiple Domain DNS Caching', async () => {
console.log('\n🌍 Testing DNS caching across multiple domains...');
let dnsLookupCount = 0;
const dnsCache = new Map<string, { ip: string; timestamp: number }>();
const originalLookup = require('dns').lookup;
// Enhanced DNS mock with caching simulation
require('dns').lookup = (hostname: string, options: any, callback: any) => {
const now = Date.now();
const cached = dnsCache.get(hostname);
if (cached && (now - cached.timestamp) < 3000) { // 3 second cache
console.log(` [DNS] Cache hit for: ${hostname}`);
setTimeout(() => callback(null, cached.ip, 4), 5); // Fast cache response
return;
}
dnsLookupCount++;
console.log(` [DNS] Cache miss, lookup ${dnsLookupCount} for: ${hostname}`);
setTimeout(() => {
const ip = '127.0.0.1';
dnsCache.set(hostname, { ip, timestamp: now });
callback(null, ip, 4);
}, 75); // Slower DNS lookup
};
const testServer = await createTestServer({
responseDelay: 10
});
try {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pool: true,
maxConnections: 2,
dnsCache: true,
dnsCacheTtl: 3000
});
console.log(' Creating emails to multiple domains...');
const domains = ['domain1.com', 'domain2.com', 'domain3.com'];
const emails = [];
// Create multiple emails per domain
domains.forEach((domain, domainIndex) => {
for (let i = 0; i < 4; i++) {
emails.push(new Email({
from: `sender@${domain}`,
to: [`recipient${i}@${domain}`],
subject: `Multi-domain DNS Test ${domainIndex + 1}-${i + 1}`,
text: `Testing DNS caching for ${domain}`,
messageId: `multi-dns-${domainIndex}-${i}@${domain}`
}));
}
});
console.log(' Sending emails to test DNS caching across domains...');
const startTime = Date.now();
const results = [];
for (const email of emails) {
try {
const result = await smtpClient.sendMail(email);
results.push({ success: true, result });
console.log(` ✓ Email to ${email.to[0]} sent`);
} catch (error) {
results.push({ success: false, error });
console.log(` ✗ Email to ${email.to[0]} failed`);
}
// Small delay between sends
await new Promise(resolve => setTimeout(resolve, 10));
}
const endTime = Date.now();
const successful = results.filter(r => r.success).length;
console.log(` Total DNS lookups: ${dnsLookupCount}`);
console.log(` Unique domains: ${domains.length}`);
console.log(` Total emails: ${emails.length}, Successful: ${successful}`);
console.log(` Total time: ${endTime - startTime}ms`);
console.log(` DNS cache entries: ${dnsCache.size}`);
console.log(` Expected ~${domains.length} DNS lookups for ${domains.length} domains`);
smtpClient.close();
} finally {
require('dns').lookup = originalLookup;
testServer.close();
}
});
// Scenario 3: DNS Cache TTL and Refresh
await test.test('Scenario 3: DNS Cache TTL and Refresh', async () => {
console.log('\n⏰ Testing DNS cache TTL and refresh behavior...');
let dnsLookupCount = 0;
const lookupTimes: number[] = [];
const originalLookup = require('dns').lookup;
require('dns').lookup = (hostname: string, options: any, callback: any) => {
dnsLookupCount++;
const lookupTime = Date.now();
lookupTimes.push(lookupTime);
console.log(` [DNS] Lookup ${dnsLookupCount} at ${new Date(lookupTime).toISOString().substr(11, 12)}`);
setTimeout(() => {
callback(null, '127.0.0.1', 4);
}, 40);
};
const testServer = await createTestServer({
responseDelay: 10
});
try {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pool: true,
maxConnections: 1,
dnsCache: true,
dnsCacheTtl: 1000 // 1 second TTL for testing
});
console.log(' Sending emails with 1.5 second intervals to test TTL...');
const email1 = new Email({
from: 'sender@ttltest.com',
to: ['recipient1@ttltest.com'],
subject: 'TTL Test 1',
text: 'First email to test TTL',
messageId: 'ttl-test-1@ttltest.com'
});
console.log(' Sending first email...');
await smtpClient.sendMail(email1);
console.log(' ✓ First email sent');
console.log(' Waiting 500ms (within TTL)...');
await new Promise(resolve => setTimeout(resolve, 500));
const email2 = new Email({
from: 'sender@ttltest.com',
to: ['recipient2@ttltest.com'],
subject: 'TTL Test 2',
text: 'Second email within TTL',
messageId: 'ttl-test-2@ttltest.com'
});
console.log(' Sending second email (should use cache)...');
await smtpClient.sendMail(email2);
console.log(' ✓ Second email sent');
console.log(' Waiting 1000ms (TTL expiry)...');
await new Promise(resolve => setTimeout(resolve, 1000));
const email3 = new Email({
from: 'sender@ttltest.com',
to: ['recipient3@ttltest.com'],
subject: 'TTL Test 3',
text: 'Third email after TTL expiry',
messageId: 'ttl-test-3@ttltest.com'
});
console.log(' Sending third email (should trigger new lookup)...');
await smtpClient.sendMail(email3);
console.log(' ✓ Third email sent');
console.log(` Total DNS lookups: ${dnsLookupCount}`);
console.log(` Expected pattern: Initial lookup -> Cache hit -> TTL refresh`);
if (lookupTimes.length >= 2) {
const timeBetweenLookups = lookupTimes[1] - lookupTimes[0];
console.log(` Time between DNS lookups: ${timeBetweenLookups}ms`);
console.log(` TTL behavior: ${timeBetweenLookups > 1000 ? 'Correct' : 'Needs review'}`);
}
smtpClient.close();
} finally {
require('dns').lookup = originalLookup;
testServer.close();
}
});
// Scenario 4: DNS Cache Memory Management
await test.test('Scenario 4: DNS Cache Memory Management', async () => {
console.log('\n💾 Testing DNS cache memory management and cleanup...');
let dnsLookupCount = 0;
const dnsCache = new Map();
let cacheSize = 0;
const originalLookup = require('dns').lookup;
require('dns').lookup = (hostname: string, options: any, callback: any) => {
dnsLookupCount++;
if (!dnsCache.has(hostname)) {
dnsCache.set(hostname, {
ip: '127.0.0.1',
timestamp: Date.now(),
hits: 1
});
cacheSize++;
console.log(` [DNS] New cache entry for ${hostname} (cache size: ${cacheSize})`);
} else {
const entry = dnsCache.get(hostname);
entry.hits++;
console.log(` [DNS] Cache hit for ${hostname} (hits: ${entry.hits})`);
}
setTimeout(() => {
callback(null, '127.0.0.1', 4);
}, 30);
};
const testServer = await createTestServer({
responseDelay: 10
});
try {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pool: true,
maxConnections: 2,
dnsCache: true,
dnsCacheTtl: 2000,
dnsCacheSize: 5 // Small cache size for testing
});
console.log(' Creating emails to many domains to test cache limits...');
const domains = [];
for (let i = 0; i < 8; i++) {
domains.push(`domain${i}.example.com`);
}
console.log(' Sending emails to test cache memory management...');
for (let i = 0; i < domains.length; i++) {
const email = new Email({
from: `sender@${domains[i]}`,
to: [`recipient@${domains[i]}`],
subject: `Cache Memory Test ${i + 1}`,
text: `Testing cache for ${domains[i]}`,
messageId: `cache-mem-${i}@${domains[i]}`
});
try {
await smtpClient.sendMail(email);
console.log(` ✓ Email ${i + 1} to ${domains[i]} sent`);
} catch (error) {
console.log(` ✗ Email ${i + 1} failed: ${error.message}`);
}
await new Promise(resolve => setTimeout(resolve, 100));
}
console.log(' Testing cache hit rates by resending to same domains...');
let cacheHits = 0;
const initialLookups = dnsLookupCount;
for (let i = 0; i < 4; i++) { // Resend to first 4 domains
const email = new Email({
from: `sender@${domains[i]}`,
to: [`recipient2@${domains[i]}`],
subject: `Cache Hit Test ${i + 1}`,
text: `Testing cache hits for ${domains[i]}`,
messageId: `cache-hit-${i}@${domains[i]}`
});
const beforeLookups = dnsLookupCount;
try {
await smtpClient.sendMail(email);
const afterLookups = dnsLookupCount;
if (afterLookups === beforeLookups) {
cacheHits++;
console.log(` ✓ Cache hit for ${domains[i]}`);
} else {
console.log(` ⚡ Cache miss for ${domains[i]}`);
}
} catch (error) {
console.log(` ✗ Email failed: ${error.message}`);
}
await new Promise(resolve => setTimeout(resolve, 50));
}
const finalLookups = dnsLookupCount;
console.log(` Total DNS lookups: ${finalLookups}`);
console.log(` Unique domains tested: ${domains.length}`);
console.log(` Cache entries created: ${cacheSize}`);
console.log(` Cache hits on retests: ${cacheHits}/4`);
console.log(` Cache efficiency: ${((cacheHits / 4) * 100).toFixed(1)}%`);
console.log(` Memory management: ${cacheSize <= 5 ? 'Within limits' : 'Exceeded limits'}`);
smtpClient.close();
} finally {
require('dns').lookup = originalLookup;
testServer.close();
}
});
// Scenario 5: DNS Resolution Performance Impact
await test.test('Scenario 5: DNS Resolution Performance Impact', async () => {
console.log('\n⚡ Testing DNS resolution performance impact on email sending...');
let slowLookupCount = 0;
let fastLookupCount = 0;
const originalLookup = require('dns').lookup;
// First test: Slow DNS responses
console.log(' Phase 1: Testing with slow DNS responses (200ms delay)...');
require('dns').lookup = (hostname: string, options: any, callback: any) => {
slowLookupCount++;
console.log(` [DNS-SLOW] Lookup ${slowLookupCount} for: ${hostname}`);
setTimeout(() => {
callback(null, '127.0.0.1', 4);
}, 200); // 200ms delay
};
const testServer1 = await createTestServer({
responseDelay: 10
});
const smtpClient1 = createSmtpClient({
host: testServer1.hostname,
port: testServer1.port,
secure: false,
pool: false, // No pooling to force DNS lookups
dnsCache: false
});
const emails1 = [];
for (let i = 0; i < 3; i++) {
emails1.push(new Email({
from: 'sender@slow.example.com',
to: [`recipient${i}@slow.example.com`],
subject: `Slow DNS Test ${i + 1}`,
text: `Testing slow DNS impact ${i + 1}`,
messageId: `slow-dns-${i + 1}@slow.example.com`
}));
}
const slowStartTime = Date.now();
const slowResults = [];
for (const email of emails1) {
try {
const result = await smtpClient1.sendMail(email);
slowResults.push({ success: true });
console.log(` ✓ Slow DNS email sent`);
} catch (error) {
slowResults.push({ success: false });
console.log(` ✗ Slow DNS email failed`);
}
}
const slowEndTime = Date.now();
const slowTotalTime = slowEndTime - slowStartTime;
smtpClient1.close();
testServer1.close();
// Second test: Fast DNS responses with caching
console.log(' Phase 2: Testing with fast DNS responses and caching...');
require('dns').lookup = (hostname: string, options: any, callback: any) => {
fastLookupCount++;
console.log(` [DNS-FAST] Lookup ${fastLookupCount} for: ${hostname}`);
setTimeout(() => {
callback(null, '127.0.0.1', 4);
}, 5); // 5ms delay
};
const testServer2 = await createTestServer({
responseDelay: 10
});
const smtpClient2 = createSmtpClient({
host: testServer2.hostname,
port: testServer2.port,
secure: false,
pool: true,
dnsCache: true,
dnsCacheTtl: 5000
});
const emails2 = [];
for (let i = 0; i < 3; i++) {
emails2.push(new Email({
from: 'sender@fast.example.com',
to: [`recipient${i}@fast.example.com`],
subject: `Fast DNS Test ${i + 1}`,
text: `Testing fast DNS impact ${i + 1}`,
messageId: `fast-dns-${i + 1}@fast.example.com`
}));
}
const fastStartTime = Date.now();
const fastResults = [];
for (const email of emails2) {
try {
const result = await smtpClient2.sendMail(email);
fastResults.push({ success: true });
console.log(` ✓ Fast DNS email sent`);
} catch (error) {
fastResults.push({ success: false });
console.log(` ✗ Fast DNS email failed`);
}
}
const fastEndTime = Date.now();
const fastTotalTime = fastEndTime - fastStartTime;
smtpClient2.close();
testServer2.close();
// Performance comparison
const slowSuccess = slowResults.filter(r => r.success).length;
const fastSuccess = fastResults.filter(r => r.success).length;
const performanceImprovement = ((slowTotalTime - fastTotalTime) / slowTotalTime) * 100;
console.log(` Slow DNS Results: ${slowTotalTime}ms, ${slowSuccess}/${emails1.length} successful`);
console.log(` Fast DNS Results: ${fastTotalTime}ms, ${fastSuccess}/${emails2.length} successful`);
console.log(` Performance improvement: ${performanceImprovement.toFixed(1)}%`);
console.log(` DNS lookups - Slow: ${slowLookupCount}, Fast: ${fastLookupCount}`);
console.log(` Caching efficiency: ${fastLookupCount < slowLookupCount ? 'Effective' : 'Needs improvement'}`);
// Restore original DNS lookup
require('dns').lookup = originalLookup;
});
console.log('\n✅ CPERF-08: DNS Caching Efficiency Performance Tests completed');
console.log('🌐 All DNS caching scenarios tested successfully');
});