performancetimer.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. // Copyright 2008 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Performance timer.
  16. *
  17. * {@see goog.testing.benchmark} for an easy way to use this functionality.
  18. *
  19. * @author attila@google.com (Attila Bodis)
  20. */
  21. goog.setTestOnly('goog.testing.PerformanceTimer');
  22. goog.provide('goog.testing.PerformanceTimer');
  23. goog.provide('goog.testing.PerformanceTimer.Task');
  24. goog.require('goog.array');
  25. goog.require('goog.async.Deferred');
  26. goog.require('goog.math');
  27. /**
  28. * Creates a performance timer that runs test functions a number of times to
  29. * generate timing samples, and provides performance statistics (minimum,
  30. * maximum, average, and standard deviation).
  31. * @param {number=} opt_numSamples Number of times to run the test function;
  32. * defaults to 10.
  33. * @param {number=} opt_timeoutInterval Number of milliseconds after which the
  34. * test is to be aborted; defaults to 5 seconds (5,000ms).
  35. * @constructor
  36. */
  37. goog.testing.PerformanceTimer = function(opt_numSamples, opt_timeoutInterval) {
  38. /**
  39. * Number of times the test function is to be run; defaults to 10.
  40. * @private {number}
  41. */
  42. this.numSamples_ = opt_numSamples || 10;
  43. /**
  44. * Number of milliseconds after which the test is to be aborted; defaults to
  45. * 5,000ms.
  46. * @private {number}
  47. */
  48. this.timeoutInterval_ = opt_timeoutInterval || 5000;
  49. /**
  50. * Whether to discard outliers (i.e. the smallest and the largest values)
  51. * from the sample set before computing statistics. Defaults to false.
  52. * @private {boolean}
  53. */
  54. this.discardOutliers_ = false;
  55. };
  56. /**
  57. * A function whose subsequent calls differ in milliseconds. Used to calculate
  58. * the start and stop checkpoint times for runs. Note that high performance
  59. * timers do not necessarily return the current time in milliseconds.
  60. * @return {number}
  61. * @private
  62. */
  63. goog.testing.PerformanceTimer.now_ = function() {
  64. // goog.now is used in DEBUG mode to make the class easier to test.
  65. return !goog.DEBUG && window.performance && window.performance.now ?
  66. window.performance.now() :
  67. goog.now();
  68. };
  69. /**
  70. * @return {number} The number of times the test function will be run.
  71. */
  72. goog.testing.PerformanceTimer.prototype.getNumSamples = function() {
  73. return this.numSamples_;
  74. };
  75. /**
  76. * Sets the number of times the test function will be run.
  77. * @param {number} numSamples Number of times to run the test function.
  78. */
  79. goog.testing.PerformanceTimer.prototype.setNumSamples = function(numSamples) {
  80. this.numSamples_ = numSamples;
  81. };
  82. /**
  83. * @return {number} The number of milliseconds after which the test times out.
  84. */
  85. goog.testing.PerformanceTimer.prototype.getTimeoutInterval = function() {
  86. return this.timeoutInterval_;
  87. };
  88. /**
  89. * Sets the number of milliseconds after which the test times out.
  90. * @param {number} timeoutInterval Timeout interval in ms.
  91. */
  92. goog.testing.PerformanceTimer.prototype.setTimeoutInterval = function(
  93. timeoutInterval) {
  94. this.timeoutInterval_ = timeoutInterval;
  95. };
  96. /**
  97. * Sets whether to ignore the smallest and the largest values when computing
  98. * stats.
  99. * @param {boolean} discard Whether to discard outlier values.
  100. */
  101. goog.testing.PerformanceTimer.prototype.setDiscardOutliers = function(discard) {
  102. this.discardOutliers_ = discard;
  103. };
  104. /**
  105. * @return {boolean} Whether outlier values are discarded prior to computing
  106. * stats.
  107. */
  108. goog.testing.PerformanceTimer.prototype.isDiscardOutliers = function() {
  109. return this.discardOutliers_;
  110. };
  111. /**
  112. * Executes the test function the required number of times (or until the
  113. * test run exceeds the timeout interval, whichever comes first). Returns
  114. * an object containing the following:
  115. * <pre>
  116. * {
  117. * 'average': average execution time (ms)
  118. * 'count': number of executions (may be fewer than expected due to timeout)
  119. * 'maximum': longest execution time (ms)
  120. * 'minimum': shortest execution time (ms)
  121. * 'standardDeviation': sample standard deviation (ms)
  122. * 'total': total execution time (ms)
  123. * }
  124. * </pre>
  125. *
  126. * @param {Function} testFn Test function whose performance is to
  127. * be measured.
  128. * @return {!Object} Object containing performance stats.
  129. */
  130. goog.testing.PerformanceTimer.prototype.run = function(testFn) {
  131. return this.runTask(
  132. new goog.testing.PerformanceTimer.Task(
  133. /** @type {goog.testing.PerformanceTimer.TestFunction} */ (testFn)));
  134. };
  135. /**
  136. * Executes the test function of the specified task as described in
  137. * {@code run}. In addition, if specified, the set up and tear down functions of
  138. * the task are invoked before and after each invocation of the test function.
  139. * @see goog.testing.PerformanceTimer#run
  140. * @param {goog.testing.PerformanceTimer.Task} task A task describing the test
  141. * function to invoke.
  142. * @return {!Object} Object containing performance stats.
  143. */
  144. goog.testing.PerformanceTimer.prototype.runTask = function(task) {
  145. var samples = [];
  146. var testStart = goog.testing.PerformanceTimer.now_();
  147. var totalRunTime = 0;
  148. var testFn = task.getTest();
  149. var setUpFn = task.getSetUp();
  150. var tearDownFn = task.getTearDown();
  151. for (var i = 0; i < this.numSamples_ && totalRunTime <= this.timeoutInterval_;
  152. i++) {
  153. setUpFn();
  154. var sampleStart = goog.testing.PerformanceTimer.now_();
  155. testFn();
  156. var sampleEnd = goog.testing.PerformanceTimer.now_();
  157. tearDownFn();
  158. samples[i] = sampleEnd - sampleStart;
  159. totalRunTime = sampleEnd - testStart;
  160. }
  161. return this.finishTask_(samples);
  162. };
  163. /**
  164. * Finishes the run of a task by creating a result object from samples, in the
  165. * format described in {@code run}.
  166. * @see goog.testing.PerformanceTimer#run
  167. * @param {!Array<number>} samples The samples to analyze.
  168. * @return {!Object} Object containing performance stats.
  169. * @private
  170. */
  171. goog.testing.PerformanceTimer.prototype.finishTask_ = function(samples) {
  172. if (this.discardOutliers_ && samples.length > 2) {
  173. goog.array.remove(samples, Math.min.apply(null, samples));
  174. goog.array.remove(samples, Math.max.apply(null, samples));
  175. }
  176. return goog.testing.PerformanceTimer.createResults(samples);
  177. };
  178. /**
  179. * Executes the test function of the specified task asynchronously. The test
  180. * function is expected to take a callback as input and has to call it to signal
  181. * that it's done. In addition, if specified, the setUp and tearDown functions
  182. * of the task are invoked before and after each invocation of the test
  183. * function. Note that setUp/tearDown functions take a callback as input and
  184. * must call this callback when they are done.
  185. * @see goog.testing.PerformanceTimer#run
  186. * @param {goog.testing.PerformanceTimer.Task} task A task describing the test
  187. * function to invoke.
  188. * @return {!goog.async.Deferred} The deferred result, eventually an object
  189. * containing performance stats.
  190. */
  191. goog.testing.PerformanceTimer.prototype.runAsyncTask = function(task) {
  192. var samples = [];
  193. var testStart = goog.testing.PerformanceTimer.now_();
  194. var testFn = task.getTest();
  195. var setUpFn = task.getSetUp();
  196. var tearDownFn = task.getTearDown();
  197. // Note that this uses a separate code path from runTask() because
  198. // implementing runTask() in terms of runAsyncTask() could easily cause
  199. // a stack overflow if there are many iterations.
  200. var result = new goog.async.Deferred();
  201. this.runAsyncTaskSample_(
  202. testFn, setUpFn, tearDownFn, result, samples, testStart);
  203. return result;
  204. };
  205. /**
  206. * Runs a task once, waits for the test function to complete asynchronously
  207. * and starts another run if not enough samples have been collected. Otherwise
  208. * finishes this task.
  209. * @param {goog.testing.PerformanceTimer.TestFunction} testFn The test function.
  210. * @param {goog.testing.PerformanceTimer.TestFunction} setUpFn The set up
  211. * function that will be called once before the test function is run.
  212. * @param {goog.testing.PerformanceTimer.TestFunction} tearDownFn The set up
  213. * function that will be called once after the test function completed.
  214. * @param {!goog.async.Deferred} result The deferred result, eventually an
  215. * object containing performance stats.
  216. * @param {!Array<number>} samples The time samples from all runs of the test
  217. * function so far.
  218. * @param {number} testStart The timestamp when the first sample was started.
  219. * @private
  220. */
  221. goog.testing.PerformanceTimer.prototype.runAsyncTaskSample_ = function(
  222. testFn, setUpFn, tearDownFn, result, samples, testStart) {
  223. var timer = this;
  224. timer.handleOptionalDeferred_(setUpFn, function() {
  225. var sampleStart = goog.testing.PerformanceTimer.now_();
  226. timer.handleOptionalDeferred_(testFn, function() {
  227. var sampleEnd = goog.testing.PerformanceTimer.now_();
  228. timer.handleOptionalDeferred_(tearDownFn, function() {
  229. samples.push(sampleEnd - sampleStart);
  230. var totalRunTime = sampleEnd - testStart;
  231. if (samples.length < timer.numSamples_ &&
  232. totalRunTime <= timer.timeoutInterval_) {
  233. timer.runAsyncTaskSample_(
  234. testFn, setUpFn, tearDownFn, result, samples, testStart);
  235. } else {
  236. result.callback(timer.finishTask_(samples));
  237. }
  238. });
  239. });
  240. });
  241. };
  242. /**
  243. * Execute a function that optionally returns a deferred object and continue
  244. * with the given continuation function only once the deferred object has a
  245. * result.
  246. * @param {goog.testing.PerformanceTimer.TestFunction} deferredFactory The
  247. * function that optionally returns a deferred object.
  248. * @param {function()} continuationFunction The function that should be called
  249. * after the optional deferred has a result.
  250. * @private
  251. */
  252. goog.testing.PerformanceTimer.prototype.handleOptionalDeferred_ = function(
  253. deferredFactory, continuationFunction) {
  254. var deferred = deferredFactory();
  255. if (deferred) {
  256. deferred.addCallback(continuationFunction);
  257. } else {
  258. continuationFunction();
  259. }
  260. };
  261. /**
  262. * Creates a performance timer results object by analyzing a given array of
  263. * sample timings.
  264. * @param {!Array<number>} samples The samples to analyze.
  265. * @return {!Object} Object containing performance stats.
  266. */
  267. goog.testing.PerformanceTimer.createResults = function(samples) {
  268. return {
  269. 'average': goog.math.average.apply(null, samples),
  270. 'count': samples.length,
  271. 'maximum': Math.max.apply(null, samples),
  272. 'minimum': Math.min.apply(null, samples),
  273. 'standardDeviation': goog.math.standardDeviation.apply(null, samples),
  274. 'total': goog.math.sum.apply(null, samples)
  275. };
  276. };
  277. /**
  278. * A test function whose performance should be measured or a setUp/tearDown
  279. * function. It may optionally return a deferred object. If it does so, the
  280. * test harness will assume the function is asynchronous and it must signal
  281. * that it's done by setting an (empty) result on the deferred object. If the
  282. * function doesn't return anything, the test harness will assume it's
  283. * synchronous.
  284. * @typedef {function():(goog.async.Deferred|undefined)}
  285. */
  286. goog.testing.PerformanceTimer.TestFunction;
  287. /**
  288. * A task for the performance timer to measure. Callers can specify optional
  289. * setUp and tearDown methods to control state before and after each run of the
  290. * test function.
  291. * @param {goog.testing.PerformanceTimer.TestFunction} test Test function whose
  292. * performance is to be measured.
  293. * @constructor
  294. * @final
  295. */
  296. goog.testing.PerformanceTimer.Task = function(test) {
  297. /**
  298. * The test function to time.
  299. * @type {goog.testing.PerformanceTimer.TestFunction}
  300. * @private
  301. */
  302. this.test_ = test;
  303. };
  304. /**
  305. * An optional set up function to run before each invocation of the test
  306. * function.
  307. * @type {goog.testing.PerformanceTimer.TestFunction}
  308. * @private
  309. */
  310. goog.testing.PerformanceTimer.Task.prototype.setUp_ = goog.nullFunction;
  311. /**
  312. * An optional tear down function to run after each invocation of the test
  313. * function.
  314. * @type {goog.testing.PerformanceTimer.TestFunction}
  315. * @private
  316. */
  317. goog.testing.PerformanceTimer.Task.prototype.tearDown_ = goog.nullFunction;
  318. /**
  319. * @return {goog.testing.PerformanceTimer.TestFunction} The test function to
  320. * time.
  321. */
  322. goog.testing.PerformanceTimer.Task.prototype.getTest = function() {
  323. return this.test_;
  324. };
  325. /**
  326. * Specifies a set up function to be invoked before each invocation of the test
  327. * function.
  328. * @param {goog.testing.PerformanceTimer.TestFunction} setUp The set up
  329. * function.
  330. * @return {!goog.testing.PerformanceTimer.Task} This task.
  331. */
  332. goog.testing.PerformanceTimer.Task.prototype.withSetUp = function(setUp) {
  333. this.setUp_ = setUp;
  334. return this;
  335. };
  336. /**
  337. * @return {goog.testing.PerformanceTimer.TestFunction} The set up function or
  338. * the default no-op function if none was specified.
  339. */
  340. goog.testing.PerformanceTimer.Task.prototype.getSetUp = function() {
  341. return this.setUp_;
  342. };
  343. /**
  344. * Specifies a tear down function to be invoked after each invocation of the
  345. * test function.
  346. * @param {goog.testing.PerformanceTimer.TestFunction} tearDown The tear down
  347. * function.
  348. * @return {!goog.testing.PerformanceTimer.Task} This task.
  349. */
  350. goog.testing.PerformanceTimer.Task.prototype.withTearDown = function(tearDown) {
  351. this.tearDown_ = tearDown;
  352. return this;
  353. };
  354. /**
  355. * @return {goog.testing.PerformanceTimer.TestFunction} The tear down function
  356. * or the default no-op function if none was specified.
  357. */
  358. goog.testing.PerformanceTimer.Task.prototype.getTearDown = function() {
  359. return this.tearDown_;
  360. };