198 lines
7.1 KiB
JavaScript
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);
|
|
});
|