Skip to content

Commit ce88b14

Browse files
committed
chore(NODE-5455): benchmark FLE
1 parent e688cb8 commit ce88b14

File tree

7 files changed

+257
-137
lines changed

7 files changed

+257
-137
lines changed

package-lock.json

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

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
"@types/mocha": "^10.0.6",
4747
"@types/node": "^20.12.7",
4848
"@typescript-eslint/eslint-plugin": "^7.7.0",
49-
"bson": "^6.6.0",
49+
"bson": "^6.7.0",
5050
"chai": "^4.4.1",
5151
"chai-subset": "^1.6.0",
5252
"clang-format": "^1.8.0",

test/benchmark/main.mjs

Lines changed: 0 additions & 56 deletions
This file was deleted.

test/benchmark/mongocrypt_binary_t.bench.mjs

Lines changed: 0 additions & 76 deletions
This file was deleted.

test/benchmarks/bench.mjs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// @ts-check
2+
/* eslint-disable no-console */
3+
import path from 'node:path';
4+
import url from 'node:url';
5+
import process from 'node:process';
6+
import fs from 'node:fs';
7+
import { EJSON, BSON } from 'bson';
8+
import { cryptoCallbacks } from './crypto_callbacks.mjs';
9+
import { MongoCrypt } from '../../lib/index.js';
10+
11+
const NEED_MONGO_KEYS = 3;
12+
const READY = 5;
13+
const ERROR = 0;
14+
15+
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
16+
17+
let { CRYPT_SHARED_LIB_PATH = '' } = process.env;
18+
const cryptSharedLibPath = CRYPT_SHARED_LIB_PATH.length !== 0 ? CRYPT_SHARED_LIB_PATH : undefined;
19+
20+
const warmupSecs = 2;
21+
const testInSecs = 57;
22+
const fieldCount = 1500;
23+
24+
const LOCAL_KEY = new Uint8Array([
25+
0x9d, 0x94, 0x4b, 0x0d, 0x93, 0xd0, 0xc5, 0x44, 0xa5, 0x72, 0xfd, 0x32, 0x1b, 0x94, 0x30, 0x90,
26+
0x23, 0x35, 0x73, 0x7c, 0xf0, 0xf6, 0xc2, 0xf4, 0xda, 0x23, 0x56, 0xe7, 0x8f, 0x04, 0xcc, 0xfa,
27+
0xde, 0x75, 0xb4, 0x51, 0x87, 0xf3, 0x8b, 0x97, 0xd7, 0x4b, 0x44, 0x3b, 0xac, 0x39, 0xa2, 0xc6,
28+
0x4d, 0x91, 0x00, 0x3e, 0xd1, 0xfa, 0x4a, 0x30, 0xc1, 0xd2, 0xc6, 0x5e, 0xfb, 0xac, 0x41, 0xf2,
29+
0x48, 0x13, 0x3c, 0x9b, 0x50, 0xfc, 0xa7, 0x24, 0x7a, 0x2e, 0x02, 0x63, 0xa3, 0xc6, 0x16, 0x25,
30+
0x51, 0x50, 0x78, 0x3e, 0x0f, 0xd8, 0x6e, 0x84, 0xa6, 0xec, 0x8d, 0x2d, 0x24, 0x47, 0xe5, 0xaf
31+
]);
32+
33+
const padNum = i => i.toString().padStart(4, '0');
34+
const kmsProviders = { local: { key: LOCAL_KEY } };
35+
const algorithm = 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic';
36+
const keyDocument = EJSON.parse(
37+
await fs.promises.readFile(path.join(__dirname, 'keyDocument.json'), 'utf8'),
38+
{ relaxed: false }
39+
);
40+
41+
function createEncryptedDocument(mongoCrypt) {
42+
const { _id: keyId } = keyDocument;
43+
44+
const encrypted = {};
45+
46+
for (let i = 0; i < fieldCount; i++) {
47+
const key = `key${padNum(i + 1)}`;
48+
const v = `value ${padNum(i + 1)}`;
49+
50+
const ctx = mongoCrypt.makeExplicitEncryptionContext(BSON.serialize({ v }), {
51+
keyId: keyId.buffer,
52+
algorithm
53+
});
54+
55+
if (ctx.state === NEED_MONGO_KEYS) {
56+
ctx.addMongoOperationResponse(BSON.serialize(keyDocument));
57+
ctx.finishMongoOperation();
58+
}
59+
60+
console.assert(ctx.state === READY);
61+
const result = ctx.finalize();
62+
console.assert(ctx.state !== ERROR);
63+
const { v: encryptedValue } = BSON.deserialize(result);
64+
encrypted[key] = encryptedValue;
65+
}
66+
67+
return encrypted;
68+
}
69+
70+
function measureMedianOpsPerSecOfDecrypt(mongoCrypt, toDecrypt, seconds) {
71+
let operationsPerSecond = [];
72+
73+
for (let second = 0; second < seconds; second++) {
74+
const startTime = performance.now();
75+
/** @type {number | null} */
76+
let operations = 0;
77+
78+
while (performance.now() - startTime < 1000) {
79+
const ctx = mongoCrypt.makeDecryptionContext(toDecrypt);
80+
if (ctx.state === NEED_MONGO_KEYS) {
81+
// We ran over a minute
82+
operations = null;
83+
break;
84+
}
85+
86+
if (ctx.state !== READY) throw new Error(`NOT READY: ${ctx.state}`);
87+
88+
ctx.finalize();
89+
operations += 1;
90+
}
91+
92+
if (operations != null) operationsPerSecond.push(operations);
93+
}
94+
95+
console.log('samples taken: ', operationsPerSecond.length);
96+
operationsPerSecond.sort((a, b) => a - b);
97+
return operationsPerSecond[Math.floor(operationsPerSecond.length / 2)];
98+
}
99+
100+
function main() {
101+
console.log(
102+
`BenchmarkRunner is using ` +
103+
`libmongocryptVersion=${MongoCrypt.libmongocryptVersion}, ` +
104+
`warmupSecs=${warmupSecs}, ` +
105+
`testInSecs=${testInSecs}`
106+
);
107+
108+
const mongoCryptOptions = {
109+
kmsProviders: BSON.serialize(kmsProviders),
110+
cryptoCallbacks
111+
};
112+
if (cryptSharedLibPath) mongoCryptOptions.cryptSharedLibPath = cryptSharedLibPath;
113+
114+
const mongoCrypt = new MongoCrypt(mongoCryptOptions);
115+
116+
const encrypted = createEncryptedDocument(mongoCrypt);
117+
const toDecrypt = BSON.serialize(encrypted);
118+
119+
const created_at = new Date();
120+
121+
// warmup
122+
measureMedianOpsPerSecOfDecrypt(mongoCrypt, toDecrypt, warmupSecs);
123+
// bench
124+
const medianOpsPerSec = measureMedianOpsPerSecOfDecrypt(mongoCrypt, toDecrypt, testInSecs);
125+
126+
const completed_at = new Date();
127+
128+
console.log(`Decrypting 1500 fields median ops/sec : ${medianOpsPerSec}`);
129+
130+
const perfSend = {
131+
info: { test_name: 'javascript_decrypt_1500' },
132+
created_at,
133+
completed_at,
134+
artifacts: [],
135+
metrics: [{ name: 'medianOpsPerSec', type: 'THROUGHPUT', value: medianOpsPerSec }],
136+
sub_tests: []
137+
};
138+
console.log(perfSend);
139+
}
140+
141+
main();

test/benchmarks/crypto_callbacks.mjs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import crypto from 'node:crypto';
2+
3+
function makeAES256Hook(method, mode) {
4+
return function (key, iv, input, output) {
5+
let result;
6+
try {
7+
const cipher = crypto[method](mode, key, iv);
8+
cipher.setAutoPadding(false);
9+
result = cipher.update(input);
10+
const final = cipher.final();
11+
if (final.length > 0) {
12+
result = Buffer.concat([result, final]);
13+
}
14+
} catch (e) {
15+
return e;
16+
}
17+
result.copy(output);
18+
return result.length;
19+
};
20+
}
21+
22+
function randomHook(buffer, count) {
23+
try {
24+
crypto.randomFillSync(buffer, 0, count);
25+
} catch (e) {
26+
return e;
27+
}
28+
return count;
29+
}
30+
31+
function sha256Hook(input, output) {
32+
let result;
33+
try {
34+
result = crypto.createHash('sha256').update(input).digest();
35+
} catch (e) {
36+
return e;
37+
}
38+
result.copy(output);
39+
return result.length;
40+
}
41+
42+
function makeHmacHook(algorithm) {
43+
return (key, input, output) => {
44+
let result;
45+
try {
46+
result = crypto.createHmac(algorithm, key).update(input).digest();
47+
} catch (e) {
48+
return e;
49+
}
50+
result.copy(output);
51+
return result.length;
52+
};
53+
}
54+
55+
function signRsaSha256Hook(key, input, output) {
56+
let result;
57+
try {
58+
const signer = crypto.createSign('sha256WithRSAEncryption');
59+
const privateKey = Buffer.from(
60+
`-----BEGIN PRIVATE KEY-----\n${key.toString('base64')}\n-----END PRIVATE KEY-----\n`
61+
);
62+
result = signer.update(input).end().sign(privateKey);
63+
} catch (e) {
64+
return e;
65+
}
66+
result.copy(output);
67+
return result.length;
68+
}
69+
70+
const aes256CbcEncryptHook = makeAES256Hook('createCipheriv', 'aes-256-cbc');
71+
const aes256CbcDecryptHook = makeAES256Hook('createDecipheriv', 'aes-256-cbc');
72+
const aes256CtrEncryptHook = makeAES256Hook('createCipheriv', 'aes-256-ctr');
73+
const aes256CtrDecryptHook = makeAES256Hook('createDecipheriv', 'aes-256-ctr');
74+
const hmacSha512Hook = makeHmacHook('sha512');
75+
const hmacSha256Hook = makeHmacHook('sha256');
76+
77+
export const cryptoCallbacks = {
78+
randomHook,
79+
sha256Hook,
80+
signRsaSha256Hook,
81+
aes256CbcEncryptHook,
82+
aes256CbcDecryptHook,
83+
aes256CtrEncryptHook,
84+
aes256CtrDecryptHook,
85+
hmacSha512Hook,
86+
hmacSha256Hook
87+
};

0 commit comments

Comments
 (0)