timeit.js (5897B)
1 /** 2 * @license Apache-2.0 3 * 4 * Copyright (c) 2018 The Stdlib Authors. 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 'use strict'; 20 21 // MODULES // 22 23 var isString = require( '@stdlib/assert/is-string' ).isPrimitive; 24 var isFunction = require( '@stdlib/assert/is-function' ); 25 var isArray = require( '@stdlib/assert/is-array' ); 26 var copy = require( './../../copy' ); 27 var cwd = require( '@stdlib/process/cwd' ); 28 var nextTick = require( './../../next-tick' ); 29 var defaults = require( './defaults.json' ); 30 var validate = require( './validate.js' ); 31 var evaluate = require( './vm_evaluate.js' ); 32 var transform = require( './transform.js' ); 33 34 35 // VARIABLES // 36 37 var FILENAME = 'timeit.js'; 38 var MIN_TIME = 0.1; // seconds 39 var ITERATIONS = 10; // 10^1 40 var MAX_ITERATIONS = 10000000000; // 10^10 41 42 43 // MAIN // 44 45 /** 46 * Times a snippet. 47 * 48 * @param {string} code - snippet to time 49 * @param {Options} [options] - function options 50 * @param {string} [options.before=""] - setup code 51 * @param {string} [options.after=""] - cleanup code 52 * @param {(PositiveInteger|null)} [options.iterations=1e6] - number of iterations 53 * @param {PositiveInteger} [options.repeats=3] - number of repeats 54 * @param {boolean} [options.asynchronous=false] - boolean indicating whether a snippet is asynchronous 55 * @param {Callback} clbk - callback to invoke upon completion 56 * @throws {TypeError} first argument must be a string 57 * @throws {TypeError} options argument must be an object 58 * @throws {TypeError} must provide valid options 59 * @throws {TypeError} callback argument must be a function 60 * @returns {void} 61 * 62 * @example 63 * var code = ''; 64 * code += 'var x = Math.pow( Math.random(), 3 );'; 65 * code += 'if ( x !== x ) {'; 66 * code += 'throw new Error( \'Something went wrong.\' );'; 67 * code += '}'; 68 * 69 * timeit( code, done ); 70 * 71 * function done( error, results ) { 72 * if ( error ) { 73 * throw error; 74 * } 75 * console.dir( results ); 76 * } 77 */ 78 function timeit( code, options, clbk ) { 79 var results; 80 var opts; 81 var dir; 82 var err; 83 var idx; 84 var cb; 85 86 if ( !isString( code ) ) { 87 throw new TypeError( 'invalid argument. First argument must be a primitive string. Value: `' + code + '`.' ); 88 } 89 opts = copy( defaults ); 90 if ( arguments.length === 2 ) { 91 cb = options; 92 } else { 93 cb = clbk; 94 err = validate( opts, options ); 95 if ( err ) { 96 throw err; 97 } 98 } 99 if ( !isFunction( cb ) ) { 100 throw new TypeError( 'invalid argument. Callback argument must be a function. Value: `' + cb + '`.' ); 101 } 102 results = new Array( opts.repeats ); 103 dir = cwd(); 104 idx = 0; 105 106 // Pretest to check for early returns and/or errors... 107 try { 108 evaluate( code, opts, FILENAME, dir, onTest ); 109 } catch ( error ) { 110 err = new Error( 'evaluation error. Encountered an error when evaluating snippet. '+error.message ); 111 return done( err ); 112 } 113 114 /** 115 * Evaluates a code snippet on the next turn of the event loop. Waiting until the next turn avoids the current turn being bogged down by a long running queue. 116 * 117 * @private 118 * @param {Callback} clbk - callback 119 */ 120 function next( clbk ) { 121 nextTick( onTick ); 122 123 /** 124 * Callback invoked upon next turn of event loop. 125 * 126 * @private 127 */ 128 function onTick() { 129 evaluate( code, opts, FILENAME, dir, clbk ); 130 } 131 } 132 133 /** 134 * Callback invoked after completing pretest. 135 * 136 * @private 137 * @param {(Error|null)} error - error object 138 * @param {NonNegativeIntegerArray} time - results 139 * @returns {void} 140 */ 141 function onTest( error, time ) { 142 if ( error ) { 143 return done( error ); 144 } 145 if ( !isArray( time ) || time.length !== 2 ) { 146 // This should only happen if someone is a bad actor and attempts to call the `done` callback without providing timing results. 147 error = new Error( 'evaluation error. Did not receive timing results.' ); 148 return done( error ); 149 } 150 if ( opts.iterations === null ) { 151 opts.iterations = ITERATIONS; 152 return next( onRun ); 153 } 154 // Begin timing the snippet... 155 return next( onFinish ); 156 } 157 158 /** 159 * Callback invoked upon running a pre-run to determine the number of iterations. 160 * 161 * @private 162 * @param {(Error|null)} error - error object 163 * @param {NonNegativeIntegerArray} time - results 164 * @returns {void} 165 */ 166 function onRun( error, time ) { 167 var t; 168 if ( error ) { 169 return done( error ); 170 } 171 t = time[ 0 ] + ( time[ 1 ]/1e9 ); 172 if ( 173 t < MIN_TIME && 174 opts.iterations < MAX_ITERATIONS 175 ) { 176 opts.iterations *= 10; 177 return next( onRun ); 178 } 179 // Begin timing the snippet... 180 return next( onFinish ); 181 } 182 183 /** 184 * Callback invoked upon executing code. 185 * 186 * @private 187 * @param {(Error|null)} error - error object 188 * @param {NonNegativeIntegerArray} time - results 189 * @returns {void} 190 */ 191 function onFinish( error, time ) { 192 if ( error ) { 193 return done( error ); 194 } 195 results[ idx ] = time; 196 idx += 1; 197 if ( idx < opts.repeats ) { 198 return next( onFinish ); 199 } 200 done( null, results ); 201 } 202 203 /** 204 * Callback invoked upon completion. 205 * 206 * @private 207 * @param {(Error|null)} error - error object 208 * @param {ArrayArray} results - raw results 209 */ 210 function done( error, results ) { 211 var out; 212 if ( !error ) { 213 out = transform( results, opts.iterations ); 214 } 215 // Avoid releasing the zalgo: 216 nextTick( onTick ); 217 218 /** 219 * Callback invoked upon the next tick. 220 * 221 * @private 222 * @returns {void} 223 */ 224 function onTick() { 225 if ( error ) { 226 return cb( error ); 227 } 228 cb( null, out ); 229 } 230 } 231 } 232 233 234 // EXPORTS // 235 236 module.exports = timeit;