Skip to content

Commit 26ccb55

Browse files
W-A-Jamesnbbeeken
andauthored
test(NODE-6756): add tags to benchmarks (#751)
Co-authored-by: Neal Beeken <[email protected]>
1 parent 00d289b commit 26ccb55

29 files changed

+556
-185
lines changed

.evergreen/config.yml

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,15 @@ functions:
4040
args:
4141
- .evergreen/install-dependencies.sh
4242

43+
perf send:
44+
- command: subprocess.exec
45+
params:
46+
working_dir: src
47+
binary: bash
48+
add_expansions_to_env: true
49+
args:
50+
- .evergreen/perf-send.sh
51+
4352
run tests:
4453
- command: subprocess.exec
4554
type: test
@@ -257,9 +266,9 @@ tasks:
257266
vars:
258267
WARMUP: 1000
259268
ITERATIONS: 1000
260-
- command: perf.send
261-
params:
262-
file: src/test/bench/etc/resultsCollectedMeans.json
269+
- func: perf send
270+
vars:
271+
TARGET_FILE: ./test/bench/etc/resultsCollectedMeans.json
263272
- name: run-custom-benchmarks
264273
commands:
265274
- func: fetch source
@@ -271,9 +280,9 @@ tasks:
271280
vars:
272281
NPM_VERSION: 9
273282
- func: run custom benchmarks
274-
- command: perf.send
275-
params:
276-
file: src/customBenchmarkResults.json
283+
- func: perf send
284+
vars:
285+
TARGET_FILE: ./customBenchmarkResults.json
277286
- name: run-spec-benchmarks
278287
commands:
279288
- func: fetch source
@@ -285,9 +294,9 @@ tasks:
285294
vars:
286295
NPM_VERSION: 9
287296
- func: run spec benchmarks
288-
- command: perf.send
289-
params:
290-
file: src/bsonBench.json
297+
- func: perf send
298+
vars:
299+
TARGET_FILE: ./bsonBench.json
291300
- name: check-eslint-plugin
292301
commands:
293302
- func: fetch source

.evergreen/perf-send.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env bash
2+
3+
source $DRIVERS_TOOLS/.evergreen/init-node-and-npm-env.sh
4+
set -o xtrace
5+
TARGET_FILE=$TARGET_FILE
6+
7+
node ./.evergreen/perf_send.mjs $TARGET_FILE

.evergreen/perf_send.mjs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import fs from 'fs/promises';
2+
import util from 'util';
3+
4+
const API_PATH = 'https://performance-monitoring-api.corp.mongodb.com/raw_perf_results';
5+
6+
const resultFile = process.argv[2];
7+
if (resultFile == null) {
8+
throw new Error('Must specify result file');
9+
}
10+
11+
// Get expansions
12+
const {
13+
execution,
14+
requester,
15+
project,
16+
task_id,
17+
task_name,
18+
revision_order_id,
19+
build_variant: variant,
20+
version_id: version
21+
} = process.env;
22+
23+
const orderSplit = revision_order_id?.split('_');
24+
const order = Number(orderSplit ? orderSplit[orderSplit.length - 1] : undefined);
25+
26+
if (!Number.isInteger(order)) throw new Error(`Failed to parse integer from order, revision_order_id=${revision_order_id}`);
27+
28+
const results = JSON.parse(await fs.readFile(resultFile, 'utf8'));
29+
30+
// FIXME(NODE-6838): We are using dummy dates here just to be able to successfully post our results
31+
for (const r of results) {
32+
r.created_at = new Date().toISOString();
33+
r.completed_at = new Date().toISOString();
34+
}
35+
36+
const body = {
37+
id: {
38+
project,
39+
version,
40+
variant,
41+
order,
42+
task_name,
43+
task_id,
44+
execution,
45+
mainline: requester === 'commit'
46+
},
47+
results
48+
};
49+
50+
console.log('POST', util.inspect(body, { depth: Infinity }));
51+
52+
const resp = await fetch(API_PATH, {
53+
method: 'POST',
54+
headers: {
55+
'Content-Type': 'application/json',
56+
accept: 'application/json'
57+
},
58+
body: JSON.stringify(body)
59+
});
60+
61+
const responseText = await resp.text();
62+
let jsonResponse = null;
63+
try {
64+
jsonResponse = JSON.parse(responseText)
65+
} catch (cause) {
66+
console.log('Failed to parse json response', cause);
67+
}
68+
69+
console.log(resp.statusText, util.inspect(jsonResponse ?? responseText, { depth: Infinity }));
70+
71+
if (jsonResponse.message == null) throw new Error("Didn't get success message");
72+
73+
console.log(jsonResponse.message);

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"benchmark": "^2.1.4",
4141
"chai": "^4.4.1",
4242
"chalk": "^5.3.0",
43-
"dbx-js-tools": "github:mongodb-js/dbx-js-tools",
43+
"dbx-js-tools": "github:mongodb-js/dbx-js-tools#main",
4444
"eslint": "^9.8.0",
4545
"eslint-config-prettier": "^9.1.0",
4646
"eslint-plugin-no-bigint-usage": "file:etc/eslint/no-bigint-usage",
@@ -102,9 +102,10 @@
102102
"check:tsd": "npm run build:dts && tsd",
103103
"check:web": "WEB=true mocha test/node",
104104
"check:web-no-bigint": "WEB=true NO_BIGINT=true mocha test/node",
105-
"check:granular-bench": "npm run build:bench && node ./test/bench/etc/run_granular_benchmarks.js",
106-
"check:spec-bench": "npm run build:bench && node ./test/bench/lib/spec/bsonBench.js",
107-
"check:custom-bench": "npm run build && node ./test/bench/custom/main.mjs",
105+
"check:granular-bench": "npm run build:bench && npm run check:baseline-bench && node ./test/bench/etc/run_granular_benchmarks.js",
106+
"check:spec-bench": "npm run build:bench && npm run check:baseline-bench && node ./test/bench/lib/spec/bsonBench.js",
107+
"check:custom-bench": "npm run build && npm run check:baseline-bench && node ./test/bench/custom/main.mjs",
108+
"check:baseline-bench": "node ./test/bench/etc/cpuBaseline.js",
108109
"build:bench": "cd test/bench && npx tsc",
109110
"build:ts": "node ./node_modules/typescript/bin/tsc",
110111
"build:dts": "npm run build:ts && api-extractor run --typescript-compiler-folder node_modules/typescript --local && node etc/clean_definition_files.cjs",

test/bench/custom/benchmarks.mjs

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
11
/* eslint-disable strict */
22
import { BSON } from '../../../lib/bson.mjs';
33

4-
const ObjectId_isValid = [
5-
function objectid_isvalid_wrong_string_length() {
6-
BSON.ObjectId.isValid('a');
7-
},
8-
/** wrong character at the start, could be the most short circuited code path */
9-
function objectid_isvalid_invalid_hex_at_start() {
10-
BSON.ObjectId.isValid('g6e84ebdc96f4c0772f0cbbf');
11-
},
12-
/** wrong character at the end, could be the least short circuited code path */
13-
function objectid_isvalid_invalid_hex_at_end() {
14-
BSON.ObjectId.isValid('66e84ebdc96f4c0772f0cbbg');
15-
},
16-
function objectid_isvalid_valid_hex_string() {
17-
BSON.ObjectId.isValid('66e84ebdc96f4c0772f0cbbf');
18-
}
19-
];
4+
const ObjectId_isValid = {
5+
name: 'ObjectId_isValid',
6+
tags: ['alerting-benchmark', 'objectid'],
7+
benchmarks: [
8+
function objectid_isvalid_wrong_string_length() {
9+
BSON.ObjectId.isValid('a');
10+
},
11+
/** wrong character at the start, could be the most short circuited code path */
12+
function objectid_isvalid_invalid_hex_at_start() {
13+
BSON.ObjectId.isValid('g6e84ebdc96f4c0772f0cbbf');
14+
},
15+
/** wrong character at the end, could be the least short circuited code path */
16+
function objectid_isvalid_invalid_hex_at_end() {
17+
BSON.ObjectId.isValid('66e84ebdc96f4c0772f0cbbg');
18+
},
19+
function objectid_isvalid_valid_hex_string() {
20+
BSON.ObjectId.isValid('66e84ebdc96f4c0772f0cbbf');
21+
}
22+
]
23+
};
2024

2125
// Add benchmarks here:
22-
export const benchmarks = [...ObjectId_isValid];
26+
export const suites = [ObjectId_isValid];

test/bench/custom/main.mjs

Lines changed: 69 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import util from 'node:util';
44
import fs from 'node:fs';
55
import os from 'node:os';
66
import benchmark from 'benchmark';
7-
import { benchmarks } from './benchmarks.mjs';
7+
import { suites } from './benchmarks.mjs';
88

99
const hw = os.cpus();
1010
const ram = os.totalmem() / 1024 ** 3;
@@ -20,20 +20,71 @@ const systemInfo = () =>
2020
].join('\n');
2121
console.log(systemInfo());
2222

23-
const suite = new benchmark.Suite();
24-
25-
for (const bench of benchmarks) suite.add(bench.name, bench);
26-
27-
suite
28-
.on('cycle', function logBenchmark(event) {
29-
console.log(String(event.target));
30-
})
31-
.on('complete', function outputPerfSend() {
32-
const data = Array.from(this).map(bench => ({
33-
info: { test_name: bench.name },
34-
metrics: [{ name: 'ops_per_sec', value: bench.hz }]
35-
}));
36-
console.log(util.inspect(data, { depth: Infinity, colors: true }));
37-
fs.writeFileSync('customBenchmarkResults.json', JSON.stringify(data), 'utf8');
38-
})
39-
.run();
23+
function logBenchmark(event) {
24+
console.log(String(event.target));
25+
}
26+
27+
function processBenchmarkResult(bench, metadata) {
28+
return {
29+
info: { test_name: bench.name },
30+
metrics: [{ name: 'ops_per_sec', value: bench.hz, metadata }]
31+
};
32+
}
33+
34+
let completedSuites = 0;
35+
async function completeSuite() {
36+
const metadata = { improvement_direction: 'up' };
37+
if (++completedSuites >= collectedSuites.length) {
38+
let cpuBaselineResults;
39+
try {
40+
cpuBaselineResults = await import('../etc/cpuBaseline.json', { assert: { type: 'json' } });
41+
} catch (cause) {
42+
throw new Error("Couldn't find baseline results", { cause });
43+
}
44+
45+
cpuBaselineResults = cpuBaselineResults.default;
46+
const cpuBaselineResult = cpuBaselineResults.hz;
47+
if (typeof cpuBaselineResult !== 'number') {
48+
throw new Error("Couldn't find baseline result");
49+
}
50+
51+
const data = [];
52+
for (const { suite, suiteConfig } of collectedSuites) {
53+
const { tags } = suiteConfig;
54+
for (const bench of Array.from(suite)) {
55+
const result = processBenchmarkResult(bench, { ...metadata, tags });
56+
result.metrics.push({
57+
name: 'normalized_throughput',
58+
value: bench.hz / cpuBaselineResult,
59+
metadata: { ...metadata, tags }
60+
});
61+
data.push(result);
62+
}
63+
64+
data.push({
65+
info: { test_name: 'cpuBaseline' },
66+
metrics: [{ name: 'ops_per_sec', value: cpuBaselineResult, metadata }]
67+
});
68+
69+
console.log(util.inspect(data, { depth: Infinity, colors: true }));
70+
fs.writeFileSync('customBenchmarkResults.json', JSON.stringify(data), 'utf8');
71+
}
72+
}
73+
}
74+
75+
function processSuite(suiteModule, cycleHandler, completeHandler) {
76+
let suite = new benchmark.Suite(suiteModule.name);
77+
for (const b of suiteModule.benchmarks) {
78+
suite.add(b.name, b);
79+
}
80+
81+
suite = suite.on('cycle', cycleHandler).on('complete', completeHandler).run({ async: true });
82+
83+
return { suite, suiteConfig: suiteModule };
84+
}
85+
86+
const collectedSuites = [];
87+
for (const suite of suites) {
88+
const newSuite = processSuite(suite, logBenchmark, completeSuite);
89+
collectedSuites.push(newSuite);
90+
}

test/bench/custom/readme.md

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,25 @@ In this directory are tests for code paths not covered by our spec or granular (
44

55
## How to write your own
66

7-
In `benchmarks.mjs` add a new test to an existing array or make a new array for a new subject area.
8-
Try to fit the name of the function into the format of: "subject area", "method or function" "test case that is being covered" (Ex. `objectid_isvalid_bestcase_false`).
7+
In `benchmarks.mjs` add a new test to an existing benchmark object or make a new object for a new subject area.
8+
Try to fit the name of the variables and the benchmark functions into the format of: "subject area", "method or function" "test case that is being covered" (Ex. `objectid_isvalid_bestcase_false`).
99
Make sure your test is added to the `benchmarks` export.
1010

1111
### Example
1212

1313
```js
14-
const ObjectId_isValid = [
15-
function objectid_isvalid_strlen() {
16-
BSON.ObjectId.isValid('a');
17-
},
18-
// ...
19-
];
20-
21-
export const benchmarks = [...ObjectId_isValid];
14+
const ObjectId_isValid = {
15+
name: 'ObjectId_isValid',
16+
tags: ['objectid'],
17+
benchmarks : [
18+
function objectid_isvalid_strlen() {
19+
BSON.ObjectId.isValid('a');
20+
},
21+
// ...
22+
]
23+
};
24+
25+
export const benchmarks = [ObjectId_isValid];
2226
```
2327

2428
## Output
@@ -28,9 +32,9 @@ The JSON emitted at the end of the benchmarks must follow our performance tracki
2832
The JSON must be an array of "`Test`"s:
2933

3034
```ts
31-
type Metric = { name: string, value: number }
35+
type Metric = { name: string, value: number, metadata: { improvement_direction: 'up' | 'down' } }
3236
type Test = {
33-
info: { test_name: string },
37+
info: { test_name: string, tags?: string[]},
3438
metrics: Metric[]
3539
}
3640
```

test/bench/etc/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
*Results.json
2-
resultsCollected.json
2+
cpuBaseline.json
3+
resultsCollected*.json

0 commit comments

Comments
 (0)