Skip to content

Commit

Permalink
feat: add JSON output write to file
Browse files Browse the repository at this point in the history
Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
  • Loading branch information
jerome-benoit committed May 13, 2024
1 parent b77b29c commit 6e7d502
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 32 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ and this project adheres to

## [Unreleased]

### Changed

- Add `file` to `run()` option allowing to save the JSON output to a file.

## [0.4.8] - 2024-05-13

### Fixed
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ await run({
units: false, // print units cheatsheet (default: false)
silent: false, // enable/disable stdout output (default: false)
json: false, // enable/disable json output (default: false)
file: 'results.json', // write json output to file (default: undefined)
colors: true, // enable/disable colors (default: true)
samples: 128, // minimum number of benchmark samples (default: 128)
time: 1_000_000_000, // minimum benchmark time in nanoseconds (default: 1_000_000_000)
Expand Down
65 changes: 33 additions & 32 deletions src/benchmark.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@ import {
defaultSamples,
defaultTime,
emptyFunction,
jsonOutputFormat,
tatamiNgGroup,
} from './constants.js';
import {
os,
AsyncFunction,
checkBenchmarkArgs,
convertReportToBmf,
cpu,
measure,
mergeDeepRight,
noColor,
version,
writeFileSync,
} from './lib.js';
import { logger } from './logger.js';
import * as clr from './reporter/clr.js';
Expand Down Expand Up @@ -196,6 +199,7 @@ export function clear() {
* @param {Boolean} [opts.units=false] print units cheatsheet
* @param {Boolean} [opts.silent=false] enable/disable stdout output
* @param {Boolean|Number|'bmf'} [opts.json=false] enable/disable json output
* @param {String} [opts.file=undefined] write json output to file
* @param {Boolean} [opts.colors=true] enable/disable colors
* @param {Number} [opts.samples=128] minimum number of benchmark samples
* @param {Number} [opts.time=1_000_000_000] minimum benchmark time in nanoseconds
Expand Down Expand Up @@ -231,6 +235,22 @@ export async function run(opts = {}) {
throw new TypeError(
`expected number or boolean or string as 'json' option, got ${opts.json.constructor.name}`,
);
if (
'string' === typeof opts.json &&
!Object.values(jsonOutputFormat).includes(opts.json)
)
throw new TypeError(
`expected one of ${Object.values(jsonOutputFormat).join(
', ',
)} as 'json' option, got ${opts.json}`,
);
if (opts.file != null && 'string' !== typeof opts.file) {
throw new TypeError(
`expected string as 'file' option, got ${opts.file.constructor.name}`,
);
}
if ('string' === typeof opts.file && opts.file.trim().length === 0)
throw new TypeError(`expected non-empty string as 'file' option`);
// biome-ignore lint/style/noParameterAssign: <explanation>
opts = mergeDeepRight(
{
Expand Down Expand Up @@ -353,41 +373,22 @@ export async function run(opts = {}) {
}

if (!opts.json && opts.units) log(table.units(opts));
if (
('boolean' === typeof opts.json || 'number' === typeof opts.json) &&
opts.json
) {
log(
JSON.stringify(
report,
undefined,
'number' !== typeof opts.json ? 0 : opts.json,
),
);
} else if ('string' === typeof opts.json) {
let bmfReport;
if (opts.json) {
let jsonReport;
switch (opts.json) {
case 'bmf':
bmfReport = report.benchmarks
.map(({ name, stats }) => {
return {
[name]: {
latency: {
value: stats?.avg,
lower_value: stats?.min,
upper_value: stats?.max,
},
throughput: {
value: stats?.iter,
},
},
};
})
.reduce((obj, item) => Object.assign(obj, item), {});
log(JSON.stringify(bmfReport));
case jsonOutputFormat.bmf:
jsonReport = JSON.stringify(convertReportToBmf(report));
log(jsonReport);
if (opts.file) writeFileSync(opts.file, jsonReport);
break;
default:
throw new Error(`unexpected 'json' option: ${opts.json}`);
jsonReport = JSON.stringify(
report,
undefined,
'number' !== typeof opts.json ? 0 : opts.json,
);
log(jsonReport);
if (opts.file) writeFileSync(opts.file, jsonReport);
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -1048,3 +1048,7 @@ export const minimumSamples = 128;
export const defaultTime = 1e9; // ns

export const defaultSamples = minimumSamples;

export const jsonOutputFormat = {
bmf: 'bmf',
};
1 change: 1 addition & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export function run(options?: {
rmoe?: boolean;
percentiles?: boolean;
json?: number | boolean | 'bmf';
file?: string;
units?: boolean;
}): Promise<Report>;

Expand Down
29 changes: 29 additions & 0 deletions src/lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,35 @@ export const noColor = (() => {
}[runtime]();
})();

export const writeFileSync = await (async () => {
return await {
unknown: () => () => {},
browser: () => () => {},
node: async () => (await import('node:fs')).writeFileSync,
deno: () => Deno.writeTextFileSync,
bun: async () => (await import('node:fs')).writeFileSync,
}[runtime]();
})();

export const convertReportToBmf = report => {
return report.benchmarks
.map(({ name, stats }) => {
return {
[name]: {
latency: {
value: stats?.avg,
lower_value: stats?.min,
upper_value: stats?.max,
},
throughput: {
value: stats?.iter,
},
},
};
})
.reduce((obj, item) => Object.assign(obj, item), {});
};

export const checkBenchmarkArgs = (fn, opts = {}) => {
if (![Function, AsyncFunction].includes(fn.constructor))
throw new TypeError(`expected function, got ${fn.constructor.name}`);
Expand Down

0 comments on commit 6e7d502

Please sign in to comment.