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).`, ],