Skip to content

Commit 2fb3bdf

Browse files
chentong7alexvy86CraigMacomberJosmithr
authored
feat(benchmark-tools): Update benchmark-tool to support custom measurement benchmarks (#21555)
## Description We've run into several scenarios in the past where we want to have benchmark tests which report their own measurements of something (other than the execution time and memory usage that benchmark-tool currently supports), and have those measurements go to Kusto so we can track them through time. POC: #20772 ## Sample ~~~ Op Size spec.js:54 Insert Nodes spec.js:54 Many Transactions spec.js:54 ✔ 100 small nodes in 100 transactions @CustomBenchmark (134ms) spec.js:83 ✔ 100 medium nodes in 100 transactions @CustomBenchmark (89ms) spec.js:83 ✔ 100 large nodes in 100 transactions @CustomBenchmark (78ms) spec.js:83 Single Transaction spec.js:54 ✔ 100 small nodes in 1 transaction @CustomBenchmark (66ms) spec.js:83 ✔ 100 medium nodes in 1 transaction @CustomBenchmark (54ms) spec.js:83 ✔ 100 large nodes in 1 transaction @CustomBenchmark (53ms) spec.js:83 Remove Nodes spec.js:54 Many Transactions spec.js:54 ✔ 100 small nodes in 100 transactions (63ms) spec.js:83 ✔ 100 medium nodes in 100 transactions (62ms) spec.js:83 ✔ 100 large nodes in 100 transactions (62ms) spec.js:83 Single Transaction spec.js:54 ✔ 100 small nodes in 1 transactions containing 1 removal of 100 nodes spec.js:76 ✔ 100 medium nodes in 1 transactions containing 1 removal of 100 nodes spec.js:76 ~~~ ## Unit test ~~~ Writing test results relative to package to nyc/junit-report.xml and nyc/junit-report.json .mocharc.cjs:11 spec.js:54 `benchmarkCustom` function spec.js:54 uses `before` and `after` spec.js:54 ✔ test @CustomBenchmark spec.js:76 ✔ run BenchmarkCustom spec.js:76 2 passing (8ms) ~~~ --------- Co-authored-by: Alex Villarreal <[email protected]> Co-authored-by: Craig Macomber (Microsoft) <[email protected]> Co-authored-by: Joshua Smithrud <[email protected]>
1 parent 87933cc commit 2fb3bdf

9 files changed

+164
-1
lines changed

tools/benchmark/CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## 0.51.0
44

5+
Provide @benchmarkCustom feature to log custom measurements. To profile custom usage, define tests using the `benchmarkCustom()` function. The argument `run` to the function includes a reporter with `addMeasurement()`
6+
to write custom data to report.
7+
58
### ⚠ BREAKING CHANGES
69

710
Mocha reporters have been consolidated into a single one that can handle arbitrary properties through `BenchmarkData.customData`, plus `BenchmarkData.customDataFormatters` to specify how each value should be printed to console.

tools/benchmark/README.md

+7
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mocha).
1010
This package exports a few functions that you'll use instead of mocha's `it()` to define profiling tests:
1111

1212
- `benchmark()` for runtime tests
13+
- `benchmarkCustom()` for custom usage tests
1314
- `benchmarkMemory()` for memory usage tests
1415

1516
More details particular to each can be found in the sections below.
@@ -85,6 +86,12 @@ when you define the test) and `@ExecutionTime` (as opposed to `@MemoryUsage` for
8586
> The problem can be alleviated but not fully fixed using the `onCycle` hook argument.
8687
> See documentation on `HookArguments` for more detail.
8788
89+
## Profiling custom usage
90+
91+
To customize profiling, define tests using the `benchmarkCustom()` function. The run argument of this function
92+
includes a reporter that uses `addMeasurement()` to record custom data for reporting. Look at the documentation
93+
on `Titled`, `BenchmarkDescription`, `MochaExclusiveOptions` for more details on what the rest of its properties do.
94+
8895
## Profiling memory usage
8996

9097
To profile memory usage, define tests using the `benchmarkMemory()` function.

tools/benchmark/api-report/benchmark.alpha.api.md

+13
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ export interface BenchmarkAsyncFunction extends BenchmarkOptions {
2323
benchmarkFnAsync: () => Promise<unknown>;
2424
}
2525

26+
// @public
27+
export function benchmarkCustom(options: CustomBenchmarkOptions): Test;
28+
2629
// @public
2730
export interface BenchmarkData {
2831
customData: Record<string, unknown>;
@@ -107,6 +110,11 @@ export interface CustomBenchmark extends BenchmarkTimingOptions {
107110
// @public (undocumented)
108111
export type CustomBenchmarkArguments = MochaExclusiveOptions & CustomBenchmark & BenchmarkDescription;
109112

113+
// @public
114+
export interface CustomBenchmarkOptions extends Titled, BenchmarkDescription, MochaExclusiveOptions {
115+
run: (reporter: IMeasurementReporter) => void | Promise<unknown>;
116+
}
117+
110118
// @public
111119
export function geometricMean(values: number[]): number;
112120

@@ -119,6 +127,11 @@ export interface HookArguments {
119127
// @public
120128
export type HookFunction = () => void | Promise<unknown>;
121129

130+
// @public
131+
export interface IMeasurementReporter {
132+
addMeasurement(key: string, value: number): void;
133+
}
134+
122135
// @public (undocumented)
123136
export interface IMemoryTestObject extends MemoryTestObjectProps {
124137
after?: HookFunction;

tools/benchmark/api-report/benchmark.beta.api.md

+13
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ export interface BenchmarkAsyncFunction extends BenchmarkOptions {
2323
benchmarkFnAsync: () => Promise<unknown>;
2424
}
2525

26+
// @public
27+
export function benchmarkCustom(options: CustomBenchmarkOptions): Test;
28+
2629
// @public
2730
export interface BenchmarkData {
2831
customData: Record<string, unknown>;
@@ -107,6 +110,11 @@ export interface CustomBenchmark extends BenchmarkTimingOptions {
107110
// @public (undocumented)
108111
export type CustomBenchmarkArguments = MochaExclusiveOptions & CustomBenchmark & BenchmarkDescription;
109112

113+
// @public
114+
export interface CustomBenchmarkOptions extends Titled, BenchmarkDescription, MochaExclusiveOptions {
115+
run: (reporter: IMeasurementReporter) => void | Promise<unknown>;
116+
}
117+
110118
// @public
111119
export function geometricMean(values: number[]): number;
112120

@@ -119,6 +127,11 @@ export interface HookArguments {
119127
// @public
120128
export type HookFunction = () => void | Promise<unknown>;
121129

130+
// @public
131+
export interface IMeasurementReporter {
132+
addMeasurement(key: string, value: number): void;
133+
}
134+
122135
// @public (undocumented)
123136
export interface IMemoryTestObject extends MemoryTestObjectProps {
124137
after?: HookFunction;

tools/benchmark/api-report/benchmark.public.api.md

+13
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ export interface BenchmarkAsyncFunction extends BenchmarkOptions {
2323
benchmarkFnAsync: () => Promise<unknown>;
2424
}
2525

26+
// @public
27+
export function benchmarkCustom(options: CustomBenchmarkOptions): Test;
28+
2629
// @public
2730
export interface BenchmarkData {
2831
customData: Record<string, unknown>;
@@ -107,6 +110,11 @@ export interface CustomBenchmark extends BenchmarkTimingOptions {
107110
// @public (undocumented)
108111
export type CustomBenchmarkArguments = MochaExclusiveOptions & CustomBenchmark & BenchmarkDescription;
109112

113+
// @public
114+
export interface CustomBenchmarkOptions extends Titled, BenchmarkDescription, MochaExclusiveOptions {
115+
run: (reporter: IMeasurementReporter) => void | Promise<unknown>;
116+
}
117+
110118
// @public
111119
export function geometricMean(values: number[]): number;
112120

@@ -119,6 +127,11 @@ export interface HookArguments {
119127
// @public
120128
export type HookFunction = () => void | Promise<unknown>;
121129

130+
// @public
131+
export interface IMeasurementReporter {
132+
addMeasurement(key: string, value: number): void;
133+
}
134+
122135
// @public (undocumented)
123136
export interface IMemoryTestObject extends MemoryTestObjectProps {
124137
after?: HookFunction;

tools/benchmark/src/index.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,15 @@ export {
3838
BenchmarkTimer,
3939
CustomBenchmarkArguments,
4040
} from "./Configuration";
41-
export { benchmark, benchmarkMemory, IMemoryTestObject, MemoryTestObjectProps } from "./mocha";
41+
export {
42+
benchmark,
43+
benchmarkMemory,
44+
benchmarkCustom,
45+
IMemoryTestObject,
46+
MemoryTestObjectProps,
47+
CustomBenchmarkOptions,
48+
IMeasurementReporter,
49+
} from "./mocha";
4250
export { prettyNumber, geometricMean } from "./RunnerUtilities";
4351
export { BenchmarkReporter } from "./Reporter";
4452
export { Phase, runBenchmark } from "./runBenchmark";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*!
2+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3+
* Licensed under the MIT License.
4+
*/
5+
6+
import { Test } from "mocha";
7+
8+
import type { BenchmarkDescription, MochaExclusiveOptions, Titled } from "../Configuration";
9+
import type { BenchmarkData } from "../ResultTypes";
10+
import { timer } from "../timer";
11+
12+
/**
13+
* Options to configure a benchmark that reports custom measurements.
14+
*
15+
* @public
16+
*/
17+
export interface CustomBenchmarkOptions
18+
extends Titled,
19+
BenchmarkDescription,
20+
MochaExclusiveOptions {
21+
/**
22+
* Runs the benchmark.
23+
*/
24+
run: (reporter: IMeasurementReporter) => void | Promise<unknown>;
25+
}
26+
27+
/**
28+
* This is a wrapper for Mocha's `it` function which runs the specified function {@link CustomBenchmarkOptions.run}
29+
* and gives it full control over the measurements that will be reported as benchmark output.
30+
*
31+
* @remarks
32+
* Tests created with this function get tagged with '\@CustomBenchmark', so mocha's --grep/--fgrep
33+
* options can be used to only run this type of tests by filtering on that value.
34+
*
35+
* @public
36+
*/
37+
export function benchmarkCustom(options: CustomBenchmarkOptions): Test {
38+
const itFunction = options.only === true ? it.only : it;
39+
const test = itFunction(`${options.title} @CustomBenchmark`, async () => {
40+
const customData: Record<string, number> = {};
41+
const customDataFormatters: Record<string, (value: unknown) => string> = {};
42+
const reporter: IMeasurementReporter = {
43+
addMeasurement: (key: string, value: number) => {
44+
if (key in customData) {
45+
throw new Error(`Measurement key '${key}' was already used.`);
46+
}
47+
customData[key] = value;
48+
},
49+
};
50+
51+
const startTime = timer.now();
52+
await options.run(reporter);
53+
const elapsedSeconds = timer.toSeconds(startTime, timer.now());
54+
55+
const results: BenchmarkData = {
56+
elapsedSeconds,
57+
customData,
58+
customDataFormatters,
59+
};
60+
61+
test.emit("benchmark end", results);
62+
});
63+
return test;
64+
}
65+
66+
/**
67+
* Allows the benchmark code to report custom measurements.
68+
*
69+
* @public
70+
*/
71+
export interface IMeasurementReporter {
72+
/**
73+
* Adds a custom measurement to the benchmark output.
74+
* @param key - Key to uniquely identify the measurement.
75+
* @param value - Measurement value.
76+
*
77+
* @remarks
78+
* A given key should be used only once per benchmark.
79+
* Trying to add a measurement with a key that was already used will throw an error.
80+
*/
81+
addMeasurement(key: string, value: number): void;
82+
}

tools/benchmark/src/mocha/index.ts

+5
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,8 @@
55

66
export { benchmark } from "./runner";
77
export { benchmarkMemory, IMemoryTestObject, MemoryTestObjectProps } from "./memoryTestRunner";
8+
export {
9+
benchmarkCustom,
10+
CustomBenchmarkOptions,
11+
IMeasurementReporter,
12+
} from "./customOutputRunner";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*!
2+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3+
* Licensed under the MIT License.
4+
*/
5+
6+
import { benchmarkCustom } from "..";
7+
import { BenchmarkType } from "../Configuration";
8+
9+
describe("`benchmarkCustom` function", () => {
10+
it("run BenchmarkCustom", async () => {
11+
benchmarkCustom({
12+
title: `test`,
13+
run: async (reporter) => {
14+
reporter.addMeasurement("test", 0);
15+
},
16+
type: BenchmarkType.OwnCorrectness,
17+
});
18+
});
19+
});

0 commit comments

Comments
 (0)