Skip to content

Commit cb6a7ba

Browse files
authored
ci(NODE-6755): add tags to benchmarks (#4419)
1 parent 421ddeb commit cb6a7ba

22 files changed

+138
-30
lines changed

test/benchmarks/driver_bench/readme.md

+17
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,30 @@ type BenchmarkModule = {
5151
run: () => Promise<void>;
5252
afterEach?: () => Promise<void>;
5353
after?: () => Promise<void>;
54+
55+
tags?: string[];
5456
};
5557
```
5658

5759
Just like mocha we have once before and once after as well as before each and after each hooks.
5860

5961
The `driver.mts` module is intended to hold various helpers for setup and teardown and help abstract some of the driver API.
6062

63+
## Benchmark tags
64+
The `tags` property of `BenchmarkModule` is where a benchmark's tags should be added to facilitate
65+
performance alerting and filter of results via our internal tools.
66+
67+
Tags are defined in `driver.mts` under the `TAG` enum.
68+
Whenever a new tag is defined it should be documented in the table below .
69+
70+
| tag variable name | tag string value | purpose |
71+
|-------------------|------------------------|--------------------------------------------------------------------------------------------------------------------------------------|
72+
| `TAG.spec` | `'spec-benchmark'` | Special tag that marks a benchmark as a spec-required benchmark |
73+
| `TAG.alert` | `'alerting-benchmark'` | Special tag that enables our perf monitoring tooling to create alerts when regressions in this benchmark's performance are detected |
74+
| `TAG.cursor` | `'cursor-benchmark'` | Tag marking a benchmark as being related to cursor performance |
75+
| `TAG.read` | `'read-benchmark'` | Tag marking a benchmark as being related to read performance |
76+
| `TAG.write` | `'write-benchmark'` | Tag marking a benchmark as being related to write performance |
77+
6178
## Wishlist
6279

6380
- Make it so runner can handle: `./lib/suites/multi_bench/grid_fs_upload.mjs` as an argument so shell path autocomplete makes it easier to pick a benchmark

test/benchmarks/driver_bench/src/driver.mts

+26-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,20 @@ import process from 'node:process';
77
const __dirname = import.meta.dirname;
88
const require = module.createRequire(__dirname);
99

10+
export const TAG = {
11+
// Special tag that marks a benchmark as a spec-required benchmark
12+
spec: 'spec-benchmark',
13+
// Special tag that enables our perf monitoring tooling to create alerts when regressions in this
14+
// benchmark's performance are detected
15+
alert: 'alerting-benchmark',
16+
// Tag marking a benchmark as being related to cursor performance
17+
cursor: 'cursor-benchmark',
18+
// Tag marking a benchmark as being related to read performance
19+
read: 'read-benchmark',
20+
// Tag marking a benchmark as being related to write performance
21+
write: 'write-benchmark'
22+
};
23+
1024
/**
1125
* The path to the MongoDB Node.js driver.
1226
* This MUST be set to the directory the driver is installed in
@@ -118,19 +132,23 @@ export const PARALLEL_DIRECTORY = path.resolve(SPEC_DIRECTORY, 'parallel');
118132
export const TEMP_DIRECTORY = path.resolve(SPEC_DIRECTORY, 'tmp');
119133

120134
export type Metric = {
121-
name: 'megabytes_per_second';
135+
name: 'megabytes_per_second' | 'normalized_throughput';
122136
value: number;
137+
metadata: {
138+
improvement_direction: 'up' | 'down';
139+
};
123140
};
124141

125142
export type MetricInfo = {
126143
info: {
127144
test_name: string;
128145
args: Record<string, number>;
146+
tags?: string[];
129147
};
130148
metrics: Metric[];
131149
};
132150

133-
export function metrics(test_name: string, result: number): MetricInfo {
151+
export function metrics(test_name: string, result: number, tags?: string[]): MetricInfo {
134152
return {
135153
info: {
136154
test_name,
@@ -141,9 +159,13 @@ export function metrics(test_name: string, result: number): MetricInfo {
141159
key,
142160
typeof value === 'number' ? value : value ? 1 : 0
143161
])
144-
)
162+
),
163+
tags
145164
},
146-
metrics: [{ name: 'megabytes_per_second', value: result }]
165+
// FIXME(NODE-6781): For now all of our metrics are of throughput so their improvement_direction is up,
166+
metrics: [
167+
{ name: 'megabytes_per_second', value: result, metadata: { improvement_direction: 'up' } }
168+
]
147169
} as const;
148170
}
149171

test/benchmarks/driver_bench/src/main.mts

+39-3
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ console.log(systemInfo());
8585

8686
const runnerPath = path.join(__dirname, 'runner.mjs');
8787

88-
const results: MetricInfo[] = [];
88+
let results: MetricInfo[] = [];
8989

9090
for (const [suite, benchmarks] of Object.entries(tests)) {
9191
console.group(snakeToCamel(suite));
@@ -198,6 +198,42 @@ function calculateCompositeBenchmarks(results: MetricInfo[]) {
198198
return [...results, ...compositeResults];
199199
}
200200

201-
const finalResults = calculateCompositeBenchmarks(results);
201+
function calculateNormalizedResults(results: MetricInfo[]): MetricInfo[] {
202+
const baselineBench = results.find(r => r.info.test_name === 'cpuBaseline');
203+
const pingBench = results.find(r => r.info.test_name === 'ping');
204+
205+
assert.ok(pingBench, 'ping bench results not found!');
206+
assert.ok(baselineBench, 'baseline results not found!');
207+
const pingThroughput = pingBench.metrics[0].value;
208+
const cpuBaseline = baselineBench.metrics[0].value;
209+
210+
for (const bench of results) {
211+
if (bench.info.test_name === 'cpuBaseline') continue;
212+
if (bench.info.test_name === 'ping') {
213+
bench.metrics.push({
214+
name: 'normalized_throughput',
215+
value: bench.metrics[0].value / cpuBaseline,
216+
metadata: {
217+
improvement_direction: 'up'
218+
}
219+
});
220+
}
221+
// Compute normalized_throughput of benchmarks against ping bench
222+
else {
223+
bench.metrics.push({
224+
name: 'normalized_throughput',
225+
value: bench.metrics[0].value / pingThroughput,
226+
metadata: {
227+
improvement_direction: 'up'
228+
}
229+
});
230+
}
231+
}
232+
233+
return results;
234+
}
235+
236+
results = calculateCompositeBenchmarks(results);
237+
results = calculateNormalizedResults(results);
202238

203-
await fs.writeFile('results.json', JSON.stringify(finalResults, undefined, 2), 'utf8');
239+
await fs.writeFile('results.json', JSON.stringify(results, undefined, 2), 'utf8');

test/benchmarks/driver_bench/src/runner.mts

+10-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type BenchmarkModule = {
1414
run: () => Promise<void>;
1515
afterEach?: () => Promise<void>;
1616
after?: () => Promise<void>;
17+
tags?: string[];
1718
};
1819

1920
const benchmarkName = snakeToCamel(path.basename(benchmarkFile, '.mjs'));
@@ -80,6 +81,14 @@ function percentileIndex(percentile: number, count: number) {
8081
const medianExecution = durations[percentileIndex(50, count)];
8182
const megabytesPerSecond = benchmark.taskSize / medianExecution;
8283

84+
const tags = benchmark.tags;
85+
if (
86+
tags &&
87+
(!Array.isArray(tags) || (tags.length > 0 && !tags.every(t => typeof t === 'string')))
88+
) {
89+
throw new Error('If tags is specified, it MUST be an array of strings');
90+
}
91+
8392
console.log(
8493
' '.repeat(3),
8594
...['total time:', totalDuration, 'sec,'],
@@ -91,6 +100,6 @@ console.log(
91100

92101
await fs.writeFile(
93102
`results_${path.basename(benchmarkFile, '.mjs')}.json`,
94-
JSON.stringify(metrics(benchmarkName, megabytesPerSecond), undefined, 2) + '\n',
103+
JSON.stringify(metrics(benchmarkName, megabytesPerSecond, tags), undefined, 2) + '\n',
95104
'utf8'
96105
);

test/benchmarks/driver_bench/src/suites/multi_bench/find_many_and_empty_cursor.mts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
/* eslint-disable @typescript-eslint/no-unused-vars */
2-
import { driver, type mongodb } from '../../driver.mjs';
2+
import { driver, type mongodb, TAG } from '../../driver.mjs';
33

44
export const taskSize = 16.22;
55

6+
export const tags = [TAG.alert, TAG.spec, TAG.cursor, TAG.read];
7+
68
let collection: mongodb.Collection;
79

810
export async function before() {

test/benchmarks/driver_bench/src/suites/multi_bench/grid_fs_download.mts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { Readable, Writable } from 'node:stream';
22
import { pipeline } from 'node:stream/promises';
33

4-
import { driver, type mongodb } from '../../driver.mjs';
4+
import { driver, type mongodb, TAG } from '../../driver.mjs';
55

66
export const taskSize = 52.43;
77

8+
export const tags = [TAG.alert, TAG.spec, TAG.cursor, TAG.read];
9+
810
let bucket: mongodb.GridFSBucket;
911
let bin: Uint8Array;
1012
let _id: mongodb.ObjectId;

test/benchmarks/driver_bench/src/suites/multi_bench/grid_fs_upload.mts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { Readable } from 'node:stream';
22
import { pipeline } from 'node:stream/promises';
33

4-
import { driver, type mongodb } from '../../driver.mjs';
4+
import { driver, type mongodb, TAG } from '../../driver.mjs';
55

66
export const taskSize = 52.43;
7+
export const tags = [TAG.alert, TAG.spec, TAG.write];
78

89
let bucket: mongodb.GridFSBucket;
910
let uploadStream: mongodb.GridFSBucketWriteStream;

test/benchmarks/driver_bench/src/suites/multi_bench/large_doc_bulk_insert.mts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { driver, type mongodb } from '../../driver.mjs';
1+
import { driver, type mongodb, TAG } from '../../driver.mjs';
22

33
export const taskSize = 27.31;
4+
export const tags = [TAG.alert, TAG.spec, TAG.write];
45

56
let collection: mongodb.Collection;
67
let documents: any[];

test/benchmarks/driver_bench/src/suites/multi_bench/small_doc_bulk_insert.mts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { driver, type mongodb } from '../../driver.mjs';
1+
import { driver, type mongodb, TAG } from '../../driver.mjs';
22

33
export const taskSize = 2.75;
4+
export const tags = [TAG.alert, TAG.spec, TAG.write];
45

56
let collection: mongodb.Collection;
67
let documents: any[];

test/benchmarks/driver_bench/src/suites/node_specific/aggregate_a_million_documents_and_to_array.mts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { driver, type mongodb } from '../../driver.mjs';
1+
import { driver, type mongodb, TAG } from '../../driver.mjs';
22

33
export const taskSize = 16;
4+
export const tags = [TAG.alert, TAG.cursor, TAG.read];
45

56
let db: mongodb.Db;
67

test/benchmarks/driver_bench/src/suites/node_specific/aggregate_a_million_tweets_and_to_array.mts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { driver, type mongodb } from '../../driver.mjs';
1+
import { driver, type mongodb, TAG } from '../../driver.mjs';
22

33
export const taskSize = 1500;
4+
export const tags = [TAG.alert, TAG.cursor, TAG.read];
45

56
let db: mongodb.Db;
67
let tweet: Record<string, any>;

test/benchmarks/driver_bench/src/suites/node_specific/primes.mts test/benchmarks/driver_bench/src/suites/node_specific/cpu_baseline.mts

+2-5
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,9 @@ const expectedPrimes = 78_498;
66
// byteLength of
77
// BSON.serialize({ primes: Buffer.from(new Int32Array(sieveOfEratosthenes(findPrimesBelow)).buffer) }).byteLength)
88
// a bin data of int32s
9-
const byteLength = 314_010;
10-
11-
export const taskSize = 3.1401000000000003; // ~3MB worth of work
12-
13-
assert.equal(taskSize, byteLength * 10e-6); // taskSize should stay hardcoded, checking here the math is done right.
149

10+
const stableRegionMean = 42.82;
11+
export const taskSize = 3.1401000000000003 / stableRegionMean; // ~3MB worth of work scaled down by the mean of the current stable region in CI to bring this value to roughly 1
1512
/** @see https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes */
1613
export function sieveOfEratosthenes(n: number) {
1714
// Create a boolean array "prime[0..n]" and initialize

test/benchmarks/driver_bench/src/suites/node_specific/find_many_and_to_array.mts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import { driver, type mongodb } from '../../driver.mjs';
1+
import { driver, type mongodb, TAG } from '../../driver.mjs';
22

33
export const taskSize = 16.22;
44

5+
export const tags = [TAG.alert, TAG.spec, TAG.cursor, TAG.read];
6+
57
let collection: mongodb.Collection;
68

79
export async function before() {

test/benchmarks/driver_bench/src/suites/node_specific/ping.mts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { driver, type mongodb } from '../../driver.mjs';
1+
import { driver, type mongodb, TAG } from '../../driver.mjs';
22

33
// { ping: 1 } is 15 bytes of BSON x 10,000 iterations
44
export const taskSize = 0.15;
5+
export const tags = [TAG.alert];
56

67
let db: mongodb.Db;
78

test/benchmarks/driver_bench/src/suites/parallel_bench/gridfs_multi_file_download.mts

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import { createReadStream, createWriteStream, promises as fs } from 'node:fs';
22
import path from 'node:path';
33
import { pipeline } from 'node:stream/promises';
44

5-
import { driver, type mongodb, PARALLEL_DIRECTORY, TEMP_DIRECTORY } from '../../driver.mjs';
5+
import { driver, type mongodb, PARALLEL_DIRECTORY, TAG, TEMP_DIRECTORY } from '../../driver.mjs';
66

77
export const taskSize = 262.144;
8+
export const tags = [TAG.spec, TAG.alert, TAG.read, TAG.cursor];
89

910
let bucket: mongodb.GridFSBucket;
1011

test/benchmarks/driver_bench/src/suites/parallel_bench/gridfs_multi_file_upload.mts

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import path from 'node:path';
33
import { Readable } from 'node:stream';
44
import { pipeline } from 'node:stream/promises';
55

6-
import { driver, type mongodb, PARALLEL_DIRECTORY } from '../../driver.mjs';
6+
import { driver, type mongodb, PARALLEL_DIRECTORY, TAG } from '../../driver.mjs';
77

88
export const taskSize = 262.144;
9+
export const tags = [TAG.spec, TAG.alert, TAG.write];
910

1011
let bucket: mongodb.GridFSBucket;
1112

test/benchmarks/driver_bench/src/suites/parallel_bench/ldjson_multi_file_export.mts

+9-1
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,17 @@ import path from 'node:path';
33
import readline from 'node:readline/promises';
44
import stream from 'node:stream/promises';
55

6-
import { driver, EJSON, type mongodb, PARALLEL_DIRECTORY, TEMP_DIRECTORY } from '../../driver.mjs';
6+
import {
7+
driver,
8+
EJSON,
9+
type mongodb,
10+
PARALLEL_DIRECTORY,
11+
TAG,
12+
TEMP_DIRECTORY
13+
} from '../../driver.mjs';
714

815
export const taskSize = 565;
16+
export const tags = [TAG.spec, TAG.alert, TAG.write];
917

1018
let collection: mongodb.Collection;
1119

test/benchmarks/driver_bench/src/suites/parallel_bench/ldjson_multi_file_upload.mts

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import { createReadStream, promises as fs } from 'node:fs';
22
import path from 'node:path';
33
import readline from 'node:readline/promises';
44

5-
import { driver, type mongodb, PARALLEL_DIRECTORY } from '../../driver.mjs';
5+
import { driver, type mongodb, PARALLEL_DIRECTORY, TAG } from '../../driver.mjs';
66

77
export const taskSize = 565;
8+
export const tags = [TAG.spec, TAG.alert, TAG.write];
89

910
const directory = path.resolve(PARALLEL_DIRECTORY, 'ldjson_multi');
1011
let collection: mongodb.Collection;

test/benchmarks/driver_bench/src/suites/single_bench/find_one.mts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { driver, type mongodb } from '../../driver.mjs';
1+
import { driver, type mongodb, TAG } from '../../driver.mjs';
22

33
export const taskSize = 16.22;
4+
export const tags = [TAG.spec, TAG.alert, TAG.cursor, TAG.read];
45

56
let collection: mongodb.Collection<{ _id: number }>;
67

test/benchmarks/driver_bench/src/suites/single_bench/large_doc_insert_one.mts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { driver, type mongodb } from '../../driver.mjs';
1+
import { driver, type mongodb, TAG } from '../../driver.mjs';
22

33
export const taskSize = 27.31;
4+
export const tags = [TAG.spec, TAG.alert, TAG.write];
45

56
let collection: mongodb.Collection;
67
let documents: Record<string, any>[];

test/benchmarks/driver_bench/src/suites/single_bench/run_command.mts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { driver, type mongodb } from '../../driver.mjs';
1+
import { driver, type mongodb, TAG } from '../../driver.mjs';
22

33
// { hello: true } is 13 bytes of BSON x 10,000 iterations
44
export const taskSize = 0.13;
5+
export const tags = [TAG.spec, TAG.alert];
56

67
let db: mongodb.Db;
78

test/benchmarks/driver_bench/src/suites/single_bench/small_doc_insert_one.mts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { driver, type mongodb } from '../../driver.mjs';
1+
import { driver, type mongodb, TAG } from '../../driver.mjs';
22

33
export const taskSize = 2.75;
4+
export const tags = [TAG.spec, TAG.alert, TAG.write];
45

56
let collection: mongodb.Collection;
67
let documents: Record<string, any>[];

0 commit comments

Comments
 (0)