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