/** * @file test.perf-06.cpu-utilization.ts * @description Performance tests for CPU utilization monitoring */ 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'; import * as os from 'os'; const corpusLoader = new CorpusLoader(); const performanceTracker = new PerformanceTracker('PERF-06: CPU Utilization'); tap.test('PERF-06: CPU Utilization - should maintain efficient CPU usage patterns', async (t) => { // Helper function to get CPU usage const getCPUUsage = () => { const cpus = os.cpus(); let user = 0; let nice = 0; let sys = 0; let idle = 0; let irq = 0; for (const cpu of cpus) { user += cpu.times.user; nice += cpu.times.nice; sys += cpu.times.sys; idle += cpu.times.idle; irq += cpu.times.irq; } const total = user + nice + sys + idle + irq; return { user: user / total, system: sys / total, idle: idle / total, total: total }; }; // Test 1: CPU usage baseline for operations const cpuBaseline = await performanceTracker.measureAsync( 'cpu-usage-baseline', async () => { const einvoice = new EInvoice(); const results = { operations: [], cpuCount: os.cpus().length, cpuModel: os.cpus()[0]?.model || 'Unknown' }; // Operations to test const operations = [ { name: 'Idle baseline', fn: async () => { await new Promise(resolve => setTimeout(resolve, 1000)); } }, { name: 'Format detection (100x)', fn: async () => { const xml = 'CPU-TEST'; for (let i = 0; i < 100; i++) { await einvoice.detectFormat(xml); } } }, { name: 'XML parsing (50x)', fn: async () => { const xml = ` CPU-PARSE 2024-01-01 ${Array(20).fill('Line').join('\n')} `; for (let i = 0; i < 50; i++) { await einvoice.parseInvoice(xml, 'ubl'); } } }, { name: 'Validation (30x)', fn: async () => { const invoice = { format: 'ubl' as const, data: { documentType: 'INVOICE', invoiceNumber: 'CPU-VAL-001', issueDate: '2024-02-15', seller: { name: 'CPU Test Seller', address: 'Address', country: 'US', taxId: 'US123' }, buyer: { name: 'CPU Test 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: 'Conversion (20x)', fn: async () => { const invoice = { format: 'ubl' as const, data: { documentType: 'INVOICE', invoiceNumber: 'CPU-CONV-001', issueDate: '2024-02-15', seller: { name: 'Seller', address: 'Address', country: 'US', taxId: 'US123' }, buyer: { name: '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 } } }; for (let i = 0; i < 20; i++) { await einvoice.convertFormat(invoice, 'cii'); } } } ]; // Execute operations and measure CPU for (const operation of operations) { const startCPU = getCPUUsage(); const startTime = Date.now(); const startUsage = process.cpuUsage(); await operation.fn(); const endUsage = process.cpuUsage(startUsage); const endTime = Date.now(); const endCPU = getCPUUsage(); const duration = endTime - startTime; const userCPU = endUsage.user / 1000; // Convert to milliseconds const systemCPU = endUsage.system / 1000; results.operations.push({ name: operation.name, duration, userCPU: userCPU.toFixed(2), systemCPU: systemCPU.toFixed(2), totalCPU: (userCPU + systemCPU).toFixed(2), cpuPercentage: ((userCPU + systemCPU) / duration * 100).toFixed(2), efficiency: (duration / (userCPU + systemCPU)).toFixed(2) }); } return results; } ); // Test 2: Multi-core utilization const multiCoreUtilization = await performanceTracker.measureAsync( 'multi-core-utilization', async () => { const einvoice = new EInvoice(); const results = { coreCount: os.cpus().length, parallelTests: [] }; // Test invoice batch const invoices = Array.from({ length: 50 }, (_, i) => ({ format: 'ubl' as const, data: { documentType: 'INVOICE', invoiceNumber: `MULTI-CORE-${i + 1}`, issueDate: '2024-02-15', seller: { name: `Seller ${i + 1}`, address: 'Address', country: 'US', taxId: `US${i}` }, buyer: { name: `Buyer ${i + 1}`, address: 'Address', country: 'US', taxId: `US${i + 1000}` }, items: Array.from({ length: 10 }, (_, j) => ({ description: `Item ${j + 1}`, quantity: 1, unitPrice: 100, vatRate: 10, lineTotal: 100 })), totals: { netAmount: 1000, vatAmount: 100, grossAmount: 1100 } } })); // Test different parallelism levels const parallelismLevels = [1, 2, 4, 8, results.coreCount]; for (const parallelism of parallelismLevels) { if (parallelism > results.coreCount) continue; const startUsage = process.cpuUsage(); const startTime = Date.now(); // Process invoices in parallel const batchSize = Math.ceil(invoices.length / parallelism); const promises = []; for (let i = 0; i < parallelism; i++) { const batch = invoices.slice(i * batchSize, (i + 1) * batchSize); promises.push( Promise.all(batch.map(async (invoice) => { await einvoice.validateInvoice(invoice); await einvoice.convertFormat(invoice, 'cii'); })) ); } await Promise.all(promises); const endTime = Date.now(); const endUsage = process.cpuUsage(startUsage); const duration = endTime - startTime; const totalCPU = (endUsage.user + endUsage.system) / 1000; const theoreticalSpeedup = parallelism; const actualSpeedup = results.parallelTests.length > 0 ? results.parallelTests[0].duration / duration : 1; results.parallelTests.push({ parallelism, duration, totalCPU: totalCPU.toFixed(2), cpuEfficiency: ((totalCPU / duration) * 100).toFixed(2), theoreticalSpeedup, actualSpeedup: actualSpeedup.toFixed(2), efficiency: ((actualSpeedup / theoreticalSpeedup) * 100).toFixed(2) }); } return results; } ); // Test 3: CPU-intensive operations profiling const cpuIntensiveOperations = await performanceTracker.measureAsync( 'cpu-intensive-operations', async () => { const einvoice = new EInvoice(); const results = { operations: [] }; // Test scenarios const scenarios = [ { name: 'Complex validation', fn: async () => { const invoice = { format: 'ubl' as const, data: { documentType: 'INVOICE', invoiceNumber: 'COMPLEX-VAL-001', issueDate: '2024-02-15', dueDate: '2024-03-15', currency: 'EUR', seller: { name: 'Complex Validation Test Seller GmbH', address: 'Hauptstraße 123', city: 'Berlin', postalCode: '10115', country: 'DE', taxId: 'DE123456789', registrationNumber: 'HRB12345', email: 'billing@seller.de', phone: '+49 30 12345678' }, buyer: { name: 'Complex Validation Test Buyer Ltd', address: 'Business Street 456', city: 'Munich', postalCode: '80331', country: 'DE', taxId: 'DE987654321', email: 'ap@buyer.de' }, items: Array.from({ length: 100 }, (_, i) => ({ description: `Complex Product ${i + 1} with detailed specifications and compliance requirements`, quantity: Math.floor(Math.random() * 100) + 1, unitPrice: Math.random() * 1000, vatRate: [0, 7, 19][Math.floor(Math.random() * 3)], lineTotal: 0, itemId: `ITEM-${String(i + 1).padStart(5, '0')}`, additionalCharges: Math.random() * 50, discounts: Math.random() * 20 })), totals: { netAmount: 0, vatAmount: 0, grossAmount: 0 } } }; // Calculate totals invoice.data.items.forEach(item => { item.lineTotal = item.quantity * item.unitPrice + (item.additionalCharges || 0) - (item.discounts || 0); 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; // Perform all validation levels await einvoice.validateInvoice(invoice, { level: 'syntax' }); await einvoice.validateInvoice(invoice, { level: 'semantic' }); await einvoice.validateInvoice(invoice, { level: 'business' }); } }, { name: 'Large XML generation', fn: async () => { const invoice = { format: 'ubl' as const, data: { documentType: 'INVOICE', invoiceNumber: 'LARGE-XML-001', issueDate: '2024-02-15', seller: { name: 'XML Generator Corp', address: 'XML Street', country: 'US', taxId: 'US123456789' }, buyer: { name: 'XML Consumer Inc', address: 'XML Avenue', country: 'US', taxId: 'US987654321' }, items: Array.from({ length: 200 }, (_, i) => ({ description: `Product ${i + 1} with very long description `.repeat(10), quantity: Math.random() * 100, unitPrice: Math.random() * 1000, vatRate: Math.random() * 25, lineTotal: 0 })), 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; await einvoice.generateXML(invoice); } }, { name: 'Chain conversions', fn: async () => { const invoice = { format: 'ubl' as const, data: { documentType: 'INVOICE', invoiceNumber: 'CHAIN-CONV-001', issueDate: '2024-02-15', seller: { name: 'Chain Seller', address: 'Chain Street', country: 'US', taxId: 'US123' }, buyer: { name: 'Chain Buyer', address: 'Chain Avenue', country: 'US', taxId: 'US456' }, items: Array.from({ length: 50 }, (_, i) => ({ description: `Chain Item ${i + 1}`, quantity: i + 1, unitPrice: 100 + i * 10, vatRate: 10, lineTotal: (i + 1) * (100 + i * 10) })), totals: { netAmount: 0, vatAmount: 0, grossAmount: 0 } } }; // Calculate totals invoice.data.items.forEach(item => { invoice.data.totals.netAmount += item.lineTotal; invoice.data.totals.vatAmount += item.lineTotal * 0.1; }); invoice.data.totals.grossAmount = invoice.data.totals.netAmount + invoice.data.totals.vatAmount; // Chain conversions let current = invoice; const formats = ['cii', 'zugferd', 'xrechnung', 'ubl']; for (const format of formats) { current = await einvoice.convertFormat(current, format); } } } ]; // Profile each scenario for (const scenario of scenarios) { const iterations = 5; const measurements = []; for (let i = 0; i < iterations; i++) { const startUsage = process.cpuUsage(); const startTime = process.hrtime.bigint(); await scenario.fn(); const endTime = process.hrtime.bigint(); const endUsage = process.cpuUsage(startUsage); const duration = Number(endTime - startTime) / 1_000_000; const cpuTime = (endUsage.user + endUsage.system) / 1000; measurements.push({ duration, cpuTime, efficiency: cpuTime / duration }); } // Calculate averages const avgDuration = measurements.reduce((sum, m) => sum + m.duration, 0) / iterations; const avgCpuTime = measurements.reduce((sum, m) => sum + m.cpuTime, 0) / iterations; const avgEfficiency = measurements.reduce((sum, m) => sum + m.efficiency, 0) / iterations; results.operations.push({ name: scenario.name, iterations, avgDuration: avgDuration.toFixed(2), avgCpuTime: avgCpuTime.toFixed(2), avgEfficiency: (avgEfficiency * 100).toFixed(2), cpuIntensity: avgCpuTime > avgDuration * 0.8 ? 'HIGH' : avgCpuTime > avgDuration * 0.5 ? 'MEDIUM' : 'LOW' }); } return results; } ); // Test 4: Corpus processing CPU profile const corpusCPUProfile = await performanceTracker.measureAsync( 'corpus-cpu-profile', async () => { const files = await corpusLoader.getFilesByPattern('**/*.xml'); const einvoice = new EInvoice(); const results = { filesProcessed: 0, totalCPUTime: 0, totalWallTime: 0, cpuByOperation: { detection: { time: 0, count: 0 }, parsing: { time: 0, count: 0 }, validation: { time: 0, count: 0 }, conversion: { time: 0, count: 0 } } }; // Sample files const sampleFiles = files.slice(0, 25); const overallStart = Date.now(); for (const file of sampleFiles) { try { const content = await plugins.fs.readFile(file, 'utf-8'); // Format detection let startUsage = process.cpuUsage(); const format = await einvoice.detectFormat(content); let endUsage = process.cpuUsage(startUsage); results.cpuByOperation.detection.time += (endUsage.user + endUsage.system) / 1000; results.cpuByOperation.detection.count++; if (!format || format === 'unknown') continue; // Parsing startUsage = process.cpuUsage(); const invoice = await einvoice.parseInvoice(content, format); endUsage = process.cpuUsage(startUsage); results.cpuByOperation.parsing.time += (endUsage.user + endUsage.system) / 1000; results.cpuByOperation.parsing.count++; // Validation startUsage = process.cpuUsage(); await einvoice.validateInvoice(invoice); endUsage = process.cpuUsage(startUsage); results.cpuByOperation.validation.time += (endUsage.user + endUsage.system) / 1000; results.cpuByOperation.validation.count++; // Conversion const targetFormat = format === 'ubl' ? 'cii' : 'ubl'; startUsage = process.cpuUsage(); await einvoice.convertFormat(invoice, targetFormat); endUsage = process.cpuUsage(startUsage); results.cpuByOperation.conversion.time += (endUsage.user + endUsage.system) / 1000; results.cpuByOperation.conversion.count++; results.filesProcessed++; } catch (error) { // Skip failed files } } results.totalWallTime = Date.now() - overallStart; // Calculate totals and averages for (const op of Object.keys(results.cpuByOperation)) { const opData = results.cpuByOperation[op]; results.totalCPUTime += opData.time; } return { filesProcessed: results.filesProcessed, totalWallTime: results.totalWallTime, totalCPUTime: results.totalCPUTime.toFixed(2), cpuEfficiency: ((results.totalCPUTime / results.totalWallTime) * 100).toFixed(2), operations: Object.entries(results.cpuByOperation).map(([op, data]) => ({ operation: op, totalTime: data.time.toFixed(2), avgTime: data.count > 0 ? (data.time / data.count).toFixed(3) : 'N/A', percentage: ((data.time / results.totalCPUTime) * 100).toFixed(1) })) }; } ); // Test 5: Sustained CPU load test const sustainedCPULoad = await performanceTracker.measureAsync( 'sustained-cpu-load', async () => { const einvoice = new EInvoice(); const testDuration = 5000; // 5 seconds const results = { samples: [], avgCPUUsage: 0, peakCPUUsage: 0, consistency: 0 }; // Test invoice const testInvoice = { format: 'ubl' as const, data: { documentType: 'INVOICE', invoiceNumber: 'SUSTAINED-CPU-001', issueDate: '2024-02-15', seller: { name: 'CPU Load Seller', address: 'Address', country: 'US', taxId: 'US123' }, buyer: { name: 'CPU Load 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 } } }; const startTime = Date.now(); let sampleCount = 0; // Run sustained load while (Date.now() - startTime < testDuration) { const sampleStart = process.cpuUsage(); const sampleStartTime = Date.now(); // Perform operations await einvoice.validateInvoice(testInvoice); await einvoice.convertFormat(testInvoice, 'cii'); const sampleEndTime = Date.now(); const sampleEnd = process.cpuUsage(sampleStart); const sampleDuration = sampleEndTime - sampleStartTime; const cpuTime = (sampleEnd.user + sampleEnd.system) / 1000; const cpuUsage = (cpuTime / sampleDuration) * 100; results.samples.push(cpuUsage); if (cpuUsage > results.peakCPUUsage) { results.peakCPUUsage = cpuUsage; } sampleCount++; } // Calculate statistics if (results.samples.length > 0) { results.avgCPUUsage = results.samples.reduce((a, b) => a + b, 0) / results.samples.length; // Calculate standard deviation for consistency const variance = results.samples.reduce((sum, val) => sum + Math.pow(val - results.avgCPUUsage, 2), 0) / results.samples.length; const stdDev = Math.sqrt(variance); results.consistency = 100 - (stdDev / results.avgCPUUsage * 100); } return { duration: Date.now() - startTime, samples: results.samples.length, avgCPUUsage: results.avgCPUUsage.toFixed(2), peakCPUUsage: results.peakCPUUsage.toFixed(2), consistency: results.consistency.toFixed(2), stable: results.consistency > 80 }; } ); // Summary t.comment('\n=== PERF-06: CPU Utilization Test Summary ==='); t.comment('\nCPU Baseline:'); t.comment(` System: ${cpuBaseline.result.cpuCount} cores, ${cpuBaseline.result.cpuModel}`); t.comment(' Operation benchmarks:'); cpuBaseline.result.operations.forEach(op => { t.comment(` ${op.name}:`); t.comment(` - Duration: ${op.duration}ms`); t.comment(` - CPU time: ${op.totalCPU}ms (user: ${op.userCPU}ms, system: ${op.systemCPU}ms)`); t.comment(` - CPU usage: ${op.cpuPercentage}%`); t.comment(` - Efficiency: ${op.efficiency}x`); }); t.comment('\nMulti-Core Utilization:'); t.comment(' Parallelism | Duration | CPU Time | Efficiency | Speedup | Scaling'); t.comment(' ------------|----------|----------|------------|---------|--------'); multiCoreUtilization.result.parallelTests.forEach(test => { t.comment(` ${String(test.parallelism).padEnd(11)} | ${String(test.duration + 'ms').padEnd(8)} | ${test.totalCPU.padEnd(8)}ms | ${test.cpuEfficiency.padEnd(10)}% | ${test.actualSpeedup.padEnd(7)}x | ${test.efficiency}%`); }); t.comment('\nCPU-Intensive Operations:'); cpuIntensiveOperations.result.operations.forEach(op => { t.comment(` ${op.name}:`); t.comment(` - Avg duration: ${op.avgDuration}ms`); t.comment(` - Avg CPU time: ${op.avgCpuTime}ms`); t.comment(` - CPU efficiency: ${op.avgEfficiency}%`); t.comment(` - Intensity: ${op.cpuIntensity}`); }); t.comment('\nCorpus CPU Profile:'); t.comment(` Files processed: ${corpusCPUProfile.result.filesProcessed}`); t.comment(` Total wall time: ${corpusCPUProfile.result.totalWallTime}ms`); t.comment(` Total CPU time: ${corpusCPUProfile.result.totalCPUTime}ms`); t.comment(` CPU efficiency: ${corpusCPUProfile.result.cpuEfficiency}%`); t.comment(' By operation:'); corpusCPUProfile.result.operations.forEach(op => { t.comment(` - ${op.operation}: ${op.totalTime}ms (${op.percentage}%), avg ${op.avgTime}ms`); }); t.comment('\nSustained CPU Load (5 seconds):'); t.comment(` Samples: ${sustainedCPULoad.result.samples}`); t.comment(` Average CPU usage: ${sustainedCPULoad.result.avgCPUUsage}%`); t.comment(` Peak CPU usage: ${sustainedCPULoad.result.peakCPUUsage}%`); t.comment(` Consistency: ${sustainedCPULoad.result.consistency}%`); t.comment(` Stable performance: ${sustainedCPULoad.result.stable ? 'YES ✅' : 'NO ⚠️'}`); // Performance targets check t.comment('\n=== Performance Targets Check ==='); const avgCPUEfficiency = parseFloat(corpusCPUProfile.result.cpuEfficiency); const cpuStability = sustainedCPULoad.result.stable; t.comment(`CPU efficiency: ${avgCPUEfficiency}% ${avgCPUEfficiency > 50 ? '✅' : '⚠️'} (target: >50%)`); t.comment(`CPU stability: ${cpuStability ? 'STABLE ✅' : 'UNSTABLE ⚠️'}`); // Overall performance summary t.comment('\n=== Overall Performance Summary ==='); performanceTracker.logSummary(); t.end(); }); tap.start();