Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support node loaders #17

Merged
merged 2 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <glob...> Globs to search for perf files.
-x, --exclude <glob...> Globs to exclude from search.
-t, --timeout <timeout> Override the timeout for each test suite.
-s, --suite <suite...> Run only matching suites.
-T, --test <test...> Run only matching test found in suites
--repeat <count> Repeat the tests. (default: 1)
--register <loader> Register a module loader. (e.g. ts-node/esm)
-h, --help display help for command
```

Expand Down Expand Up @@ -82,15 +85,26 @@ suite('map', 'Measure .map performance with different functions', async (test) =
<!--- @@inject: static/example.txt --->

```
[ '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.
```

<!--- @@inject-end: static/example.txt --->

## 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
```
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
26 changes: 20 additions & 6 deletions packages/perf-insight/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <glob...> Globs to search for perf files.
-x, --exclude <glob...> Globs to exclude from search.
-t, --timeout <timeout> Override the timeout for each test suite.
-s, --suite <suite...> Run only matching suites.
-T, --test <test...> Run only matching test found in suites
--repeat <count> Repeat the tests. (default: 1)
--register <loader> Register a module loader. (e.g. ts-node/esm)
-h, --help display help for command
```

Expand Down Expand Up @@ -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
```

<!--- @@inject-end: ../../README.md --->
38 changes: 29 additions & 9 deletions packages/perf-insight/src/app.mts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -24,33 +27,41 @@ export async function app(program = defaultCommand): Promise<Command> {
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 <glob...>', 'Globs to search for perf files.', appendValue)
.option('-x, --exclude <glob...>', 'Globs to exclude from search.', appendValue)
.option('-t, --timeout <timeout>', 'Override the timeout for each test suite.', (v) => Number(v))
.option('-s, --suite <suite...>', 'Run only matching suites.', (v, a: string[] | undefined) =>
(a || []).concat(v),
)
.option('-T, --test <test...>', 'Run only matching test found in suites', (v, a: string[] | undefined) =>
(a || []).concat(v),
)
.option('-s, --suite <suite...>', 'Run only matching suites.', appendValue)
.option('-T, --test <test...>', 'Run only matching test found in suites', appendValue)
.option('--repeat <count>', 'Repeat the tests.', (v) => Number(v), 1)
.option('--register <loader>', '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) =>
!suiteNamesToRun.length ||
suiteNamesToRun.some((name) => file.toLowerCase().includes(name.toLowerCase())),
);

console.log('%o', files);

await spawnRunners(files, options);

console.log(chalk.green('done.'));
Expand Down Expand Up @@ -81,6 +92,10 @@ async function spawnRunners(files: string[], options: AppOptions): Promise<void>
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]);
Expand Down Expand Up @@ -126,3 +141,8 @@ export async function run(argv?: string[], program?: Command): Promise<void> {
const prog = await app(program);
await prog.parseAsync(argv);
}

function appendValue(v: string, prev: string[] | undefined): string[] {
if (!prev) return [v];
return [...prev, v];
}
3 changes: 2 additions & 1 deletion packages/perf-insight/src/findFiles.mts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
};
Expand Down
21 changes: 21 additions & 0 deletions packages/perf-insight/src/runBenchmarkCli.mts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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[] = [];

Expand All @@ -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));
Loading
Loading