diff --git a/README.md b/README.md index 50e73a1..cf887a2 100644 --- a/README.md +++ b/README.md @@ -17,17 +17,20 @@ Performance Benchmarking Suite for NodeJS. ``` Usage: perf-insight [options] [filter...] -Benchmark performance suites. +Benchmark performance suites found in `**/*.perf.{js,cjs,mjs}`. Arguments: filter Perf file filter. Options: -a, --all Run all perf files. (default: false) + -f, --file Globs to search for perf files. + -x, --exclude Globs to exclude from search. -t, --timeout Override the timeout for each test suite. -s, --suite Run only matching suites. -T, --test Run only matching test found in suites --repeat Repeat the tests. (default: 1) + --register Register a module loader. (e.g. ts-node/esm) -h, --help display help for command ``` @@ -82,15 +85,26 @@ suite('map', 'Measure .map performance with different functions', async (test) = ``` +[ 'examples/dist/exampleMap.perf.mjs', [length]: 1 ] File: examples/dist/exampleMap.perf.mjs Running Perf Suite: map Measure .map performance with different functions -✔ map((a) => a.length) 27203.18 ops/sec 13352 iterations 490.82ms time -✔ .map((a) => { return a.length; }) 15047.53 ops/sec 7437 iterations 494.23ms time -✔ .map(Boolean) 7700.32 ops/sec 3827 iterations 496.99ms time -✔ .map((a) => !a.length) 11501.15 ops/sec 5700 iterations 495.60ms time -✔ .map((a) => { return a.length; }) 16183.73 ops/sec 8018 iterations 495.44ms time +✔ map((a) => a.length) 24059.07 ops/sec 11792 iterations 490.13ms time +✔ .map((a) => { return a.length; }) 13708.22 ops/sec 6778 iterations 494.45ms time +✔ .map(Boolean) 7407.73 ops/sec 3682 iterations 497.05ms time +✔ .map((a) => !a.length) 13040.03 ops/sec 6469 iterations 496.09ms time +✔ .map((a) => { return a.length; }) 11657.43 ops/sec 5774 iterations 495.31ms time done. ``` + +## TypeScript Support + +It is necessary to register a TypeScript loader like [ts-node](https://typestrong.org/ts-node/). + +**Usage:** + +``` +npx perf-insight --file "**/*.perf.mts" --timeout 500 --register ts-node/esm +``` diff --git a/package.json b/package.json index 59cc0b2..9edd10c 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ }, "scripts": { "build": "pnpm -r --stream --workspace-concurrency=2 run build", + "watch": "cd packages/perf-insight && pnpm run watch", "build:readme": "pnpm build:readme:help && pnpm build:readme:example && pnpm build:readme:inject && prettier -w \"**/README.md\"", "build:readme:help": "./bin.mjs --help > static/help.txt", "build:readme:example": "./bin.mjs exampleMap.perf.mjs -t 500 > static/example.txt", @@ -61,6 +62,7 @@ "lorem-ipsum": "^2.0.8", "perf-insight": "workspace:*", "prettier": "^3.2.5", + "ts-node": "^10.9.2", "typescript": "^5.4.5", "typescript-eslint": "^7.7.0", "vite": "^5.2.10", diff --git a/packages/perf-insight/README.md b/packages/perf-insight/README.md index 51feb32..1028051 100644 --- a/packages/perf-insight/README.md +++ b/packages/perf-insight/README.md @@ -17,17 +17,20 @@ Performance Benchmarking Suite for NodeJS. ``` Usage: perf-insight [options] [filter...] -Benchmark performance suites. +Benchmark performance suites found in `**/*.perf.{js,cjs,mjs}`. Arguments: filter Perf file filter. Options: -a, --all Run all perf files. (default: false) + -f, --file Globs to search for perf files. + -x, --exclude Globs to exclude from search. -t, --timeout Override the timeout for each test suite. -s, --suite Run only matching suites. -T, --test Run only matching test found in suites --repeat Repeat the tests. (default: 1) + --register Register a module loader. (e.g. ts-node/esm) -h, --help display help for command ``` @@ -74,15 +77,26 @@ suite('map', 'Measure .map performance with different functions', async (test) = **`npx perf-insight exampleMap.perf.mjs --timeout 500`** ``` +[ 'examples/dist/exampleMap.perf.mjs', [length]: 1 ] File: examples/dist/exampleMap.perf.mjs Running Perf Suite: map Measure .map performance with different functions -✔ map((a) => a.length) 27203.18 ops/sec 13352 iterations 490.82ms time -✔ .map((a) => { return a.length; }) 15047.53 ops/sec 7437 iterations 494.23ms time -✔ .map(Boolean) 7700.32 ops/sec 3827 iterations 496.99ms time -✔ .map((a) => !a.length) 11501.15 ops/sec 5700 iterations 495.60ms time -✔ .map((a) => { return a.length; }) 16183.73 ops/sec 8018 iterations 495.44ms time +✔ map((a) => a.length) 24059.07 ops/sec 11792 iterations 490.13ms time +✔ .map((a) => { return a.length; }) 13708.22 ops/sec 6778 iterations 494.45ms time +✔ .map(Boolean) 7407.73 ops/sec 3682 iterations 497.05ms time +✔ .map((a) => !a.length) 13040.03 ops/sec 6469 iterations 496.09ms time +✔ .map((a) => { return a.length; }) 11657.43 ops/sec 5774 iterations 495.31ms time done. ``` +## TypeScript Support + +It is necessary to register a TypeScript loader like [ts-node](https://typestrong.org/ts-node/). + +**Usage:** + +``` +npx perf-insight --file "**/*.perf.mts" --timeout 500 --register ts-node/esm +``` + diff --git a/packages/perf-insight/src/app.mts b/packages/perf-insight/src/app.mts index 4172079..3f1190a 100644 --- a/packages/perf-insight/src/app.mts +++ b/packages/perf-insight/src/app.mts @@ -10,8 +10,11 @@ interface AppOptions { repeat?: number; timeout?: number; all?: boolean; + file?: string[]; + exclude?: string[]; suite?: string[]; test?: string[]; + register?: string[]; } const urlRunnerCli = new URL('./runBenchmarkCli.mjs', import.meta.url).toString(); @@ -24,26 +27,32 @@ export async function app(program = defaultCommand): Promise { program .name('perf-insight') .addArgument(argument) - .description('Benchmark performance suites.') + .description('Benchmark performance suites found in `**/*.perf.{js,cjs,mjs}`.') .option('-a, --all', 'Run all perf files.', false) + .option('-f, --file ', 'Globs to search for perf files.', appendValue) + .option('-x, --exclude ', 'Globs to exclude from search.', appendValue) .option('-t, --timeout ', 'Override the timeout for each test suite.', (v) => Number(v)) - .option('-s, --suite ', 'Run only matching suites.', (v, a: string[] | undefined) => - (a || []).concat(v), - ) - .option('-T, --test ', 'Run only matching test found in suites', (v, a: string[] | undefined) => - (a || []).concat(v), - ) + .option('-s, --suite ', 'Run only matching suites.', appendValue) + .option('-T, --test ', 'Run only matching test found in suites', appendValue) .option('--repeat ', 'Repeat the tests.', (v) => Number(v), 1) + .option('--register ', 'Register a module loader. (e.g. ts-node/esm)', appendValue) .action(async (suiteNamesToRun: string[], options: AppOptions, command: Command) => { - if (!suiteNamesToRun.length && !options.all) { + if (!suiteNamesToRun.length && !(options.all || options.file?.length)) { console.error(chalk.red('No tests to run.')); console.error(chalk.yellow(`Use ${chalk.green('--all')} to run all tests.\n`)); command.help(); } // console.log('%o', options); + const fileGlobs = options.file?.length ? options.file : ['**/*.perf.{js,mjs,cjs}']; + const excludes = options.exclude?.length ? options.exclude : []; - const found = await findFiles(['**/*.perf.{js,mjs,cjs}', '!**/node_modules/**']); + const found = await findFiles([...fileGlobs, '!**/node_modules/**'], { excludes }); + + if (!found.length) { + console.error(chalk.red('No perf files found.')); + return; + } const files = found.filter( (file) => @@ -51,6 +60,8 @@ export async function app(program = defaultCommand): Promise { suiteNamesToRun.some((name) => file.toLowerCase().includes(name.toLowerCase())), ); + console.log('%o', files); + await spawnRunners(files, options); console.log(chalk.green('done.')); @@ -81,6 +92,10 @@ async function spawnRunners(files: string[], options: AppOptions): Promise cliOptions.push(...options.test.flatMap((t) => ['--test', t])); } + if (options.register?.length) { + cliOptions.push(...options.register.flatMap((r) => ['--register', r])); + } + for (const file of files) { try { const code = await spawnRunner([file, ...cliOptions]); @@ -126,3 +141,8 @@ export async function run(argv?: string[], program?: Command): Promise { const prog = await app(program); await prog.parseAsync(argv); } + +function appendValue(v: string, prev: string[] | undefined): string[] { + if (!prev) return [v]; + return [...prev, v]; +} diff --git a/packages/perf-insight/src/findFiles.mts b/packages/perf-insight/src/findFiles.mts index 1e09514..669484a 100644 --- a/packages/perf-insight/src/findFiles.mts +++ b/packages/perf-insight/src/findFiles.mts @@ -5,11 +5,12 @@ const excludes = ['node_modules']; export interface FindFileOptions { onlyFiles?: boolean; cwd?: string; + excludes?: string[]; } export async function findFiles(globs: string[], options?: FindFileOptions) { const globOptions: GlobbyOptions = { - ignore: excludes, + ignore: options?.excludes ?? excludes, onlyFiles: options?.onlyFiles ?? true, cwd: options?.cwd || process.cwd(), }; diff --git a/packages/perf-insight/src/runBenchmarkCli.mts b/packages/perf-insight/src/runBenchmarkCli.mts index 7937645..438f99d 100644 --- a/packages/perf-insight/src/runBenchmarkCli.mts +++ b/packages/perf-insight/src/runBenchmarkCli.mts @@ -20,6 +20,7 @@ async function run(args: string[]) { timeout: { type: 'string', short: 't' }, test: { type: 'string', short: 'T', multiple: true }, suite: { type: 'string', short: 'S', multiple: true }, + register: { type: 'string', multiple: true }, }, } as const satisfies ParseArgsConfig; @@ -29,6 +30,7 @@ async function run(args: string[]) { const timeout = Number(parsed.values['timeout'] || '0') || undefined; const tests = parsed.values['test']; const suites = parsed.values['suite']; + await registerLoaders(parsed.values['register']); const errors: Error[] = []; @@ -55,4 +57,23 @@ async function run(args: string[]) { await runBenchmarkSuites(undefined, { repeat, timeout, tests, suites }); } +async function registerLoaders(loaders: string[] | undefined) { + if (!loaders?.length) return; + + const module = await import('module'); + + if (!('register' in module)) { + console.error('Module loader registration is not supported by the current version of Node.js'); + return; + } + + const register = module.register as (loader: string, cwd: URL) => void; + + function registerLoader(loader: string) { + register(loader, cwdUrl); + } + + loaders.forEach(registerLoader); +} + run(process.argv.slice(2)); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 31a919d..cba6197 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,6 +65,9 @@ importers: prettier: specifier: ^3.2.5 version: 3.2.5 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@20.12.7)(typescript@5.4.5) typescript: specifier: ^5.4.5 version: 5.4.5 @@ -464,6 +467,13 @@ packages: engines: {node: '>=18'} dev: true + /@cspotcode/source-map-support@0.8.1: + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + dev: true + /@esbuild/aix-ppc64@0.20.2: resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} engines: {node: '>=12'} @@ -775,6 +785,13 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true + /@jridgewell/trace-mapping@0.3.9: + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -934,6 +951,22 @@ packages: resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} engines: {node: '>=18'} + /@tsconfig/node10@1.0.11: + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + dev: true + + /@tsconfig/node12@1.0.11: + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + dev: true + + /@tsconfig/node14@1.0.3: + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + dev: true + + /@tsconfig/node16@1.0.4: + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + dev: true + /@tsconfig/node20@20.1.4: resolution: {integrity: sha512-sqgsT69YFeLWf5NtJ4Xq/xAF8p4ZQHlmGW74Nu2tD4+g5fAsposc4ZfaaPixVu4y01BEiDCWLRDCvDM5JOsRxg==} dev: true @@ -1249,6 +1282,10 @@ packages: engines: {node: '>=10'} dev: true + /arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + dev: true + /argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true @@ -1529,6 +1566,10 @@ packages: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} dev: true + /create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + dev: true + /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -1763,6 +1804,11 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dev: true + /diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + dev: true + /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -2958,6 +3004,10 @@ packages: semver: 7.6.0 dev: true + /make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + dev: true + /markdown-table@3.0.3: resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} dev: true @@ -4238,6 +4288,37 @@ packages: typescript: 5.4.5 dev: true + /ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5): + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.12.7 + acorn: 8.11.3 + acorn-walk: 8.3.2 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.4.5 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + /tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} dependencies: @@ -4437,6 +4518,10 @@ packages: punycode: 2.3.1 dev: true + /v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + dev: true + /vfile-message@4.0.2: resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} dependencies: @@ -4671,6 +4756,11 @@ packages: hasBin: true dev: true + /yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + dev: true + /yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} diff --git a/static/example.txt b/static/example.txt index 71c2a27..7907a30 100644 --- a/static/example.txt +++ b/static/example.txt @@ -1,9 +1,10 @@ +[ 'examples/dist/exampleMap.perf.mjs', [length]: 1 ] File: examples/dist/exampleMap.perf.mjs Running Perf Suite: map Measure .map performance with different functions -✔ map((a) => a.length) 27203.18 ops/sec 13352 iterations 490.82ms time -✔ .map((a) => { return a.length; }) 15047.53 ops/sec 7437 iterations 494.23ms time -✔ .map(Boolean) 7700.32 ops/sec 3827 iterations 496.99ms time -✔ .map((a) => !a.length) 11501.15 ops/sec 5700 iterations 495.60ms time -✔ .map((a) => { return a.length; }) 16183.73 ops/sec 8018 iterations 495.44ms time +✔ map((a) => a.length) 24059.07 ops/sec 11792 iterations 490.13ms time +✔ .map((a) => { return a.length; }) 13708.22 ops/sec 6778 iterations 494.45ms time +✔ .map(Boolean) 7407.73 ops/sec 3682 iterations 497.05ms time +✔ .map((a) => !a.length) 13040.03 ops/sec 6469 iterations 496.09ms time +✔ .map((a) => { return a.length; }) 11657.43 ops/sec 5774 iterations 495.31ms time done. diff --git a/static/help.txt b/static/help.txt index 33295a0..4f8a24d 100644 --- a/static/help.txt +++ b/static/help.txt @@ -1,14 +1,17 @@ Usage: perf-insight [options] [filter...] -Benchmark performance suites. +Benchmark performance suites found in `**/*.perf.{js,cjs,mjs}`. Arguments: filter Perf file filter. Options: -a, --all Run all perf files. (default: false) + -f, --file Globs to search for perf files. + -x, --exclude Globs to exclude from search. -t, --timeout Override the timeout for each test suite. -s, --suite Run only matching suites. -T, --test Run only matching test found in suites --repeat Repeat the tests. (default: 1) + --register Register a module loader. (e.g. ts-node/esm) -h, --help display help for command