Skip to content

Commit 2dfc125

Browse files
authored
ci(NODE-5403): add string deserialization benchmark to ci (#593)
1 parent 76eca2b commit 2dfc125

File tree

12 files changed

+278
-12
lines changed

12 files changed

+278
-12
lines changed

.evergreen/config.yml

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,24 +41,25 @@ functions:
4141
- .evergreen/install-dependencies.sh
4242

4343
run tests:
44-
- command: shell.exec
44+
- command: subprocess.exec
4545
type: test
4646
params:
4747
working_dir: src
48-
script: |
49-
${PREPARE_SHELL}
50-
echo "NO_BIGINT=${NO_BIGINT} TEST_TARGET=${TEST_TARGET}"
51-
NO_BIGINT=${NO_BIGINT} ${PROJECT_DIRECTORY}/.evergreen/run-tests.sh ${TEST_TARGET}
48+
add_expansions_to_env: true
49+
binary: bash
50+
args:
51+
- .evergreen/run-tests.sh
5252

5353
run checks:
54-
- command: shell.exec
54+
- command: subprocess.exec
5555
type: test
5656
params:
5757
working_dir: src
58-
script: |
59-
${PREPARE_SHELL}
60-
echo "TEST_TARGET=${TEST_TARGET}"
61-
bash ${PROJECT_DIRECTORY}/.evergreen/run-checks.sh
58+
add_expansions_to_env: true
59+
binary: bash
60+
args:
61+
- .evergreen/run-checks.sh
62+
6263
run typescript:
6364
- command: subprocess.exec
6465
type: test
@@ -93,7 +94,15 @@ functions:
9394
PROJECT_DIRECTORY: ${PROJECT_DIRECTORY}
9495
args:
9596
- .evergreen/run-bundling-test.sh
96-
97+
run benchmarks:
98+
- command: subprocess.exec
99+
type: test
100+
params:
101+
working_dir: src
102+
binary: bash
103+
add_expansions_to_env: true
104+
args:
105+
- .evergreen/run-benchmarks.sh
97106
tasks:
98107
- name: node-tests-v16
99108
tags: ["node"]
@@ -200,6 +209,18 @@ tasks:
200209
vars:
201210
TS_VERSION: "next"
202211
TRY_COMPILING_LIBRARY: "false"
212+
- name: run-benchmarks-node
213+
commands:
214+
- func: fetch source
215+
vars:
216+
NODE_LTS_VERSION: v18.16.0
217+
- func: install dependencies
218+
- func: run benchmarks
219+
vars:
220+
WEB: false
221+
- command: perf.send
222+
params:
223+
file: src/benchmarks.json
203224
- name: check-eslint-plugin
204225
commands:
205226
- func: fetch source
@@ -222,3 +243,8 @@ buildvariants:
222243
- check-typescript-current
223244
- check-typescript-next
224245
- bundling-tests
246+
- name: perf
247+
display_name: RHEL 9.0 perf
248+
run_on: rhel90-dbx-perf-large
249+
tasks:
250+
- run-benchmarks-node

.evergreen/install-dependencies.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ while IFS=$'\t' read -r -a row; do
3636
node_index_lts="${row[9]}"
3737
[[ "$node_index_version" = "version" ]] && continue # skip tsv header
3838
[[ "$NODE_LTS_VERSION" = "latest" ]] && break # first line is latest
39+
[[ "$NODE_LTS_VERSION" = "$node_index_version" ]] && break # match full version if specified
3940
[[ "$NODE_LTS_VERSION" = "$node_index_major_version" ]] && break # case insensitive compare
4041
done < node_index.tab
4142

.evergreen/run-benchmarks.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env bash
2+
3+
source "${PROJECT_DIRECTORY}/.evergreen/init-node-and-npm-env.sh"
4+
5+
npm run check:bench

.evergreen/run-tests.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
source "${PROJECT_DIRECTORY}/.evergreen/init-node-and-npm-env.sh"
44

5-
case $1 in
5+
case "${TEST_TARGET}" in
66
"node")
77
npm run check:coverage
88
;;

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
"check:tsd": "npm run build:dts && tsd",
104104
"check:web": "WEB=true mocha test/node",
105105
"check:web-no-bigint": "WEB=true NO_BIGINT=true mocha test/node",
106+
"check:bench":"cd test/bench && npx tsc && node ./lib/index.js && mv benchmarks.json ../../.",
106107
"build:ts": "node ./node_modules/typescript/bin/tsc",
107108
"build:dts": "npm run build:ts && api-extractor run --typescript-compiler-folder node_modules/typescript --local && node etc/clean_definition_files.cjs",
108109
"build:bundle": "rollup -c rollup.config.mjs",

test/bench/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lib
2+
node_modules

test/bench/src/index.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { getStringDeserializationSuite } from './suites/string_deserialization';
2+
import { type PerfSendData } from './util';
3+
import { writeFile } from 'fs';
4+
import { cpus, totalmem } from 'os';
5+
6+
const hw = cpus();
7+
const platform = { name: hw[0].model, cores: hw.length, ram: `${totalmem() / 1024 ** 3}GiB` };
8+
9+
const results: PerfSendData[][] = [];
10+
11+
console.log(
12+
[
13+
`\n- cpu: ${platform.name}`,
14+
`- cores: ${platform.cores}`,
15+
`- os: ${process.platform}`,
16+
`- ram: ${platform.ram}`
17+
].join('\n')
18+
);
19+
20+
for (const suite of [getStringDeserializationSuite()]) {
21+
suite.run();
22+
results.push(suite.results);
23+
}
24+
25+
writeFile('benchmarks.json', JSON.stringify(results.flat()), err => {
26+
if (err) {
27+
console.error(err);
28+
process.exit(1);
29+
}
30+
31+
console.log('Wrote results to benchmarks.json');
32+
});

test/bench/src/suite.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import { Task } from './task';
3+
import { type PerfSendData } from './util';
4+
5+
export class Suite {
6+
name: string;
7+
tasks: Task[];
8+
results: PerfSendData[];
9+
constructor(name: string) {
10+
this.name = name;
11+
this.tasks = [];
12+
this.results = [];
13+
}
14+
15+
task(opts: {
16+
name: string;
17+
data: any;
18+
fn: (data: any) => void;
19+
iterations: number;
20+
resultUnit?: string;
21+
transform?: (x: number) => number;
22+
args?: Record<string, string>;
23+
}) {
24+
this.tasks.push(
25+
new Task(
26+
this,
27+
opts.name,
28+
opts.data,
29+
opts.fn,
30+
opts.iterations,
31+
opts.resultUnit,
32+
opts.transform,
33+
opts.args
34+
)
35+
);
36+
return this;
37+
}
38+
39+
run() {
40+
console.log(`Running suite: ${this.name}`);
41+
42+
for (const task of this.tasks) {
43+
task.run();
44+
}
45+
}
46+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Suite } from '../suite';
2+
import * as BSON from '../../../../';
3+
4+
export function getStringDeserializationSuite(): Suite {
5+
const DOC: Uint8Array = BSON.serialize({
6+
nextBatch: Array.from({ length: 1000 }, () => {
7+
return {
8+
_id: new BSON.ObjectId(),
9+
arrayField: Array.from({ length: 20 }, (_, i) => `5e99f3f5d3ab06936d36000${i}`)
10+
};
11+
})
12+
});
13+
const suite = new Suite('string deserialization');
14+
for (const utf8Validation of [true, false]) {
15+
suite.task({
16+
name: `stringDeserializationUTF8${utf8Validation ? 'On' : 'Off'}-${
17+
process.env.WEB === 'true' ? 'web' : 'node'
18+
}`,
19+
data: DOC,
20+
fn: serializedDoc =>
21+
BSON.deserialize(serializedDoc, { validation: { utf8: utf8Validation } }),
22+
iterations: 10_000,
23+
resultUnit: 'megabytes_per_second',
24+
transform: (runtimeMS: number) => {
25+
return DOC.byteLength / 1024 ** 2 / (runtimeMS / 1000);
26+
}
27+
});
28+
}
29+
30+
return suite;
31+
}

test/bench/src/task.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { type Suite } from './suite';
2+
import { convertToPerfSendFormat } from './util';
3+
/* eslint-disable @typescript-eslint/no-explicit-any */
4+
import { performance } from 'perf_hooks';
5+
6+
export class Task {
7+
name: string;
8+
parent: Suite;
9+
data: any;
10+
fn: (data: any) => void;
11+
iterations: number;
12+
transform?: (x: number) => number;
13+
resultUnit: string;
14+
args?: Record<string, string>;
15+
16+
constructor(
17+
parent: Suite,
18+
name: string,
19+
data: any,
20+
fn: (data: any) => void,
21+
iterations: number,
22+
resultUnit?: string,
23+
transform?: (x: number) => number,
24+
args?: Record<string, string>
25+
) {
26+
this.parent = parent;
27+
this.name = name;
28+
this.iterations = iterations;
29+
this.data = data;
30+
this.fn = fn;
31+
this.transform = transform;
32+
this.args = args;
33+
this.resultUnit = resultUnit ? resultUnit : 'ms';
34+
}
35+
36+
// TODO: Ensure that each task runs on a separate node process
37+
run() {
38+
console.log(`\t ${this.name} - iters: ${this.iterations}`);
39+
const data = this.data;
40+
const fn = this.fn;
41+
// Warmup
42+
for (let i = 0; i < this.iterations; i++) {
43+
fn(data);
44+
}
45+
const results: number[] = [];
46+
47+
for (let i = 0; i < this.iterations; i++) {
48+
const start = performance.now();
49+
fn(data);
50+
const end = performance.now();
51+
results.push(end - start); // ms
52+
}
53+
this.parent.results.push(
54+
convertToPerfSendFormat(
55+
this.name,
56+
[
57+
{
58+
name: this.resultUnit,
59+
results: this.transform ? results.map(this.transform) : results
60+
}
61+
],
62+
this.args
63+
)
64+
);
65+
}
66+
}

test/bench/src/util.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
export type PerfSendData = {
2+
info: {
3+
test_name: string;
4+
tags?: string[];
5+
args?: Record<string, string>;
6+
};
7+
metrics: { name: string; value: number }[];
8+
};
9+
10+
export function convertToPerfSendFormat(
11+
benchmarkName: string,
12+
metrics: {
13+
name: string;
14+
results: number[];
15+
}[],
16+
args?: Record<string, string>
17+
): PerfSendData {
18+
return {
19+
info: {
20+
test_name: benchmarkName.replaceAll(' ', '_'),
21+
tags: ['js-bson'],
22+
args: args
23+
},
24+
metrics: metrics.map(({ name, results }) => ({
25+
name,
26+
value: results.reduce((acc, x) => acc + x, 0) / results.length
27+
}))
28+
};
29+
}

test/bench/tsconfig.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"compilerOptions": {
3+
"allowJs": false,
4+
"checkJs": false,
5+
"strict": true,
6+
"alwaysStrict": true,
7+
"target": "es2021",
8+
"module": "commonjs",
9+
"moduleResolution": "node",
10+
"skipLibCheck": true,
11+
"lib": [
12+
"es2021"
13+
],
14+
"outDir": "./lib",
15+
"importHelpers": false,
16+
"noEmitHelpers": false,
17+
"noEmitOnError": true,
18+
"emitDeclarationOnly": false,
19+
"sourceMap": true,
20+
"inlineSourceMap": false,
21+
"inlineSources": false,
22+
"types": ["node"]
23+
},
24+
"include": [
25+
"./src/**/*.ts"
26+
]
27+
}

0 commit comments

Comments
 (0)