update
This commit is contained in:
238
test/suite/smtpserver_performance/test.perf-04.memory-usage.ts
Normal file
238
test/suite/smtpserver_performance/test.perf-04.memory-usage.ts
Normal file
@ -0,0 +1,238 @@
|
||||
import * as plugins from '@git.zone/tstest/tapbundle';
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import * as net from 'net';
|
||||
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js';
|
||||
|
||||
const TEST_PORT = 2525;
|
||||
|
||||
let testServer;
|
||||
|
||||
// Helper function to wait for SMTP response
|
||||
const waitForResponse = (socket: net.Socket, expectedCode: string, timeout = 5000): Promise<string> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let buffer = '';
|
||||
const timer = setTimeout(() => {
|
||||
socket.removeListener('data', handler);
|
||||
reject(new Error(`Timeout waiting for ${expectedCode} response`));
|
||||
}, timeout);
|
||||
|
||||
const handler = (data: Buffer) => {
|
||||
buffer += data.toString();
|
||||
const lines = buffer.split('\r\n');
|
||||
|
||||
// Check if we have a complete response
|
||||
for (const line of lines) {
|
||||
if (line.startsWith(expectedCode + ' ')) {
|
||||
clearTimeout(timer);
|
||||
socket.removeListener('data', handler);
|
||||
resolve(buffer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
socket.on('data', handler);
|
||||
});
|
||||
};
|
||||
|
||||
tap.test('prepare server', async () => {
|
||||
testServer = await startTestServer({ port: TEST_PORT });
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
});
|
||||
|
||||
tap.test('PERF-04: Memory usage - Connection memory test', async (tools) => {
|
||||
const done = tools.defer();
|
||||
const connectionCount = 10; // Reduced from 20 to make test faster
|
||||
const connections: net.Socket[] = [];
|
||||
|
||||
try {
|
||||
// Force garbage collection if available
|
||||
if (global.gc) {
|
||||
global.gc();
|
||||
}
|
||||
|
||||
// Record initial memory usage
|
||||
const initialMemory = process.memoryUsage();
|
||||
console.log(`Initial memory usage: ${Math.round(initialMemory.heapUsed / (1024 * 1024))}MB`);
|
||||
|
||||
// Create multiple connections with large email content
|
||||
console.log(`Creating ${connectionCount} connections with large emails...`);
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
// Read greeting
|
||||
await waitForResponse(socket, '220');
|
||||
|
||||
// Send EHLO
|
||||
socket.write(`EHLO testhost-mem-${i}\r\n`);
|
||||
await waitForResponse(socket, '250');
|
||||
|
||||
// Send email transaction
|
||||
socket.write(`MAIL FROM:<sender${i}@example.com>\r\n`);
|
||||
await waitForResponse(socket, '250');
|
||||
|
||||
socket.write(`RCPT TO:<recipient${i}@example.com>\r\n`);
|
||||
await waitForResponse(socket, '250');
|
||||
|
||||
socket.write('DATA\r\n');
|
||||
await waitForResponse(socket, '354');
|
||||
|
||||
// Send large email content
|
||||
const largeContent = 'This is a large email content for memory testing. '.repeat(100);
|
||||
const emailContent = [
|
||||
`From: sender${i}@example.com`,
|
||||
`To: recipient${i}@example.com`,
|
||||
`Subject: Memory Usage Test ${i}`,
|
||||
'',
|
||||
largeContent,
|
||||
'.',
|
||||
''
|
||||
].join('\r\n');
|
||||
|
||||
socket.write(emailContent);
|
||||
await waitForResponse(socket, '250');
|
||||
|
||||
// Pause every 5 connections
|
||||
if (i > 0 && i % 5 === 0) {
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
const intermediateMemory = process.memoryUsage();
|
||||
console.log(`Memory after ${i} connections: ${Math.round(intermediateMemory.heapUsed / (1024 * 1024))}MB`);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait to let memory stabilize
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
// Record final memory usage
|
||||
const finalMemory = process.memoryUsage();
|
||||
const memoryIncreaseMB = (finalMemory.heapUsed - initialMemory.heapUsed) / (1024 * 1024);
|
||||
const memoryPerConnectionKB = (memoryIncreaseMB * 1024) / connectionCount;
|
||||
|
||||
console.log(`\nMemory Usage Results:`);
|
||||
console.log(`Initial heap: ${Math.round(initialMemory.heapUsed / (1024 * 1024))}MB`);
|
||||
console.log(`Final heap: ${Math.round(finalMemory.heapUsed / (1024 * 1024))}MB`);
|
||||
console.log(`Memory increase: ${memoryIncreaseMB.toFixed(2)}MB`);
|
||||
console.log(`Memory per connection: ${memoryPerConnectionKB.toFixed(2)}KB`);
|
||||
console.log(`RSS increase: ${Math.round((finalMemory.rss - initialMemory.rss) / (1024 * 1024))}MB`);
|
||||
|
||||
// Clean up connections
|
||||
for (const socket of connections) {
|
||||
if (socket.writable) {
|
||||
socket.write('QUIT\r\n');
|
||||
socket.end();
|
||||
}
|
||||
}
|
||||
|
||||
// Test passes if memory increase is reasonable (less than 30MB for 10 connections)
|
||||
expect(memoryIncreaseMB).toBeLessThan(30);
|
||||
done.resolve();
|
||||
} catch (error) {
|
||||
// Clean up on error
|
||||
connections.forEach(socket => socket.destroy());
|
||||
done.reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('PERF-04: Memory usage - Memory leak detection', async (tools) => {
|
||||
const done = tools.defer();
|
||||
const iterations = 3; // Reduced from 5
|
||||
const connectionsPerIteration = 3; // Reduced from 5
|
||||
|
||||
try {
|
||||
// Force GC if available
|
||||
if (global.gc) {
|
||||
global.gc();
|
||||
}
|
||||
|
||||
const initialMemory = process.memoryUsage();
|
||||
const memorySnapshots: number[] = [];
|
||||
|
||||
console.log(`\nRunning memory leak detection (${iterations} iterations)...`);
|
||||
|
||||
for (let iteration = 0; iteration < iterations; iteration++) {
|
||||
const sockets: net.Socket[] = [];
|
||||
|
||||
// Create and close connections
|
||||
for (let i = 0; i < connectionsPerIteration; i++) {
|
||||
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);
|
||||
});
|
||||
|
||||
// Quick transaction
|
||||
await waitForResponse(socket, '220');
|
||||
|
||||
socket.write('EHLO leaktest\r\n');
|
||||
await waitForResponse(socket, '250');
|
||||
|
||||
socket.write('QUIT\r\n');
|
||||
await waitForResponse(socket, '221');
|
||||
|
||||
socket.end();
|
||||
sockets.push(socket);
|
||||
}
|
||||
|
||||
// Wait for sockets to close
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
// Force cleanup
|
||||
sockets.forEach(s => s.destroy());
|
||||
|
||||
// Force GC if available
|
||||
if (global.gc) {
|
||||
global.gc();
|
||||
}
|
||||
|
||||
// Record memory after each iteration
|
||||
const currentMemory = process.memoryUsage();
|
||||
const memoryMB = currentMemory.heapUsed / (1024 * 1024);
|
||||
memorySnapshots.push(memoryMB);
|
||||
|
||||
console.log(`Iteration ${iteration + 1}: ${memoryMB.toFixed(2)}MB`);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
|
||||
// Check for memory leak pattern
|
||||
const firstSnapshot = memorySnapshots[0];
|
||||
const lastSnapshot = memorySnapshots[memorySnapshots.length - 1];
|
||||
const memoryGrowth = lastSnapshot - firstSnapshot;
|
||||
const avgGrowthPerIteration = memoryGrowth / (iterations - 1);
|
||||
|
||||
console.log(`\nMemory Leak Detection Results:`);
|
||||
console.log(`First snapshot: ${firstSnapshot.toFixed(2)}MB`);
|
||||
console.log(`Last snapshot: ${lastSnapshot.toFixed(2)}MB`);
|
||||
console.log(`Total growth: ${memoryGrowth.toFixed(2)}MB`);
|
||||
console.log(`Average growth per iteration: ${avgGrowthPerIteration.toFixed(2)}MB`);
|
||||
|
||||
// Test passes if average growth per iteration is less than 2MB
|
||||
expect(avgGrowthPerIteration).toBeLessThan(2);
|
||||
done.resolve();
|
||||
} catch (error) {
|
||||
done.reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('cleanup server', async () => {
|
||||
await stopTestServer(testServer);
|
||||
});
|
||||
|
||||
tap.start();
|
Reference in New Issue
Block a user