| /* | |
| * Copyright (C) 2016-2017 Apple Inc. All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions | |
| * are met: | |
| * 1. Redistributions of source code must retain the above copyright | |
| * notice, this list of conditions and the following disclaimer. | |
| * 2. Redistributions in binary form must reproduce the above copyright | |
| * notice, this list of conditions and the following disclaimer in the | |
| * documentation and/or other materials provided with the distribution. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY | |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR | |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| */ | |
| "use strict"; | |
| class Driver { | |
| constructor(statusCell, triggerCell, triggerLink, magicCell, summaryCell, key) | |
| { | |
| this._benchmarks = new Map(); | |
| this._statusCell = statusCell; | |
| this._triggerCell = triggerCell; | |
| this._triggerLink = triggerLink; | |
| this._magicCell = magicCell; | |
| this._summary = new Stats(summaryCell, "summary"); | |
| this._key = key; | |
| if (isInBrowser) | |
| window[key] = this; | |
| } | |
| addBenchmark(benchmark) | |
| { | |
| this._benchmarks.set(benchmark, new Results(benchmark)); | |
| } | |
| readyTrigger() | |
| { | |
| if (isInBrowser) { | |
| this._triggerCell.addEventListener('click', this._triggerLink); | |
| this._triggerCell.classList.add('ready'); | |
| } | |
| } | |
| disableTrigger() | |
| { | |
| if (isInBrowser) { | |
| this._triggerCell.removeEventListener('click', this._triggerLink); | |
| this._triggerCell.classList.remove('ready'); | |
| } | |
| } | |
| start(numIterations) | |
| { | |
| this.disableTrigger(); | |
| this._updateIterations(); | |
| this._summary.reset(); | |
| for (let [benchmark, results] of this._benchmarks) | |
| results.reset(); | |
| this._isRunning = true; | |
| this._startIterations = this._numIterations = numIterations; | |
| this._iterator = null; | |
| this._iterate(); | |
| } | |
| reportResult(...args) | |
| { | |
| this._benchmarks.get(this._benchmark).reportResult(...args); | |
| this._recomputeSummary(); | |
| this._iterate(); | |
| } | |
| reportError(...args) | |
| { | |
| if (isInBrowser) | |
| console.log("Error encountered: ", ...args); | |
| this._benchmarks.get(this._benchmark).reportError(...args); | |
| if (isInBrowser) { | |
| this._statusCell.innerHTML = "Test failure \u2014 error in console"; | |
| this._statusCell.classList.add("failed"); | |
| } else | |
| print("Test failure"); | |
| } | |
| _recomputeSummary() | |
| { | |
| class Geomean { | |
| constructor() | |
| { | |
| this._count = 0; | |
| this._sum = 0; | |
| } | |
| add(value) | |
| { | |
| this._count++; | |
| this._sum += Math.log(value); | |
| } | |
| get result() | |
| { | |
| return Math.exp(this._sum * (1 / this._count)); | |
| } | |
| } | |
| let statses = []; | |
| for (let results of this._benchmarks.values()) { | |
| for (let subResult of Results.subResults) | |
| statses.push(results[subResult]); | |
| } | |
| let numIterations = Math.min(...statses.map(stats => stats.numIterations)); | |
| let data = new Array(numIterations); | |
| for (let i = 0; i < data.length; ++i) | |
| data[i] = new Geomean(); | |
| for (let stats of statses) { | |
| for (let i = 0; i < data.length; ++i) | |
| data[i].add(stats.valueForIteration(i)); | |
| } | |
| let geomeans = data.map(geomean => geomean.result); | |
| this._summary.reset(...geomeans); | |
| } | |
| _iterate() | |
| { | |
| this._benchmark = this._iterator ? this._iterator.next().value : null; | |
| if (!this._benchmark) { | |
| if (!this._numIterations) { | |
| if (isInBrowser) { | |
| this._statusCell.innerHTML = "Restart"; | |
| this.readyTrigger(); | |
| } else | |
| print("Success! Benchmark is now finished."); | |
| return; | |
| } | |
| this._numIterations--; | |
| this._updateIterations(); | |
| this._iterator = this._benchmarks.keys(); | |
| this._benchmark = this._iterator.next().value; | |
| } | |
| this._benchmarks.get(this._benchmark).reportRunning(); | |
| let benchmark = this._benchmark; | |
| if (isInBrowser) { | |
| window.setTimeout(() => { | |
| if (!this._isRunning) | |
| return; | |
| this._magicCell.contentDocument.body.textContent = ""; | |
| this._magicCell.contentDocument.body.innerHTML = "<iframe id=\"magicFrame\" frameborder=\"0\">"; | |
| let magicFrame = this._magicCell.contentDocument.getElementById("magicFrame"); | |
| magicFrame.contentDocument.open(); | |
| magicFrame.contentDocument.write( | |
| `<!DOCTYPE html><head><title>benchmark payload</title></head><body><script>` + | |
| `window.onerror = top.${this._key}.reportError.bind(top.${this._key});\n` + | |
| `function reportResult()\n` + | |
| `{\n` + | |
| ` var driver = top.${this._key};\n` + | |
| ` driver.reportResult.apply(driver, arguments);\n` + | |
| `}\n` + | |
| `</script>\n` + | |
| `${this._benchmark.code}</body></html>`); | |
| }, 100); | |
| } else { | |
| Promise.resolve(20).then(() => { | |
| if (!this._isRunning) | |
| return; | |
| try { | |
| print(`Running... ${this._benchmark.name} ( ${this._numIterations + 1} to go)`); | |
| benchmark.run(); | |
| print("\n"); | |
| } catch(e) { | |
| print(e); | |
| print(e.stack); | |
| } | |
| }); | |
| } | |
| } | |
| _updateIterations() | |
| { | |
| if (isInBrowser) { | |
| this._statusCell.innerHTML = "Running Tests... " + ( this._startIterations - this._numIterations ) + "/" + this._startIterations; | |
| } | |
| } | |
| } |