einvoice/test/suite/einvoice_performance/test.perf-06.cpu-utilization.ts

669 lines
25 KiB
TypeScript
Raw Normal View History

2025-05-25 19:45:37 +00:00
/**
* @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 = '<?xml version="1.0"?><Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"><ID>CPU-TEST</ID></Invoice>';
for (let i = 0; i < 100; i++) {
await einvoice.detectFormat(xml);
}
}
},
{
name: 'XML parsing (50x)',
fn: async () => {
const xml = `<?xml version="1.0"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<ID>CPU-PARSE</ID>
<IssueDate>2024-01-01</IssueDate>
${Array(20).fill('<InvoiceLine><ID>Line</ID></InvoiceLine>').join('\n')}
</Invoice>`;
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();