Make StyleBench compatible with run-benchmark and run-perf-tests
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 29 Jan 2018 22:09:07 +0000 (22:09 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 29 Jan 2018 22:09:07 +0000 (22:09 +0000)
https://bugs.webkit.org/show_bug.cgi?id=182262

Reviewed by Antti Koivisto.

Copied resource files referenced from Speedometer directory since run-benchmark needs to be able
to checkout each benchmark separately.

Removed the code to create tests of the same name five times in makeSteps since this
won't be compatible with either run-benchmark or run-perf-tests.

* StyleBench/index.html: Removed the code to show warnings for local files since run-benchmark
doesn't use HTTP server in WebDriver mode.
* StyleBench/resources/benchmark-report.js: Copied from resources/benchmark-report.js.
* StyleBench/resources/benchmark-runner.js: Copied from resources/benchmark-runner.js.
(BenchmarkRunner.prototype._finalize): Use the correction factor of 8 instead of 5 in StyleBench.
* StyleBench/resources/gauge.png: Copied from resources/gauge.png.
* StyleBench/resources/gauge@2x.png: Copied from resources/gauge@2x.png.
* StyleBench/resources/main.css: Copied from resources/main.css.
* StyleBench/resources/main.js: Copied from resources/main.js.
* StyleBench/resources/tests.js:
(makeSteps): Only make each test once.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@227756 268f45cc-cd09-0410-ab3c-d52691b4dbfc

PerformanceTests/ChangeLog
PerformanceTests/StyleBench/index.html
PerformanceTests/StyleBench/resources/benchmark-report.js [new file with mode: 0644]
PerformanceTests/StyleBench/resources/benchmark-runner.js [new file with mode: 0644]
PerformanceTests/StyleBench/resources/gauge.png [new file with mode: 0644]
PerformanceTests/StyleBench/resources/gauge@2x.png [new file with mode: 0644]
PerformanceTests/StyleBench/resources/main.css [new file with mode: 0644]
PerformanceTests/StyleBench/resources/main.js [new file with mode: 0644]
PerformanceTests/StyleBench/resources/tests.js

index 2c19602..d6172bd 100644 (file)
@@ -1,3 +1,28 @@
+2018-01-29  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Make StyleBench compatible with run-benchmark and run-perf-tests
+        https://bugs.webkit.org/show_bug.cgi?id=182262
+
+        Reviewed by Antti Koivisto.
+
+        Copied resource files referenced from Speedometer directory since run-benchmark needs to be able
+        to checkout each benchmark separately.
+
+        Removed the code to create tests of the same name five times in makeSteps since this
+        won't be compatible with either run-benchmark or run-perf-tests.
+
+        * StyleBench/index.html: Removed the code to show warnings for local files since run-benchmark
+        doesn't use HTTP server in WebDriver mode.
+        * StyleBench/resources/benchmark-report.js: Copied from resources/benchmark-report.js.
+        * StyleBench/resources/benchmark-runner.js: Copied from resources/benchmark-runner.js.
+        (BenchmarkRunner.prototype._finalize): Use the correction factor of 8 instead of 5 in StyleBench.
+        * StyleBench/resources/gauge.png: Copied from resources/gauge.png.
+        * StyleBench/resources/gauge@2x.png: Copied from resources/gauge@2x.png.
+        * StyleBench/resources/main.css: Copied from resources/main.css.
+        * StyleBench/resources/main.js: Copied from resources/main.js.
+        * StyleBench/resources/tests.js:
+        (makeSteps): Only make each test once.
+
 2018-01-26  Chris Nardi  <cnardi@chromium.org>
 
         Addressing post-review comments after r226614
index 06396bd..1269f30 100644 (file)
@@ -3,19 +3,13 @@
 <head>
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
     <title>StyleBench 0.2</title>
-    <link rel="stylesheet" href="../Speedometer/resources/main.css">
-    <script src="../Speedometer/resources/main.js" defer></script>
-    <script src="../Speedometer/resources/benchmark-runner.js" defer></script>
-    <script src="../Speedometer/resources/benchmark-report.js" defer></script>
+    <link rel="stylesheet" href="resources/main.css">
+    <script src="resources/main.js" defer></script>
+    <script src="resources/benchmark-runner.js" defer></script>
+    <script src="resources/benchmark-report.js" defer></script>
     <script src="../resources/statistics.js" defer></script>
     <script src="resources/style-bench.js" defer></script>
     <script src="resources/tests.js" defer></script>
-    <script>
-        addEventListener('load', () => {
-            if (!window.location.protocol.startsWith('http'))
-                showSection('local-message', false);
-        });
-    </script>
 </head>
 <body>
 <main>
diff --git a/PerformanceTests/StyleBench/resources/benchmark-report.js b/PerformanceTests/StyleBench/resources/benchmark-report.js
new file mode 100644 (file)
index 0000000..c4b4c64
--- /dev/null
@@ -0,0 +1,79 @@
+// This file can be customized to report results as needed.
+
+(function () {
+    if (!window.testRunner && location.search != '?webkit' && location.hash != '#webkit')
+        return;
+
+    if (window.testRunner)
+        testRunner.waitUntilDone();
+
+    var scriptElement = document.createElement('script');
+    scriptElement.src = '../resources/runner.js';
+    document.head.appendChild(scriptElement);
+
+    var styleElement = document.createElement('style');
+    styleElement.textContent = 'pre { padding-top: 600px; }';
+    document.head.appendChild(styleElement);
+
+    var createTest;
+    var valuesByIteration = new Array;
+
+    window.onload = function () {
+        document.body.removeChild(document.querySelector('main'));
+        startBenchmark();
+    }
+
+    window.benchmarkClient = {
+        iterationCount: 5, // Use 4 different instances of DRT/WTR to run 5 iterations.
+        willStartFirstIteration: function (iterationCount) {
+            createTest = function (name, aggregator, isLastTest) {
+                return {
+                    customIterationCount: iterationCount,
+                    doNotIgnoreInitialRun: true,
+                    doNotMeasureMemoryUsage: true,
+                    continueTesting: !isLastTest,
+                    unit: 'ms',
+                    name: name,
+                    aggregator: aggregator};
+            }
+            PerfTestRunner.prepareToMeasureValuesAsync(createTest(null, 'Total'));
+        },
+        didRunSuites: function (measuredValues) {
+            PerfTestRunner.measureValueAsync(measuredValues.total);
+            valuesByIteration.push(measuredValues.tests);
+        },
+        didFinishLastIteration: function () {
+            document.head.removeChild(document.querySelector('style'));
+
+            var measuredValuesByFullName = {};
+            function addToMeasuredValue(value, fullName, aggregator) {
+                var values = measuredValuesByFullName[fullName] || new Array;
+                measuredValuesByFullName[fullName] = values;
+                values.push(value);
+                values.aggregator = aggregator;
+            }
+
+            valuesByIteration.forEach(function (measuredValues) {
+                for (var suiteName in measuredValues) {
+                    var suite = measuredValues[suiteName];
+                    for (var testName in suite.tests) {
+                        var test = suite.tests[testName];
+                        for (var subtestName in test.tests)
+                            addToMeasuredValue(test.tests[subtestName], suiteName + '/' + testName + '/' + subtestName);
+                        addToMeasuredValue(test.total, suiteName + '/' + testName, 'Total');
+                    }
+                    addToMeasuredValue(suite.total, suiteName, 'Total');
+                }
+            });
+
+            var fullNames = new Array;
+            for (var fullName in measuredValuesByFullName)
+                fullNames.push(fullName);
+
+            for (var i = 0; i < fullNames.length; i++) {
+                var values = measuredValuesByFullName[fullNames[i]];
+                PerfTestRunner.reportValues(createTest(fullNames[i], values.aggregator, i + 1 == fullNames.length), values);
+            }
+        }
+    };
+})();
diff --git a/PerformanceTests/StyleBench/resources/benchmark-runner.js b/PerformanceTests/StyleBench/resources/benchmark-runner.js
new file mode 100644 (file)
index 0000000..e578590
--- /dev/null
@@ -0,0 +1,299 @@
+// 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)
+            return promise.resolve(element);
+        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');
+        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 = 8; // 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();
+}
diff --git a/PerformanceTests/StyleBench/resources/gauge.png b/PerformanceTests/StyleBench/resources/gauge.png
new file mode 100644 (file)
index 0000000..35febef
Binary files /dev/null and b/PerformanceTests/StyleBench/resources/gauge.png differ
diff --git a/PerformanceTests/StyleBench/resources/gauge@2x.png b/PerformanceTests/StyleBench/resources/gauge@2x.png
new file mode 100644 (file)
index 0000000..215d7c2
Binary files /dev/null and b/PerformanceTests/StyleBench/resources/gauge@2x.png differ
diff --git a/PerformanceTests/StyleBench/resources/main.css b/PerformanceTests/StyleBench/resources/main.css
new file mode 100644 (file)
index 0000000..126cdcd
--- /dev/null
@@ -0,0 +1,286 @@
+body {
+    background-color: rgb(46, 51, 55);
+    color: rgb(235, 235, 235);
+    font-family: "Helvetica Neue", Helvetica, Verdana, sans-serif;
+}
+
+::selection {
+    color: rgb(46, 51, 55);
+    background-color: rgb(235, 235, 235);
+}
+
+h1,
+button {
+    font-family: "Futura-Medium", Futura, "Helvetica Neue", Helvetica, Verdana, sans-serif;
+}
+
+code {
+    font-family: Menlo, Monaco, monospace;
+    font-size: smaller;
+}
+
+button {
+    cursor: pointer;
+}
+
+hr {
+    border: 1px solid rgb(235, 235, 235);
+    width: 50%;
+    margin: 40px auto;
+}
+
+img {
+    -webkit-user-select: none;
+    -webkit-user-drag: none;
+}
+
+main {
+    display: block;
+    position: absolute;
+    width: 800px;
+    height: 600px;
+    top: 50%;
+    left: 50%;
+    margin-top: -321px;
+    margin-left: -421px;
+    padding: 15px;
+    border: 6px solid rgb(235, 235, 235);
+    border-radius: 20px;
+}
+
+#logo {
+    position: absolute;
+    left: -70px;
+    top: 115px;
+    width: 75px;
+    height: 406px;
+}
+
+h1 {
+    margin-top: 30px;
+    font-size: 40px;
+    font-weight: normal;
+    color: rgb(235, 235, 235);
+    text-align: center;
+}
+
+p {
+    font-size: 16px;
+    line-height: 21px;
+}
+
+a {
+    color: inherit;
+}
+
+.buttons {
+    margin-top: 30px;
+    text-align: center;
+}
+
+button {
+    -webkit-appearance: none;
+    border: 3px solid rgb(235, 235, 235);
+    border-radius: 10px;
+    min-width: 200px;
+    padding: 5px 20px;
+    margin: 0 40px;
+    font-size: 25px;
+    color: rgb(235, 235, 235);
+    background-color: transparent;
+
+    -webkit-user-select: none;
+}
+
+button:active {
+    background-color: rgb(235, 235, 235);
+    color: rgb(46, 51, 55);
+    border-color: rgb(235, 235, 235) !important;
+}
+
+button:focus {
+    outline: none;
+    border-color: rgb(232, 79, 79);
+}
+
+section {
+    display: none;
+}
+
+section > p {
+    margin: 10px 20px;
+}
+
+section.selected {
+    display: block;
+}
+
+#testContainer {
+    position: absolute;
+    top: 15px;
+    left: 15px;
+    width: 800px;
+    height: 600px;
+}
+
+section#home > p {
+    margin: 0 auto;
+    width: 70%;
+    text-align: center;
+}
+
+section#home > p:first-child {
+    margin-top: 160px;
+    text-align: center;
+}
+
+section#home > .show-about {
+    margin-top: 100px;
+}
+
+section#home > .buttons {
+    margin-top: 80px;
+}
+
+section#running > #progress {
+    position: absolute;
+    bottom: -6px;
+    left: 60px;
+    right: 60px;
+    height: 6px;
+    background-color: rgb(128, 128, 128);
+    border-left: 6px solid rgb(46, 51, 55);
+    border-right: 6px solid rgb(46, 51, 55);
+}
+
+section#running #progress-completed {
+    position: absolute;
+    top: 0;
+    left: 0;
+    height: 6px;
+    width: 0;
+    background-color: rgb(235, 235, 235);
+}
+
+section#running > #info {
+    position: absolute;
+    bottom: -25px;
+    left: 60px;
+    right: 60px;
+    height: 12px;
+    color: rgb(128, 128, 128);
+    text-align: center;
+    font-size: 12px;
+}
+
+section#summarized-results > #result-number,
+section#summarized-results > #confidence-number {
+    font-family: "Futura-CondensedMedium", Futura, "Helvetica Neue", Helvetica, Verdana, sans-serif;
+}
+
+section#summarized-results > #result-number {
+    text-align: center;
+    font-size: 145px;
+    line-height: 145px;
+}
+
+section#summarized-results > #confidence-number {
+    text-align: center;
+    font-size: 36px;
+    line-height: 36px;
+    color: rgb(128, 128, 128);
+}
+
+section#detailed-results > table {
+    float: left;
+    width: 50%;
+}
+
+section#detailed-results > .arithmetic-mean {
+    clear: both;
+    padding-top: 32px;
+    text-align: center;
+}
+
+section#detailed-results > .arithmetic-mean > label {
+    font-weight: bold;
+    margin-right: 10px;
+    color: rgb(128, 128, 128);
+}
+
+section#detailed-results > .show-about {
+    margin-top: 30px;
+    text-align: center;
+}
+
+section#about h1 {
+    margin-top: 10px;
+    margin-bottom: 0px;
+    font-size: 25px;
+}
+
+section#about .note {
+    color: rgb(128, 128, 128);
+}
+
+table {
+    border-spacing: 0;
+    border-collapse: collapse;
+}
+
+th,
+td {
+    padding: 5px;
+}
+
+th {
+    text-align: right;
+    color: rgb(128, 128, 128);
+}
+
+.gauge {
+    position: relative;
+    width: 738px;
+    height: 78px;
+    background-image: url(gauge.png);
+    background-size: 100% 100%;
+    background-repat: no-repeat;
+    margin: 0 auto;
+}
+
+.gauge > .window {
+    position: absolute;
+    left: 0;
+    top: 33px;
+    bottom: 0;
+    right: 0;
+    overflow: hidden;
+}
+
+.gauge > .window > .needle {
+    position: absolute;
+    left: 363px;
+    bottom: -88px;
+    width: 4px;
+    height: 400px;
+    background-color: rgb(247, 148, 29);
+
+    -webkit-transform: rotate(-70deg);
+    -webkit-transform-origin: 2px 400px;
+
+    -moz-transform: rotate(-70deg);
+    -moz-transform-origin: 2px 400px;
+
+    transform: rotate(-70deg);
+    transform-origin: 2px 400px;
+}
+
+@media (-webkit-min-device-pixel-ratio: 2), (min--moz-device-pixel-ratio: 2), (min-resolution: 2dppx), (min-resolution: 192dpi) {
+    #logo {
+        content: url(logo@2x.png); /* FIXME: This does not work in Firefox. */
+    }
+
+    .gauge {
+        background-image: url(gauge@2x.png);
+    }
+}
diff --git a/PerformanceTests/StyleBench/resources/main.js b/PerformanceTests/StyleBench/resources/main.js
new file mode 100644 (file)
index 0000000..2ab8777
--- /dev/null
@@ -0,0 +1,246 @@
+window.benchmarkClient = {
+    displayUnit: 'runs/min',
+    iterationCount: 10,
+    stepCount: null,
+    suitesCount: null,
+    _measuredValuesList: [],
+    _finishedTestCount: 0,
+    _progressCompleted: null,
+    willAddTestFrame: function (frame) {
+        var main = document.querySelector('main');
+        var style = getComputedStyle(main);
+        frame.style.left = main.offsetLeft + parseInt(style.borderLeftWidth) + parseInt(style.paddingLeft) + 'px';
+        frame.style.top = main.offsetTop + parseInt(style.borderTopWidth) + parseInt(style.paddingTop) + 'px';
+    },
+    willRunTest: function (suite, test) {
+        document.getElementById('info').textContent = suite.name + ' ( ' + this._finishedTestCount + ' / ' + this.stepCount + ' )';
+    },
+    didRunTest: function () {
+        this._finishedTestCount++;
+        this._progressCompleted.style.width = (this._finishedTestCount * 100 / this.stepCount) + '%';
+    },
+    didRunSuites: function (measuredValues) {
+        this._measuredValuesList.push(measuredValues);
+    },
+    willStartFirstIteration: function () {
+        this._measuredValuesList = [];
+        this._finishedTestCount = 0;
+        this._progressCompleted = document.getElementById('progress-completed');
+        document.getElementById('logo-link').onclick = function (event) { event.preventDefault(); return false; }
+    },
+    didFinishLastIteration: function () {
+        document.getElementById('logo-link').onclick = null;
+
+        var results = this._computeResults(this._measuredValuesList, this.displayUnit);
+
+        this._updateGaugeNeedle(results.mean);
+        document.getElementById('result-number').textContent = results.formattedMean;
+        if (results.formattedDelta)
+            document.getElementById('confidence-number').textContent = '\u00b1 ' + results.formattedDelta;
+
+        this._populateDetailedResults(results.formattedValues);
+        document.getElementById('results-with-statistics').textContent = results.formattedMeanAndDelta;
+
+        if (this.displayUnit == 'ms') {
+            document.getElementById('show-summary').style.display = 'none';
+            showResultDetails();
+        } else
+            showResultsSummary();
+    },
+    _computeResults: function (measuredValuesList, displayUnit) {
+        var suitesCount = this.suitesCount;
+        function valueForUnit(measuredValues) {
+            if (displayUnit == 'ms')
+                return measuredValues.geomean;
+            return measuredValues.score;
+        }
+
+        function sigFigFromPercentDelta(percentDelta) {
+            return Math.ceil(-Math.log(percentDelta)/Math.log(10)) + 3;
+        }
+
+        function toSigFigPrecision(number, sigFig) {
+            var nonDecimalDigitCount = number < 1 ? 0 : (Math.floor(Math.log(number)/Math.log(10)) + 1);
+            return number.toPrecision(Math.max(nonDecimalDigitCount, Math.min(6, sigFig)));
+        }
+
+        var values = measuredValuesList.map(valueForUnit);
+        var sum = values.reduce(function (a, b) { return a + b; }, 0);
+        var arithmeticMean = sum / values.length;
+        var meanSigFig = 4;
+        var formattedDelta;
+        var formattedPercentDelta;
+        if (window.Statistics) {
+            var delta = Statistics.confidenceIntervalDelta(0.95, values.length, sum, Statistics.squareSum(values));
+            if (!isNaN(delta)) {
+                var percentDelta = delta * 100 / arithmeticMean;
+                meanSigFig = sigFigFromPercentDelta(percentDelta);
+                formattedDelta = toSigFigPrecision(delta, 2);
+                formattedPercentDelta = toSigFigPrecision(percentDelta, 2) + '%';
+            }
+        }
+
+        var formattedMean = toSigFigPrecision(arithmeticMean, Math.max(meanSigFig, 3));
+
+        return {
+            formattedValues: values.map(function (value) {
+                return toSigFigPrecision(value, 4) + ' ' + displayUnit;
+            }),
+            mean: arithmeticMean,
+            formattedMean: formattedMean,
+            formattedDelta: formattedDelta,
+            formattedMeanAndDelta: formattedMean + (formattedDelta ? ' \xb1 ' + formattedDelta + ' (' + formattedPercentDelta + ')' : ''),
+        };
+    },
+    _addDetailedResultsRow: function (table, iterationNumber, value) {
+        var row = document.createElement('tr');
+        var th = document.createElement('th');
+        th.textContent = 'Iteration ' + (iterationNumber + 1);
+        var td = document.createElement('td');
+        td.textContent = value;
+        row.appendChild(th);
+        row.appendChild(td);
+        table.appendChild(row);
+    },
+    _updateGaugeNeedle: function (rpm) {
+        var needleAngle = Math.max(0, Math.min(rpm, 140)) - 70;
+        var needleRotationValue = 'rotate(' + needleAngle + 'deg)';
+
+        var gaugeNeedleElement = document.querySelector('#summarized-results > .gauge .needle');
+        gaugeNeedleElement.style.setProperty('-webkit-transform', needleRotationValue);
+        gaugeNeedleElement.style.setProperty('-moz-transform', needleRotationValue);
+        gaugeNeedleElement.style.setProperty('-ms-transform', needleRotationValue);
+        gaugeNeedleElement.style.setProperty('transform', needleRotationValue);
+    },
+    _populateDetailedResults: function (formattedValues) {
+        var resultsTables = document.querySelectorAll('.results-table');
+        var i = 0;
+        resultsTables[0].innerHTML = '';
+        for (; i < Math.ceil(formattedValues.length / 2); i++)
+            this._addDetailedResultsRow(resultsTables[0], i, formattedValues[i]);
+        resultsTables[1].innerHTML = '';
+        for (; i < formattedValues.length; i++)
+            this._addDetailedResultsRow(resultsTables[1], i, formattedValues[i]);
+    },
+    prepareUI: function () {
+        window.addEventListener('popstate', function (event) {
+            if (event.state) {
+                var sectionToShow = event.state.section;
+                if (sectionToShow) {
+                    var sections = document.querySelectorAll('main > section');
+                    for (var i = 0; i < sections.length; i++) {
+                        if (sections[i].id === sectionToShow)
+                            return showSection(sectionToShow, false);
+                    }
+                }
+            }
+            return showSection('home', false);
+        }, false);
+
+        function updateScreenSize() {
+            // FIXME: Detect when the window size changes during the test.
+            var screenIsTooSmall = window.innerWidth < 850 || window.innerHeight < 650;
+            document.getElementById('screen-size').textContent = window.innerWidth + 'px by ' + window.innerHeight + 'px';
+            document.getElementById('screen-size-warning').style.display = screenIsTooSmall ? null : 'none';
+        }
+
+        window.addEventListener('resize', updateScreenSize);
+        updateScreenSize();
+    }
+}
+
+function enableOneSuite(suites, suiteToEnable)
+{
+    suiteToEnable = suiteToEnable.toLowerCase();
+    var found = false;
+    for (var i = 0; i < suites.length; i++) {
+        var currentSuite = suites[i];
+        if (currentSuite.name.toLowerCase() == suiteToEnable) {
+            currentSuite.disabled = false;
+            found = true;
+        } else
+            currentSuite.disabled = true;
+    }
+    return found;
+}
+
+function startBenchmark() {
+    if (location.search.length > 1) {
+        var parts = location.search.substring(1).split('&');
+        for (var i = 0; i < parts.length; i++) {
+            var keyValue = parts[i].split('=');
+            var key = keyValue[0];
+            var value = keyValue[1];
+            switch (key) {
+            case 'unit':
+                if (value == 'ms')
+                    benchmarkClient.displayUnit = 'ms';
+                else
+                    console.error('Invalid unit: ' + value);
+                break;
+            case 'iterationCount':
+                var parsedValue = parseInt(value);
+                if (!isNaN(parsedValue))
+                    benchmarkClient.iterationCount = parsedValue;
+                else
+                    console.error('Invalid iteration count: ' + value);
+                break;
+            case 'suite':
+                if (!enableOneSuite(Suites, value)) {
+                    alert('Suite "' + value + '" does not exist. No tests to run.');
+                    return false;
+                }
+                break;
+            }
+        }
+    }
+
+    var enabledSuites = Suites.filter(function (suite) { return !suite.disabled; });
+    var totalSubtestsCount = enabledSuites.reduce(function (testsCount, suite) { return testsCount + suite.tests.length; }, 0);
+    benchmarkClient.stepCount = benchmarkClient.iterationCount * totalSubtestsCount;
+    benchmarkClient.suitesCount = enabledSuites.length;
+    var runner = new BenchmarkRunner(Suites, benchmarkClient);
+    runner.runMultipleIterations(benchmarkClient.iterationCount);
+
+    return true;
+}
+
+function showSection(sectionIdentifier, pushState) {
+    var currentSectionElement = document.querySelector('section.selected');
+    console.assert(currentSectionElement);
+
+    var newSectionElement = document.getElementById(sectionIdentifier);
+    console.assert(newSectionElement);
+
+    currentSectionElement.classList.remove('selected');
+    newSectionElement.classList.add('selected');
+
+    if (pushState)
+        history.pushState({section: sectionIdentifier}, document.title);
+}
+
+function showHome() {
+    showSection('home', true);
+}
+
+function startTest() {
+    if (startBenchmark())
+        showSection('running');
+}
+
+function showResultsSummary() {
+    showSection('summarized-results', true);
+}
+
+function showResultDetails() {
+    showSection('detailed-results', true);
+}
+
+function showAbout() {
+    showSection('about', true);
+}
+
+window.addEventListener('DOMContentLoaded', function () {
+    if (benchmarkClient.prepareUI)
+        benchmarkClient.prepareUI();
+});
index bd554c0..817e305 100644 (file)
@@ -1,20 +1,18 @@
 function makeSteps(count)
 {
-    let steps = [];
-    for (let i = 0; i < count; ++i) {
-        steps.push(new BenchmarkTestStep('Adding classes', (bench, contentWindow, contentDocument) => {
-            bench.addClasses(100);
-        }));
-        steps.push(new BenchmarkTestStep('Removing classes', (bench, contentWindow, contentDocument) => {
-            bench.removeClasses(100);
-        }));
-        steps.push(new BenchmarkTestStep('Adding leaf elements', (bench, contentWindow, contentDocument) => {
-            bench.addLeafElements(100);
-        }));
-        steps.push(new BenchmarkTestStep('Removing leaf elements', (bench, contentWindow, contentDocument) => {
-            bench.removeLeafElements(100);
-        }));
-    }
+    const steps = [];
+    steps.push(new BenchmarkTestStep('Adding classes', (bench, contentWindow, contentDocument) => {
+        bench.addClasses(100);
+    }));
+    steps.push(new BenchmarkTestStep('Removing classes', (bench, contentWindow, contentDocument) => {
+        bench.removeClasses(100);
+    }));
+    steps.push(new BenchmarkTestStep('Adding leaf elements', (bench, contentWindow, contentDocument) => {
+        bench.addLeafElements(100);
+    }));
+    steps.push(new BenchmarkTestStep('Removing leaf elements', (bench, contentWindow, contentDocument) => {
+        bench.removeLeafElements(100);
+    }));
     return steps;
 }
 
@@ -28,7 +26,7 @@ function makeSuite(configuration)
                 return contentWindow.createBenchmark(configuration);
             });
         },
-        tests: makeSteps(5),
+        tests: makeSteps(),
     };
 }