dcrouter/test/suite/smtpserver_performance/test.perf-03.cpu-utilization.ts
2025-05-25 19:05:43 +00:00

245 lines
7.5 KiB
TypeScript

import * as plugins from '@git.zone/tstest/tapbundle';
import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 2525;
let testServer;
tap.test('prepare server', async () => {
testServer = await startTestServer({ port: TEST_PORT });
await new Promise(resolve => setTimeout(resolve, 100));
});
tap.test('PERF-03: CPU utilization - Load test', async (tools) => {
const done = tools.defer();
const monitoringDuration = 3000; // 3 seconds (reduced from 5)
const connectionCount = 5; // Reduced from 10
const connections: net.Socket[] = [];
// Add timeout to prevent hanging
const testTimeout = setTimeout(() => {
console.log('CPU test timeout reached, cleaning up...');
for (const socket of connections) {
if (!socket.destroyed) socket.destroy();
}
done.resolve();
}, 30000); // 30 second timeout
try {
// Record initial CPU usage
const initialCpuUsage = process.cpuUsage();
const startTime = Date.now();
// Create multiple connections and send emails
console.log(`Creating ${connectionCount} connections for CPU load test...`);
for (let i = 0; i < connectionCount; i++) {
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: 30000
});
connections.push(socket);
await new Promise<void>((resolve, reject) => {
socket.once('connect', () => {
resolve();
});
socket.once('error', reject);
});
// Process greeting
await new Promise<void>((resolve) => {
let greeting = '';
const handleGreeting = (chunk: Buffer) => {
greeting += chunk.toString();
if (greeting.includes('220') && greeting.includes('\r\n')) {
socket.removeListener('data', handleGreeting);
resolve();
}
};
socket.on('data', handleGreeting);
});
// Send EHLO
socket.write(`EHLO testhost-cpu-${i}\r\n`);
await new Promise<void>((resolve) => {
let data = '';
const handleData = (chunk: Buffer) => {
data += chunk.toString();
if (data.includes('250 ')) {
socket.removeListener('data', handleData);
resolve();
}
};
socket.on('data', handleData);
});
// Keep connection active, don't send full transaction to avoid timeout
}
// Keep connections active during monitoring period
console.log(`Monitoring CPU usage for ${monitoringDuration}ms...`);
// Send periodic NOOP commands to keep connections active
const noopInterval = setInterval(() => {
connections.forEach((socket, idx) => {
if (socket.writable) {
socket.write('NOOP\r\n');
}
});
}, 1000);
await new Promise(resolve => setTimeout(resolve, monitoringDuration));
clearInterval(noopInterval);
// Calculate CPU usage
const finalCpuUsage = process.cpuUsage(initialCpuUsage);
const totalCpuTimeMs = (finalCpuUsage.user + finalCpuUsage.system) / 1000;
const elapsedTime = Date.now() - startTime;
const cpuUtilizationPercent = (totalCpuTimeMs / elapsedTime) * 100;
console.log(`\nCPU Utilization Results:`);
console.log(`Total CPU time: ${totalCpuTimeMs.toFixed(0)}ms`);
console.log(`Elapsed time: ${elapsedTime}ms`);
console.log(`CPU utilization: ${cpuUtilizationPercent.toFixed(1)}%`);
console.log(`User CPU: ${(finalCpuUsage.user / 1000).toFixed(0)}ms`);
console.log(`System CPU: ${(finalCpuUsage.system / 1000).toFixed(0)}ms`);
// Clean up connections
for (const socket of connections) {
if (socket.writable) {
socket.write('QUIT\r\n');
socket.end();
}
}
// Test passes if CPU usage is reasonable (less than 80%)
expect(cpuUtilizationPercent).toBeLessThan(80);
clearTimeout(testTimeout);
done.resolve();
} catch (error) {
// Clean up on error
connections.forEach(socket => socket.destroy());
clearTimeout(testTimeout);
done.reject(error);
}
});
tap.test('PERF-03: CPU utilization - Stress test', async (tools) => {
const done = tools.defer();
const testDuration = 2000; // 2 seconds (reduced from 3)
let requestCount = 0;
// Add timeout to prevent hanging
const testTimeout = setTimeout(() => {
console.log('Stress test timeout reached, completing...');
done.resolve();
}, 15000); // 15 second timeout
try {
const initialCpuUsage = process.cpuUsage();
const startTime = Date.now();
console.log(`\nRunning CPU stress test for ${testDuration}ms...`);
// Create a single connection for rapid requests
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: 30000
});
await new Promise<void>((resolve, reject) => {
socket.once('connect', resolve);
socket.once('error', reject);
});
// Read greeting
await new Promise<void>((resolve) => {
let greeting = '';
const handleGreeting = (chunk: Buffer) => {
greeting += chunk.toString();
if (greeting.includes('220') && greeting.includes('\r\n')) {
socket.removeListener('data', handleGreeting);
resolve();
}
};
socket.on('data', handleGreeting);
});
// Send EHLO
socket.write('EHLO stresstest\r\n');
await new Promise<void>((resolve) => {
let data = '';
const handleData = (chunk: Buffer) => {
data += chunk.toString();
if (data.includes('250 ')) {
socket.removeListener('data', handleData);
resolve();
}
};
socket.on('data', handleData);
});
// Rapid command loop
const endTime = Date.now() + testDuration;
const commands = ['NOOP', 'RSET', 'VRFY test@example.com', 'HELP'];
let commandIndex = 0;
while (Date.now() < endTime) {
const command = commands[commandIndex % commands.length];
socket.write(`${command}\r\n`);
await new Promise<void>((resolve) => {
socket.once('data', () => {
requestCount++;
resolve();
});
});
commandIndex++;
// Small delay to avoid overwhelming
if (requestCount % 20 === 0) {
await new Promise(resolve => setTimeout(resolve, 10));
}
}
// Calculate final CPU usage
const finalCpuUsage = process.cpuUsage(initialCpuUsage);
const totalCpuTimeMs = (finalCpuUsage.user + finalCpuUsage.system) / 1000;
const elapsedTime = Date.now() - startTime;
const cpuUtilizationPercent = (totalCpuTimeMs / elapsedTime) * 100;
const requestsPerSecond = (requestCount / elapsedTime) * 1000;
console.log(`\nStress Test Results:`);
console.log(`Requests processed: ${requestCount}`);
console.log(`Requests per second: ${requestsPerSecond.toFixed(1)}`);
console.log(`CPU utilization: ${cpuUtilizationPercent.toFixed(1)}%`);
console.log(`CPU time per request: ${(totalCpuTimeMs / requestCount).toFixed(2)}ms`);
socket.write('QUIT\r\n');
socket.end();
// Test passes if CPU usage per request is reasonable
const cpuPerRequest = totalCpuTimeMs / requestCount;
expect(cpuPerRequest).toBeLessThan(10); // Less than 10ms CPU per request
clearTimeout(testTimeout);
done.resolve();
} catch (error) {
clearTimeout(testTimeout);
done.reject(error);
}
});
tap.test('cleanup server', async () => {
await stopTestServer(testServer);
});
export default tap.start();