Files
smartpreview/test/test.performance.ts

415 lines
10 KiB
TypeScript
Raw Normal View History

import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as smartpreview from '../ts/index.js';
// Create test data with different sizes
const createTestPdf = (complexity: 'simple' | 'complex'): Buffer => {
if (complexity === 'simple') {
const pdfContent = `%PDF-1.4
1 0 obj
<<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj
<<
/Type /Pages
/Kids [3 0 R]
/Count 1
>>
endobj
3 0 obj
<<
/Type /Page
/Parent 2 0 R
/MediaBox [0 0 612 792]
/Resources <<
/Font <<
/F1 4 0 R
>>
>>
/Contents 5 0 R
>>
endobj
4 0 obj
<<
/Type /Font
/Subtype /Type1
/BaseFont /Helvetica
>>
endobj
5 0 obj
<<
/Length 44
>>
stream
BT
/F1 12 Tf
72 720 Td
(Hello World) Tj
ET
endstream
endobj
xref
0 6
0000000000 65535 f
0000000010 00000 n
0000000079 00000 n
0000000136 00000 n
0000000273 00000 n
0000000362 00000 n
trailer
<<
/Size 6
/Root 1 0 R
>>
startxref
456
%%EOF`;
return Buffer.from(pdfContent, 'utf8');
} else {
// Complex PDF with more content
const complexPdfContent = `%PDF-1.4
1 0 obj
<<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj
<<
/Type /Pages
/Kids [3 0 R 7 0 R]
/Count 2
>>
endobj
3 0 obj
<<
/Type /Page
/Parent 2 0 R
/MediaBox [0 0 612 792]
/Resources <<
/Font <<
/F1 4 0 R
>>
>>
/Contents 5 0 R
>>
endobj
4 0 obj
<<
/Type /Font
/Subtype /Type1
/BaseFont /Helvetica
>>
endobj
5 0 obj
<<
/Length 200
>>
stream
BT
/F1 12 Tf
72 720 Td
(Complex PDF Document - Page 1) Tj
0 -20 Td
(This is a more complex PDF with multiple lines) Tj
0 -20 Td
(Line 3 with some content) Tj
0 -20 Td
(Line 4 with more text for testing) Tj
0 -20 Td
(Final line on page 1) Tj
ET
endstream
endobj
7 0 obj
<<
/Type /Page
/Parent 2 0 R
/MediaBox [0 0 612 792]
/Resources <<
/Font <<
/F1 4 0 R
>>
>>
/Contents 8 0 R
>>
endobj
8 0 obj
<<
/Length 150
>>
stream
BT
/F1 12 Tf
72 720 Td
(Complex PDF Document - Page 2) Tj
0 -20 Td
(Second page content) Tj
0 -20 Td
(More text on page 2) Tj
0 -20 Td
(End of document) Tj
ET
endstream
endobj
xref
0 9
0000000000 65535 f
0000000010 00000 n
0000000079 00000 n
0000000136 00000 n
0000000340 00000 n
0000000429 00000 n
0000000000 65535 f
0000000680 00000 n
0000000884 00000 n
trailer
<<
/Size 9
/Root 1 0 R
>>
startxref
1085
%%EOF`;
return Buffer.from(complexPdfContent, 'utf8');
}
};
interface IPerformanceMetrics {
initTime: number;
conversionTimes: number[];
averageConversionTime: number;
minConversionTime: number;
maxConversionTime: number;
standardDeviation: number;
throughput: number; // conversions per second
memoryUsed?: number;
}
// Comprehensive performance benchmark
tap.test('Performance Benchmark Suite', async () => {
console.log('\n🚀 Starting SmartPreview Performance Benchmark\n');
const results: { [key: string]: IPerformanceMetrics } = {};
// Test different configurations
const testConfigs = [
{ name: 'Low Quality Small', quality: 30, width: 200, height: 150 },
{ name: 'Medium Quality Medium', quality: 60, width: 400, height: 300 },
{ name: 'High Quality Large', quality: 90, width: 800, height: 600 },
{ name: 'Ultra Quality XLarge', quality: 100, width: 1200, height: 900 },
];
for (const config of testConfigs) {
console.log(`📊 Testing configuration: ${config.name}`);
const preview = new smartpreview.SmartPreview();
const testPdf = createTestPdf('simple');
const iterations = 5;
try {
// Measure initialization time
const initStartTime = performance.now();
await preview.init();
const initTime = performance.now() - initStartTime;
// Measure memory before conversions
const memoryBefore = process.memoryUsage();
// Run multiple conversions to get average
const conversionTimes: number[] = [];
for (let i = 0; i < iterations; i++) {
const startTime = performance.now();
await preview.generatePreview(testPdf, {
quality: config.quality,
width: config.width,
height: config.height
});
const conversionTime = performance.now() - startTime;
conversionTimes.push(conversionTime);
}
// Measure memory after conversions
const memoryAfter = process.memoryUsage();
const memoryUsed = (memoryAfter.heapUsed - memoryBefore.heapUsed) / 1024 / 1024; // MB
// Calculate metrics
const averageConversionTime = conversionTimes.reduce((a, b) => a + b, 0) / conversionTimes.length;
const minConversionTime = Math.min(...conversionTimes);
const maxConversionTime = Math.max(...conversionTimes);
const variance = conversionTimes.reduce((acc, time) => acc + Math.pow(time - averageConversionTime, 2), 0) / conversionTimes.length;
const standardDeviation = Math.sqrt(variance);
const throughput = 1000 / averageConversionTime; // conversions per second
results[config.name] = {
initTime,
conversionTimes,
averageConversionTime,
minConversionTime,
maxConversionTime,
standardDeviation,
throughput,
memoryUsed
};
// Print results for this config
console.log(` ⚡ Init Time: ${initTime.toFixed(2)}ms`);
console.log(` ⏱️ Avg Conversion: ${averageConversionTime.toFixed(2)}ms`);
console.log(` 📈 Throughput: ${throughput.toFixed(2)} conversions/sec`);
console.log(` 💾 Memory Used: ${memoryUsed.toFixed(2)}MB`);
console.log(` 📊 Std Dev: ${standardDeviation.toFixed(2)}ms\n`);
await preview.cleanup();
} catch (error) {
console.log(` ❌ Test skipped: ${error.message}\n`);
// Expected if dependencies are not available
expect(error).toBeInstanceOf(smartpreview.PreviewError);
}
}
// Performance assertions
for (const [configName, metrics] of Object.entries(results)) {
// Initialization should be reasonable
expect(metrics.initTime).toBeLessThan(10000); // < 10 seconds
// Conversion times should be consistent (allow some variance for very fast operations)
expect(metrics.standardDeviation).toBeLessThan(metrics.averageConversionTime * 5); // Std dev shouldn't be more than 5x average
// Should achieve minimum throughput
expect(metrics.throughput).toBeGreaterThan(0.1); // At least 0.1 conversions per second
console.log(`${configName} performance benchmarks passed`);
}
});
// Memory usage analysis
tap.test('Memory Usage Analysis', async () => {
console.log('\n🧠 Memory Usage Analysis\n');
const preview = new smartpreview.SmartPreview();
const testPdf = createTestPdf('complex');
try {
await preview.init();
const initialMemory = process.memoryUsage();
console.log(`📊 Initial Memory Usage:`);
console.log(` Heap Used: ${(initialMemory.heapUsed / 1024 / 1024).toFixed(2)}MB`);
console.log(` Heap Total: ${(initialMemory.heapTotal / 1024 / 1024).toFixed(2)}MB`);
console.log(` RSS: ${(initialMemory.rss / 1024 / 1024).toFixed(2)}MB\n`);
// Perform multiple conversions to check for memory leaks
const iterations = 10;
const memorySnapshots: any[] = [];
for (let i = 0; i < iterations; i++) {
await preview.generatePreview(testPdf, {
quality: 80,
width: 600,
height: 400
});
const memory = process.memoryUsage();
memorySnapshots.push({
iteration: i + 1,
heapUsed: memory.heapUsed / 1024 / 1024,
heapTotal: memory.heapTotal / 1024 / 1024,
rss: memory.rss / 1024 / 1024
});
// Force garbage collection if available
if (global.gc) {
global.gc();
}
}
// Analyze memory growth
const firstHeap = memorySnapshots[0].heapUsed;
const lastHeap = memorySnapshots[memorySnapshots.length - 1].heapUsed;
const memoryGrowth = lastHeap - firstHeap;
console.log(`📈 Memory Growth Analysis:`);
console.log(` First Iteration: ${firstHeap.toFixed(2)}MB`);
console.log(` Last Iteration: ${lastHeap.toFixed(2)}MB`);
console.log(` Total Growth: ${memoryGrowth.toFixed(2)}MB`);
console.log(` Growth per Conversion: ${(memoryGrowth / iterations).toFixed(2)}MB\n`);
// Memory growth should be minimal (indicating no major memory leaks)
expect(memoryGrowth).toBeLessThan(50); // Less than 50MB growth for 10 conversions
await preview.cleanup();
} catch (error) {
console.log(`❌ Memory test skipped: ${error.message}`);
expect(error).toBeInstanceOf(smartpreview.PreviewError);
}
});
// Stress test
tap.test('Stress Test - Rapid Conversions', async () => {
console.log('\n🔥 Stress Test - Rapid Conversions\n');
const preview = new smartpreview.SmartPreview();
const testPdf = createTestPdf('simple');
const rapidIterations = 20;
try {
await preview.init();
const startTime = performance.now();
const promises: Promise<any>[] = [];
// Start multiple conversions simultaneously
for (let i = 0; i < rapidIterations; i++) {
const promise = preview.generatePreview(testPdf, {
quality: 70,
width: 300,
height: 200
});
promises.push(promise);
}
// Wait for all conversions to complete
const results = await Promise.allSettled(promises);
const totalTime = performance.now() - startTime;
const successful = results.filter(r => r.status === 'fulfilled').length;
const failed = results.filter(r => r.status === 'rejected').length;
console.log(`⚡ Stress Test Results:`);
console.log(` Total Conversions: ${rapidIterations}`);
console.log(` Successful: ${successful}`);
console.log(` Failed: ${failed}`);
console.log(` Total Time: ${totalTime.toFixed(2)}ms`);
console.log(` Average Time per Conversion: ${(totalTime / successful).toFixed(2)}ms`);
console.log(` Concurrent Throughput: ${(successful * 1000 / totalTime).toFixed(2)} conversions/sec\n`);
// Most conversions should succeed
expect(successful).toBeGreaterThan(rapidIterations * 0.8); // At least 80% success rate
expect(failed).toBeLessThan(rapidIterations * 0.2); // Less than 20% failure rate
await preview.cleanup();
} catch (error) {
console.log(`❌ Stress test skipped: ${error.message}`);
expect(error).toBeInstanceOf(smartpreview.PreviewError);
}
});
export default tap.start();