Files
dashlibstorage/scripts/benchmark.js
2026-09-22 08:31:01 +02:00

198 lines
7.1 KiB
JavaScript

#!/usr/bin/env node
import 'dotenv/config';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { performance } from 'node:perf_hooks';
import chalk from 'chalk';
import LibDashStore from '../src/index.js';
import { BetterSqlite3Provider } from '../src/providers/sqlite3/index.js';
import { MssqlProvider } from '../src/providers/mssql/index.js';
import { MetricsCollector, instrumentStore } from './benchmark/instrument.js';
import { fillDatabase, benchmarkFilledDatabase, testLifecycleDeletions } from './benchmark/workload.js';
import { BenchmarkReporter } from './benchmark/reporter.js';
// CLI argument parsing
function parseArgs() {
const args = process.argv.slice(2);
const options = {
driver: 'both',
scale: 'small',
iterations: 5,
reportFile: 'BENCHMARK_REPORT.md',
keepDb: false,
};
for (const arg of args) {
if (arg.startsWith('--driver=')) {
options.driver = arg.split('=')[1].toLowerCase();
} else if (arg.startsWith('--scale=')) {
options.scale = arg.split('=')[1].toLowerCase();
} else if (arg.startsWith('--iterations=')) {
options.iterations = parseInt(arg.split('=')[1], 10);
} else if (arg.startsWith('--report-file=')) {
options.reportFile = arg.split('=')[1];
} else if (arg === '--keep-db') {
options.keepDb = true;
}
}
return options;
}
async function runSqliteBenchmark(options) {
console.log(chalk.blue(`\n▶ Starting SQLite benchmark (scale: ${options.scale}, iterations: ${options.iterations})...`));
const tempDir = mkdtempSync(join(tmpdir(), 'libdashstore-bench-sqlite-'));
const dbFile = join(tempDir, 'benchmark.db');
const storageDir = join(tempDir, 'storage');
const provider = new BetterSqlite3Provider({
filename: dbFile,
storageDir,
});
await provider.initialize();
const rawStore = await LibDashStore.create(provider);
const metrics = new MetricsCollector();
const { store } = instrumentStore(rawStore, metrics);
const startTotal = performance.now();
try {
console.log(chalk.dim(' 1. Filling SQLite database using domain APIs...'));
const fillStart = performance.now();
const ctx = await fillDatabase(store, options);
console.log(chalk.green(` ✓ Filled SQLite DB in ${((performance.now() - fillStart) / 1000).toFixed(2)}s`));
console.log(chalk.dim(' 2. Benchmarking queries and lookups on filled database...'));
const benchStart = performance.now();
await benchmarkFilledDatabase(store, ctx, options);
console.log(chalk.green(` ✓ Read queries completed in ${((performance.now() - benchStart) / 1000).toFixed(2)}s`));
console.log(chalk.dim(' 3. Testing lifecycle deletions...'));
const delStart = performance.now();
await testLifecycleDeletions(store, ctx);
console.log(chalk.green(` ✓ Deletions completed in ${((performance.now() - delStart) / 1000).toFixed(2)}s`));
const totalElapsedMs = performance.now() - startTotal;
const stats = metrics.getAllStats();
return { stats, totalElapsedMs };
} finally {
provider.close();
if (!options.keepDb) {
try {
rmSync(tempDir, { recursive: true, force: true });
} catch {
// ignore
}
} else {
console.log(chalk.dim(` [i] SQLite test database kept at: ${tempDir}`));
}
}
}
async function runMssqlBenchmark(options) {
console.log(chalk.blue(`\n▶ Starting MSSQL benchmark (scale: ${options.scale}, iterations: ${options.iterations})...`));
const config = {
server: process.env.MSSQL_SERVER || '127.0.0.1',
database: process.env.MSSQL_DATABASE || 'dashback',
user: process.env.MSSQL_USER || 'dashback',
password: process.env.MSSQL_PASSWORD || 'dashback',
options: {
encrypt: false,
trustServerCertificate: true,
},
};
const provider = new MssqlProvider(config);
await provider.ensurePool();
console.log(chalk.dim(' Resetting MSSQL schema (purge & init)...'));
await provider.purge();
await provider.initialize();
const rawStore = await LibDashStore.create(provider);
const metrics = new MetricsCollector();
const { store } = instrumentStore(rawStore, metrics);
const startTotal = performance.now();
try {
console.log(chalk.dim(' 1. Filling MSSQL database using domain APIs...'));
const fillStart = performance.now();
const ctx = await fillDatabase(store, options);
console.log(chalk.green(` ✓ Filled MSSQL DB in ${((performance.now() - fillStart) / 1000).toFixed(2)}s`));
console.log(chalk.dim(' 2. Benchmarking queries and lookups on filled database...'));
const benchStart = performance.now();
await benchmarkFilledDatabase(store, ctx, options);
console.log(chalk.green(` ✓ Read queries completed in ${((performance.now() - benchStart) / 1000).toFixed(2)}s`));
console.log(chalk.dim(' 3. Testing lifecycle deletions...'));
const delStart = performance.now();
await testLifecycleDeletions(store, ctx);
console.log(chalk.green(` ✓ Deletions completed in ${((performance.now() - delStart) / 1000).toFixed(2)}s`));
const totalElapsedMs = performance.now() - startTotal;
const stats = metrics.getAllStats();
return { stats, totalElapsedMs };
} finally {
await provider.close();
}
}
async function main() {
const options = parseArgs();
console.log(chalk.bold.yellow(`========================================================================`));
console.log(chalk.bold.yellow(` LIBDASHSTORE DATABASE DRIVERS TEST & BENCHMARK SYSTEM`));
console.log(chalk.bold.yellow(`========================================================================`));
console.log(`Driver mode : ${chalk.cyan(options.driver)}`);
console.log(`Scale : ${chalk.cyan(options.scale)}`);
console.log(`Iterations : ${chalk.cyan(options.iterations)}`);
console.log(`Report File : ${chalk.cyan(options.reportFile)}`);
let sqliteResult = null;
let mssqlResult = null;
if (options.driver === 'both' || options.driver === 'sqlite') {
sqliteResult = await runSqliteBenchmark(options);
BenchmarkReporter.printDriverReport('better-sqlite3', sqliteResult.stats, sqliteResult.totalElapsedMs);
}
if (options.driver === 'both' || options.driver === 'mssql') {
mssqlResult = await runMssqlBenchmark(options);
BenchmarkReporter.printDriverReport('mssql', mssqlResult.stats, mssqlResult.totalElapsedMs);
}
if (sqliteResult && mssqlResult) {
BenchmarkReporter.printComparisonReport(
mssqlResult.stats,
sqliteResult.stats,
mssqlResult.totalElapsedMs,
sqliteResult.totalElapsedMs,
);
}
const markdown = BenchmarkReporter.generateMarkdownReport({
mssqlStats: mssqlResult?.stats,
sqliteStats: sqliteResult?.stats,
mssqlElapsedMs: mssqlResult?.totalElapsedMs,
sqliteElapsedMs: sqliteResult?.totalElapsedMs,
options,
});
if (options.reportFile) {
BenchmarkReporter.saveReportToFile(options.reportFile, markdown);
}
console.log(chalk.bold.green(`\n✓ All database driver tests & benchmarks finished successfully!`));
}
main().catch((err) => {
console.error(chalk.bold.red(`\nBenchmark failed with error:`), err);
process.exit(1);
});