aios-core
Version:
Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework
383 lines (331 loc) • 11.1 kB
JavaScript
/**
* AIOS Installer Performance Benchmark
* Story INS-2: Installer Performance Optimization
*
* Measures baseline performance metrics for the installer to track optimization progress.
*
* Usage:
* node performance-benchmark.js [--output <file>] [--runs <n>]
*
* Output:
* JSON report with phase timings and statistics
*/
const fs = require('fs');
const fse = require('fs-extra');
const path = require('path');
const crypto = require('crypto');
const { performance } = require('perf_hooks');
// Configuration
const CONFIG = {
runs: 3, // Number of runs for averaging
outputFile: null, // Output file path (null = stdout)
testProjectSize: 1000, // Number of files for test project
verbose: false,
};
// Parse CLI arguments
process.argv.slice(2).forEach((arg, i, arr) => {
if (arg === '--output' && arr[i + 1]) CONFIG.outputFile = arr[i + 1];
if (arg === '--runs' && arr[i + 1]) CONFIG.runs = parseInt(arr[i + 1], 10);
if (arg === '--verbose' || arg === '-v') CONFIG.verbose = true;
if (arg === '--help' || arg === '-h') {
console.log(`
AIOS Installer Performance Benchmark
Usage: node performance-benchmark.js [options]
Options:
--output <file> Save JSON report to file (default: stdout)
--runs <n> Number of benchmark runs (default: 3)
--verbose, -v Show detailed progress
--help, -h Show this help
Example:
node performance-benchmark.js --output baseline.json --runs 5
`);
process.exit(0);
}
});
// Benchmark results structure
const results = {
timestamp: new Date().toISOString(),
system: {
platform: process.platform,
arch: process.arch,
nodeVersion: process.version,
cpus: require('os').cpus().length,
totalMemory: Math.round(require('os').totalmem() / 1024 / 1024) + ' MB',
},
config: { ...CONFIG },
phases: {},
summary: {},
};
/**
* Timer utility for measuring phase durations
*/
class Timer {
constructor(name) {
this.name = name;
this.start = null;
this.end = null;
this.runs = [];
}
begin() {
this.start = performance.now();
}
stop() {
this.end = performance.now();
const duration = this.end - this.start;
this.runs.push(duration);
return duration;
}
getStats() {
if (this.runs.length === 0) return null;
const sorted = [...this.runs].sort((a, b) => a - b);
return {
min: Math.round(sorted[0]),
max: Math.round(sorted[sorted.length - 1]),
avg: Math.round(this.runs.reduce((a, b) => a + b, 0) / this.runs.length),
median: Math.round(sorted[Math.floor(sorted.length / 2)]),
runs: this.runs.map((r) => Math.round(r)),
unit: 'ms',
};
}
}
// Phase timers
const timers = {
directoryRead: new Timer('Directory Read (readdirSync)'),
directoryReadWithTypes: new Timer('Directory Read (withFileTypes)'),
statLoop: new Timer('Stat Loop (statSync per file)'),
realpathSingle: new Timer('Realpath (single call)'),
realpathDouble: new Timer('Realpath (double call - current)'),
hashSequential: new Timer('Hash Files (sequential)'),
hashParallelBatch: new Timer('Hash Files (parallel batch)'),
fileCopySequential: new Timer('File Copy (sequential)'),
fileCopyParallel: new Timer('File Copy (parallel)'),
totalInstallSimulation: new Timer('Total Install Simulation'),
};
/**
* Log if verbose mode is enabled
*/
function log(msg) {
if (CONFIG.verbose) console.log(`[benchmark] ${msg}`);
}
/**
* Get the .aios-core directory for benchmarking
*/
function getAiosCoreDir() {
const projectRoot = path.resolve(__dirname, '../../../../');
return path.join(projectRoot, '.aios-core');
}
/**
* Benchmark: Directory read comparison
*/
async function benchmarkDirectoryRead(dir) {
const files = fs.readdirSync(dir);
// Method 1: readdirSync + statSync for each
timers.statLoop.begin();
for (const file of files) {
const fullPath = path.join(dir, file);
fs.statSync(fullPath).isDirectory();
}
timers.statLoop.stop();
// Method 2: readdirSync with withFileTypes
timers.directoryReadWithTypes.begin();
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
entry.isDirectory();
}
timers.directoryReadWithTypes.stop();
return files.length;
}
/**
* Benchmark: Realpath comparison
*/
async function benchmarkRealpath(files) {
const sampleFiles = files.slice(0, 100); // Sample 100 files
// Method 1: Single realpath call
timers.realpathSingle.begin();
for (const file of sampleFiles) {
fs.realpathSync(file);
}
timers.realpathSingle.stop();
// Method 2: Double realpath call (current behavior)
timers.realpathDouble.begin();
for (const file of sampleFiles) {
fs.realpathSync(file);
fs.realpathSync(path.dirname(file)); // Simulates the duplicate call
}
timers.realpathDouble.stop();
}
/**
* Benchmark: File hashing comparison
*/
async function benchmarkHashing(files) {
const sampleFiles = files.slice(0, 200); // Sample 200 files for hashing
// Method 1: Sequential hashing
timers.hashSequential.begin();
for (const file of sampleFiles) {
try {
const content = fs.readFileSync(file);
crypto.createHash('sha256').update(content).digest('hex');
} catch {
// Skip files that can't be read
}
}
timers.hashSequential.stop();
// Method 2: Parallel batch hashing
timers.hashParallelBatch.begin();
const batchSize = 50;
for (let i = 0; i < sampleFiles.length; i += batchSize) {
const batch = sampleFiles.slice(i, i + batchSize);
await Promise.all(
batch.map(async (file) => {
try {
const content = await fse.readFile(file);
crypto.createHash('sha256').update(content).digest('hex');
} catch {
// Skip files that can't be read
}
}),
);
}
timers.hashParallelBatch.stop();
}
/**
* Collect all files recursively
*/
function collectFiles(dir, maxFiles = 1000) {
const files = [];
function walk(currentDir) {
if (files.length >= maxFiles) return;
try {
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
for (const entry of entries) {
if (files.length >= maxFiles) break;
const fullPath = path.join(currentDir, entry.name);
if (entry.isDirectory()) {
walk(fullPath);
} else if (entry.isFile()) {
files.push(fullPath);
}
}
} catch {
// Skip directories we can't read
}
}
walk(dir);
return files;
}
/**
* Run all benchmarks
*/
async function runBenchmarks() {
const aiosCoreDir = getAiosCoreDir();
if (!fs.existsSync(aiosCoreDir)) {
console.error(`Error: .aios-core directory not found at ${aiosCoreDir}`);
process.exit(1);
}
log(`Starting benchmark with ${CONFIG.runs} runs`);
log(`Using .aios-core at: ${aiosCoreDir}`);
// Collect files for benchmarking
log('Collecting files...');
const allFiles = collectFiles(aiosCoreDir, CONFIG.testProjectSize);
log(`Collected ${allFiles.length} files`);
results.fileCount = allFiles.length;
// Run benchmarks multiple times
for (let run = 1; run <= CONFIG.runs; run++) {
log(`\n--- Run ${run}/${CONFIG.runs} ---`);
// Directory read benchmarks
const agentsDir = path.join(aiosCoreDir, 'development', 'agents');
if (fs.existsSync(agentsDir)) {
log('Benchmarking directory read...');
await benchmarkDirectoryRead(agentsDir);
}
// Realpath benchmarks
log('Benchmarking realpath...');
await benchmarkRealpath(allFiles);
// Hashing benchmarks
log('Benchmarking file hashing...');
await benchmarkHashing(allFiles);
// Total simulation
log('Running total install simulation...');
timers.totalInstallSimulation.begin();
// Simulate full install: read dirs + hash files
const devDir = path.join(aiosCoreDir, 'development');
if (fs.existsSync(devDir)) {
const subdirs = fs.readdirSync(devDir, { withFileTypes: true });
for (const subdir of subdirs) {
if (subdir.isDirectory()) {
const fullSubdir = path.join(devDir, subdir.name);
fs.readdirSync(fullSubdir);
}
}
}
// Simulate sequential file processing
for (const file of allFiles.slice(0, 500)) {
try {
fs.statSync(file);
fs.readFileSync(file);
} catch {
// Skip
}
}
timers.totalInstallSimulation.stop();
}
// Compile results
log('\nCompiling results...');
for (const [name, timer] of Object.entries(timers)) {
const stats = timer.getStats();
if (stats) {
results.phases[name] = {
description: timer.name,
...stats,
};
}
}
// Calculate summary
const hashSeq = results.phases.hashSequential?.avg || 0;
const hashPar = results.phases.hashParallelBatch?.avg || 0;
const realpathSingle = results.phases.realpathSingle?.avg || 0;
const realpathDouble = results.phases.realpathDouble?.avg || 0;
const statLoop = results.phases.statLoop?.avg || 0;
const withTypes = results.phases.directoryReadWithTypes?.avg || 0;
results.summary = {
totalFiles: results.fileCount,
hashingSpeedup: hashSeq > 0 ? `${(hashSeq / hashPar).toFixed(2)}x` : 'N/A',
realpathSavings: realpathDouble > 0 ? `${Math.round(((realpathDouble - realpathSingle) / realpathDouble) * 100)}%` : 'N/A',
statLoopSavings: statLoop > 0 ? `${Math.round(((statLoop - withTypes) / statLoop) * 100)}%` : 'N/A',
estimatedTotalTime: results.phases.totalInstallSimulation?.avg || 0,
target: '<30000ms for 1000 files',
baseline: `${results.phases.totalInstallSimulation?.avg || 'TBD'}ms`,
};
// Output results
const output = JSON.stringify(results, null, 2);
if (CONFIG.outputFile) {
fs.writeFileSync(CONFIG.outputFile, output);
console.log(`Benchmark results saved to: ${CONFIG.outputFile}`);
} else {
console.log(output);
}
// Print summary to stderr for visibility
console.error('\n' + '='.repeat(60));
console.error('AIOS Installer Performance Baseline');
console.error('='.repeat(60));
console.error(`Files analyzed: ${results.fileCount}`);
console.error(`Runs: ${CONFIG.runs}`);
console.error('');
console.error('Phase Results (avg ms):');
console.error(` Directory stat loop: ${statLoop}ms`);
console.error(` Directory withFileTypes: ${withTypes}ms (${results.summary.statLoopSavings} faster)`);
console.error(` Realpath single: ${realpathSingle}ms`);
console.error(` Realpath double: ${realpathDouble}ms (${results.summary.realpathSavings} overhead)`);
console.error(` Hash sequential: ${hashSeq}ms`);
console.error(` Hash parallel: ${hashPar}ms (${results.summary.hashingSpeedup} faster)`);
console.error('');
console.error(`Total Install Simulation: ${results.summary.baseline}`);
console.error(`Target: ${results.summary.target}`);
console.error('='.repeat(60));
}
// Run benchmarks
runBenchmarks().catch((err) => {
console.error('Benchmark failed:', err);
process.exit(1);
});