569 lines
22 KiB
TypeScript
569 lines
22 KiB
TypeScript
/**
|
|
* @file test.perf-05.memory-usage.ts
|
|
* @description Performance tests for memory usage profiling
|
|
*/
|
|
|
|
import { tap } from '@git.zone/tstest/tapbundle';
|
|
import * as plugins from '../../plugins.js';
|
|
import { EInvoice } from '../../../ts/index.js';
|
|
import { CorpusLoader } from '../../suite/corpus.loader.js';
|
|
import { PerformanceTracker } from '../../suite/performance.tracker.js';
|
|
|
|
const corpusLoader = new CorpusLoader();
|
|
const performanceTracker = new PerformanceTracker('PERF-05: Memory Usage Profiling');
|
|
|
|
tap.test('PERF-05: Memory Usage Profiling - should maintain efficient memory usage patterns', async (t) => {
|
|
// Test 1: Baseline memory usage for different operations
|
|
const baselineMemoryUsage = await performanceTracker.measureAsync(
|
|
'baseline-memory-usage',
|
|
async () => {
|
|
const einvoice = new EInvoice();
|
|
const results = {
|
|
operations: [],
|
|
initialMemory: null,
|
|
finalMemory: null
|
|
};
|
|
|
|
// Force garbage collection if available
|
|
if (global.gc) global.gc();
|
|
results.initialMemory = process.memoryUsage();
|
|
|
|
// Test different operations
|
|
const operations = [
|
|
{
|
|
name: 'Format Detection',
|
|
fn: async () => {
|
|
const xml = '<?xml version="1.0"?><Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"><ID>TEST</ID></Invoice>';
|
|
for (let i = 0; i < 100; i++) {
|
|
await einvoice.detectFormat(xml);
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: 'XML Parsing',
|
|
fn: async () => {
|
|
const xml = `<?xml version="1.0"?>
|
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
|
<ID>MEM-TEST</ID>
|
|
<IssueDate>2024-01-01</IssueDate>
|
|
${Array(10).fill('<InvoiceLine><ID>Line</ID></InvoiceLine>').join('\n')}
|
|
</Invoice>`;
|
|
for (let i = 0; i < 50; i++) {
|
|
await einvoice.parseInvoice(xml, 'ubl');
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: 'Validation',
|
|
fn: async () => {
|
|
const invoice = {
|
|
format: 'ubl' as const,
|
|
data: {
|
|
documentType: 'INVOICE',
|
|
invoiceNumber: 'MEM-VAL-001',
|
|
issueDate: '2024-02-10',
|
|
seller: { name: 'Seller', address: 'Address', country: 'US', taxId: 'US123' },
|
|
buyer: { name: 'Buyer', address: 'Address', country: 'US', taxId: 'US456' },
|
|
items: Array.from({ length: 20 }, (_, i) => ({
|
|
description: `Item ${i + 1}`,
|
|
quantity: 1,
|
|
unitPrice: 100,
|
|
vatRate: 10,
|
|
lineTotal: 100
|
|
})),
|
|
totals: { netAmount: 2000, vatAmount: 200, grossAmount: 2200 }
|
|
}
|
|
};
|
|
for (let i = 0; i < 30; i++) {
|
|
await einvoice.validateInvoice(invoice);
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: 'Format Conversion',
|
|
fn: async () => {
|
|
const invoice = {
|
|
format: 'ubl' as const,
|
|
data: {
|
|
documentType: 'INVOICE',
|
|
invoiceNumber: 'MEM-CONV-001',
|
|
issueDate: '2024-02-10',
|
|
seller: { name: 'Seller', address: 'Address', country: 'US', taxId: 'US123' },
|
|
buyer: { name: 'Buyer', address: 'Address', country: 'US', taxId: 'US456' },
|
|
items: [{ description: 'Item', quantity: 1, unitPrice: 100, vatRate: 10, lineTotal: 100 }],
|
|
totals: { netAmount: 100, vatAmount: 10, grossAmount: 110 }
|
|
}
|
|
};
|
|
for (let i = 0; i < 20; i++) {
|
|
await einvoice.convertFormat(invoice, 'cii');
|
|
}
|
|
}
|
|
}
|
|
];
|
|
|
|
// Execute operations and measure memory
|
|
for (const operation of operations) {
|
|
if (global.gc) global.gc();
|
|
const beforeMemory = process.memoryUsage();
|
|
|
|
await operation.fn();
|
|
|
|
if (global.gc) global.gc();
|
|
const afterMemory = process.memoryUsage();
|
|
|
|
results.operations.push({
|
|
name: operation.name,
|
|
heapUsedBefore: (beforeMemory.heapUsed / 1024 / 1024).toFixed(2),
|
|
heapUsedAfter: (afterMemory.heapUsed / 1024 / 1024).toFixed(2),
|
|
heapIncrease: ((afterMemory.heapUsed - beforeMemory.heapUsed) / 1024 / 1024).toFixed(2),
|
|
externalIncrease: ((afterMemory.external - beforeMemory.external) / 1024 / 1024).toFixed(2),
|
|
rssIncrease: ((afterMemory.rss - beforeMemory.rss) / 1024 / 1024).toFixed(2)
|
|
});
|
|
}
|
|
|
|
if (global.gc) global.gc();
|
|
results.finalMemory = process.memoryUsage();
|
|
|
|
return results;
|
|
}
|
|
);
|
|
|
|
// Test 2: Memory scaling with invoice complexity
|
|
const memoryScaling = await performanceTracker.measureAsync(
|
|
'memory-scaling',
|
|
async () => {
|
|
const einvoice = new EInvoice();
|
|
const results = {
|
|
scalingData: [],
|
|
memoryFormula: null
|
|
};
|
|
|
|
// Test with increasing invoice sizes
|
|
const itemCounts = [1, 10, 50, 100, 200, 500, 1000];
|
|
|
|
for (const itemCount of itemCounts) {
|
|
if (global.gc) global.gc();
|
|
const beforeMemory = process.memoryUsage();
|
|
|
|
// Create invoice with specified number of items
|
|
const invoice = {
|
|
format: 'ubl' as const,
|
|
data: {
|
|
documentType: 'INVOICE',
|
|
invoiceNumber: `SCALE-${itemCount}`,
|
|
issueDate: '2024-02-10',
|
|
seller: {
|
|
name: 'Memory Test Seller Corporation Ltd.',
|
|
address: '123 Memory Lane, Suite 456',
|
|
city: 'Test City',
|
|
postalCode: '12345',
|
|
country: 'US',
|
|
taxId: 'US123456789'
|
|
},
|
|
buyer: {
|
|
name: 'Memory Test Buyer Enterprises Inc.',
|
|
address: '789 RAM Avenue, Floor 10',
|
|
city: 'Cache Town',
|
|
postalCode: '67890',
|
|
country: 'US',
|
|
taxId: 'US987654321'
|
|
},
|
|
items: Array.from({ length: itemCount }, (_, i) => ({
|
|
description: `Product Item Number ${i + 1} with detailed description and specifications`,
|
|
quantity: Math.floor(Math.random() * 100) + 1,
|
|
unitPrice: Math.random() * 1000,
|
|
vatRate: [5, 10, 15, 20][Math.floor(Math.random() * 4)],
|
|
lineTotal: 0,
|
|
itemId: `ITEM-${String(i + 1).padStart(6, '0')}`,
|
|
additionalInfo: {
|
|
weight: `${Math.random() * 10}kg`,
|
|
dimensions: `${Math.random() * 100}x${Math.random() * 100}x${Math.random() * 100}`,
|
|
notes: `Additional notes for item ${i + 1}`
|
|
}
|
|
})),
|
|
totals: { netAmount: 0, vatAmount: 0, grossAmount: 0 }
|
|
}
|
|
};
|
|
|
|
// Calculate totals
|
|
invoice.data.items.forEach(item => {
|
|
item.lineTotal = item.quantity * item.unitPrice;
|
|
invoice.data.totals.netAmount += item.lineTotal;
|
|
invoice.data.totals.vatAmount += item.lineTotal * (item.vatRate / 100);
|
|
});
|
|
invoice.data.totals.grossAmount = invoice.data.totals.netAmount + invoice.data.totals.vatAmount;
|
|
|
|
// Process invoice through multiple operations
|
|
const parsed = await einvoice.parseInvoice(JSON.stringify(invoice), 'json');
|
|
await einvoice.validateInvoice(parsed);
|
|
await einvoice.convertFormat(parsed, 'cii');
|
|
|
|
if (global.gc) global.gc();
|
|
const afterMemory = process.memoryUsage();
|
|
|
|
const memoryUsed = (afterMemory.heapUsed - beforeMemory.heapUsed) / 1024 / 1024;
|
|
const invoiceSize = JSON.stringify(invoice).length / 1024; // KB
|
|
|
|
results.scalingData.push({
|
|
itemCount,
|
|
invoiceSizeKB: invoiceSize.toFixed(2),
|
|
memoryUsedMB: memoryUsed.toFixed(2),
|
|
memoryPerItemKB: ((memoryUsed * 1024) / itemCount).toFixed(2),
|
|
memoryEfficiency: (invoiceSize / (memoryUsed * 1024)).toFixed(3)
|
|
});
|
|
}
|
|
|
|
// Calculate memory scaling formula (linear regression)
|
|
if (results.scalingData.length > 2) {
|
|
const n = results.scalingData.length;
|
|
const sumX = results.scalingData.reduce((sum, d) => sum + d.itemCount, 0);
|
|
const sumY = results.scalingData.reduce((sum, d) => sum + parseFloat(d.memoryUsedMB), 0);
|
|
const sumXY = results.scalingData.reduce((sum, d) => sum + d.itemCount * parseFloat(d.memoryUsedMB), 0);
|
|
const sumX2 = results.scalingData.reduce((sum, d) => sum + d.itemCount * d.itemCount, 0);
|
|
|
|
const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
|
|
const intercept = (sumY - slope * sumX) / n;
|
|
|
|
results.memoryFormula = {
|
|
slope: slope.toFixed(4),
|
|
intercept: intercept.toFixed(4),
|
|
formula: `Memory(MB) = ${slope.toFixed(4)} * items + ${intercept.toFixed(4)}`
|
|
};
|
|
}
|
|
|
|
return results;
|
|
}
|
|
);
|
|
|
|
// Test 3: Memory leak detection
|
|
const memoryLeakDetection = await performanceTracker.measureAsync(
|
|
'memory-leak-detection',
|
|
async () => {
|
|
const einvoice = new EInvoice();
|
|
const results = {
|
|
iterations: 100,
|
|
memorySnapshots: [],
|
|
leakDetected: false,
|
|
leakRate: 0
|
|
};
|
|
|
|
// Test invoice for repeated operations
|
|
const testInvoice = {
|
|
format: 'ubl' as const,
|
|
data: {
|
|
documentType: 'INVOICE',
|
|
invoiceNumber: 'LEAK-TEST-001',
|
|
issueDate: '2024-02-10',
|
|
seller: { name: 'Leak Test Seller', address: 'Address', country: 'US', taxId: 'US123' },
|
|
buyer: { name: 'Leak Test Buyer', address: 'Address', country: 'US', taxId: 'US456' },
|
|
items: Array.from({ length: 10 }, (_, i) => ({
|
|
description: `Item ${i + 1}`,
|
|
quantity: 1,
|
|
unitPrice: 100,
|
|
vatRate: 10,
|
|
lineTotal: 100
|
|
})),
|
|
totals: { netAmount: 1000, vatAmount: 100, grossAmount: 1100 }
|
|
}
|
|
};
|
|
|
|
// Take memory snapshots during repeated operations
|
|
for (let i = 0; i < results.iterations; i++) {
|
|
if (i % 10 === 0) {
|
|
if (global.gc) global.gc();
|
|
const memory = process.memoryUsage();
|
|
results.memorySnapshots.push({
|
|
iteration: i,
|
|
heapUsedMB: memory.heapUsed / 1024 / 1024
|
|
});
|
|
}
|
|
|
|
// Perform operations that might leak memory
|
|
const xml = await einvoice.generateXML(testInvoice);
|
|
const parsed = await einvoice.parseInvoice(xml, 'ubl');
|
|
await einvoice.validateInvoice(parsed);
|
|
await einvoice.convertFormat(parsed, 'cii');
|
|
}
|
|
|
|
// Final snapshot
|
|
if (global.gc) global.gc();
|
|
const finalMemory = process.memoryUsage();
|
|
results.memorySnapshots.push({
|
|
iteration: results.iterations,
|
|
heapUsedMB: finalMemory.heapUsed / 1024 / 1024
|
|
});
|
|
|
|
// Analyze for memory leaks
|
|
if (results.memorySnapshots.length > 2) {
|
|
const firstSnapshot = results.memorySnapshots[0];
|
|
const lastSnapshot = results.memorySnapshots[results.memorySnapshots.length - 1];
|
|
const memoryIncrease = lastSnapshot.heapUsedMB - firstSnapshot.heapUsedMB;
|
|
|
|
results.leakRate = memoryIncrease / results.iterations; // MB per iteration
|
|
results.leakDetected = results.leakRate > 0.1; // Threshold: 0.1MB per iteration
|
|
|
|
// Calculate trend
|
|
const midpoint = Math.floor(results.memorySnapshots.length / 2);
|
|
const firstHalf = results.memorySnapshots.slice(0, midpoint);
|
|
const secondHalf = results.memorySnapshots.slice(midpoint);
|
|
|
|
const firstHalfAvg = firstHalf.reduce((sum, s) => sum + s.heapUsedMB, 0) / firstHalf.length;
|
|
const secondHalfAvg = secondHalf.reduce((sum, s) => sum + s.heapUsedMB, 0) / secondHalf.length;
|
|
|
|
results.trend = {
|
|
firstHalfAvgMB: firstHalfAvg.toFixed(2),
|
|
secondHalfAvgMB: secondHalfAvg.toFixed(2),
|
|
increasing: secondHalfAvg > firstHalfAvg * 1.1
|
|
};
|
|
}
|
|
|
|
return results;
|
|
}
|
|
);
|
|
|
|
// Test 4: Corpus processing memory profile
|
|
const corpusMemoryProfile = await performanceTracker.measureAsync(
|
|
'corpus-memory-profile',
|
|
async () => {
|
|
const files = await corpusLoader.getFilesByPattern('**/*.xml');
|
|
const einvoice = new EInvoice();
|
|
const results = {
|
|
filesProcessed: 0,
|
|
memoryByFormat: new Map<string, { count: number; totalMemory: number }>(),
|
|
memoryBySize: {
|
|
small: { count: 0, avgMemory: 0, total: 0 },
|
|
medium: { count: 0, avgMemory: 0, total: 0 },
|
|
large: { count: 0, avgMemory: 0, total: 0 }
|
|
},
|
|
peakMemory: 0,
|
|
totalAllocated: 0
|
|
};
|
|
|
|
// Initial memory state
|
|
if (global.gc) global.gc();
|
|
const startMemory = process.memoryUsage();
|
|
|
|
// Process sample files
|
|
const sampleFiles = files.slice(0, 30);
|
|
|
|
for (const file of sampleFiles) {
|
|
try {
|
|
const content = await plugins.fs.readFile(file, 'utf-8');
|
|
const fileSize = Buffer.byteLength(content, 'utf-8');
|
|
const sizeCategory = fileSize < 10240 ? 'small' :
|
|
fileSize < 102400 ? 'medium' : 'large';
|
|
|
|
const beforeProcess = process.memoryUsage();
|
|
|
|
// Process file
|
|
const format = await einvoice.detectFormat(content);
|
|
if (!format || format === 'unknown') continue;
|
|
|
|
const invoice = await einvoice.parseInvoice(content, format);
|
|
await einvoice.validateInvoice(invoice);
|
|
|
|
const afterProcess = process.memoryUsage();
|
|
const memoryUsed = (afterProcess.heapUsed - beforeProcess.heapUsed) / 1024 / 1024;
|
|
|
|
// Update statistics
|
|
results.filesProcessed++;
|
|
results.totalAllocated += memoryUsed;
|
|
|
|
// By format
|
|
if (!results.memoryByFormat.has(format)) {
|
|
results.memoryByFormat.set(format, { count: 0, totalMemory: 0 });
|
|
}
|
|
const formatStats = results.memoryByFormat.get(format)!;
|
|
formatStats.count++;
|
|
formatStats.totalMemory += memoryUsed;
|
|
|
|
// By size
|
|
results.memoryBySize[sizeCategory].count++;
|
|
results.memoryBySize[sizeCategory].total += memoryUsed;
|
|
|
|
// Track peak
|
|
if (afterProcess.heapUsed > results.peakMemory) {
|
|
results.peakMemory = afterProcess.heapUsed;
|
|
}
|
|
|
|
} catch (error) {
|
|
// Skip failed files
|
|
}
|
|
}
|
|
|
|
// Calculate averages
|
|
for (const category of Object.keys(results.memoryBySize)) {
|
|
const stats = results.memoryBySize[category];
|
|
if (stats.count > 0) {
|
|
stats.avgMemory = stats.total / stats.count;
|
|
}
|
|
}
|
|
|
|
// Format statistics
|
|
const formatStats = Array.from(results.memoryByFormat.entries()).map(([format, stats]) => ({
|
|
format,
|
|
count: stats.count,
|
|
avgMemoryMB: (stats.totalMemory / stats.count).toFixed(2)
|
|
}));
|
|
|
|
return {
|
|
filesProcessed: results.filesProcessed,
|
|
totalAllocatedMB: results.totalAllocated.toFixed(2),
|
|
peakMemoryMB: ((results.peakMemory - startMemory.heapUsed) / 1024 / 1024).toFixed(2),
|
|
avgMemoryPerFileMB: (results.totalAllocated / results.filesProcessed).toFixed(2),
|
|
formatStats,
|
|
sizeStats: {
|
|
small: { ...results.memoryBySize.small, avgMemory: results.memoryBySize.small.avgMemory.toFixed(2) },
|
|
medium: { ...results.memoryBySize.medium, avgMemory: results.memoryBySize.medium.avgMemory.toFixed(2) },
|
|
large: { ...results.memoryBySize.large, avgMemory: results.memoryBySize.large.avgMemory.toFixed(2) }
|
|
}
|
|
};
|
|
}
|
|
);
|
|
|
|
// Test 5: Garbage collection impact
|
|
const gcImpact = await performanceTracker.measureAsync(
|
|
'gc-impact',
|
|
async () => {
|
|
const einvoice = new EInvoice();
|
|
const results = {
|
|
withManualGC: { times: [], avgTime: 0 },
|
|
withoutGC: { times: [], avgTime: 0 },
|
|
gcOverhead: 0
|
|
};
|
|
|
|
// Test invoice
|
|
const testInvoice = {
|
|
format: 'ubl' as const,
|
|
data: {
|
|
documentType: 'INVOICE',
|
|
invoiceNumber: 'GC-TEST-001',
|
|
issueDate: '2024-02-10',
|
|
seller: { name: 'GC Test Seller', address: 'Address', country: 'US', taxId: 'US123' },
|
|
buyer: { name: 'GC Test Buyer', address: 'Address', country: 'US', taxId: 'US456' },
|
|
items: Array.from({ length: 50 }, (_, i) => ({
|
|
description: `Item ${i + 1}`,
|
|
quantity: 1,
|
|
unitPrice: 100,
|
|
vatRate: 10,
|
|
lineTotal: 100
|
|
})),
|
|
totals: { netAmount: 5000, vatAmount: 500, grossAmount: 5500 }
|
|
}
|
|
};
|
|
|
|
// Test with manual GC
|
|
if (global.gc) {
|
|
for (let i = 0; i < 20; i++) {
|
|
global.gc();
|
|
const start = process.hrtime.bigint();
|
|
|
|
await einvoice.parseInvoice(JSON.stringify(testInvoice), 'json');
|
|
await einvoice.validateInvoice(testInvoice);
|
|
await einvoice.convertFormat(testInvoice, 'cii');
|
|
|
|
const end = process.hrtime.bigint();
|
|
results.withManualGC.times.push(Number(end - start) / 1_000_000);
|
|
}
|
|
}
|
|
|
|
// Test without manual GC
|
|
for (let i = 0; i < 20; i++) {
|
|
const start = process.hrtime.bigint();
|
|
|
|
await einvoice.parseInvoice(JSON.stringify(testInvoice), 'json');
|
|
await einvoice.validateInvoice(testInvoice);
|
|
await einvoice.convertFormat(testInvoice, 'cii');
|
|
|
|
const end = process.hrtime.bigint();
|
|
results.withoutGC.times.push(Number(end - start) / 1_000_000);
|
|
}
|
|
|
|
// Calculate averages
|
|
if (results.withManualGC.times.length > 0) {
|
|
results.withManualGC.avgTime = results.withManualGC.times.reduce((a, b) => a + b, 0) / results.withManualGC.times.length;
|
|
}
|
|
results.withoutGC.avgTime = results.withoutGC.times.reduce((a, b) => a + b, 0) / results.withoutGC.times.length;
|
|
|
|
if (results.withManualGC.avgTime > 0) {
|
|
results.gcOverhead = ((results.withManualGC.avgTime - results.withoutGC.avgTime) / results.withoutGC.avgTime * 100);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
);
|
|
|
|
// Summary
|
|
t.comment('\n=== PERF-05: Memory Usage Profiling Test Summary ===');
|
|
|
|
t.comment('\nBaseline Memory Usage:');
|
|
baselineMemoryUsage.result.operations.forEach(op => {
|
|
t.comment(` ${op.name}:`);
|
|
t.comment(` - Heap before: ${op.heapUsedBefore}MB, after: ${op.heapUsedAfter}MB`);
|
|
t.comment(` - Heap increase: ${op.heapIncrease}MB`);
|
|
t.comment(` - RSS increase: ${op.rssIncrease}MB`);
|
|
});
|
|
|
|
t.comment('\nMemory Scaling with Invoice Complexity:');
|
|
t.comment(' Item Count | Invoice Size | Memory Used | Memory/Item | Efficiency');
|
|
t.comment(' -----------|--------------|-------------|-------------|------------');
|
|
memoryScaling.result.scalingData.forEach(data => {
|
|
t.comment(` ${String(data.itemCount).padEnd(10)} | ${data.invoiceSizeKB.padEnd(12)}KB | ${data.memoryUsedMB.padEnd(11)}MB | ${data.memoryPerItemKB.padEnd(11)}KB | ${data.memoryEfficiency}`);
|
|
});
|
|
if (memoryScaling.result.memoryFormula) {
|
|
t.comment(` Memory scaling formula: ${memoryScaling.result.memoryFormula.formula}`);
|
|
}
|
|
|
|
t.comment('\nMemory Leak Detection:');
|
|
t.comment(` Iterations: ${memoryLeakDetection.result.iterations}`);
|
|
t.comment(` Leak detected: ${memoryLeakDetection.result.leakDetected ? 'YES ⚠️' : 'NO ✅'}`);
|
|
t.comment(` Leak rate: ${(memoryLeakDetection.result.leakRate * 1000).toFixed(3)}KB per iteration`);
|
|
if (memoryLeakDetection.result.trend) {
|
|
t.comment(` Memory trend: ${memoryLeakDetection.result.trend.increasing ? 'INCREASING ⚠️' : 'STABLE ✅'}`);
|
|
t.comment(` - First half avg: ${memoryLeakDetection.result.trend.firstHalfAvgMB}MB`);
|
|
t.comment(` - Second half avg: ${memoryLeakDetection.result.trend.secondHalfAvgMB}MB`);
|
|
}
|
|
|
|
t.comment('\nCorpus Memory Profile:');
|
|
t.comment(` Files processed: ${corpusMemoryProfile.result.filesProcessed}`);
|
|
t.comment(` Total allocated: ${corpusMemoryProfile.result.totalAllocatedMB}MB`);
|
|
t.comment(` Peak memory: ${corpusMemoryProfile.result.peakMemoryMB}MB`);
|
|
t.comment(` Avg per file: ${corpusMemoryProfile.result.avgMemoryPerFileMB}MB`);
|
|
t.comment(' By format:');
|
|
corpusMemoryProfile.result.formatStats.forEach(stat => {
|
|
t.comment(` - ${stat.format}: ${stat.count} files, avg ${stat.avgMemoryMB}MB`);
|
|
});
|
|
t.comment(' By size:');
|
|
['small', 'medium', 'large'].forEach(size => {
|
|
const stats = corpusMemoryProfile.result.sizeStats[size];
|
|
if (stats.count > 0) {
|
|
t.comment(` - ${size}: ${stats.count} files, avg ${stats.avgMemory}MB`);
|
|
}
|
|
});
|
|
|
|
t.comment('\nGarbage Collection Impact:');
|
|
if (gcImpact.result.withManualGC.avgTime > 0) {
|
|
t.comment(` With manual GC: ${gcImpact.result.withManualGC.avgTime.toFixed(3)}ms avg`);
|
|
}
|
|
t.comment(` Without GC: ${gcImpact.result.withoutGC.avgTime.toFixed(3)}ms avg`);
|
|
if (gcImpact.result.gcOverhead !== 0) {
|
|
t.comment(` GC overhead: ${gcImpact.result.gcOverhead.toFixed(1)}%`);
|
|
}
|
|
|
|
// Performance targets check
|
|
t.comment('\n=== Performance Targets Check ===');
|
|
const avgMemoryPerInvoice = parseFloat(corpusMemoryProfile.result.avgMemoryPerFileMB);
|
|
const targetMemory = 100; // Target: <100MB per invoice
|
|
const leakDetected = memoryLeakDetection.result.leakDetected;
|
|
|
|
t.comment(`Memory usage: ${avgMemoryPerInvoice}MB ${avgMemoryPerInvoice < targetMemory ? '✅' : '⚠️'} (target: <${targetMemory}MB per invoice)`);
|
|
t.comment(`Memory leaks: ${leakDetected ? 'DETECTED ⚠️' : 'NONE ✅'}`);
|
|
|
|
// Overall performance summary
|
|
t.comment('\n=== Overall Performance Summary ===');
|
|
performanceTracker.logSummary();
|
|
|
|
t.end();
|
|
});
|
|
|
|
tap.start(); |