From 64b3ee97bd543711541ec57b9ddf4256886af50b Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Thu, 15 Sep 2022 10:01:05 -0400 Subject: [PATCH] refactor(NODE-4617): use promise apis in benchmarks (#3399) Co-authored-by: Neal Beeken --- test/benchmarks/mongoBench/benchmark.js | 22 +++++- test/benchmarks/mongoBench/runner.js | 37 ++++------ .../benchmarks/mongoBench/suites/bsonBench.js | 12 ++-- .../mongoBench/suites/multiBench.js | 68 +++++++++--------- .../mongoBench/suites/singleBench.js | 69 +++++++------------ 5 files changed, 97 insertions(+), 111 deletions(-) diff --git a/test/benchmarks/mongoBench/benchmark.js b/test/benchmarks/mongoBench/benchmark.js index da24d8e01a..c2a3d5324d 100644 --- a/test/benchmarks/mongoBench/benchmark.js +++ b/test/benchmarks/mongoBench/benchmark.js @@ -14,6 +14,7 @@ class Benchmark { // Meta information this._taskSize = null; this._description = null; + this._taskType = 'async'; } /** @@ -96,6 +97,23 @@ class Benchmark { return this; } + /** + * Sets the task type - either a synchronous or asynchronous task. The default is async. + * + * @param {'async' | 'sync'} type - the type of task + */ + taskType(type) { + if (['async', 'sync'].includes(type)) { + this._taskType = type; + } else { + throw new Error( + `Invalid value for benchmark field _taskType: expected either 'async' or 'sync', but received ${type}` + ); + } + + return this; + } + /** * Set the Description * @@ -122,11 +140,11 @@ class Benchmark { * @throws Error */ validate() { - ['_task', '_taskSize'].forEach(key => { + for (const key of ['_task', '_taskSize', '_taskType']) { if (!this[key]) { throw new Error(`Benchmark is missing required field ${key}`); } - }); + } } toObj() { diff --git a/test/benchmarks/mongoBench/runner.js b/test/benchmarks/mongoBench/runner.js index a93a114562..064f51dec5 100644 --- a/test/benchmarks/mongoBench/runner.js +++ b/test/benchmarks/mongoBench/runner.js @@ -9,33 +9,18 @@ function percentileIndex(percentile, total) { return Math.max(Math.floor((total * percentile) / 100 - 1), 0); } -function timeDoneTask(task, ctx) { - return new Promise((resolve, reject) => { - let called = false; - const start = performance.now(); - task.call(ctx, err => { - const end = performance.now(start); - if (called) return; - if (err) return reject(err); - return resolve((end - start) / 1000); - }); - }); -} +function timeSyncTask(task, ctx) { + const start = performance.now(); + task.call(ctx); + const end = performance.now(); -async function timeTask(task, ctx) { - // Some tasks are async, so they take callbacks. - if (task.length) { - return timeDoneTask(task, ctx); - } + return (end - start) / 1000; +} +async function timeAsyncTask(task, ctx) { const start = performance.now(); - const ret = task.call(ctx); - let end = performance.now(); - - if (ret && ret.then) { - await ret; - end = performance.now(); - } + await task.call(ctx); + const end = performance.now(); return (end - start) / 1000; } @@ -180,9 +165,11 @@ class Runner { let time = performance.now() - start; let count = 1; + const taskTimer = benchmark._taskType === 'sync' ? timeSyncTask : timeAsyncTask; + while (time < maxExecutionTime && (time < minExecutionTime || count < minExecutionCount)) { await benchmark.beforeTask.call(ctx); - const executionTime = await timeTask(benchmark.task, ctx); + const executionTime = await taskTimer(benchmark.task, ctx); rawData.push(executionTime); count++; time = performance.now(); diff --git a/test/benchmarks/mongoBench/suites/bsonBench.js b/test/benchmarks/mongoBench/suites/bsonBench.js index 3abbb3034a..e0fa434edc 100644 --- a/test/benchmarks/mongoBench/suites/bsonBench.js +++ b/test/benchmarks/mongoBench/suites/bsonBench.js @@ -27,22 +27,22 @@ function makeBsonBench({ suite, BSON }) { } return suite .benchmark('flatBsonEncoding', benchmark => - benchmark.taskSize(75.31).setup(makeBSONLoader('flat_bson')).task(encodeBSON) + benchmark.taskSize(75.31).taskType('sync').setup(makeBSONLoader('flat_bson')).task(encodeBSON) ) .benchmark('flatBsonDecoding', benchmark => - benchmark.taskSize(75.31).setup(makeBSONLoader('flat_bson')).task(decodeBSON) + benchmark.taskSize(75.31).taskType('sync').setup(makeBSONLoader('flat_bson')).task(decodeBSON) ) .benchmark('deepBsonEncoding', benchmark => - benchmark.taskSize(19.64).setup(makeBSONLoader('deep_bson')).task(encodeBSON) + benchmark.taskSize(19.64).taskType('sync').setup(makeBSONLoader('deep_bson')).task(encodeBSON) ) .benchmark('deepBsonDecoding', benchmark => - benchmark.taskSize(19.64).setup(makeBSONLoader('deep_bson')).task(decodeBSON) + benchmark.taskSize(19.64).taskType('sync').setup(makeBSONLoader('deep_bson')).task(decodeBSON) ) .benchmark('fullBsonEncoding', benchmark => - benchmark.taskSize(57.34).setup(makeBSONLoader('full_bson')).task(encodeBSON) + benchmark.taskSize(57.34).taskType('sync').setup(makeBSONLoader('full_bson')).task(encodeBSON) ) .benchmark('fullBsonDecoding', benchmark => - benchmark.taskSize(57.34).setup(makeBSONLoader('full_bson')).task(decodeBSON) + benchmark.taskSize(57.34).taskType('sync').setup(makeBSONLoader('full_bson')).task(decodeBSON) ); } diff --git a/test/benchmarks/mongoBench/suites/multiBench.js b/test/benchmarks/mongoBench/suites/multiBench.js index d247d290d4..b1b2464f9c 100644 --- a/test/benchmarks/mongoBench/suites/multiBench.js +++ b/test/benchmarks/mongoBench/suites/multiBench.js @@ -1,3 +1,5 @@ +const { Readable } = require('stream'); +const { pipeline } = require('stream/promises'); const { loadSpecFile, makeLoadJSON, @@ -12,30 +14,24 @@ const { createCollection, dropCollection, dropBucket, - initBucket + initBucket, + writeSingleByteFileToBucket } = require('../../driverBench/common'); function loadGridFs() { this.bin = loadSpecFile(['single_and_multi_document', 'gridfs_large.bin']); } -function findManyAndEmptyCursor(done) { - return this.collection.find({}).forEach(() => {}, done); -} - -function docBulkInsert(done) { - return this.collection.insertMany(this.docs, { ordered: true }, done); -} - function gridFsInitUploadStream() { - this.stream = this.bucket.openUploadStream('gridfstest'); + this.uploadStream = this.bucket.openUploadStream('gridfstest'); } -function writeSingleByteToUploadStream() { - return new Promise((resolve, reject) => { - this.stream.write('\0', null, err => (err ? reject(err) : resolve())); - }); +async function gridFsUpload() { + const uploadData = Readable.from(this.bin); + const uploadStream = this.uploadStream; + await pipeline(uploadData, uploadStream); } + function makeMultiBench(suite) { return suite .benchmark('findManyAndEmptyCursor', benchmark => @@ -48,7 +44,12 @@ function makeMultiBench(suite) { .setup(dropDb) .setup(initCollection) .setup(makeLoadTweets(false)) - .task(findManyAndEmptyCursor) + .task(async function () { + // eslint-disable-next-line no-unused-vars + for await (const _ of this.collection.find({})) { + // do nothing + } + }) .teardown(dropDb) .teardown(disconnectClient) ) @@ -67,7 +68,9 @@ function makeMultiBench(suite) { .beforeTask(dropCollection) .beforeTask(createCollection) .beforeTask(initCollection) - .task(docBulkInsert) + .task(async function () { + await this.collection.insertMany(this.docs, { ordered: true }); + }) .teardown(dropDb) .teardown(disconnectClient) ) @@ -86,7 +89,9 @@ function makeMultiBench(suite) { .beforeTask(dropCollection) .beforeTask(createCollection) .beforeTask(initCollection) - .task(docBulkInsert) + .task(async function () { + await this.collection.insertMany(this.docs, { ordered: true }); + }) .teardown(dropDb) .teardown(disconnectClient) ) @@ -103,10 +108,8 @@ function makeMultiBench(suite) { .beforeTask(dropBucket) .beforeTask(initBucket) .beforeTask(gridFsInitUploadStream) - .beforeTask(writeSingleByteToUploadStream) - .task(function (done) { - this.stream.on('error', done).end(this.bin, null, () => done()); - }) + .beforeTask(writeSingleByteFileToBucket) + .task(gridFsUpload) .teardown(dropDb) .teardown(disconnectClient) ) @@ -123,21 +126,16 @@ function makeMultiBench(suite) { .setup(dropBucket) .setup(initBucket) .setup(gridFsInitUploadStream) - .setup(function () { - return new Promise((resolve, reject) => { - this.stream.end(this.bin, null, err => { - if (err) { - return reject(err); - } - - this.id = this.stream.id; - this.stream = undefined; - resolve(); - }); - }); + .setup(async function () { + await gridFsUpload.call(this); + this.id = this.uploadStream.id; + this.uploadData = undefined; }) - .task(function (done) { - this.bucket.openDownloadStream(this.id).resume().on('end', done); + .task(async function () { + // eslint-disable-next-line no-unused-vars + for await (const _ of this.bucket.openDownloadStream(this.id)) { + // do nothing + } }) .teardown(dropDb) .teardown(disconnectClient) diff --git a/test/benchmarks/mongoBench/suites/singleBench.js b/test/benchmarks/mongoBench/suites/singleBench.js index 60b9c30795..d38bc287f3 100644 --- a/test/benchmarks/mongoBench/suites/singleBench.js +++ b/test/benchmarks/mongoBench/suites/singleBench.js @@ -11,45 +11,6 @@ const { makeLoadTweets } = require('../../driverBench/common'); -function makeTestInsertOne(numberOfOps) { - return function (done) { - const loop = _id => { - if (_id > numberOfOps) { - return done(); - } - - const doc = Object.assign({}, this.doc); - - this.collection.insertOne(doc, err => (err ? done(err) : loop(_id + 1))); - }; - - loop(1); - }; -} - -function findOneById(done) { - const loop = _id => { - if (_id > 10000) { - return done(); - } - - return this.collection.findOne({ _id }, err => (err ? done(err) : loop(_id + 1))); - }; - - return loop(1); -} - -function runCommand(done) { - const loop = _id => { - if (_id > 10000) { - return done(); - } - return this.db.command({ hello: true }, err => (err ? done(err) : loop(_id + 1))); - }; - - return loop(1); -} - function makeSingleBench(suite) { suite .benchmark('runCommand', benchmark => @@ -58,7 +19,11 @@ function makeSingleBench(suite) { .setup(makeClient) .setup(connectClient) .setup(initDb) - .task(runCommand) + .task(async function () { + for (let i = 0; i < 10000; ++i) { + await this.db.command({ hello: true }); + } + }) .teardown(disconnectClient) ) .benchmark('findOne', benchmark => @@ -71,7 +36,11 @@ function makeSingleBench(suite) { .setup(dropDb) .setup(initCollection) .setup(makeLoadTweets(true)) - .task(findOneById) + .task(async function () { + for (let _id = 0; _id < 10000; ++_id) { + await this.collection.findOne({ _id }); + } + }) .teardown(dropDb) .teardown(disconnectClient) ) @@ -89,7 +58,14 @@ function makeSingleBench(suite) { .beforeTask(dropCollection) .beforeTask(createCollection) .beforeTask(initCollection) - .task(makeTestInsertOne(10000)) + .beforeTask(function () { + this.docs = Array.from({ length: 10000 }, () => Object.assign({}, this.doc)); + }) + .task(async function () { + for (const doc of this.docs) { + await this.collection.insertOne(doc); + } + }) .teardown(dropDb) .teardown(disconnectClient) ) @@ -107,7 +83,14 @@ function makeSingleBench(suite) { .beforeTask(dropCollection) .beforeTask(createCollection) .beforeTask(initCollection) - .task(makeTestInsertOne(10)) + .beforeTask(function () { + this.docs = Array.from({ length: 10 }, () => Object.assign({}, this.doc)); + }) + .task(async function () { + for (const doc of this.docs) { + await this.collection.insertOne(doc); + } + }) .teardown(dropDb) .teardown(disconnectClient) );