update
This commit is contained in:
569
test/suite/einvoice_performance/test.perf-05.memory-usage.ts
Normal file
569
test/suite/einvoice_performance/test.perf-05.memory-usage.ts
Normal file
@ -0,0 +1,569 @@
|
||||
/**
|
||||
* @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();
|
Reference in New Issue
Block a user