dcrouter/test/suite/smtpclient_performance/test.cperf-04.cpu-utilization.ts

670 lines
23 KiB
TypeScript
Raw Normal View History

2025-05-24 18:12:08 +00:00
import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as plugins from './plugins.js';
import { createTestServer } from '../../helpers/server.loader.js';
import { createSmtpClient } from '../../helpers/smtp.client.js';
tap.test('CPERF-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 ✓`);
});