Rename DoYouEvenBench 0.17 to Speedometer 1.0 and add a new look.
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 2 Jun 2014 19:57:39 +0000 (19:57 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 2 Jun 2014 19:57:39 +0000 (19:57 +0000)
https://bugs.webkit.org/show_bug.cgi?id=133455

Reviewed by Timothy Hatcher.

Renamed the benchmark to Speedometer and added the new look designed by Timothy Hatcher.

Also changed the unit of measurements from milliseconds to runs-per-minute averaged over the number
of the benchmark suites (7 for 1.0). You can divide 420000 by the old benchmark score (in milliseconds)
to get the new value for the set of tests that are enabled by default in 1.0. You can continue to see
results in milliseconds on Full.html#ms.

* DoYouEvenBench/Full.html: Added a bunch of sections and the description of the benchmark.

* DoYouEvenBench/resources/benchmark-report.js: Remove the newly added content when ran inside a DRT or
WTR so that run-perf-tests wouldn't error.
* DoYouEvenBench/resources/benchmark-runner.js:
(BenchmarkRunner.prototype._appendFrame): Call a newly added willAddTestFrame callback when it exists.

* DoYouEvenBench/resources/gauge.png: Added.
* DoYouEvenBench/resources/gauge@2x.png: Added.
* DoYouEvenBench/resources/logo.png: Added.
* DoYouEvenBench/resources/logo@2x.png: Added.
* DoYouEvenBench/resources/main.css: Replaced the style.

* DoYouEvenBench/resources/main.js:
(window.benchmarkClient.willAddTestFrame): Place the iframe right where #testContainer is shown.
(window.benchmarkClient.willRunTest): Show the name of the suite (e.g. EmberJS-TodoMVC) to run next.
(window.benchmarkClient.didRunSuites):
(window.benchmarkClient.willStartFirstIteration): Initialize _timeValues and _finishedTestCount now that
we have an UI to run the benchmark multiple times without reloading the page.
(window.benchmarkClient.didFinishLastIteration): Split into smaller pieces.
(window.benchmarkClient._computeResults): Computes the mean and the statistics for the given time values,
and also format them in a human readable form.
(window.benchmarkClient._computeResults.totalTimeInDisplayUnit): Converts ms to runs/min.
(window.benchmarkClient._computeResults.sigFigFromPercentDelta): Given a percentage error (e.g. 1%),
returns the number of significant digits required for the mean.
(window.benchmarkClient._computeResults.toSigFigPrecision): Calls toPrecision with the specified precision
constrained to be at least the number of non-decimal digits and at most 6.
(window.benchmarkClient._addDetailedResultsRow): Renamed from _addResult. It now takes the table to which
to add a row and the iteration number.
(window.benchmarkClient._updateGaugeNeedle): Added. Controls the angle of the speed indicator.
(window.benchmarkClient._populateDetailedResults): Added.
(window.benchmarkClient.prepareUI): Added. It adds an event listener to show a specified section when
the push state of the document changes, and shows a warning sign when the view port size is too small.
We do this inside a callback to avoid running it inside DRT / WTR.
(startBenchmark):
(showSection): Added.
(startTest): Added.
(showResultsSummary): Added.
(showResultDetails): Added.
(showAbout): Added.

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

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

index 8e4d1bc..1a3b29d 100644 (file)
@@ -1,3 +1,58 @@
+2014-06-02  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Rename DoYouEvenBench 0.17 to Speedometer 1.0 and add a new look.
+        https://bugs.webkit.org/show_bug.cgi?id=133455
+
+        Reviewed by Timothy Hatcher.
+
+        Renamed the benchmark to Speedometer and added the new look designed by Timothy Hatcher.
+
+        Also changed the unit of measurements from milliseconds to runs-per-minute averaged over the number
+        of the benchmark suites (7 for 1.0). You can divide 420000 by the old benchmark score (in milliseconds)
+        to get the new value for the set of tests that are enabled by default in 1.0. You can continue to see
+        results in milliseconds on Full.html#ms.
+
+        * DoYouEvenBench/Full.html: Added a bunch of sections and the description of the benchmark.
+
+        * DoYouEvenBench/resources/benchmark-report.js: Remove the newly added content when ran inside a DRT or
+        WTR so that run-perf-tests wouldn't error.
+        * DoYouEvenBench/resources/benchmark-runner.js:
+        (BenchmarkRunner.prototype._appendFrame): Call a newly added willAddTestFrame callback when it exists.
+
+        * DoYouEvenBench/resources/gauge.png: Added.
+        * DoYouEvenBench/resources/gauge@2x.png: Added.
+        * DoYouEvenBench/resources/logo.png: Added.
+        * DoYouEvenBench/resources/logo@2x.png: Added.
+        * DoYouEvenBench/resources/main.css: Replaced the style.
+
+        * DoYouEvenBench/resources/main.js:
+        (window.benchmarkClient.willAddTestFrame): Place the iframe right where #testContainer is shown.
+        (window.benchmarkClient.willRunTest): Show the name of the suite (e.g. EmberJS-TodoMVC) to run next.
+        (window.benchmarkClient.didRunSuites):
+        (window.benchmarkClient.willStartFirstIteration): Initialize _timeValues and _finishedTestCount now that
+        we have an UI to run the benchmark multiple times without reloading the page.
+        (window.benchmarkClient.didFinishLastIteration): Split into smaller pieces.
+        (window.benchmarkClient._computeResults): Computes the mean and the statistics for the given time values,
+        and also format them in a human readable form.
+        (window.benchmarkClient._computeResults.totalTimeInDisplayUnit): Converts ms to runs/min.
+        (window.benchmarkClient._computeResults.sigFigFromPercentDelta): Given a percentage error (e.g. 1%),
+        returns the number of significant digits required for the mean.
+        (window.benchmarkClient._computeResults.toSigFigPrecision): Calls toPrecision with the specified precision
+        constrained to be at least the number of non-decimal digits and at most 6.
+        (window.benchmarkClient._addDetailedResultsRow): Renamed from _addResult. It now takes the table to which
+        to add a row and the iteration number.
+        (window.benchmarkClient._updateGaugeNeedle): Added. Controls the angle of the speed indicator.
+        (window.benchmarkClient._populateDetailedResults): Added.
+        (window.benchmarkClient.prepareUI): Added. It adds an event listener to show a specified section when
+        the push state of the document changes, and shows a warning sign when the view port size is too small.
+        We do this inside a callback to avoid running it inside DRT / WTR.
+        (startBenchmark):
+        (showSection): Added.
+        (startTest): Added.
+        (showResultsSummary): Added.
+        (showResultDetails): Added.
+        (showAbout): Added.
+
 2014-06-01  Ryosuke Niwa  <rniwa@webkit.org>
 
         DYEBench: Move test states into benchmarkClient and remove the closure
index a0c238e..419b9f2 100644 (file)
@@ -2,7 +2,7 @@
 <html>
 <head>
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-    <title>DoYouEvenBench v0.17</title>
+    <title>Speedometer 1.0</title>
     <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/tests.js" defer></script>
 </head>
 <body>
+<main>
+    <a id="logo-link" href="javascript:showHome()"><img id="logo" src="resources/logo.png"></a>
+
+    <section id="home" class="selected">
+        <p>
+            Speedometer is a browser benchmark that measures the responsiveness of Web applications.
+            It uses demo web applications to simulate user actions such as adding to-do items.
+        </p>
+        <p id="screen-size-warning"><strong>
+            Your browser window is too small. For most accurate results, please make the view port size at least 850px by 650px.<br>
+            It's currently <span id="screen-size"></span>.
+        </strong></p>
+        <div class="buttons">
+            <button onclick="startTest()">Start Test</button>
+        </div>
+        <p class="show-about"><a href="javascript:showAbout()">About Speedometer</a></p>
+    </section>
+
+    <section id="running">
+        <div id="testContainer"></div>
+        <div id="progress"><div id="progress-completed"></div></div>
+        <div id="info"></div>
+    </section>
+
+    <section id="summarized-results">
+        <h1>Runs / Minute</h1>
+        <div class="gauge"><div class="window"><div class="needle"></div></div></div>
+        <hr>
+        <div id="result-number"></div>
+        <div id="confidence-number"></div>
+        <div class="buttons">
+            <button onclick="startTest()">Test Again</button>
+            <button class="show-details" onclick="showResultDetails()">Details</button>
+        </div>
+    </section>
+
+    <section id="detailed-results">
+        <h1>Detailed Results</h1>
+        <table class="results-table"></table>
+        <table class="results-table"></table>
+        <div class="arithmetic-mean"><label>Arithmetic Mean:</label><span id="results-with-statistics"></span></div>
+        <div class="buttons">
+            <button onclick="startTest()">Test Again</button>
+            <button id="show-summary" onclick="showResultsSummary()">Summary</button>
+        </div>
+        <p class="show-about"><a href="javascript:showAbout()">About Speedometer</a></p>
+    </section>
+
+    <section id="about">
+        <h1>About Speedometer</h1>
+
+        <p>Speedometer measures simulated user interactions in web applications.</p>
+
+        <p>
+            The current benchmark uses TodoMVC to simulate user actions for adding, completing, and removing to-do items.
+            Speedometer repeats the same actions using DOM APIs &mdash;
+            a core set of web platform APIs used extensively in web applications &mdash;
+            as well as six popular JavaScript frameworks: Ember.js, Backbone.js, jQuery, AngularJS, React, and Flight.
+            Many of these frameworks are used on the most popular websites in the world, such as Facebook and Twitter.
+            The performance of these types of operations depends on the speed of the DOM APIs, the JavaScript engine,
+            CSS style resolution, layout, and other technologies.
+        </p>
+
+        <p>
+            Although user-driven actions like mouse movements and keyboard input cannot be accurately emulated in JavaScript,
+            Speedometer does its best to faithfully replay a typical workload within the demo applications.
+            To make the run time long enough to measure with the limited precision,
+            we synchronously execute a large number of the operations, such as adding one hundred to-do items.
+        </p>
+
+        <p>
+            Some browser engines use an optimization strategy of doing some work asynchronously to reduce the run time of synchronous operations.
+            While returning control back to JavaScript execution as soon as possible is worth pursuing,
+            a holistic, accurate measurement of web application performance involves measuring
+            when these related, asynchronous computations actually complete.
+            Thus, Speedometer measures the time browser spends executing those asynchronous tasks in Speedometer,
+            estimated as the time between when a zero-second delay timer is scheduled and when it is fired.</p>
+
+        <p class="note">
+            <strong>Note:</strong> Speedometer is not meant to compare the performance of different JavaScript frameworks.
+            The mechanism we use to simulate user actions is different for each framework,
+            and we’re forcing frameworks to do more work synchronously than needed in some cases to ensure run time can be measured.
+        </p>
+    </section>
+</main>
 </body>
 </html>
index f7424e5..c4b4c64 100644 (file)
     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) {
index 0b46731..36c03c4 100644 (file)
@@ -83,6 +83,9 @@ BenchmarkRunner.prototype._appendFrame = function (src) {
         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;
diff --git a/PerformanceTests/DoYouEvenBench/resources/gauge.png b/PerformanceTests/DoYouEvenBench/resources/gauge.png
new file mode 100644 (file)
index 0000000..35febef
Binary files /dev/null and b/PerformanceTests/DoYouEvenBench/resources/gauge.png differ
diff --git a/PerformanceTests/DoYouEvenBench/resources/gauge@2x.png b/PerformanceTests/DoYouEvenBench/resources/gauge@2x.png
new file mode 100644 (file)
index 0000000..215d7c2
Binary files /dev/null and b/PerformanceTests/DoYouEvenBench/resources/gauge@2x.png differ
diff --git a/PerformanceTests/DoYouEvenBench/resources/logo.png b/PerformanceTests/DoYouEvenBench/resources/logo.png
new file mode 100644 (file)
index 0000000..524a6c8
Binary files /dev/null and b/PerformanceTests/DoYouEvenBench/resources/logo.png differ
diff --git a/PerformanceTests/DoYouEvenBench/resources/logo@2x.png b/PerformanceTests/DoYouEvenBench/resources/logo@2x.png
new file mode 100644 (file)
index 0000000..1869587
Binary files /dev/null and b/PerformanceTests/DoYouEvenBench/resources/logo@2x.png differ
index 8eb9bf3..3c8e10c 100644 (file)
@@ -1,7 +1,285 @@
-caption { margin: 0; padding: 0; font-family: sans-serif; font-size: 1em; font-weight: bold; white-space: nowrap; }
-#progressContainer { padding: 605px 0 10px 0; width: 800px; }
-#progressContainer div { background-color: #ccc; width: 0; height: 5px; overflow: hidden; }
-table { font-family: sans-serif; }
-table, td, th { border: solid 1px #ccc; border-collapse: collapse; padding: 5px; }
-th { text-align: right; }
-td { text-align: left; }
+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: 20px;
+    font-size: 30px;
+}
+
+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);
+    }
+}
index f197824..887febb 100644 (file)
 window.benchmarkClient = {
     iterationCount: 20,
+    testsCount: null,
+    suitesCount: null,
     _timeValues: [],
     _finishedTestCount: 0,
-    _iterationNumber: 0,
-    _progress: null,
     _progressCompleted: null,
-    _resultContainer: null,
-    willRunTest: function () { },
+    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.testsCount + ' )';
+    },
     didRunTest: function () {
         this._finishedTestCount++;
         this._progressCompleted.style.width = (this._finishedTestCount * 100 / this.testsCount) + '%';
     },
     didRunSuites: function (measuredValues) {
         this._timeValues.push(measuredValues.total);
-        this._iterationNumber++;
-        this._addResult('Iteration ' + this._iterationNumber, measuredValues.total.toFixed(2) + ' ms');
     },
     willStartFirstIteration: function () {
-        // We don't use the real progress element as some implementations animate it.
-        this._progress = document.createElement('div');
-        this._progress.appendChild(document.createElement('div'));
-        this._progress.id = 'progressContainer';
-        document.body.appendChild(this._progress);
-        this._progressCompleted = this._progress.firstChild;
-
-        this._resultContainer = document.createElement('table');
-        var caption = document.createElement('caption');
-        caption.textContent = document.title;
-        this._resultContainer.appendChild(caption);
-        document.body.appendChild(this._resultContainer);
+        this._timeValues = [];
+        this._finishedTestCount = 0;
+        this._progressCompleted = document.getElementById('progress-completed');
+        document.getElementById('logo-link').onclick = function (event) { event.preventDefault(); return false; }
     },
     didFinishLastIteration: function () {
-        var values = this._timeValues;
+        document.getElementById('logo-link').onclick = null;
+
+        var displayUnit = location.search == '?ms' || location.hash == '#ms' ? 'ms' : 'runs/min';
+        var results = this._computeResults(this._timeValues, 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 (displayUnit == 'ms') {
+            document.getElementById('show-summary').style.display = 'none';
+            showResultDetails();
+        } else
+            showResultsSummary();
+    },
+    _computeResults: function (timeValues, displayUnit) {
+        var suitesCount = this.suitesCount;
+        function totalTimeInDisplayUnit(time) {
+            if (displayUnit == 'ms')
+                return time;
+            return 60 * 1000 * suitesCount / time;
+        }
+
+        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 = timeValues.map(totalTimeInDisplayUnit);
         var sum = values.reduce(function (a, b) { return a + b; }, 0);
         var arithmeticMean = sum / values.length;
-        var meanLabel = arithmeticMean.toFixed(2) + ' ms';
+        var meanSigFig = 4;
+        var formattedDelta;
+        var formattedPercentDelta;
         if (window.Statistics) {
             var delta = Statistics.confidenceIntervalDelta(0.95, values.length, sum, Statistics.squareSum(values));
-            var precentDelta = delta * 100 / arithmeticMean;
-            meanLabel += ' \xb1 ' + delta.toFixed(2) + ' ms (' + precentDelta.toFixed(2) + '%)';
+            if (!isNaN(delta)) {
+                var percentDelta = delta * 100 / arithmeticMean;
+                meanSigFig = sigFigFromPercentDelta(percentDelta);
+                formattedDelta = toSigFigPrecision(delta, 2);
+                formattedPercentDelta = toSigFigPrecision(percentDelta, 2) + '%';
+            }
         }
-        this._addResult('Arithmetic Mean', meanLabel);
-        this._progress.parentNode.removeChild(this._progress);
+
+        var formattedMean = toSigFigPrecision(arithmeticMean, Math.max(meanSigFig, 3));
+
+        return {
+            formattedValues: timeValues.map(function (time) {
+                return toSigFigPrecision(totalTimeInDisplayUnit(time), 4) + ' ' + displayUnit;
+            }),
+            mean: arithmeticMean,
+            formattedMean: formattedMean,
+            formattedDelta: formattedDelta,
+            formattedMeanAndDelta: formattedMean + (formattedDelta ? ' \xb1 ' + formattedDelta + ' (' + formattedPercentDelta + ')' : ''),
+        };
     },
-    _addResult: function (title, value) {
+    _addDetailedResultsRow: function (table, iterationNumber, value) {
         var row = document.createElement('tr');
         var th = document.createElement('th');
-        th.textContent = title;
+        th.textContent = 'Iteration ' + (iterationNumber + 1);
         var td = document.createElement('td');
         td.textContent = value;
         row.appendChild(th);
         row.appendChild(td);
-        this._resultContainer.appendChild(row);
+        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();
     }
 }
 
@@ -59,8 +153,47 @@ function startBenchmark() {
     var enabledSuites = Suites.filter(function (suite) { return !suite.disabled });
     var totalSubtestCount = enabledSuites.reduce(function (testsCount, suite) { return testsCount + suite.tests.length; }, 0);
     benchmarkClient.testsCount = benchmarkClient.iterationCount * totalSubtestCount;
+    benchmarkClient.suitesCount = enabledSuites.length;
     var runner = new BenchmarkRunner(Suites, benchmarkClient);
     runner.runMultipleIterations(benchmarkClient.iterationCount);
 }
 
-window.onload = startBenchmark;
+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() {
+    showSection('running');
+    startBenchmark();
+}
+
+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();
+});