diff --git a/src/write.ts b/src/write.ts
index 4658d913b..1a98056ab 100644
--- a/src/write.ts
+++ b/src/write.ts
@@ -149,12 +149,85 @@ function floatStr(n: number) {
return n.toString();
}
-function strVal(b: BenchmarkResult): string {
- let s = `\`${b.value}\` ${b.unit}`;
- if (b.range) {
- s += ` (\`${b.range}\`)`;
+const round = (n: number, places = 3): number => parseFloat(n.toFixed(places));
+
+function strDuration(n: number): string {
+ let unit = 'ns';
+ if (n >= 6e10) {
+ let secs = n / 1e9;
+ const mins = Math.floor(secs / 60);
+ secs %= 60;
+ return `${mins}m ${round(secs)}s`;
+ }
+
+ if (n >= 1e9) {
+ unit = 's';
+ n /= 1e9;
+ } else if (n >= 1e6) {
+ unit = 'ms';
+ n /= 1e6;
+ } else if (n >= 1e3) {
+ unit = 'μs';
+ n /= 1e3;
+ }
+
+ return `${round(n)}${unit}`;
+}
+
+const NS_PER_TIME_UNIT: { [unit: string]: number } = {
+ ns: 1,
+ us: 1e3,
+ ms: 1e6,
+ s: 1e9,
+};
+
+interface DurationFormat {
+ timeUnit: string;
+ suffix: string;
+}
+
+function parseDurationFormat(unit: string): DurationFormat | null {
+ const perIter = unit.match(/^(ns|us|ms|s)\/(\w+)$/);
+ if (perIter) {
+ return { timeUnit: perIter[1], suffix: `/${perIter[2]}` };
}
- return s;
+
+ const duration = unit.match(/^(ns|us|ms|s)$/);
+ if (duration) {
+ return { timeUnit: duration[1], suffix: '' };
+ }
+
+ return null;
+}
+
+function toNanoseconds(value: number, timeUnit: string): number {
+ return value * NS_PER_TIME_UNIT[timeUnit];
+}
+
+const RANGE_REGEX = /^±\s*(\d+(?:\.\d+)?)$/;
+
+function strRange(range: string | undefined, timeUnit: string): string | undefined {
+ const m = range?.match(RANGE_REGEX);
+ if (!m) {
+ return range;
+ }
+ return '± ' + strDuration(toNanoseconds(parseFloat(m[1]), timeUnit));
+}
+
+function strRawValue(b: BenchmarkResult): string {
+ const value = `\`${b.value} ${b.unit}\``;
+ return b.range ? `${value}
(\`${b.range}\`)` : value;
+}
+
+function strDurationValue(b: BenchmarkResult, format: DurationFormat): string {
+ const value = `\`${strDuration(toNanoseconds(b.value, format.timeUnit))}${format.suffix}\``;
+ const range = strRange(b.range, format.timeUnit);
+ return range ? `${value}
(\`${range}\`)` : value;
+}
+
+function strVal(b: BenchmarkResult): string {
+ const duration = parseDurationFormat(b.unit);
+ return duration === null ? strRawValue(b) : strDurationValue(b, duration);
}
function commentFooter(): string {
diff --git a/test/buildComment.test.ts b/test/buildComment.test.ts
index ea9431c51..cd1fe7546 100644
--- a/test/buildComment.test.ts
+++ b/test/buildComment.test.ts
@@ -1,4 +1,5 @@
import { buildComment } from '../src/write';
+import { Benchmark, BenchmarkResult } from '../src/extract';
import { FakedOctokit, fakedRepos } from './fakedOctokit';
import dedent from 'dedent';
@@ -111,9 +112,9 @@ describe('buildComment', () => {
| Benchmark suite | Current: testCommitIdCurrent | Previous: testCommitIdPrevious | Ratio |
|-|-|-|-|
- | \`TestBench<1>\` | \`0\` testUnit | \`0\` testUnit | \`1\` |
- | \`TestBench<2>\` | \`1\` testUnit | \`0\` testUnit | \`+∞\` |
- | \`TestBench<3>\` | \`-1\` testUnit | \`0\` testUnit | \`-∞\` |
+ | \`TestBench<1>\` | \`0 testUnit\` | \`0 testUnit\` | \`1\` |
+ | \`TestBench<2>\` | \`1 testUnit\` | \`0 testUnit\` | \`+∞\` |
+ | \`TestBench<3>\` | \`-1 testUnit\` | \`0 testUnit\` | \`-∞\` |
@@ -197,13 +198,76 @@ describe('buildComment', () => {
| Benchmark suite | Current: testCommitIdCurrent | Previous: testCommitIdPrevious | Ratio |
|-|-|-|-|
- | \`TestBench<1>\` | \`0\` testUnit | \`0\` testUnit | \`1\` |
- | \`TestBench<2>\` | \`0\` testUnit | \`1\` testUnit | \`+∞\` |
- | \`TestBench<3>\` | \`0\` testUnit | \`-1\` testUnit | \`-∞\` |
+ | \`TestBench<1>\` | \`0 testUnit\` | \`0 testUnit\` | \`1\` |
+ | \`TestBench<2>\` | \`0 testUnit\` | \`1 testUnit\` | \`+∞\` |
+ | \`TestBench<3>\` | \`0 testUnit\` | \`-1 testUnit\` | \`-∞\` |
This comment was automatically generated by [workflow](https://github.com/user/repo/actions?query=workflow%3AWorkflow%20name) using [github-action-benchmark](https://github.com/marketplace/actions/continuous-benchmark).
`);
});
+
+ describe('value formatting', () => {
+ function suite(bench: BenchmarkResult): Benchmark {
+ return {
+ date: 12345,
+ commit: {
+ id: 'testCommitId',
+ author: { name: 'TestUser' },
+ committer: { name: 'TestUser' },
+ message: 'Test commit message',
+ url: 'https://test.commit.url',
+ },
+ tool: 'customSmallerIsBetter',
+ benches: [bench],
+ };
+ }
+
+ function renderValue(bench: BenchmarkResult): string {
+ const comment = buildComment('TestSuite', suite(bench), suite(bench), false);
+ const line = comment.split('\n').find((l) => l.startsWith('| `bench`'));
+ if (line === undefined) {
+ throw new Error(`Benchmark row not found in comment:\n${comment}`);
+ }
+ return line.split('|')[2].trim();
+ }
+
+ const cases: Array<[string, BenchmarkResult, string]> = [
+ ['bare nanoseconds', { name: 'bench', value: 25000, unit: 'ns' }, '`25μs`'],
+ ['bare microseconds', { name: 'bench', value: 25000, unit: 'us' }, '`25ms`'],
+ ['bare milliseconds', { name: 'bench', value: 25000, unit: 'ms' }, '`25s`'],
+ ['bare seconds', { name: 'bench', value: 100, unit: 's' }, '`1m 40s`'],
+ ['nanoseconds per iteration', { name: 'bench', value: 25000, unit: 'ns/iter' }, '`25μs/iter`'],
+ ['microseconds per iteration', { name: 'bench', value: 25000, unit: 'us/iter' }, '`25ms/iter`'],
+ ['nanoseconds per op', { name: 'bench', value: 25000, unit: 'ns/op' }, '`25μs/op`'],
+ [
+ 'range in nanoseconds',
+ { name: 'bench', value: 25000, unit: 'ns/iter', range: '± 1000' },
+ '`25μs/iter`
(`± 1μs`)',
+ ],
+ [
+ 'range in microseconds',
+ { name: 'bench', value: 25000, unit: 'us/iter', range: '± 1.5' },
+ '`25ms/iter`
(`± 1.5μs`)',
+ ],
+ [
+ 'unrecognized range is unchanged',
+ { name: 'bench', value: 25000, unit: 'ns/iter', range: '± 2%' },
+ '`25μs/iter`
(`± 2%`)',
+ ],
+ ['unknown unit is unchanged', { name: 'bench', value: 5, unit: 'testUnit' }, '`5 testUnit`'],
+ [
+ 'unsupported time unit is unchanged',
+ { name: 'bench', value: 20, unit: 'ops/sec', range: '+-20' },
+ '`20 ops/sec`
(`+-20`)',
+ ],
+ ];
+
+ for (const [description, bench, expected] of cases) {
+ it(`formats ${description}`, () => {
+ expect(renderValue(bench)).toBe(expected);
+ });
+ }
+ });
});
diff --git a/test/write.spec.ts b/test/write.spec.ts
index 2b8a97f23..d40cf546b 100644
--- a/test/write.spec.ts
+++ b/test/write.spec.ts
@@ -415,8 +415,8 @@ describe.each(['https://github.com', 'https://github.enterprise.corp'])('writeBe
'',
'| Benchmark suite | Current: current commit id | Previous: prev commit id | Ratio |',
'|-|-|-|-|',
- '| `bench_fib_10` | `210` ns/iter (`± 20`) | `100` ns/iter (`± 20`) | `2.10` |',
- '| `bench_fib_20` | `25000` ns/iter (`± 20`) | `10000` ns/iter (`± 20`) | `2.50` |',
+ '| `bench_fib_10` | `210ns/iter`
(`± 20ns`) | `100ns/iter`
(`± 20ns`) | `2.10` |',
+ '| `bench_fib_20` | `25μs/iter`
(`± 20ns`) | `10μs/iter`
(`± 20ns`) | `2.50` |',
'',
`This comment was automatically generated by [workflow](${serverUrl}/user/repo/actions?query=workflow%3AWorkflow%20name) using [github-action-benchmark](https://github.com/marketplace/actions/continuous-benchmark).`,
'',
@@ -463,8 +463,8 @@ describe.each(['https://github.com', 'https://github.enterprise.corp'])('writeBe
'',
'| Benchmark suite | Current: current commit id | Previous: prev commit id | Ratio |',
'|-|-|-|-|',
- '| `bench_fib_10` | `210` ns/iter (`± 20`) | `100` ns/iter (`± 20`) | `2.10` |',
- '| `bench_fib_20` | `2250` ns/iter (`± 20`) | `900` ns/iter (`± 20`) | `2.50` |',
+ '| `bench_fib_10` | `210ns/iter`
(`± 20ns`) | `100ns/iter`
(`± 20ns`) | `2.10` |',
+ '| `bench_fib_20` | `2.25μs/iter`
(`± 20ns`) | `900ns/iter`
(`± 20ns`) | `2.50` |',
'',
`This comment was automatically generated by [workflow](${serverUrl}/user/repo/actions?query=workflow%3AWorkflow%20name) using [github-action-benchmark](https://github.com/marketplace/actions/continuous-benchmark).`,
'',
@@ -502,7 +502,7 @@ describe.each(['https://github.com', 'https://github.enterprise.corp'])('writeBe
'',
'| Benchmark suite | Current: current commit id | Previous: prev commit id | Ratio |',
'|-|-|-|-|',
- '| `benchFib10` | `20` ops/sec (`+-20`) | `100` ops/sec (`+-20`) | `5` |',
+ '| `benchFib10` | `20 ops/sec`
(`+-20`) | `100 ops/sec`
(`+-20`) | `5` |',
'',
`This comment was automatically generated by [workflow](${serverUrl}/user/repo/actions?query=workflow%3AWorkflow%20name) using [github-action-benchmark](https://github.com/marketplace/actions/continuous-benchmark).`,
'',
@@ -540,7 +540,7 @@ describe.each(['https://github.com', 'https://github.enterprise.corp'])('writeBe
'',
'| Benchmark suite | Current: current commit id | Previous: prev commit id | Ratio |',
'|-|-|-|-|',
- '| `bench_fib_10` | `210` ns/iter (`± 20`) | `100` ns/iter (`± 20`) | `2.10` |',
+ '| `bench_fib_10` | `210ns/iter`
(`± 20ns`) | `100ns/iter`
(`± 20ns`) | `2.10` |',
'',
`This comment was automatically generated by [workflow](${serverUrl}/user/repo/actions?query=workflow%3AWorkflow%20name) using [github-action-benchmark](https://github.com/marketplace/actions/continuous-benchmark).`,
'',
@@ -578,7 +578,7 @@ describe.each(['https://github.com', 'https://github.enterprise.corp'])('writeBe
'',
'| Benchmark suite | Current: current commit id | Previous: prev commit id | Ratio |',
'|-|-|-|-|',
- '| `bench_fib_10` | `210` ns/iter (`± 20`) | `100` ns/iter (`± 20`) | `2.10` |',
+ '| `bench_fib_10` | `210ns/iter`
(`± 20ns`) | `100ns/iter`
(`± 20ns`) | `2.10` |',
'',
`This comment was automatically generated by [workflow](${serverUrl}/user/repo/actions?query=workflow%3AWorkflow%20name) using [github-action-benchmark](https://github.com/marketplace/actions/continuous-benchmark).`,
],
@@ -719,8 +719,8 @@ describe.each(['https://github.com', 'https://github.enterprise.corp'])('writeBe
'',
'| Benchmark suite | Current: current commit id | Previous: prev commit id | Ratio |',
'|-|-|-|-|',
- '| `bench_fib_10` | `210` ns/iter (`± 20`) | `100` ns/iter (`± 20`) | `2.10` |',
- '| `bench_fib_20` | `25000` ns/iter (`± 20`) | `10000` ns/iter (`± 20`) | `2.50` |',
+ '| `bench_fib_10` | `210ns/iter`
(`± 20ns`) | `100ns/iter`
(`± 20ns`) | `2.10` |',
+ '| `bench_fib_20` | `25μs/iter`
(`± 20ns`) | `10μs/iter`
(`± 20ns`) | `2.50` |',
'',
`This comment was automatically generated by [workflow](${serverUrl}/user/repo/actions?query=workflow%3AWorkflow%20name) using [github-action-benchmark](https://github.com/marketplace/actions/continuous-benchmark).`,
'',
@@ -758,7 +758,7 @@ describe.each(['https://github.com', 'https://github.enterprise.corp'])('writeBe
'',
'| Benchmark suite | Current: current commit id | Previous: prev commit id | Ratio |',
'|-|-|-|-|',
- '| `benchFib10` | `100` ops/sec (`+-20`) | `100` ops/sec (`+-20`) | `1` |',
+ '| `benchFib10` | `100 ops/sec`
(`+-20`) | `100 ops/sec`
(`+-20`) | `1` |',
'',
`This comment was automatically generated by [workflow](${serverUrl}/user/repo/actions?query=workflow%3AWorkflow%20name) using [github-action-benchmark](https://github.com/marketplace/actions/continuous-benchmark).`,
'',
@@ -798,7 +798,7 @@ describe.each(['https://github.com', 'https://github.enterprise.corp'])('writeBe
'',
'| Benchmark suite | Current: current commit id | Previous: prev commit id | Ratio |',
'|-|-|-|-|',
- '| `bench_fib_10` | `350` ns/iter (`± 20`) | `100` ns/iter (`± 20`) | `3.50` |',
+ '| `bench_fib_10` | `350ns/iter`
(`± 20ns`) | `100ns/iter`
(`± 20ns`) | `3.50` |',
'',
`This comment was automatically generated by [workflow](${serverUrl}/user/repo/actions?query=workflow%3AWorkflow%20name) using [github-action-benchmark](https://github.com/marketplace/actions/continuous-benchmark).`,
'',
@@ -1243,7 +1243,7 @@ describe.each(['https://github.com', 'https://github.enterprise.corp'])('writeBe
'',
'| Benchmark suite | Current: current commit id | Previous: prev commit id | Ratio |',
'|-|-|-|-|',
- '| `bench_fib_10` | `210` ns/iter (`± 20`) | `100` ns/iter (`± 20`) | `2.10` |',
+ '| `bench_fib_10` | `210ns/iter`
(`± 20ns`) | `100ns/iter`
(`± 20ns`) | `2.10` |',
'',
`This comment was automatically generated by [workflow](${serverUrl}/user/repo/actions?query=workflow%3AWorkflow%20name) using [github-action-benchmark](https://github.com/marketplace/actions/continuous-benchmark).`,
],
@@ -1267,7 +1267,7 @@ describe.each(['https://github.com', 'https://github.enterprise.corp'])('writeBe
'',
'| Benchmark suite | Current: current commit id | Previous: prev commit id | Ratio |',
'|-|-|-|-|',
- '| `bench_fib_10` | `210` ns/iter (`± 20`) | `100` ns/iter (`± 20`) | `2.10` |',
+ '| `bench_fib_10` | `210ns/iter`
(`± 20ns`) | `100ns/iter`
(`± 20ns`) | `2.10` |',
'',
`This comment was automatically generated by [workflow](${serverUrl}/user/repo/actions?query=workflow%3AWorkflow%20name) using [github-action-benchmark](https://github.com/marketplace/actions/continuous-benchmark).`,
],