mirror of https://github.com/seald/nedb
parent
792eecd1a1
commit
f906b0082e
@ -1,287 +0,0 @@ |
|||||||
/** |
|
||||||
* Functions that are used in several benchmark tests |
|
||||||
*/ |
|
||||||
const fs = require('fs') |
|
||||||
const Datastore = require('../lib/datastore') |
|
||||||
const Persistence = require('../lib/persistence') |
|
||||||
const { callbackify } = require('util') |
|
||||||
let executeAsap |
|
||||||
|
|
||||||
try { |
|
||||||
executeAsap = setImmediate |
|
||||||
} catch (e) { |
|
||||||
executeAsap = process.nextTick |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Configure the benchmark |
|
||||||
*/ |
|
||||||
module.exports.getConfiguration = function (benchDb) { |
|
||||||
const program = require('commander') |
|
||||||
|
|
||||||
program |
|
||||||
.option('-n --number [number]', 'Size of the collection to test on', parseInt) |
|
||||||
.option('-i --with-index', 'Use an index') |
|
||||||
.option('-m --in-memory', 'Test with an in-memory only store') |
|
||||||
.parse(process.argv) |
|
||||||
|
|
||||||
const n = program.number || 10000 |
|
||||||
|
|
||||||
console.log('----------------------------') |
|
||||||
console.log('Test with ' + n + ' documents') |
|
||||||
console.log(program.withIndex ? 'Use an index' : 'Don\'t use an index') |
|
||||||
console.log(program.inMemory ? 'Use an in-memory datastore' : 'Use a persistent datastore') |
|
||||||
console.log('----------------------------') |
|
||||||
|
|
||||||
const d = new Datastore({ |
|
||||||
filename: benchDb, |
|
||||||
inMemoryOnly: program.inMemory |
|
||||||
}) |
|
||||||
|
|
||||||
return { n, d, program } |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Ensure the workspace stat and the db datafile is empty |
|
||||||
*/ |
|
||||||
module.exports.prepareDb = function (filename, cb) { |
|
||||||
callbackify((filename) => Persistence.ensureParentDirectoryExistsAsync(filename))(filename, function () { |
|
||||||
fs.access(filename, fs.constants.FS_OK, function (err) { |
|
||||||
if (!err) { |
|
||||||
fs.unlink(filename, cb) |
|
||||||
} else { return cb() } |
|
||||||
}) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Return an array with the numbers from 0 to n-1, in a random order |
|
||||||
* Uses Fisher Yates algorithm |
|
||||||
* Useful to get fair tests |
|
||||||
*/ |
|
||||||
function getRandomArray (n) { |
|
||||||
const res = [] |
|
||||||
let i |
|
||||||
let j |
|
||||||
let temp |
|
||||||
|
|
||||||
for (i = 0; i < n; i += 1) { res[i] = i } |
|
||||||
|
|
||||||
for (i = n - 1; i >= 1; i -= 1) { |
|
||||||
j = Math.floor((i + 1) * Math.random()) |
|
||||||
temp = res[i] |
|
||||||
res[i] = res[j] |
|
||||||
res[j] = temp |
|
||||||
} |
|
||||||
|
|
||||||
return res |
|
||||||
} |
|
||||||
module.exports.getRandomArray = getRandomArray |
|
||||||
|
|
||||||
/** |
|
||||||
* Insert a certain number of documents for testing |
|
||||||
*/ |
|
||||||
module.exports.insertDocs = function (d, n, profiler, cb) { |
|
||||||
const order = getRandomArray(n) |
|
||||||
|
|
||||||
profiler.step('Begin inserting ' + n + ' docs') |
|
||||||
|
|
||||||
function runFrom (i) { |
|
||||||
if (i === n) { // Finished
|
|
||||||
const opsPerSecond = Math.floor(1000 * n / profiler.elapsedSinceLastStep()) |
|
||||||
console.log('===== RESULT (insert) ===== ' + opsPerSecond + ' ops/s') |
|
||||||
profiler.step('Finished inserting ' + n + ' docs') |
|
||||||
profiler.insertOpsPerSecond = opsPerSecond |
|
||||||
return cb() |
|
||||||
} |
|
||||||
|
|
||||||
// eslint-disable-next-line n/handle-callback-err
|
|
||||||
d.insert({ docNumber: order[i] }, function (err) { |
|
||||||
executeAsap(function () { |
|
||||||
runFrom(i + 1) |
|
||||||
}) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
runFrom(0) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Find documents with find |
|
||||||
*/ |
|
||||||
module.exports.findDocs = function (d, n, profiler, cb) { |
|
||||||
const order = getRandomArray(n) |
|
||||||
|
|
||||||
profiler.step('Finding ' + n + ' documents') |
|
||||||
|
|
||||||
function runFrom (i) { |
|
||||||
if (i === n) { // Finished
|
|
||||||
console.log('===== RESULT (find) ===== ' + Math.floor(1000 * n / profiler.elapsedSinceLastStep()) + ' ops/s') |
|
||||||
profiler.step('Finished finding ' + n + ' docs') |
|
||||||
return cb() |
|
||||||
} |
|
||||||
|
|
||||||
// eslint-disable-next-line n/handle-callback-err
|
|
||||||
d.find({ docNumber: order[i] }, function (err, docs) { |
|
||||||
if (docs.length !== 1 || docs[0].docNumber !== order[i]) { return cb(new Error('One find didnt work')) } |
|
||||||
executeAsap(function () { |
|
||||||
runFrom(i + 1) |
|
||||||
}) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
runFrom(0) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Find documents with find and the $in operator |
|
||||||
*/ |
|
||||||
module.exports.findDocsWithIn = function (d, n, profiler, cb) { |
|
||||||
const ins = [] |
|
||||||
const arraySize = Math.min(10, n) |
|
||||||
|
|
||||||
// Preparing all the $in arrays, will take some time
|
|
||||||
for (let i = 0; i < n; i += 1) { |
|
||||||
ins[i] = [] |
|
||||||
|
|
||||||
for (let j = 0; j < arraySize; j += 1) { |
|
||||||
ins[i].push((i + j) % n) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
profiler.step('Finding ' + n + ' documents WITH $IN OPERATOR') |
|
||||||
|
|
||||||
function runFrom (i) { |
|
||||||
if (i === n) { // Finished
|
|
||||||
console.log('===== RESULT (find with in selector) ===== ' + Math.floor(1000 * n / profiler.elapsedSinceLastStep()) + ' ops/s') |
|
||||||
profiler.step('Finished finding ' + n + ' docs') |
|
||||||
return cb() |
|
||||||
} |
|
||||||
|
|
||||||
// eslint-disable-next-line n/handle-callback-err
|
|
||||||
d.find({ docNumber: { $in: ins[i] } }, function (err, docs) { |
|
||||||
if (docs.length !== arraySize) { return cb(new Error('One find didnt work')) } |
|
||||||
executeAsap(function () { |
|
||||||
runFrom(i + 1) |
|
||||||
}) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
runFrom(0) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Find documents with findOne |
|
||||||
*/ |
|
||||||
module.exports.findOneDocs = function (d, n, profiler, cb) { |
|
||||||
const order = getRandomArray(n) |
|
||||||
|
|
||||||
profiler.step('FindingOne ' + n + ' documents') |
|
||||||
|
|
||||||
function runFrom (i) { |
|
||||||
if (i === n) { // Finished
|
|
||||||
console.log('===== RESULT (findOne) ===== ' + Math.floor(1000 * n / profiler.elapsedSinceLastStep()) + ' ops/s') |
|
||||||
profiler.step('Finished finding ' + n + ' docs') |
|
||||||
return cb() |
|
||||||
} |
|
||||||
|
|
||||||
// eslint-disable-next-line n/handle-callback-err
|
|
||||||
d.findOne({ docNumber: order[i] }, function (err, doc) { |
|
||||||
if (!doc || doc.docNumber !== order[i]) { return cb(new Error('One find didnt work')) } |
|
||||||
executeAsap(function () { |
|
||||||
runFrom(i + 1) |
|
||||||
}) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
runFrom(0) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Update documents |
|
||||||
* options is the same as the options object for update |
|
||||||
*/ |
|
||||||
module.exports.updateDocs = function (options, d, n, profiler, cb) { |
|
||||||
const order = getRandomArray(n) |
|
||||||
|
|
||||||
profiler.step('Updating ' + n + ' documents') |
|
||||||
|
|
||||||
function runFrom (i) { |
|
||||||
if (i === n) { // Finished
|
|
||||||
console.log('===== RESULT (update) ===== ' + Math.floor(1000 * n / profiler.elapsedSinceLastStep()) + ' ops/s') |
|
||||||
profiler.step('Finished updating ' + n + ' docs') |
|
||||||
return cb() |
|
||||||
} |
|
||||||
|
|
||||||
// Will not actually modify the document but will take the same time
|
|
||||||
d.update({ docNumber: order[i] }, { docNumber: order[i] }, options, function (err, nr) { |
|
||||||
if (err) { return cb(err) } |
|
||||||
if (nr !== 1) { return cb(new Error('One update didnt work')) } |
|
||||||
executeAsap(function () { |
|
||||||
runFrom(i + 1) |
|
||||||
}) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
runFrom(0) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Remove documents |
|
||||||
* options is the same as the options object for update |
|
||||||
*/ |
|
||||||
module.exports.removeDocs = function (options, d, n, profiler, cb) { |
|
||||||
const order = getRandomArray(n) |
|
||||||
|
|
||||||
profiler.step('Removing ' + n + ' documents') |
|
||||||
|
|
||||||
function runFrom (i) { |
|
||||||
if (i === n) { // Finished
|
|
||||||
// opsPerSecond corresponds to 1 insert + 1 remove, needed to keep collection size at 10,000
|
|
||||||
// We need to subtract the time taken by one insert to get the time actually taken by one remove
|
|
||||||
const opsPerSecond = Math.floor(1000 * n / profiler.elapsedSinceLastStep()) |
|
||||||
const removeOpsPerSecond = Math.floor(1 / ((1 / opsPerSecond) - (1 / profiler.insertOpsPerSecond))) |
|
||||||
console.log('===== RESULT (remove) ===== ' + removeOpsPerSecond + ' ops/s') |
|
||||||
profiler.step('Finished removing ' + n + ' docs') |
|
||||||
return cb() |
|
||||||
} |
|
||||||
|
|
||||||
d.remove({ docNumber: order[i] }, options, function (err, nr) { |
|
||||||
if (err) { return cb(err) } |
|
||||||
if (nr !== 1) { return cb(new Error('One remove didnt work')) } |
|
||||||
// eslint-disable-next-line n/handle-callback-err
|
|
||||||
d.insert({ docNumber: order[i] }, function (err) { // We need to reinsert the doc so that we keep the collection's size at n
|
|
||||||
// So actually we're calculating the average time taken by one insert + one remove
|
|
||||||
executeAsap(function () { |
|
||||||
runFrom(i + 1) |
|
||||||
}) |
|
||||||
}) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
runFrom(0) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Load database |
|
||||||
*/ |
|
||||||
module.exports.loadDatabase = function (d, n, profiler, cb) { |
|
||||||
profiler.step('Loading the database ' + n + ' times') |
|
||||||
|
|
||||||
function runFrom (i) { |
|
||||||
if (i === n) { // Finished
|
|
||||||
console.log('===== RESULT ===== ' + Math.floor(1000 * n / profiler.elapsedSinceLastStep()) + ' ops/s') |
|
||||||
profiler.step('Finished loading a database' + n + ' times') |
|
||||||
return cb() |
|
||||||
} |
|
||||||
|
|
||||||
// eslint-disable-next-line n/handle-callback-err
|
|
||||||
d.loadDatabase(function (err) { |
|
||||||
executeAsap(function () { |
|
||||||
runFrom(i + 1) |
|
||||||
}) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
runFrom(0) |
|
||||||
} |
|
@ -1,50 +0,0 @@ |
|||||||
const program = require('commander') |
|
||||||
const { apply, waterfall } = require('../test/utils.test.js') |
|
||||||
const Datastore = require('../lib/datastore') |
|
||||||
const commonUtilities = require('./commonUtilities') |
|
||||||
const Profiler = require('./profiler') |
|
||||||
|
|
||||||
const profiler = new Profiler('INSERT BENCH') |
|
||||||
const benchDb = 'workspace/insert.bench.db' |
|
||||||
const d = new Datastore(benchDb) |
|
||||||
|
|
||||||
program |
|
||||||
.option('-n --number [number]', 'Size of the collection to test on', parseInt) |
|
||||||
.option('-i --with-index', 'Test with an index') |
|
||||||
.parse(process.argv) |
|
||||||
|
|
||||||
const n = program.number || 10000 |
|
||||||
|
|
||||||
console.log('----------------------------') |
|
||||||
console.log('Test with ' + n + ' documents') |
|
||||||
console.log('----------------------------') |
|
||||||
|
|
||||||
waterfall([ |
|
||||||
apply(commonUtilities.prepareDb, benchDb), |
|
||||||
function (cb) { |
|
||||||
d.loadDatabase(function (err) { |
|
||||||
if (err) { return cb(err) } |
|
||||||
cb() |
|
||||||
}) |
|
||||||
}, |
|
||||||
function (cb) { profiler.beginProfiling(); return cb() }, |
|
||||||
apply(commonUtilities.insertDocs, d, n, profiler), |
|
||||||
function (cb) { |
|
||||||
let i |
|
||||||
|
|
||||||
profiler.step('Begin calling ensureIndex ' + n + ' times') |
|
||||||
|
|
||||||
for (i = 0; i < n; i += 1) { |
|
||||||
d.ensureIndex({ fieldName: 'docNumber' }) |
|
||||||
delete d.indexes.docNumber |
|
||||||
} |
|
||||||
|
|
||||||
console.log('Average time for one ensureIndex: ' + (profiler.elapsedSinceLastStep() / n) + 'ms') |
|
||||||
profiler.step('Finished calling ensureIndex ' + n + ' times') |
|
||||||
cb() |
|
||||||
} |
|
||||||
], function (err) { |
|
||||||
profiler.step('Benchmark finished') |
|
||||||
|
|
||||||
if (err) { return console.log('An error was encountered: ', err) } |
|
||||||
}) |
|
@ -1,27 +0,0 @@ |
|||||||
const { apply, waterfall } = require('../test/utils.test.js') |
|
||||||
const commonUtilities = require('./commonUtilities') |
|
||||||
const Profiler = require('./profiler') |
|
||||||
|
|
||||||
const profiler = new Profiler('FIND BENCH') |
|
||||||
const benchDb = 'workspace/find.bench.db' |
|
||||||
const config = commonUtilities.getConfiguration(benchDb) |
|
||||||
const d = config.d |
|
||||||
const n = config.n |
|
||||||
|
|
||||||
waterfall([ |
|
||||||
apply(commonUtilities.prepareDb, benchDb), |
|
||||||
function (cb) { |
|
||||||
d.loadDatabase(function (err) { |
|
||||||
if (err) { return cb(err) } |
|
||||||
if (config.program.withIndex) { d.ensureIndex({ fieldName: 'docNumber' }) } |
|
||||||
cb() |
|
||||||
}) |
|
||||||
}, |
|
||||||
function (cb) { profiler.beginProfiling(); return cb() }, |
|
||||||
apply(commonUtilities.insertDocs, d, n, profiler), |
|
||||||
apply(commonUtilities.findDocs, d, n, profiler) |
|
||||||
], function (err) { |
|
||||||
profiler.step('Benchmark finished') |
|
||||||
|
|
||||||
if (err) { return console.log('An error was encountered: ', err) } |
|
||||||
}) |
|
@ -1,28 +0,0 @@ |
|||||||
const { apply, waterfall } = require('../test/utils.test.js') |
|
||||||
const commonUtilities = require('./commonUtilities') |
|
||||||
const Profiler = require('./profiler') |
|
||||||
|
|
||||||
const benchDb = 'workspace/findOne.bench.db' |
|
||||||
const profiler = new Profiler('FINDONE BENCH') |
|
||||||
const config = commonUtilities.getConfiguration(benchDb) |
|
||||||
const d = config.d |
|
||||||
const n = config.n |
|
||||||
|
|
||||||
waterfall([ |
|
||||||
apply(commonUtilities.prepareDb, benchDb), |
|
||||||
function (cb) { |
|
||||||
d.loadDatabase(function (err) { |
|
||||||
if (err) { return cb(err) } |
|
||||||
if (config.program.withIndex) { d.ensureIndex({ fieldName: 'docNumber' }) } |
|
||||||
cb() |
|
||||||
}) |
|
||||||
}, |
|
||||||
function (cb) { profiler.beginProfiling(); return cb() }, |
|
||||||
apply(commonUtilities.insertDocs, d, n, profiler), |
|
||||||
function (cb) { setTimeout(function () { cb() }, 500) }, |
|
||||||
apply(commonUtilities.findOneDocs, d, n, profiler) |
|
||||||
], function (err) { |
|
||||||
profiler.step('Benchmark finished') |
|
||||||
|
|
||||||
if (err) { return console.log('An error was encountered: ', err) } |
|
||||||
}) |
|
@ -1,27 +0,0 @@ |
|||||||
const { apply, waterfall } = require('../test/utils.test.js') |
|
||||||
const commonUtilities = require('./commonUtilities') |
|
||||||
const Profiler = require('./profiler') |
|
||||||
|
|
||||||
const benchDb = 'workspace/find.bench.db' |
|
||||||
const profiler = new Profiler('FIND BENCH') |
|
||||||
const config = commonUtilities.getConfiguration(benchDb) |
|
||||||
const d = config.d |
|
||||||
const n = config.n |
|
||||||
|
|
||||||
waterfall([ |
|
||||||
apply(commonUtilities.prepareDb, benchDb), |
|
||||||
function (cb) { |
|
||||||
d.loadDatabase(function (err) { |
|
||||||
if (err) { return cb(err) } |
|
||||||
if (config.program.withIndex) { d.ensureIndex({ fieldName: 'docNumber' }) } |
|
||||||
cb() |
|
||||||
}) |
|
||||||
}, |
|
||||||
function (cb) { profiler.beginProfiling(); return cb() }, |
|
||||||
apply(commonUtilities.insertDocs, d, n, profiler), |
|
||||||
apply(commonUtilities.findDocsWithIn, d, n, profiler) |
|
||||||
], function (err) { |
|
||||||
profiler.step('Benchmark finished') |
|
||||||
|
|
||||||
if (err) { return console.log('An error was encountered: ', err) } |
|
||||||
}) |
|
@ -1,32 +0,0 @@ |
|||||||
const { apply, waterfall } = require('../test/utils.test.js') |
|
||||||
const commonUtilities = require('./commonUtilities') |
|
||||||
const Profiler = require('./profiler') |
|
||||||
|
|
||||||
const benchDb = 'workspace/insert.bench.db' |
|
||||||
const profiler = new Profiler('INSERT BENCH') |
|
||||||
const config = commonUtilities.getConfiguration(benchDb) |
|
||||||
const d = config.d |
|
||||||
let n = config.n |
|
||||||
|
|
||||||
waterfall([ |
|
||||||
apply(commonUtilities.prepareDb, benchDb), |
|
||||||
function (cb) { |
|
||||||
d.loadDatabase(function (err) { |
|
||||||
if (err) { return cb(err) } |
|
||||||
if (config.program.withIndex) { |
|
||||||
d.ensureIndex({ fieldName: 'docNumber' }) |
|
||||||
n = 2 * n // We will actually insert twice as many documents
|
|
||||||
// because the index is slower when the collection is already
|
|
||||||
// big. So the result given by the algorithm will be a bit worse than
|
|
||||||
// actual performance
|
|
||||||
} |
|
||||||
cb() |
|
||||||
}) |
|
||||||
}, |
|
||||||
function (cb) { profiler.beginProfiling(); return cb() }, |
|
||||||
apply(commonUtilities.insertDocs, d, n, profiler) |
|
||||||
], function (err) { |
|
||||||
profiler.step('Benchmark finished') |
|
||||||
|
|
||||||
if (err) { return console.log('An error was encountered: ', err) } |
|
||||||
}) |
|
@ -1,35 +0,0 @@ |
|||||||
const { apply, waterfall } = require('../test/utils.test.js') |
|
||||||
const program = require('commander') |
|
||||||
const Datastore = require('../lib/datastore') |
|
||||||
const commonUtilities = require('./commonUtilities') |
|
||||||
const Profiler = require('./profiler') |
|
||||||
|
|
||||||
const benchDb = 'workspace/loaddb.bench.db' |
|
||||||
const profiler = new Profiler('LOADDB BENCH') |
|
||||||
const d = new Datastore(benchDb) |
|
||||||
|
|
||||||
program |
|
||||||
.option('-n --number [number]', 'Size of the collection to test on', parseInt) |
|
||||||
.option('-i --with-index', 'Test with an index') |
|
||||||
.parse(process.argv) |
|
||||||
|
|
||||||
const n = program.number || 10000 |
|
||||||
|
|
||||||
console.log('----------------------------') |
|
||||||
console.log('Test with ' + n + ' documents') |
|
||||||
console.log(program.withIndex ? 'Use an index' : "Don't use an index") |
|
||||||
console.log('----------------------------') |
|
||||||
|
|
||||||
waterfall([ |
|
||||||
apply(commonUtilities.prepareDb, benchDb), |
|
||||||
function (cb) { |
|
||||||
d.loadDatabase(cb) |
|
||||||
}, |
|
||||||
function (cb) { profiler.beginProfiling(); return cb() }, |
|
||||||
apply(commonUtilities.insertDocs, d, n, profiler), |
|
||||||
apply(commonUtilities.loadDatabase, d, n, profiler) |
|
||||||
], function (err) { |
|
||||||
profiler.step('Benchmark finished') |
|
||||||
|
|
||||||
if (err) { return console.log('An error was encountered: ', err) } |
|
||||||
}) |
|
@ -1,92 +0,0 @@ |
|||||||
const util = require('util') |
|
||||||
|
|
||||||
function formatTime (time, precision) { |
|
||||||
// If we're dealing with ms, round up to seconds when time is at least 1 second
|
|
||||||
if (time > 1000 && precision === 'ms') { |
|
||||||
return (Math.floor(time / 100) / 10) + ' s' |
|
||||||
} else { |
|
||||||
return time.toFixed(3) + ' ' + precision |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// get time in ns
|
|
||||||
function getTime () { |
|
||||||
const t = process.hrtime() |
|
||||||
return (t[0] * 1e9 + t[1]) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Create a profiler with name testName to monitor the execution time of a route |
|
||||||
* The profiler has two arguments: a step msg and an optional reset for the internal timer |
|
||||||
* It will display the execution time per step and total from latest rest |
|
||||||
* |
|
||||||
* Optional logToConsole flag, which defaults to true, causes steps to be printed to console. |
|
||||||
* otherwise, they can be accessed from Profiler.steps array. |
|
||||||
*/ |
|
||||||
function Profiler (name, logToConsole, precision) { |
|
||||||
this.name = name |
|
||||||
this.steps = [] |
|
||||||
this.sinceBeginning = null |
|
||||||
this.lastStep = null |
|
||||||
this.logToConsole = typeof (logToConsole) === 'undefined' ? true : logToConsole |
|
||||||
this.precision = typeof (precision) === 'undefined' ? 'ms' : precision |
|
||||||
this.divisor = 1 |
|
||||||
|
|
||||||
if (this.precision === 'ms') this.divisor = 1e6 |
|
||||||
} |
|
||||||
|
|
||||||
Profiler.prototype.beginProfiling = function () { |
|
||||||
if (this.logToConsole) { console.log(this.name + ' - Begin profiling') } |
|
||||||
this.resetTimers() |
|
||||||
} |
|
||||||
|
|
||||||
Profiler.prototype.resetTimers = function () { |
|
||||||
this.sinceBeginning = getTime() |
|
||||||
this.lastStep = getTime() |
|
||||||
this.steps.push(['BEGIN_TIMER', this.lastStep]) |
|
||||||
} |
|
||||||
|
|
||||||
Profiler.prototype.elapsedSinceBeginning = function () { |
|
||||||
return (getTime() - this.sinceBeginning) / this.divisor |
|
||||||
} |
|
||||||
|
|
||||||
Profiler.prototype.elapsedSinceLastStep = function () { |
|
||||||
return (getTime() - this.lastStep) / this.divisor |
|
||||||
} |
|
||||||
|
|
||||||
// Return the deltas between steps, in nanoseconds
|
|
||||||
|
|
||||||
Profiler.prototype.getSteps = function () { |
|
||||||
const divisor = this.divisor |
|
||||||
|
|
||||||
return this.steps.map(function (curr, index, arr) { |
|
||||||
if (index === 0) return undefined |
|
||||||
const delta = (curr[1] - arr[index - 1][1]) |
|
||||||
return [curr[0], (delta / divisor)] |
|
||||||
}).slice(1) |
|
||||||
} |
|
||||||
|
|
||||||
Profiler.prototype.step = function (msg) { |
|
||||||
if (!this.sinceBeginning || !this.lastStep) { |
|
||||||
console.log(util.format( |
|
||||||
'%s - %s - You must call beginProfiling before registering steps', |
|
||||||
this.name, |
|
||||||
msg |
|
||||||
)) |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
if (this.logToConsole) { |
|
||||||
console.log(util.format('%s - %s - %s (total: %s)', |
|
||||||
this.name, |
|
||||||
msg, |
|
||||||
formatTime(this.elapsedSinceLastStep(), this.precision), |
|
||||||
formatTime(this.elapsedSinceBeginning(), this.precision) |
|
||||||
)) |
|
||||||
} |
|
||||||
|
|
||||||
this.lastStep = getTime() |
|
||||||
this.steps.push([msg, this.lastStep]) |
|
||||||
} |
|
||||||
|
|
||||||
module.exports = Profiler |
|
@ -1,35 +0,0 @@ |
|||||||
const { apply, waterfall } = require('../test/utils.test.js') |
|
||||||
const commonUtilities = require('./commonUtilities') |
|
||||||
const Profiler = require('./profiler') |
|
||||||
|
|
||||||
const benchDb = 'workspace/remove.bench.db' |
|
||||||
const profiler = new Profiler('REMOVE BENCH') |
|
||||||
const config = commonUtilities.getConfiguration(benchDb) |
|
||||||
const d = config.d |
|
||||||
const n = config.n |
|
||||||
|
|
||||||
waterfall([ |
|
||||||
apply(commonUtilities.prepareDb, benchDb), |
|
||||||
function (cb) { |
|
||||||
d.loadDatabase(function (err) { |
|
||||||
if (err) { return cb(err) } |
|
||||||
if (config.program.withIndex) { d.ensureIndex({ fieldName: 'docNumber' }) } |
|
||||||
cb() |
|
||||||
}) |
|
||||||
}, |
|
||||||
function (cb) { profiler.beginProfiling(); return cb() }, |
|
||||||
apply(commonUtilities.insertDocs, d, n, profiler), |
|
||||||
|
|
||||||
// Test with remove only one document
|
|
||||||
function (cb) { profiler.step('MULTI: FALSE'); return cb() }, |
|
||||||
apply(commonUtilities.removeDocs, { multi: false }, d, n, profiler), |
|
||||||
// Test with multiple documents
|
|
||||||
function (cb) { d.remove({}, { multi: true }, function () { return cb() }) }, |
|
||||||
apply(commonUtilities.insertDocs, d, n, profiler), |
|
||||||
function (cb) { profiler.step('MULTI: TRUE'); return cb() }, |
|
||||||
apply(commonUtilities.removeDocs, { multi: true }, d, n, profiler) |
|
||||||
], function (err) { |
|
||||||
profiler.step('Benchmark finished') |
|
||||||
|
|
||||||
if (err) { return console.log('An error was encountered: ', err) } |
|
||||||
}) |
|
@ -1,37 +0,0 @@ |
|||||||
const { apply, waterfall } = require('../test/utils.test.js') |
|
||||||
const commonUtilities = require('./commonUtilities') |
|
||||||
const Profiler = require('./profiler') |
|
||||||
|
|
||||||
const benchDb = 'workspace/update.bench.db' |
|
||||||
const profiler = new Profiler('UPDATE BENCH') |
|
||||||
const config = commonUtilities.getConfiguration(benchDb) |
|
||||||
const d = config.d |
|
||||||
const n = config.n |
|
||||||
|
|
||||||
waterfall([ |
|
||||||
apply(commonUtilities.prepareDb, benchDb), |
|
||||||
function (cb) { |
|
||||||
d.loadDatabase(function (err) { |
|
||||||
if (err) { return cb(err) } |
|
||||||
if (config.program.withIndex) { d.ensureIndex({ fieldName: 'docNumber' }) } |
|
||||||
cb() |
|
||||||
}) |
|
||||||
}, |
|
||||||
function (cb) { profiler.beginProfiling(); return cb() }, |
|
||||||
apply(commonUtilities.insertDocs, d, n, profiler), |
|
||||||
|
|
||||||
// Test with update only one document
|
|
||||||
function (cb) { profiler.step('MULTI: FALSE'); return cb() }, |
|
||||||
apply(commonUtilities.updateDocs, { multi: false }, d, n, profiler), |
|
||||||
|
|
||||||
// Test with multiple documents
|
|
||||||
// eslint-disable-next-line n/handle-callback-err
|
|
||||||
function (cb) { d.remove({}, { multi: true }, function (err) { return cb() }) }, |
|
||||||
apply(commonUtilities.insertDocs, d, n, profiler), |
|
||||||
function (cb) { profiler.step('MULTI: TRUE'); return cb() }, |
|
||||||
apply(commonUtilities.updateDocs, { multi: true }, d, n, profiler) |
|
||||||
], function (err) { |
|
||||||
profiler.step('Benchmark finished') |
|
||||||
|
|
||||||
if (err) { return console.log('An error was encountered: ', err) } |
|
||||||
}) |
|
@ -0,0 +1,292 @@ |
|||||||
|
import { promisify } from 'util' |
||||||
|
import { performance } from 'node:perf_hooks' |
||||||
|
import Persistence from '../src/persistence.js' |
||||||
|
import { access, unlink, constants } from 'fs/promises' |
||||||
|
import Datastore from '../src/datastore.js' |
||||||
|
|
||||||
|
const getRandomArray = n => { |
||||||
|
const res = [] |
||||||
|
let i |
||||||
|
let j |
||||||
|
let temp |
||||||
|
|
||||||
|
for (i = 0; i < n; i += 1) { res[i] = i } |
||||||
|
|
||||||
|
for (i = n - 1; i >= 1; i -= 1) { |
||||||
|
j = Math.floor((i + 1) * Math.random()) |
||||||
|
temp = res[i] |
||||||
|
res[i] = res[j] |
||||||
|
res[j] = temp |
||||||
|
} |
||||||
|
|
||||||
|
return res |
||||||
|
} |
||||||
|
|
||||||
|
const insertDocs = async (d, n) => { |
||||||
|
const order = getRandomArray(n) |
||||||
|
|
||||||
|
for (let i = 0; i <= n; i++) { |
||||||
|
await d.insertAsync({ docNumber: order[i] }) |
||||||
|
await promisify(process.nextTick)() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const measure = async (name, n, cb) => { |
||||||
|
performance.mark(`${name} start`) |
||||||
|
|
||||||
|
for (let i = 0; i < n; i += 1) { |
||||||
|
await cb(i) |
||||||
|
} |
||||||
|
|
||||||
|
performance.mark(`${name} end`) |
||||||
|
|
||||||
|
const { duration } = performance.measure(`${name} average duration for ${n} operations`, `${name} start`, `${name} end`) |
||||||
|
const opsPerSecond = Math.floor(1000 * n / duration) |
||||||
|
|
||||||
|
console.log(`===== RESULT (${name}) ===== ${opsPerSecond} ops/s ===== ${Math.floor(duration / n * 1000) / 1000}ms/execution`) |
||||||
|
} |
||||||
|
|
||||||
|
describe('Performance tests without index', function () { |
||||||
|
const n = 1000 |
||||||
|
const benchDb = 'workspace/bench.db' |
||||||
|
let d |
||||||
|
|
||||||
|
before(function () { |
||||||
|
if (!process.argv.includes('--benchmark')) this.skip() |
||||||
|
else this.timeout(60000) |
||||||
|
}) |
||||||
|
|
||||||
|
beforeEach('Prepare database', async () => { |
||||||
|
try { |
||||||
|
await access(benchDb, constants.F_OK) |
||||||
|
await unlink(benchDb) |
||||||
|
} catch {} |
||||||
|
await Persistence.ensureParentDirectoryExistsAsync(benchDb) |
||||||
|
d = new Datastore({ filename: benchDb }) |
||||||
|
await d.loadDatabaseAsync() |
||||||
|
}) |
||||||
|
|
||||||
|
afterEach('Clean database', async () => { |
||||||
|
try { |
||||||
|
await d.this.executor.queue.guardian |
||||||
|
d = null |
||||||
|
await access(benchDb, constants.F_OK) |
||||||
|
await unlink(benchDb) |
||||||
|
} catch {} |
||||||
|
}) |
||||||
|
|
||||||
|
it('ensureIndex', async () => { |
||||||
|
await insertDocs(d, n) |
||||||
|
await measure('ensureIndex', n, async (i) => { |
||||||
|
d.ensureIndexAsync({ fieldName: 'docNumber' }) |
||||||
|
delete d.indexes.docNumber |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('find', async () => { |
||||||
|
await insertDocs(d, n) |
||||||
|
const order = getRandomArray(n) |
||||||
|
await measure('find', n, async (i) => { |
||||||
|
const docs = await d.findAsync({ docNumber: order[i] }) |
||||||
|
if (docs.length !== 1 || docs[0].docNumber !== order[i]) throw new Error('One find didnt work') |
||||||
|
await promisify(process.nextTick)() |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('findOne', async () => { |
||||||
|
await insertDocs(d, n) |
||||||
|
const order = getRandomArray(n) |
||||||
|
await measure('findOne', n, async (i) => { |
||||||
|
const doc = await d.findOneAsync({ docNumber: order[i] }) |
||||||
|
if (doc == null || doc.docNumber !== order[i]) throw new Error('One find didnt work') |
||||||
|
await promisify(process.nextTick)() |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('find with $in', async () => { |
||||||
|
await insertDocs(d, n) |
||||||
|
|
||||||
|
const ins = [] |
||||||
|
const arraySize = Math.min(10, n) |
||||||
|
|
||||||
|
// Preparing all the $in arrays, will take some time
|
||||||
|
for (let i = 0; i < n; i += 1) { |
||||||
|
ins[i] = [] |
||||||
|
|
||||||
|
for (let j = 0; j < arraySize; j += 1) { |
||||||
|
ins[i].push((i + j) % n) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
await measure('find with $in', n, async (i) => { |
||||||
|
const docs = await d.findAsync({ docNumber: { $in: ins[i] } }) |
||||||
|
if (docs.length !== arraySize) throw new Error('One find didnt work') |
||||||
|
await promisify(process.nextTick)() |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('insert docs', async () => { |
||||||
|
const order = getRandomArray(n) |
||||||
|
await measure('insert docs', n, async (i) => { |
||||||
|
await d.insertAsync({ docNumber: order[i] }) |
||||||
|
await promisify(process.nextTick)() |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('load database', async () => { |
||||||
|
await insertDocs(d, n) |
||||||
|
await measure('load database', n, async (i) => { |
||||||
|
await d.loadDatabaseAsync() |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('Insert and remove one', async () => { |
||||||
|
// TODO measure only remove
|
||||||
|
await insertDocs(d, n) |
||||||
|
const order = getRandomArray(n) |
||||||
|
await measure('insert and remove one', n, async (i) => { |
||||||
|
const nr = await d.removeAsync({ docNumber: order[i] }, { multi: false }) |
||||||
|
if (nr !== 1) throw new Error('One remove didnt work') |
||||||
|
await d.insertAsync({ docNumber: order[i] }) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('Insert and remove one with multi option', async () => { |
||||||
|
// TODO measure only remove
|
||||||
|
await insertDocs(d, n) |
||||||
|
const order = getRandomArray(n) |
||||||
|
await measure('insert and remove one with multi option', n, async (i) => { |
||||||
|
const nr = await d.removeAsync({ docNumber: order[i] }, { multi: true }) |
||||||
|
if (nr !== 1) throw new Error('One remove didnt work') |
||||||
|
await d.insertAsync({ docNumber: order[i] }) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('Update', async () => { |
||||||
|
await insertDocs(d, n) |
||||||
|
const order = getRandomArray(n) |
||||||
|
await measure('insert and remove one with multi option', n, async (i) => { |
||||||
|
const { numAffected } = await d.updateAsync({ docNumber: order[i] }, { docNumber: order[i] }, { multi: false }) |
||||||
|
if (numAffected !== 1) throw new Error('One update didnt work') |
||||||
|
await d.insertAsync({ docNumber: order[i] }) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('Update with multi option', async () => { |
||||||
|
await insertDocs(d, n) |
||||||
|
const order = getRandomArray(n) |
||||||
|
await measure('insert and remove one with multi option', n, async (i) => { |
||||||
|
const { numAffected } = await d.updateAsync({ docNumber: order[i] }, { docNumber: order[i] }, { multi: true }) |
||||||
|
if (numAffected !== 1) throw new Error('One update didnt work') |
||||||
|
await d.insertAsync({ docNumber: order[i] }) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('find with index', async () => { |
||||||
|
await d.ensureIndexAsync({ fieldName: 'docNumber' }) |
||||||
|
await insertDocs(d, n) |
||||||
|
const order = getRandomArray(n) |
||||||
|
await measure('find with index', n, async (i) => { |
||||||
|
const docs = await d.findAsync({ docNumber: order[i] }) |
||||||
|
if (docs.length !== 1 || docs[0].docNumber !== order[i]) throw new Error('One find didnt work') |
||||||
|
await promisify(process.nextTick)() |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('findOne with index', async () => { |
||||||
|
await d.ensureIndexAsync({ fieldName: 'docNumber' }) |
||||||
|
await insertDocs(d, n) |
||||||
|
const order = getRandomArray(n) |
||||||
|
await measure('findOne with index', n, async (i) => { |
||||||
|
const doc = await d.findOneAsync({ docNumber: order[i] }) |
||||||
|
if (doc == null || doc.docNumber !== order[i]) throw new Error('One find didnt work') |
||||||
|
await promisify(process.nextTick)() |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('find with $in with index', async () => { |
||||||
|
await d.ensureIndexAsync({ fieldName: 'docNumber' }) |
||||||
|
await insertDocs(d, n) |
||||||
|
|
||||||
|
const ins = [] |
||||||
|
const arraySize = Math.min(10, n) |
||||||
|
|
||||||
|
// Preparing all the $in arrays, will take some time
|
||||||
|
for (let i = 0; i < n; i += 1) { |
||||||
|
ins[i] = [] |
||||||
|
|
||||||
|
for (let j = 0; j < arraySize; j += 1) { |
||||||
|
ins[i].push((i + j) % n) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
await measure('find with $in with index', n, async (i) => { |
||||||
|
const docs = await d.findAsync({ docNumber: { $in: ins[i] } }) |
||||||
|
if (docs.length !== arraySize) throw new Error('One find didnt work') |
||||||
|
await promisify(process.nextTick)() |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('insert docs with index', async () => { |
||||||
|
await d.ensureIndexAsync({ fieldName: 'docNumber' }) |
||||||
|
const order = getRandomArray(n) |
||||||
|
await measure('insert docs with index', n, async (i) => { |
||||||
|
await d.insertAsync({ docNumber: order[i] }) |
||||||
|
await promisify(process.nextTick)() |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('load database with index', async () => { |
||||||
|
await d.ensureIndexAsync({ fieldName: 'docNumber' }) |
||||||
|
await insertDocs(d, n) |
||||||
|
await measure('load database with index', n, async (i) => { |
||||||
|
await d.loadDatabaseAsync() |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('Insert and remove one with index', async () => { |
||||||
|
// TODO measure only remove
|
||||||
|
await d.ensureIndexAsync({ fieldName: 'docNumber' }) |
||||||
|
await insertDocs(d, n) |
||||||
|
const order = getRandomArray(n) |
||||||
|
await measure('insert and remove one with index', n, async (i) => { |
||||||
|
const nr = await d.removeAsync({ docNumber: order[i] }, { multi: false }) |
||||||
|
if (nr !== 1) throw new Error('One remove didnt work') |
||||||
|
await d.insertAsync({ docNumber: order[i] }) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('Insert and remove one with multi option with index', async () => { |
||||||
|
// TODO measure only remove
|
||||||
|
await d.ensureIndexAsync({ fieldName: 'docNumber' }) |
||||||
|
await insertDocs(d, n) |
||||||
|
const order = getRandomArray(n) |
||||||
|
await measure('insert and remove one with multi option with index', n, async (i) => { |
||||||
|
const nr = await d.removeAsync({ docNumber: order[i] }, { multi: true }) |
||||||
|
if (nr !== 1) throw new Error('One remove didnt work') |
||||||
|
await d.insertAsync({ docNumber: order[i] }) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('Update with index', async () => { |
||||||
|
await d.ensureIndexAsync({ fieldName: 'docNumber' }) |
||||||
|
await insertDocs(d, n) |
||||||
|
const order = getRandomArray(n) |
||||||
|
await measure('insert and remove one with multi option with index', n, async (i) => { |
||||||
|
const { numAffected } = await d.updateAsync({ docNumber: order[i] }, { docNumber: order[i] }, { multi: false }) |
||||||
|
if (numAffected !== 1) throw new Error('One update didnt work') |
||||||
|
await d.insertAsync({ docNumber: order[i] }) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('Update with multi option with index', async () => { |
||||||
|
await d.ensureIndexAsync({ fieldName: 'docNumber' }) |
||||||
|
await insertDocs(d, n) |
||||||
|
const order = getRandomArray(n) |
||||||
|
await measure('insert and remove one with multi option with index', n, async (i) => { |
||||||
|
const { numAffected } = await d.updateAsync({ docNumber: order[i] }, { docNumber: order[i] }, { multi: true }) |
||||||
|
if (numAffected !== 1) throw new Error('One update didnt work') |
||||||
|
await d.insertAsync({ docNumber: order[i] }) |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
Loading…
Reference in new issue