summaryrefslogtreecommitdiffstats
path: root/third_party/webkit/PerformanceTests/Speedometer/resources/benchmark-runner.js
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/webkit/PerformanceTests/Speedometer/resources/benchmark-runner.js')
-rw-r--r--third_party/webkit/PerformanceTests/Speedometer/resources/benchmark-runner.js305
1 files changed, 305 insertions, 0 deletions
diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/benchmark-runner.js b/third_party/webkit/PerformanceTests/Speedometer/resources/benchmark-runner.js
new file mode 100644
index 0000000000..751992b4f1
--- /dev/null
+++ b/third_party/webkit/PerformanceTests/Speedometer/resources/benchmark-runner.js
@@ -0,0 +1,305 @@
+// FIXME: Use the real promise if available.
+// FIXME: Make sure this interface is compatible with the real Promise.
+function SimplePromise() {
+ this._chainedPromise = null;
+ this._callback = null;
+}
+
+SimplePromise.prototype.then = function (callback) {
+ if (this._callback)
+ throw "SimplePromise doesn't support multiple calls to then";
+ this._callback = callback;
+ this._chainedPromise = new SimplePromise;
+
+ if (this._resolved)
+ this.resolve(this._resolvedValue);
+
+ return this._chainedPromise;
+}
+
+SimplePromise.prototype.resolve = function (value) {
+ if (!this._callback) {
+ this._resolved = true;
+ this._resolvedValue = value;
+ return;
+ }
+
+ var result = this._callback(value);
+ if (result instanceof SimplePromise) {
+ var chainedPromise = this._chainedPromise;
+ result.then(function (result) { chainedPromise.resolve(result); });
+ } else
+ this._chainedPromise.resolve(result);
+}
+
+function BenchmarkTestStep(testName, testFunction) {
+ this.name = testName;
+ this.run = testFunction;
+}
+
+function BenchmarkRunner(suites, client) {
+ this._suites = suites;
+ this._prepareReturnValue = null;
+ this._client = client;
+}
+
+BenchmarkRunner.prototype.waitForElement = function (selector) {
+ var promise = new SimplePromise;
+ var contentDocument = this._frame.contentDocument;
+
+ function resolveIfReady() {
+ var element = contentDocument.querySelector(selector);
+ if (element) {
+ window.requestAnimationFrame(function () {
+ return promise.resolve(element);
+ });
+ return;
+ }
+ setTimeout(resolveIfReady, 50);
+ }
+
+ resolveIfReady();
+ return promise;
+}
+
+BenchmarkRunner.prototype._removeFrame = function () {
+ if (this._frame) {
+ this._frame.parentNode.removeChild(this._frame);
+ this._frame = null;
+ }
+}
+
+BenchmarkRunner.prototype._appendFrame = function (src) {
+ var frame = document.createElement('iframe');
+ frame.style.width = '800px';
+ frame.style.height = '600px';
+ frame.style.border = '0px none';
+ frame.style.position = 'absolute';
+ frame.setAttribute('scrolling', 'no');
+
+ var marginLeft = parseInt(getComputedStyle(document.body).marginLeft);
+ var marginTop = parseInt(getComputedStyle(document.body).marginTop);
+ if (window.innerWidth > 800 + marginLeft && window.innerHeight > 600 + marginTop) {
+ frame.style.left = marginLeft + 'px';
+ frame.style.top = marginTop + 'px';
+ } else {
+ frame.style.left = '0px';
+ frame.style.top = '0px';
+ }
+
+ if (this._client && this._client.willAddTestFrame)
+ this._client.willAddTestFrame(frame);
+
+ document.body.insertBefore(frame, document.body.firstChild);
+ this._frame = frame;
+ return frame;
+}
+
+BenchmarkRunner.prototype._waitAndWarmUp = function () {
+ var startTime = Date.now();
+
+ function Fibonacci(n) {
+ if (Date.now() - startTime > 100)
+ return;
+ if (n <= 0)
+ return 0;
+ else if (n == 1)
+ return 1;
+ return Fibonacci(n - 2) + Fibonacci(n - 1);
+ }
+
+ var promise = new SimplePromise;
+ setTimeout(function () {
+ Fibonacci(100);
+ promise.resolve();
+ }, 200);
+ return promise;
+}
+
+BenchmarkRunner.prototype._writeMark = function(name) {
+ if (window.performance && window.performance.mark)
+ window.performance.mark(name);
+}
+
+// This function ought be as simple as possible. Don't even use SimplePromise.
+BenchmarkRunner.prototype._runTest = function(suite, test, prepareReturnValue, callback)
+{
+ var self = this;
+ var now = window.performance && window.performance.now ? function () { return window.performance.now(); } : Date.now;
+
+ var contentWindow = self._frame.contentWindow;
+ var contentDocument = self._frame.contentDocument;
+
+ self._writeMark(suite.name + '.' + test.name + '-start');
+ var startTime = now();
+ test.run(prepareReturnValue, contentWindow, contentDocument);
+ var endTime = now();
+ self._writeMark(suite.name + '.' + test.name + '-sync-end');
+
+ var syncTime = endTime - startTime;
+
+ var startTime = now();
+ setTimeout(function () {
+ // Some browsers don't immediately update the layout for paint.
+ // Force the layout here to ensure we're measuring the layout time.
+ var height = self._frame.contentDocument.body.getBoundingClientRect().height;
+ var endTime = now();
+ self._frame.contentWindow._unusedHeightValue = height; // Prevent dead code elimination.
+ self._writeMark(suite.name + '.' + test.name + '-async-end');
+ window.requestAnimationFrame(function () {
+ callback(syncTime, endTime - startTime, height);
+ });
+ }, 0);
+}
+
+function BenchmarkState(suites) {
+ this._suites = suites;
+ this._suiteIndex = -1;
+ this._testIndex = 0;
+ this.next();
+}
+
+BenchmarkState.prototype.currentSuite = function() {
+ return this._suites[this._suiteIndex];
+}
+
+BenchmarkState.prototype.currentTest = function () {
+ var suite = this.currentSuite();
+ return suite ? suite.tests[this._testIndex] : null;
+}
+
+BenchmarkState.prototype.next = function () {
+ this._testIndex++;
+
+ var suite = this._suites[this._suiteIndex];
+ if (suite && this._testIndex < suite.tests.length)
+ return this;
+
+ this._testIndex = 0;
+ do {
+ this._suiteIndex++;
+ } while (this._suiteIndex < this._suites.length && this._suites[this._suiteIndex].disabled);
+
+ return this;
+}
+
+BenchmarkState.prototype.isFirstTest = function () {
+ return !this._testIndex;
+}
+
+BenchmarkState.prototype.prepareCurrentSuite = function (runner, frame) {
+ var suite = this.currentSuite();
+ var promise = new SimplePromise;
+ frame.onload = function () {
+ suite.prepare(runner, frame.contentWindow, frame.contentDocument).then(function (result) { promise.resolve(result); });
+ }
+ frame.src = 'resources/' + suite.url;
+ return promise;
+}
+
+BenchmarkRunner.prototype.step = function (state) {
+ if (!state) {
+ state = new BenchmarkState(this._suites);
+ this._measuredValues = {tests: {}, total: 0, mean: NaN, geomean: NaN, score: NaN};
+ }
+
+ var suite = state.currentSuite();
+ if (!suite) {
+ this._finalize();
+ var promise = new SimplePromise;
+ promise.resolve();
+ return promise;
+ }
+
+ if (state.isFirstTest()) {
+ this._removeFrame();
+ var self = this;
+ return state.prepareCurrentSuite(this, this._appendFrame()).then(function (prepareReturnValue) {
+ self._prepareReturnValue = prepareReturnValue;
+ return self._runTestAndRecordResults(state);
+ });
+ }
+
+ return this._runTestAndRecordResults(state);
+}
+
+BenchmarkRunner.prototype.runAllSteps = function (startingState) {
+ var nextCallee = this.runAllSteps.bind(this);
+ this.step(startingState).then(function (nextState) {
+ if (nextState)
+ nextCallee(nextState);
+ });
+}
+
+BenchmarkRunner.prototype.runMultipleIterations = function (iterationCount) {
+ var self = this;
+ var currentIteration = 0;
+
+ this._runNextIteration = function () {
+ currentIteration++;
+ if (currentIteration < iterationCount)
+ self.runAllSteps();
+ else if (this._client && this._client.didFinishLastIteration)
+ this._client.didFinishLastIteration();
+ }
+
+ if (this._client && this._client.willStartFirstIteration)
+ this._client.willStartFirstIteration(iterationCount);
+
+ self.runAllSteps();
+}
+
+BenchmarkRunner.prototype._runTestAndRecordResults = function (state) {
+ var promise = new SimplePromise;
+ var suite = state.currentSuite();
+ var test = state.currentTest();
+
+ if (this._client && this._client.willRunTest)
+ this._client.willRunTest(suite, test);
+
+ var self = this;
+ setTimeout(function () {
+ self._runTest(suite, test, self._prepareReturnValue, function (syncTime, asyncTime) {
+ var suiteResults = self._measuredValues.tests[suite.name] || {tests:{}, total: 0};
+ var total = syncTime + asyncTime;
+ self._measuredValues.tests[suite.name] = suiteResults;
+ suiteResults.tests[test.name] = {tests: {'Sync': syncTime, 'Async': asyncTime}, total: total};
+ suiteResults.total += total;
+
+ if (self._client && self._client.didRunTest)
+ self._client.didRunTest(suite, test);
+
+ state.next();
+ promise.resolve(state);
+ });
+ }, 0);
+ return promise;
+}
+
+BenchmarkRunner.prototype._finalize = function () {
+ this._removeFrame();
+
+ if (this._client && this._client.didRunSuites) {
+ var product = 1;
+ var values = [];
+ for (var suiteName in this._measuredValues.tests) {
+ var suiteTotal = this._measuredValues.tests[suiteName].total;
+ product *= suiteTotal;
+ values.push(suiteTotal);
+ }
+
+ values.sort(function (a, b) { return a - b }); // Avoid the loss of significance for the sum.
+ var total = values.reduce(function (a, b) { return a + b });
+ var geomean = Math.pow(product, 1 / values.length);
+
+ var correctionFactor = 3; // This factor makes the test score look reasonably fit within 0 to 140.
+ this._measuredValues.total = total;
+ this._measuredValues.mean = total / values.length;
+ this._measuredValues.geomean = geomean;
+ this._measuredValues.score = 60 * 1000 / geomean / correctionFactor;
+ this._client.didRunSuites(this._measuredValues);
+ }
+
+ if (this._runNextIteration)
+ this._runNextIteration();
+}