/ * *
* Functions that are used in several benchmark tests
* /
var customUtils = require ( '../lib/customUtils' )
, fs = require ( 'fs' )
, path = require ( 'path' )
, Datastore = require ( '../lib/datastore' )
, Persistence = require ( '../lib/persistence' )
, executeAsap // process.nextTick or setImmediate depending on your Node version
;
try {
executeAsap = setImmediate ;
} catch ( e ) {
executeAsap = process . nextTick ;
}
/ * *
* Configure the benchmark
* /
module . exports . getConfiguration = function ( benchDb ) {
var d , n
, 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 ) ;
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 ( "----------------------------" ) ;
d = new Datastore ( { filename : benchDb
, inMemoryOnly : program . inMemory
} ) ;
return { n : n , d : d , program : program } ;
} ;
/ * *
* Ensure the workspace exists and the db datafile is empty
* /
module . exports . prepareDb = function ( filename , cb ) {
Persistence . ensureDirectoryExists ( path . dirname ( filename ) , function ( ) {
fs . exists ( filename , function ( exists ) {
if ( exists ) {
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 ) {
var res = [ ]
, i , j , 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 ) {
var beg = new Date ( )
, order = getRandomArray ( n )
;
profiler . step ( 'Begin inserting ' + n + ' docs' ) ;
function runFrom ( i ) {
if ( i === n ) { // Finished
console . log ( "===== RESULT (insert) ===== " + Math . floor ( 1000 * n / profiler . elapsedSinceLastStep ( ) ) + " ops/s" ) ;
profiler . step ( 'Finished inserting ' + n + ' docs' ) ;
return cb ( ) ;
}
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 ) {
var beg = new Date ( )
, order = getRandomArray ( n )
;
profiler . step ( "Finding " + n + " documents" ) ;
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 ( ) ;
}
d . find ( { docNumber : order [ i ] } , function ( err , docs ) {
if ( docs . length !== 1 || docs [ 0 ] . docNumber !== order [ i ] ) { return cb ( '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 ) {
var beg = new Date ( )
, order = getRandomArray ( n )
, ins = [ ] , i , j
, arraySize = Math . min ( 10 , n ) // The array for $in needs to be smaller than n (inclusive)
;
// Preparing all the $in arrays, will take some time
for ( i = 0 ; i < n ; i += 1 ) {
ins [ i ] = [ ] ;
for ( 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 ( ) ;
}
d . find ( { docNumber : { $in : ins [ i ] } } , function ( err , docs ) {
if ( docs . length !== arraySize ) { return cb ( 'One find didnt work' ) ; }
executeAsap ( function ( ) {
runFrom ( i + 1 ) ;
} ) ;
} ) ;
}
runFrom ( 0 ) ;
} ;
/ * *
* Find documents with findOne
* /
module . exports . findOneDocs = function ( d , n , profiler , cb ) {
var beg = new Date ( )
, 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 ( ) ;
}
d . findOne ( { docNumber : order [ i ] } , function ( err , doc ) {
if ( ! doc || doc . docNumber !== order [ i ] ) { return cb ( '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 ) {
var beg = new Date ( )
, 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 ( '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 ) {
var beg = new Date ( )
, order = getRandomArray ( n )
;
profiler . step ( "Removing " + n + " documents" ) ;
function runFrom ( i ) {
if ( i === n ) { // Finished
console . log ( "===== RESULT (1 remove + 1 insert) ===== " + Math . floor ( 1000 * n / profiler . elapsedSinceLastStep ( ) ) + " ops/s" ) ;
console . log ( "====== IMPORTANT: Please note that this is the time that was needed to perform " + n + " removes and " + n + " inserts" ) ;
console . log ( "====== The extra inserts are needed to keep collection size at " + n + " items for the benchmark to make sense" ) ;
console . log ( "====== Use the insert speed logged above to calculate the actual remove speed, which is higher (should be significantly so if you use indexing)" ) ;
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 ( 'One remove didnt work' ) ; }
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 ) {
var beg = new Date ( )
, order = getRandomArray ( n )
;
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 ( ) ;
}
d . loadDatabase ( function ( err ) {
executeAsap ( function ( ) {
runFrom ( i + 1 ) ;
} ) ;
} ) ;
}
runFrom ( 0 ) ;
} ;