<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[194523] trunk/PerformanceTests</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/194523">194523</a></dd>
<dt>Author</dt> <dd>jonlee@apple.com</dd>
<dt>Date</dt> <dd>2016-01-03 18:36:57 -0800 (Sun, 03 Jan 2016)</dd>
</dl>

<h3>Log Message</h3>
<pre>Update data reporting and analysis
https://bugs.webkit.org/show_bug.cgi?id=152670

Reviewed by Simon Fraser.

Show new graph data. Provide controls to show different series data. Provide an
interactive cursor that shows the data at a given sample.

* Animometer/developer.html: Add a nav section in #results. Each part of the graph
has a checkbox for visual toggling, as well as companion spans to contain the data.
        The numbers will always be shown even if the SVG isn't.
* Animometer/resources/debug-runner/animometer.css:
(#suites): Adjust spacing when doing fixed complexity.
(#test-graph nav): Place the nav in the upper right corner.
(#test-graph-data &gt; svg): Fix the FPS scale from 0-60. It makes the raw FPS goes past
that scale. Allow it to show.
(.target-fps): Add a dotted line for where the benchmark is supposed to settle for FPS.
(#cursor line): The cursor contains a line and highlight circles for the data being shown.
(#cursor circle):
(#complexity path): This and rules afterward are named by series type.
(#complexity circle):
(#filteredFPS path):
(#filteredFPS circle):
(#rawFPS path):
(#intervalFPS circle):
(.left-samples): Deleted.
(.right-samples): Deleted.
* Animometer/resources/debug-runner/animometer.js:
(initialize): Add a &quot;changed&quot; listener when the checkboxes change in the nav.
(onBenchmarkOptionsChanged): Renamed.
(showTestGraph): All graph data is passed in as graphData instead of as arguments.
* Animometer/resources/debug-runner/graph.js: Extend BenchmarkController. When showing
a new graph, call updateGraphData(). It creates all of the d3 graphs. onGraphOptionsChanged()
toggles the data on and off.
(updateGraphData): Add the axes. Add the average lines and markers for sample time and
target FPS. Add the cursor group. Use helper function addData() to add the data. On top of
everything add a transparent area which will catch all of the mouse events. When the mouse
moves in the graph, find the closest data point, show the data in the nav area, and highlight
the data points.
(addData): Adds a line and circle for each data point. Also adds a highlight cursor with a
size a little larger than the circle radius for the data points.
(onGraphOptionsChanged): Called when data is visually toggled.
(showOrHideNodes): Helper function to toggle the .hidden class.
* Animometer/resources/extensions.js:
(ResultsDashboard.prototype.get data): Get rid of the arguments for _processData.
(ResultsTable.prototype._addGraphButton): Shove all of the graph data into a singular object.

Producing the JSON can take a while with all of the data. Make it on-demand with a
button.

* Animometer/resources/debug-runner/animometer.js:
(showResults): When showing the results, don't serialize the JSON data. Move that to...
(showJSONResults): ...here. Remove the button.

* Animometer/developer.html: Add a button. The button will remove itself and populate
the textarea with the JSON data.
* Animometer/resources/debug-runner/animometer.css:
(.hidden): Add a universal hidden class.
(#results button.small-button): Promote the small-button styles to the whole results
section for use in the JSON button.
(#results button.small-button:active):
(#results-data button.small-button): Deleted.
(#results-data button.small-button:active): Deleted.

Refactor how Animator does its recording.

* Animometer/tests/resources/math.js: Create a new, simple estimator that just returns
the same interval frame rate for adjustment.
* Animometer/tests/resources/main.js:
(Animator): Remove _dropFrameCount, and make variables more accurate described.
(Animator.prototype.initialize): Use the identity estimator instead of using a bool.
(Animator.prototype._intervalTimeDelta): Rename, only used internally.
(Animator.prototype._shouldRequestAnotherFrame): Assume we drop one frame for adjustment
of the scene. If we are within the number of frames to measure for the interval, just
record the timestamp. Otherwise we are ready to evaluate and adjust the scene. Record
the interval frame rate and the estimator's frame rate.

Avoid processing the data through the Experiment while the test is running. Reconfigure
the sampler to just record the raw samples. After the test is done, run the samples through
the Experiment to get the score.

* Animometer/resources/sampler.js:
(Experiment): Fold _init() into the constructor since nobody else will call it. This is not
needed until the test concludes, so remove startSampling(). Clients should just call sample().
(Sampler): Pre-allocate arrays given the number of data points being recorded, and a capacity
of how many samples will be used. The processor is a called when it's time to process the data
since that is the client also telling the Sampler what to record.
        Introduce the notion of marks as well, which allows the client to mark when an
event occurs. When we mark sample start, we can attach the timestamp there, instead of storing
it separately.
(Sampler.prototype.startSampling): Deleted. Clients should just call record().
(Sampler.prototype.record): The data to record is passed in as variable arguments.
(Sampler.prototype.mark): When adding a mark, a client needs to provide a unique string, and
can provide extra data object for later retrieval.
(Sampler.prototype.process): Renamed from toJSON. Trim the sampling arrays to what was used.
Call the processor to process the samples.
* Animometer/resources/debug-runner/benchmark-runner.js:
(BenchmarkRunner.prototype._runBenchmarkAndRecordResults): Call process().

* Animometer/resources/strings.js: Add some new strings, remove the graph ones since they are
not used.
* Animometer/tests/resources/main.js:
(Benchmark): Create a sampler with 4 series. The maximum number of points expected is the
number of seconds multiplied by 60 fps. Benchmark, as a client of the Sampler, knows about all
of the data being added to the Sampler. It is added through record(), and processed through
processSamples().
(Benchmark.prototype.update): Mark when we've passed warmup and are starting to sample. Include
the timestamp in the custom data for the mark. This avoids the need to store is separately in
the Sampler. Fold what was in record() here, since nothing else needs this functionality.
record() now just relays the information to the sampler.
(Benchmark.prototype.record): Called by Animator, which provides the data to the sampler.
Animator's calls to this is part of a later patch. Requires each stage to return its complexity.
(Benchmark.prototype.processSamples): If the sampling mark exists, add it to the results.
        Go through all of the samples. All samples contain a timestamp and complexity. We
calculate &quot;raw FPS&quot; which is the time differential from the previous sample. At regular intervals
the Kalman-filtered FPS and the interval average FPS are also recorded. We also create two
experiments, to get the scores for the complexity and smoothed FPS, and add those samples to
the experiments. Grab those scores and add them into results also.

Add complexity() to the tests for Benchmark.record().
* Animometer/tests/bouncing-particles/resources/bouncing-particles.js:
* Animometer/tests/misc/resources/canvas-electrons.js:
* Animometer/tests/misc/resources/canvas-stars.js:
* Animometer/tests/text/resources/layering-text.js:</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkPerformanceTestsAnimometerdeveloperhtml">trunk/PerformanceTests/Animometer/developer.html</a></li>
<li><a href="#trunkPerformanceTestsAnimometerresourcesdebugrunneranimometercss">trunk/PerformanceTests/Animometer/resources/debug-runner/animometer.css</a></li>
<li><a href="#trunkPerformanceTestsAnimometerresourcesdebugrunneranimometerjs">trunk/PerformanceTests/Animometer/resources/debug-runner/animometer.js</a></li>
<li><a href="#trunkPerformanceTestsAnimometerresourcesdebugrunnerbenchmarkrunnerjs">trunk/PerformanceTests/Animometer/resources/debug-runner/benchmark-runner.js</a></li>
<li><a href="#trunkPerformanceTestsAnimometerresourcesdebugrunnergraphjs">trunk/PerformanceTests/Animometer/resources/debug-runner/graph.js</a></li>
<li><a href="#trunkPerformanceTestsAnimometerresourcesextensionsjs">trunk/PerformanceTests/Animometer/resources/extensions.js</a></li>
<li><a href="#trunkPerformanceTestsAnimometerresourcessamplerjs">trunk/PerformanceTests/Animometer/resources/sampler.js</a></li>
<li><a href="#trunkPerformanceTestsAnimometerresourcesstringsjs">trunk/PerformanceTests/Animometer/resources/strings.js</a></li>
<li><a href="#trunkPerformanceTestsAnimometertestsbouncingparticlesresourcesbouncingparticlesjs">trunk/PerformanceTests/Animometer/tests/bouncing-particles/resources/bouncing-particles.js</a></li>
<li><a href="#trunkPerformanceTestsAnimometertestsmiscresourcescanvaselectronsjs">trunk/PerformanceTests/Animometer/tests/misc/resources/canvas-electrons.js</a></li>
<li><a href="#trunkPerformanceTestsAnimometertestsmiscresourcescanvasstarsjs">trunk/PerformanceTests/Animometer/tests/misc/resources/canvas-stars.js</a></li>
<li><a href="#trunkPerformanceTestsAnimometertestsresourcesmainjs">trunk/PerformanceTests/Animometer/tests/resources/main.js</a></li>
<li><a href="#trunkPerformanceTestsAnimometertestsresourcesmathjs">trunk/PerformanceTests/Animometer/tests/resources/math.js</a></li>
<li><a href="#trunkPerformanceTestsAnimometerteststextresourceslayeringtextjs">trunk/PerformanceTests/Animometer/tests/text/resources/layering-text.js</a></li>
<li><a href="#trunkPerformanceTestsChangeLog">trunk/PerformanceTests/ChangeLog</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkPerformanceTestsAnimometerdeveloperhtml"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/Animometer/developer.html (194522 => 194523)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/Animometer/developer.html        2016-01-04 01:19:38 UTC (rev 194522)
+++ trunk/PerformanceTests/Animometer/developer.html        2016-01-04 02:36:57 UTC (rev 194523)
</span><span class="lines">@@ -78,8 +78,11 @@
</span><span class="cx">                 &lt;table id=&quot;results-header&quot;&gt;&lt;/table&gt;
</span><span class="cx">             &lt;/div&gt;
</span><span class="cx">             &lt;div id=&quot;results-json&quot;&gt;
</span><del>-                JSON:
-                &lt;textarea rows=1 onclick=&quot;this.focus();this.select()&quot; readonly&gt;&lt;/textarea&gt;
</del><ins>+                &lt;button class=&quot;small-button&quot; onclick=&quot;benchmarkController.showJSONResults()&quot;&gt;JSON results&lt;/button&gt;
+                &lt;div class=&quot;hidden&quot;&gt;
+                    JSON:
+                    &lt;textarea rows=1 onclick=&quot;this.focus();this.select()&quot; readonly&gt;&lt;/textarea&gt;
+                &lt;/div&gt;
</ins><span class="cx">             &lt;/div&gt;
</span><span class="cx">             &lt;button onclick=&quot;benchmarkController.startBenchmark()&quot;&gt;Test Again&lt;/button&gt;
</span><span class="cx">             &lt;p&gt;'s': Select different data for copy/paste&lt;/p&gt;
</span><span class="lines">@@ -89,6 +92,23 @@
</span><span class="cx">                 &lt;button onclick=&quot;benchmarkController.showResults()&quot;&gt;&amp;lt; Results&lt;/button&gt;
</span><span class="cx">                 &lt;h1&gt;Graph:&lt;/h1&gt;
</span><span class="cx">             &lt;/header&gt;
</span><ins>+            &lt;nav&gt;
+                &lt;form name=&quot;graph-options&quot;&gt;
+                    &lt;ul&gt;
+                        &lt;li&gt;&lt;label&gt;&lt;input type=&quot;checkbox&quot; name=&quot;markers&quot; checked&gt; Markers&lt;/label&gt;
+                            &lt;span&gt;time: &lt;span class=&quot;time&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
+                        &lt;li&gt;&lt;label&gt;&lt;input type=&quot;checkbox&quot; name=&quot;averages&quot; checked&gt; Averages&lt;/label&gt;&lt;/li&gt;
+                        &lt;li&gt;&lt;label&gt;&lt;input type=&quot;checkbox&quot; name=&quot;complexity&quot; checked&gt; Complexity&lt;/label&gt;
+                            &lt;span class=&quot;complexity&quot;&gt;&lt;/span&gt;&lt;/li&gt;
+                        &lt;li&gt;&lt;label&gt;&lt;input type=&quot;checkbox&quot; name=&quot;rawFPS&quot;&gt; Raw FPS&lt;/label&gt;
+                            &lt;span class=&quot;rawFPS&quot;&gt;&lt;/span&gt;&lt;/li&gt;
+                        &lt;li&gt;&lt;label&gt;&lt;input type=&quot;checkbox&quot; name=&quot;filteredFPS&quot; checked&gt; Filtered FPS&lt;/label&gt;
+                            &lt;span class=&quot;filteredFPS&quot;&gt;&lt;/span&gt;&lt;/li&gt;
+                        &lt;li&gt;&lt;label&gt;&lt;input type=&quot;checkbox&quot; name=&quot;intervalFPS&quot;&gt; Average FPS per sample interval&lt;/label&gt;
+                            &lt;span class=&quot;intervalFPS&quot;&gt;&lt;/span&gt;&lt;/li&gt;
+                    &lt;/ul&gt;
+                &lt;/form&gt;
+            &lt;/nav&gt;
</ins><span class="cx">             &lt;p class=&quot;score&quot;&gt;&lt;/p&gt;
</span><span class="cx">             &lt;p class=&quot;mean&quot;&gt;&lt;/p&gt;
</span><span class="cx">             &lt;div id=&quot;test-graph-data&quot;&gt;&lt;/div&gt;
</span></span></pre></div>
<a id="trunkPerformanceTestsAnimometerresourcesdebugrunneranimometercss"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/Animometer/resources/debug-runner/animometer.css (194522 => 194523)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/Animometer/resources/debug-runner/animometer.css        2016-01-04 01:19:38 UTC (rev 194522)
+++ trunk/PerformanceTests/Animometer/resources/debug-runner/animometer.css        2016-01-04 02:36:57 UTC (rev 194523)
</span><span class="lines">@@ -3,6 +3,10 @@
</span><span class="cx">     color: rgb(235, 235, 235);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+.hidden {
+    display: none;
+}
+
</ins><span class="cx"> h1 {
</span><span class="cx">     margin: 5vh 0;
</span><span class="cx"> }
</span><span class="lines">@@ -139,6 +143,7 @@
</span><span class="cx"> 
</span><span class="cx"> #suites {
</span><span class="cx">     padding-left: 15vw;
</span><ins>+    padding-right: 2em;
</ins><span class="cx">     flex: 0 1 40%;
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="lines">@@ -149,7 +154,7 @@
</span><span class="cx"> #intro input[type=&quot;number&quot;] {
</span><span class="cx">     width: 50px;
</span><span class="cx"> }
</span><del>- 
</del><ins>+
</ins><span class="cx"> #suites input[type=&quot;number&quot;] {
</span><span class="cx">     display: none;
</span><span class="cx">     float: right;
</span><span class="lines">@@ -266,6 +271,20 @@
</span><span class="cx">     font-size: 2em;
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+#results button.small-button {
+    border: 1px solid rgba(235, 235, 235, .9);
+    color: rgba(235, 235, 235, .9);
+    border-radius: 2px;
+    padding: 1px 4px;
+    margin: 0 0 0 1em;
+    font-size: 9px;
+}
+
+#results button.small-button:active {
+    background-color: rgba(235, 235, 235, .2);
+    color: inherit;
+}
+
</ins><span class="cx"> .score {
</span><span class="cx">     font-size: 3em;
</span><span class="cx"> }
</span><span class="lines">@@ -287,20 +306,6 @@
</span><span class="cx">     padding-left: .25em;
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-#results-data button.small-button {
-    border: 1px solid rgba(235, 235, 235, .9);
-    color: rgba(235, 235, 235, .9);
-    border-radius: 2px;
-    padding: 1px 4px;
-    margin: 0 0 0 1em;
-    font-size: 9px;
-}
-
-#results-data button.small-button:active {
-    background-color: rgba(235, 235, 235, .2);
-    color: inherit;
-}
-
</del><span class="cx"> #results-tables td.noisy-results {
</span><span class="cx">     color: rgb(255, 104, 104);
</span><span class="cx"> }
</span><span class="lines">@@ -346,6 +351,36 @@
</span><span class="cx">     align-self: stretch;
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+#test-graph nav {
+    position: absolute;
+    top: 1.5em;
+    right: 0;
+    font-size: .8em;
+    width: 25%;
+}
+
+#test-graph nav ul {
+    margin: 0 30px 0 0;
+    padding: 0;
+    list-style: none;
+}
+
+#test-graph nav ul ul {
+    padding-left: 2em;
+}
+
+#test-graph nav li {
+    padding: .1em 0;
+}
+
+#test-graph nav li &gt; span {
+    float: right;
+}
+
+#test-graph nav.hide-data span {
+    display: none;
+}
+
</ins><span class="cx"> /* -------------------------------------------------------------------------- */
</span><span class="cx"> /*                           Graph Section                                    */
</span><span class="cx"> /* -------------------------------------------------------------------------- */
</span><span class="lines">@@ -357,6 +392,7 @@
</span><span class="cx"> 
</span><span class="cx"> #test-graph-data &gt; svg {
</span><span class="cx">     fill: none;
</span><ins>+    overflow: visible;
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> .axis path,
</span><span class="lines">@@ -366,16 +402,6 @@
</span><span class="cx">     shape-rendering: crispEdges;
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-.left-samples {
-    stroke: #7ADD49;
-    stroke-width: 1.5px;
-}
-
-.right-samples {
-    stroke: #FA4925;
-    stroke-width: 1.5px;
-}
-
</del><span class="cx"> .sample-time {
</span><span class="cx">     stroke: #5493D6;
</span><span class="cx"> }
</span><span class="lines">@@ -385,7 +411,50 @@
</span><span class="cx">     opacity: .8;
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+.target-fps {
+    stroke: rgba(250, 73, 37, .4);
+    stroke-width: 1px;
+    stroke-dasharray: 10, 10;
+}
+
</ins><span class="cx"> .right-mean {
</span><span class="cx">     stroke: #FA4925;
</span><span class="cx">     opacity: .8;
</span><span class="cx"> }
</span><ins>+
+#cursor line {
+    stroke: rgb(250, 250, 250);
+    stroke-width: 1px;
+}
+
+#cursor circle {
+    fill: rgb(250, 250, 250);
+}
+
+#complexity path {
+    stroke: rgba(122, 221, 73, .7);
+    stroke-width: 2px;
+}
+
+#complexity circle {
+    fill: rgb(122, 221, 73);
+}
+
+#filteredFPS path {
+    stroke: rgba(250, 73, 37, .7);
+    stroke-width: 2px;
+}
+
+#filteredFPS circle {
+    fill: rgb(250, 73, 37);
+}
+
+#rawFPS path {
+    stroke: rgba(250, 73, 37, .7);
+    stroke-width: 1px;
+}
+
+#rawFPS circle,
+#intervalFPS circle {
+    fill: rgb(250, 73, 37);
+}
</ins></span></pre></div>
<a id="trunkPerformanceTestsAnimometerresourcesdebugrunneranimometerjs"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/Animometer/resources/debug-runner/animometer.js (194522 => 194523)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/Animometer/resources/debug-runner/animometer.js        2016-01-04 01:19:38 UTC (rev 194522)
+++ trunk/PerformanceTests/Animometer/resources/debug-runner/animometer.js        2016-01-04 02:36:57 UTC (rev 194523)
</span><span class="lines">@@ -308,7 +308,8 @@
</span><span class="cx"> Utilities.extendObject(window.benchmarkController, {
</span><span class="cx">     initialize: function()
</span><span class="cx">     {
</span><del>-        document.forms[&quot;benchmark-options&quot;].addEventListener(&quot;change&quot;, benchmarkController.onFormChanged, true);
</del><ins>+        document.forms[&quot;benchmark-options&quot;].addEventListener(&quot;change&quot;, benchmarkController.onBenchmarkOptionsChanged, true);
+        document.forms[&quot;graph-options&quot;].addEventListener(&quot;change&quot;, benchmarkController.onGraphOptionsChanged, true);
</ins><span class="cx">         optionsManager.updateUIFromLocalStorage();
</span><span class="cx">         suitesManager.createElements();
</span><span class="cx">         suitesManager.updateUIFromLocalStorage();
</span><span class="lines">@@ -316,7 +317,7 @@
</span><span class="cx">         suitesManager.updateEditsElementsState();
</span><span class="cx">     },
</span><span class="cx"> 
</span><del>-    onFormChanged: function(event)
</del><ins>+    onBenchmarkOptionsChanged: function(event)
</ins><span class="cx">     {
</span><span class="cx">         if (event.target.name == &quot;adjustment&quot;) {
</span><span class="cx">             suitesManager.updateEditsElementsState();
</span><span class="lines">@@ -346,22 +347,28 @@
</span><span class="cx">         sectionsManager.populateTable(&quot;results-header&quot;, Headers.testName, data);
</span><span class="cx">         sectionsManager.populateTable(&quot;results-score&quot;, Headers.score, data);
</span><span class="cx">         sectionsManager.populateTable(&quot;results-data&quot;, Headers.details, data);
</span><ins>+        sectionsManager.showSection(&quot;results&quot;, true);
+
+        suitesManager.updateLocalStorageFromJSON(data[0]);
+    },
+
+    showJSONResults: function()
+    {
</ins><span class="cx">         document.querySelector(&quot;#results-json textarea&quot;).textContent = JSON.stringify(benchmarkRunnerClient.results.data, function(key, value) {
</span><span class="cx">             if (typeof value == &quot;number&quot;)
</span><span class="cx">                 return value.toFixed(2);
</span><span class="cx">             return value;
</span><span class="cx">         });
</span><del>-        sectionsManager.showSection(&quot;results&quot;, true);
-
-        suitesManager.updateLocalStorageFromJSON(data[0]);
</del><ins>+        document.querySelector(&quot;#results-json button&quot;).remove();
+        document.querySelector(&quot;#results-json div&quot;).classList.remove(&quot;hidden&quot;);
</ins><span class="cx">     },
</span><span class="cx"> 
</span><del>-    showTestGraph: function(testName, score, mean, axes, samples, samplingTimeOffset)
</del><ins>+    showTestGraph: function(testName, score, mean, graphData)
</ins><span class="cx">     {
</span><span class="cx">         sectionsManager.setSectionHeader(&quot;test-graph&quot;, testName);
</span><span class="cx">         sectionsManager.setSectionScore(&quot;test-graph&quot;, score, mean);
</span><span class="cx">         sectionsManager.showSection(&quot;test-graph&quot;, true);
</span><del>-        graph(&quot;#test-graph-data&quot;, new Insets(10, 20, 30, 40), axes, samples, samplingTimeOffset);
</del><ins>+        this.updateGraphData(graphData);
</ins><span class="cx">     }
</span><span class="cx"> });
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkPerformanceTestsAnimometerresourcesdebugrunnerbenchmarkrunnerjs"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/Animometer/resources/debug-runner/benchmark-runner.js (194522 => 194523)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/Animometer/resources/debug-runner/benchmark-runner.js        2016-01-04 01:19:38 UTC (rev 194522)
+++ trunk/PerformanceTests/Animometer/resources/debug-runner/benchmark-runner.js        2016-01-04 02:36:57 UTC (rev 194523)
</span><span class="lines">@@ -96,7 +96,7 @@
</span><span class="cx">         var benchmark = new contentWindow.benchmarkClass(options);
</span><span class="cx">         benchmark.run().then(function(sampler) {
</span><span class="cx">             var samplers = self._suitesSamplers[suite.name] || {};
</span><del>-            samplers[test.name] = sampler.toJSON(true, true);
</del><ins>+            samplers[test.name] = sampler.process(options);
</ins><span class="cx">             self._suitesSamplers[suite.name] = samplers;
</span><span class="cx"> 
</span><span class="cx">             if (self._client &amp;&amp; self._client.didRunTest)
</span></span></pre></div>
<a id="trunkPerformanceTestsAnimometerresourcesdebugrunnergraphjs"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/Animometer/resources/debug-runner/graph.js (194522 => 194523)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/Animometer/resources/debug-runner/graph.js        2016-01-04 01:19:38 UTC (rev 194522)
+++ trunk/PerformanceTests/Animometer/resources/debug-runner/graph.js        2016-01-04 02:36:57 UTC (rev 194523)
</span><span class="lines">@@ -1,142 +1,252 @@
</span><del>-function graph(selector, margins, axes, samples, samplingTimeOffset)
-{
-    var element = document.querySelector(selector);
-    element.innerHTML = '';
</del><ins>+Utilities.extendObject(window.benchmarkController, {
+    updateGraphData: function(graphData)
+    {
+        var element = document.getElementById(&quot;test-graph-data&quot;);
+        element.innerHTML = &quot;&quot;;
+        var margins = new Insets(10, 30, 30, 40);
+        var size = Point.elementClientSize(element).subtract(margins.size);
</ins><span class="cx"> 
</span><del>-    var size = Point.elementClientSize(element).subtract(margins.size);
</del><ins>+        var svg = d3.select(&quot;#test-graph-data&quot;).append(&quot;svg&quot;)
+            .attr(&quot;width&quot;, size.width + margins.left + margins.right)
+            .attr(&quot;height&quot;, size.height + margins.top + margins.bottom)
+            .append(&quot;g&quot;)
+                .attr(&quot;transform&quot;, &quot;translate(&quot; + margins.left + &quot;,&quot; + margins.top + &quot;)&quot;);
</ins><span class="cx"> 
</span><del>-    var x = d3.scale.linear()
-            .range([0, size.width])
-            .domain(d3.extent(samples, function(d) { return d.timeOffset; }));            
</del><ins>+        var axes = graphData.axes;
+        var targetFPS = graphData.targetFPS;
</ins><span class="cx"> 
</span><del>-    var yLeft = d3.scale.linear()
-            .range([size.height, 0])
-            .domain([0, d3.max(samples, function(d) { return d.values[0]; })]);
</del><ins>+        // Axis scales
+        var x = d3.scale.linear()
+                .range([0, size.width])
+                .domain([0, d3.max(graphData.samples, function(s) { return s.time; })]);
+        var yLeft = d3.scale.linear()
+                .range([size.height, 0])
+                .domain([0, d3.max(graphData.samples, function(s) { return s.complexity; })]);
+        var yRight = d3.scale.linear()
+                .range([size.height, 0])
+                .domain([0, 60]);
</ins><span class="cx"> 
</span><del>-    var yRight = d3.scale.linear()
-            .range([size.height, 0])
-            .domain([0, d3.max(samples, function(d) { return d.values[1]; })]);
</del><ins>+        // Axes
+        var xAxis = d3.svg.axis()
+                .scale(x)
+                .orient(&quot;bottom&quot;);
+        var yAxisLeft = d3.svg.axis()
+                .scale(yLeft)
+                .orient(&quot;left&quot;);
+        var yAxisRight = d3.svg.axis()
+                .scale(yRight)
+                .orient(&quot;right&quot;);
</ins><span class="cx"> 
</span><del>-    var xAxis = d3.svg.axis()
-            .scale(x)
-            .orient(&quot;bottom&quot;);
</del><ins>+        // x-axis
+        svg.append(&quot;g&quot;)
+            .attr(&quot;class&quot;, &quot;x axis&quot;)
+            .attr(&quot;fill&quot;, &quot;rgb(235, 235, 235)&quot;)
+            .attr(&quot;transform&quot;, &quot;translate(0,&quot; + size.height + &quot;)&quot;)
+            .call(xAxis)
+            .append(&quot;text&quot;)
+                .attr(&quot;class&quot;, &quot;label&quot;)
+                .attr(&quot;x&quot;, size.width)
+                .attr(&quot;y&quot;, -6)
+                .attr(&quot;fill&quot;, &quot;rgb(235, 235, 235)&quot;)
+                .style(&quot;text-anchor&quot;, &quot;end&quot;)
+                .text(&quot;time&quot;);
</ins><span class="cx"> 
</span><del>-    var yAxisLeft = d3.svg.axis()
-            .scale(yLeft)
-            .orient(&quot;left&quot;);
</del><ins>+        // yLeft-axis
+        svg.append(&quot;g&quot;)
+            .attr(&quot;class&quot;, &quot;y axis&quot;)
+            .attr(&quot;fill&quot;, &quot;#7ADD49&quot;)
+            .call(yAxisLeft)
+            .append(&quot;text&quot;)
+                .attr(&quot;class&quot;, &quot;label&quot;)
+                .attr(&quot;transform&quot;, &quot;rotate(-90)&quot;)
+                .attr(&quot;y&quot;, 6)
+                .attr(&quot;fill&quot;, &quot;#7ADD49&quot;)
+                .attr(&quot;dy&quot;, &quot;.71em&quot;)
+                .style(&quot;text-anchor&quot;, &quot;end&quot;)
+                .text(axes[0]);
</ins><span class="cx"> 
</span><del>-    var yAxisRight = d3.svg.axis()
-            .scale(yRight)
-            .orient(&quot;right&quot;);
</del><ins>+        // yRight-axis
+        svg.append(&quot;g&quot;)
+            .attr(&quot;class&quot;, &quot;y axis&quot;)
+            .attr(&quot;fill&quot;, &quot;#FA4925&quot;)
+            .attr(&quot;transform&quot;, &quot;translate(&quot; + size.width + &quot;, 0)&quot;)
+            .call(yAxisRight)
+            .append(&quot;text&quot;)
+                .attr(&quot;class&quot;, &quot;label&quot;)
+                .attr(&quot;transform&quot;, &quot;rotate(-90)&quot;)
+                .attr(&quot;y&quot;, 6)
+                .attr(&quot;fill&quot;, &quot;#FA4925&quot;)
+                .attr(&quot;dy&quot;, &quot;.71em&quot;)
+                .style(&quot;text-anchor&quot;, &quot;end&quot;)
+                .text(axes[1]);
</ins><span class="cx"> 
</span><del>-    var lineLeft = d3.svg.line()
-            .x(function(d) { return x(d.timeOffset); })
-            .y(function(d) { return yLeft(d.values[0]); });
</del><ins>+        // samplingTimeOffset
+        svg.append(&quot;line&quot;)
+            .attr(&quot;x1&quot;, x(graphData.samplingTimeOffset))
+            .attr(&quot;x2&quot;, x(graphData.samplingTimeOffset))
+            .attr(&quot;y1&quot;, yLeft(0))
+            .attr(&quot;y2&quot;, yLeft(yAxisLeft.scale().domain()[1]))
+            .attr(&quot;class&quot;, &quot;sample-time marker&quot;);
</ins><span class="cx"> 
</span><del>-    var lineRight = d3.svg.line()
-            .x(function(d) { return x(d.timeOffset); })
-            .y(function(d) { return yRight(d.values[1]); });
</del><ins>+        // left-mean
+        svg.append(&quot;line&quot;)
+            .attr(&quot;x1&quot;, x(0))
+            .attr(&quot;x2&quot;, size.width)
+            .attr(&quot;y1&quot;, yLeft(graphData.mean[0]))
+            .attr(&quot;y2&quot;, yLeft(graphData.mean[0]))
+            .attr(&quot;class&quot;, &quot;left-mean mean&quot;);
</ins><span class="cx"> 
</span><del>-    samples.forEach(function(d) {
-        d.timeOffset = +d.timeOffset;
-        d.values[0] = +d.values[0];
-        d.values[1] = +d.values[1];        
-    });    
</del><ins>+        // right-mean
+        svg.append(&quot;line&quot;)
+            .attr(&quot;x1&quot;, x(0))
+            .attr(&quot;x2&quot;, size.width)
+            .attr(&quot;y1&quot;, yRight(graphData.mean[1]))
+            .attr(&quot;y2&quot;, yRight(graphData.mean[1]))
+            .attr(&quot;class&quot;, &quot;right-mean mean&quot;);
</ins><span class="cx"> 
</span><del>-    var sampledSamples = samples.filter(function(d) {
-        return d.timeOffset &gt;= samplingTimeOffset;
-    });
-    
-    var meanLeft = d3.mean(sampledSamples, function(d) {
-        return +d.values[0];
-    });
</del><ins>+        // right-target
+        if (targetFPS) {
+            svg.append(&quot;line&quot;)
+                .attr(&quot;x1&quot;, x(0))
+                .attr(&quot;x2&quot;, size.width)
+                .attr(&quot;y1&quot;, yRight(targetFPS))
+                .attr(&quot;y2&quot;, yRight(targetFPS))
+                .attr(&quot;class&quot;, &quot;target-fps marker&quot;);
+        }
</ins><span class="cx"> 
</span><del>-    var meanRight = d3.mean(sampledSamples, function(d) {
-        return +d.values[1];
-    });
-                            
-    var svg = d3.select(selector).append(&quot;svg&quot;)
-        .attr(&quot;width&quot;, size.width + margins.left + margins.right)
-        .attr(&quot;height&quot;, size.height + margins.top + margins.bottom)
-        .append(&quot;g&quot;)
-            .attr(&quot;transform&quot;, &quot;translate(&quot; + margins.left + &quot;,&quot; + margins.top + &quot;)&quot;);
</del><ins>+        // Cursor
+        var cursorGroup = svg.append(&quot;g&quot;).attr(&quot;id&quot;, &quot;cursor&quot;);
+        cursorGroup.append(&quot;line&quot;)
+            .attr(&quot;x1&quot;, 0)
+            .attr(&quot;x2&quot;, 0)
+            .attr(&quot;y1&quot;, yLeft(0))
+            .attr(&quot;y2&quot;, yLeft(0));
</ins><span class="cx"> 
</span><del>-    // x-axis
-    svg.append(&quot;g&quot;)
-        .attr(&quot;class&quot;, &quot;x axis&quot;)
-        .attr(&quot;fill&quot;, &quot;rgb(235, 235, 235)&quot;)
-        .attr(&quot;transform&quot;, &quot;translate(0,&quot; + size.height + &quot;)&quot;)
-        .call(xAxis)
-        .append(&quot;text&quot;)
-            .attr(&quot;class&quot;, &quot;label&quot;)
-            .attr(&quot;x&quot;, size.width)
-            .attr(&quot;y&quot;, -6)
-            .attr(&quot;fill&quot;, &quot;rgb(235, 235, 235)&quot;)
-            .style(&quot;text-anchor&quot;, &quot;end&quot;)
-            .text(&quot;time&quot;);
-                 
-    // yLeft-axis
-    svg.append(&quot;g&quot;)
-        .attr(&quot;class&quot;, &quot;y axis&quot;)
-        .attr(&quot;fill&quot;, &quot;#7ADD49&quot;)
-        .call(yAxisLeft)
-        .append(&quot;text&quot;)
-            .attr(&quot;class&quot;, &quot;label&quot;)
-            .attr(&quot;transform&quot;, &quot;rotate(-90)&quot;)
-            .attr(&quot;y&quot;, 6)
-            .attr(&quot;fill&quot;, &quot;#7ADD49&quot;)
-            .attr(&quot;dy&quot;, &quot;.71em&quot;)
-            .style(&quot;text-anchor&quot;, &quot;end&quot;)
-            .text(axes[0]);
</del><ins>+        // Data
+        var allData = graphData.samples;
+        var filteredData = graphData.samples.filter(function (sample) {
+            return &quot;smoothedFPS&quot; in sample;
+        });
</ins><span class="cx"> 
</span><del>-    // yRight-axis
-    svg.append(&quot;g&quot;)
-        .attr(&quot;class&quot;, &quot;y axis&quot;)
-        .attr(&quot;fill&quot;, &quot;#FA4925&quot;)
-        .attr(&quot;transform&quot;, &quot;translate(&quot; + size.width + &quot;, 0)&quot;)
-        .call(yAxisRight)
-        .append(&quot;text&quot;)
-            .attr(&quot;class&quot;, &quot;label&quot;)
-            .attr(&quot;transform&quot;, &quot;rotate(-90)&quot;)
-            .attr(&quot;y&quot;, 6)
-            .attr(&quot;fill&quot;, &quot;#FA4925&quot;)
-            .attr(&quot;dy&quot;, &quot;.71em&quot;)
-            .style(&quot;text-anchor&quot;, &quot;end&quot;)
-            .text(axes[1]);
</del><ins>+        function addData(name, data, yCoordinateCallback, pointRadius, omitLine) {
+            var svgGroup = svg.append(&quot;g&quot;).attr(&quot;id&quot;, name);
+            if (!omitLine) {
+                svgGroup.append(&quot;path&quot;)
+                    .datum(data)
+                    .attr(&quot;d&quot;, d3.svg.line()
+                        .x(function(d) { return x(d.time); })
+                        .y(yCoordinateCallback));
+            }
+            svgGroup.selectAll(&quot;circle&quot;)
+                .data(data)
+                .enter()
+                .append(&quot;circle&quot;)
+                .attr(&quot;cx&quot;, function(d) { return x(d.time); })
+                .attr(&quot;cy&quot;, yCoordinateCallback)
+                .attr(&quot;r&quot;, pointRadius);
</ins><span class="cx"> 
</span><del>-    // left-mean
-    svg.append(&quot;svg:line&quot;)
-        .attr(&quot;x1&quot;, x(0))
-        .attr(&quot;x2&quot;, size.width)
-        .attr(&quot;y1&quot;, yLeft(meanLeft))
-        .attr(&quot;y2&quot;, yLeft(meanLeft))
-        .attr(&quot;class&quot;, &quot;left-mean&quot;);
</del><ins>+            cursorGroup.append(&quot;circle&quot;)
+                .attr(&quot;class&quot;, name)
+                .attr(&quot;r&quot;, pointRadius + 2);
+        }
</ins><span class="cx"> 
</span><del>-    // right-mean
-    svg.append(&quot;svg:line&quot;)
-        .attr(&quot;x1&quot;, x(0))
-        .attr(&quot;x2&quot;, size.width)
-        .attr(&quot;y1&quot;, yRight(meanRight))
-        .attr(&quot;y2&quot;, yRight(meanRight))
-        .attr(&quot;class&quot;, &quot;right-mean&quot;);        
</del><ins>+        addData(&quot;complexity&quot;, allData, function(d) { return yLeft(d.complexity); }, 2);
+        addData(&quot;rawFPS&quot;, allData, function(d) { return yRight(d.fps); }, 1);
+        addData(&quot;filteredFPS&quot;, filteredData, function(d) { return yRight(d.smoothedFPS); }, 2);
+        addData(&quot;intervalFPS&quot;, filteredData, function(d) { return yRight(d.intervalFPS); }, 3, true);
</ins><span class="cx"> 
</span><del>-    // samplingTimeOffset
-    svg.append(&quot;line&quot;)
-        .attr(&quot;x1&quot;, x(samplingTimeOffset))
-        .attr(&quot;x2&quot;, x(samplingTimeOffset))
-        .attr(&quot;y1&quot;, yLeft(0))
-        .attr(&quot;y2&quot;, yLeft(yAxisLeft.scale().domain()[1]))
-        .attr(&quot;class&quot;, &quot;sample-time&quot;);
</del><ins>+        // Area to handle mouse events
+        var area = svg.append(&quot;rect&quot;)
+            .attr(&quot;fill&quot;, &quot;transparent&quot;)
+            .attr(&quot;x&quot;, 0)
+            .attr(&quot;y&quot;, 0)
+            .attr(&quot;width&quot;, size.x)
+            .attr(&quot;height&quot;, size.y);
</ins><span class="cx"> 
</span><del>-    // left-samples
-    svg.append(&quot;path&quot;)
-        .datum(samples)
-        .attr(&quot;class&quot;, &quot;left-samples&quot;)
-        .attr(&quot;d&quot;, lineLeft);
-        
-    // right-samples
-    svg.append(&quot;path&quot;)
-        .datum(samples)
-        .attr(&quot;class&quot;, &quot;right-samples&quot;)
-        .attr(&quot;d&quot;, lineRight);
-}
</del><ins>+        var timeBisect = d3.bisector(function(d) { return d.time; }).right;
+        var statsToHighlight = [&quot;complexity&quot;, &quot;rawFPS&quot;, &quot;filteredFPS&quot;, &quot;intervalFPS&quot;];
+        area.on(&quot;mouseover&quot;, function() {
+            document.getElementById(&quot;cursor&quot;).classList.remove(&quot;hidden&quot;);
+            document.querySelector(&quot;#test-graph nav&quot;).classList.remove(&quot;hide-data&quot;);
+        }).on(&quot;mouseout&quot;, function() {
+            document.getElementById(&quot;cursor&quot;).classList.add(&quot;hidden&quot;);
+            document.querySelector(&quot;#test-graph nav&quot;).classList.add(&quot;hide-data&quot;);
+        }).on(&quot;mousemove&quot;, function() {
+            var form = document.forms[&quot;graph-options&quot;].elements;
+
+            var mx_domain = x.invert(d3.mouse(this)[0]);
+            var index = Math.min(timeBisect(allData, mx_domain), allData.length - 1);
+            var data = allData[index];
+            var cursor_x = x(data.time);
+            var cursor_y = yAxisRight.scale().domain()[1];
+            if (form[&quot;rawFPS&quot;].checked)
+                cursor_y = Math.max(cursor_y, data.fps);
+            cursorGroup.select(&quot;line&quot;)
+                .attr(&quot;x1&quot;, cursor_x)
+                .attr(&quot;x2&quot;, cursor_x)
+                .attr(&quot;y2&quot;, yRight(cursor_y));
+
+            document.querySelector(&quot;#test-graph nav .time&quot;).textContent = data.time.toFixed(3) + &quot;s (&quot; + index + &quot;)&quot;;
+            statsToHighlight.forEach(function(name) {
+                var element = document.querySelector(&quot;#test-graph nav .&quot; + name);
+                var content = &quot;&quot;;
+                var data_y = null;
+                switch (name) {
+                case &quot;complexity&quot;:
+                    content = data.complexity;
+                    data_y = yLeft(data.complexity);
+                    break;
+                case &quot;rawFPS&quot;:
+                    content = data.fps.toFixed(2);
+                    data_y = yRight(data.fps);
+                    break;
+                case &quot;filteredFPS&quot;:
+                    if (&quot;smoothedFPS&quot; in data) {
+                        content = data.smoothedFPS.toFixed(2);
+                        data_y = yRight(data.smoothedFPS);
+                    }
+                    break;
+                case &quot;intervalFPS&quot;:
+                    if (&quot;intervalFPS&quot; in data) {
+                        content = data.intervalFPS.toFixed(2);
+                        data_y = yRight(data.intervalFPS);
+                    }
+                    break;
+                }
+
+                element.textContent = content;
+
+                if (form[name].checked &amp;&amp; data_y !== null) {
+                    cursorGroup.select(&quot;.&quot; + name)
+                        .attr(&quot;cx&quot;, cursor_x)
+                        .attr(&quot;cy&quot;, data_y);
+                    document.querySelector(&quot;#cursor .&quot; + name).classList.remove(&quot;hidden&quot;);
+                } else
+                    document.querySelector(&quot;#cursor .&quot; + name).classList.add(&quot;hidden&quot;);
+            });
+        });
+        this.onGraphOptionsChanged();
+    },
+
+    onGraphOptionsChanged: function() {
+        var form = document.forms[&quot;graph-options&quot;].elements;
+
+        function showOrHideNodes(isShown, selector) {
+            var nodeList = document.querySelectorAll(selector);
+            if (isShown) {
+                for (var i = 0; i &lt; nodeList.length; ++i)
+                    nodeList[i].classList.remove(&quot;hidden&quot;);
+            } else {
+                for (var i = 0; i &lt; nodeList.length; ++i)
+                    nodeList[i].classList.add(&quot;hidden&quot;);
+            }
+        }
+
+        showOrHideNodes(form[&quot;markers&quot;].checked, &quot;.marker&quot;);
+        showOrHideNodes(form[&quot;averages&quot;].checked, &quot;.mean&quot;);
+        showOrHideNodes(form[&quot;complexity&quot;].checked, &quot;#complexity&quot;);
+        showOrHideNodes(form[&quot;rawFPS&quot;].checked, &quot;#rawFPS&quot;);
+        showOrHideNodes(form[&quot;filteredFPS&quot;].checked, &quot;#filteredFPS&quot;);
+        showOrHideNodes(form[&quot;intervalFPS&quot;].checked, &quot;#intervalFPS&quot;);
+    }
+});
</ins></span></pre></div>
<a id="trunkPerformanceTestsAnimometerresourcesextensionsjs"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/Animometer/resources/extensions.js (194522 => 194523)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/Animometer/resources/extensions.js        2016-01-04 01:19:38 UTC (rev 194522)
+++ trunk/PerformanceTests/Animometer/resources/extensions.js        2016-01-04 02:36:57 UTC (rev 194523)
</span><span class="lines">@@ -248,7 +248,7 @@
</span><span class="cx">         this._iterationsSamplers.push(suitesSamplers);
</span><span class="cx">     },
</span><span class="cx"> 
</span><del>-    _processData: function(statistics, graph)
</del><ins>+    _processData: function()
</ins><span class="cx">     {
</span><span class="cx">         var iterationsResults = [];
</span><span class="cx">         var iterationsScores = [];
</span><span class="lines">@@ -290,7 +290,7 @@
</span><span class="cx">     {
</span><span class="cx">         if (this._processedData)
</span><span class="cx">             return this._processedData;
</span><del>-        this._processData(true, true);
</del><ins>+        this._processData();
</ins><span class="cx">         return this._processedData;
</span><span class="cx">     },
</span><span class="cx"> 
</span><span class="lines">@@ -346,9 +346,6 @@
</span><span class="cx">         var button = DocumentExtension.createElement(&quot;button&quot;, { class: &quot;small-button&quot; }, td);
</span><span class="cx"> 
</span><span class="cx">         button.addEventListener(&quot;click&quot;, function() {
</span><del>-            var samples = data[Strings.json.graph.points];
-            var samplingTimeOffset = data[Strings.json.graph.samplingTimeOffset];
-            var axes = [Strings.text.experiments.complexity, Strings.text.experiments.frameRate];
</del><span class="cx">             var score = testResults[Strings.json.score].toFixed(2);
</span><span class="cx">             var complexity = testResults[Strings.json.experiments.complexity];
</span><span class="cx">             var mean = [
</span><span class="lines">@@ -360,7 +357,19 @@
</span><span class="cx">                 complexity[Strings.json.measurements.percent].toFixed(2),
</span><span class="cx">                 &quot;%), worst 5%: &quot;,
</span><span class="cx">                 complexity[Strings.json.measurements.concern].toFixed(2)].join(&quot;&quot;);
</span><del>-            benchmarkController.showTestGraph(testName, score, mean, axes, samples, samplingTimeOffset);
</del><ins>+
+            var graphData = {
+                axes: [Strings.text.experiments.complexity, Strings.text.experiments.frameRate],
+                mean: [
+                    testResults[Strings.json.experiments.complexity][Strings.json.measurements.average],
+                    testResults[Strings.json.experiments.frameRate][Strings.json.measurements.average]
+                ],
+                samples: data,
+                samplingTimeOffset: testResults[Strings.json.samplingTimeOffset]
+            }
+            if (testResults[Strings.json.targetFPS])
+                graphData.targetFPS = testResults[Strings.json.targetFPS];
+            benchmarkController.showTestGraph(testName, score, mean, graphData);
</ins><span class="cx">         });
</span><span class="cx"> 
</span><span class="cx">         button.textContent = Strings.text.results.graph + &quot;...&quot;;
</span></span></pre></div>
<a id="trunkPerformanceTestsAnimometerresourcessamplerjs"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/Animometer/resources/sampler.js (194522 => 194523)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/Animometer/resources/sampler.js        2016-01-04 01:19:38 UTC (rev 194522)
+++ trunk/PerformanceTests/Animometer/resources/sampler.js        2016-01-04 02:36:57 UTC (rev 194523)
</span><span class="lines">@@ -22,12 +22,14 @@
</span><span class="cx">             return 0;
</span><span class="cx">         var roots = values.map(function(value) { return  Math.pow(value, 1 / values.length); })
</span><span class="cx">         return roots.reduce(function(a, b) { return a * b; });
</span><del>-    }   
</del><ins>+    }
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> function Experiment()
</span><span class="cx"> {
</span><del>-    this._init();
</del><ins>+    this._sum = 0;
+    this._squareSum = 0;
+    this._numberOfSamples = 0;
</ins><span class="cx">     this._maxHeap = Algorithm.createMaxHeap(Experiment.defaults.CONCERN_SIZE);
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="lines">@@ -39,22 +41,6 @@
</span><span class="cx"> 
</span><span class="cx"> Experiment.prototype =
</span><span class="cx"> {
</span><del>-    _init: function()
-    {
-        this._sum = 0;
-        this._squareSum = 0;
-        this._numberOfSamples = 0;
-    },
-    
-    // Called after a warm-up period
-    startSampling: function()
-    {
-        var mean = this.mean();
-        this._init();
-        this._maxHeap.init();
-        this.sample(mean);
-    },
-    
</del><span class="cx">     sample: function(value)
</span><span class="cx">     {
</span><span class="cx">         this._sum += value;
</span><span class="lines">@@ -62,91 +48,82 @@
</span><span class="cx">         this._maxHeap.push(value);
</span><span class="cx">         ++this._numberOfSamples;
</span><span class="cx">     },
</span><del>-    
</del><ins>+
</ins><span class="cx">     mean: function()
</span><span class="cx">     {
</span><span class="cx">         return Statistics.sampleMean(this._numberOfSamples, this._sum);
</span><span class="cx">     },
</span><del>-    
</del><ins>+
</ins><span class="cx">     standardDeviation: function()
</span><span class="cx">     {
</span><span class="cx">         return Statistics.unbiasedSampleStandardDeviation(this._numberOfSamples, this._sum, this._squareSum);
</span><span class="cx">     },
</span><del>-    
</del><ins>+
</ins><span class="cx">     percentage: function()
</span><span class="cx">     {
</span><span class="cx">         var mean = this.mean();
</span><span class="cx">         return mean ? this.standardDeviation() * 100 / mean : 0;
</span><span class="cx">     },
</span><del>-    
</del><ins>+
</ins><span class="cx">     concern: function(percentage)
</span><span class="cx">     {
</span><span class="cx">         var size = Math.ceil(this._numberOfSamples * percentage / 100);
</span><span class="cx">         var values = this._maxHeap.values(size);
</span><span class="cx">         return values.length ? values.reduce(function(a, b) { return a + b; }) / values.length : 0;
</span><span class="cx">     },
</span><del>-    
</del><ins>+
</ins><span class="cx">     score: function(percentage)
</span><span class="cx">     {
</span><span class="cx">         return Statistics.geometricMean([this.mean(), Math.max(this.concern(percentage), 1)]);
</span><span class="cx">     }
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-function Sampler(count)
</del><ins>+function Sampler(seriesCount, expectedSampleCount, processor)
</ins><span class="cx"> {
</span><del>-    this.experiments = [];
-    while (count--)
-        this.experiments.push(new Experiment());
</del><ins>+    this._processor = processor;
+
</ins><span class="cx">     this.samples = [];
</span><del>-    this.samplingTimeOffset = 0;
</del><ins>+    for (var i = 0; i &lt; seriesCount; ++i) {
+        var array = new Array(expectedSampleCount);
+        array.fill(0);
+        this.samples[i] = array;
+    }
+    this.sampleCount = 0;
+    this.marks = {};
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> Sampler.prototype =
</span><span class="cx"> {
</span><del>-    startSampling: function(samplingTimeOffset)
-    {
-        this.experiments.forEach(function(experiment) {
-            experiment.startSampling();
-        });
-            
-        this.samplingTimeOffset = samplingTimeOffset / 1000;
</del><ins>+    record: function() {
+        // Assume that arguments.length == this.samples.length
+        for (var i = 0; i &lt; arguments.length; i++) {
+            this.samples[i][this.sampleCount] = arguments[i];
+        }
+        ++this.sampleCount;
</ins><span class="cx">     },
</span><del>-    
-    sample: function(timeOffset, values)
-    {
-        if (values.length &lt; this.experiments.length)
-            throw &quot;Not enough sample points&quot;;
</del><span class="cx"> 
</span><del>-        this.experiments.forEach(function(experiment, index) {
-            experiment.sample(values[index]);
-        });
-                    
-        this.samples.push({ timeOffset: timeOffset / 1000, values: values });
</del><ins>+    mark: function(comment, data) {
+        data = data || {};
+        // The mark exists after the last recorded sample
+        data.index = this.sampleCount;
+
+        this.marks[comment] = data;
</ins><span class="cx">     },
</span><del>-    
-    toJSON: function(statistics, graph)
</del><ins>+
+    process: function(options)
</ins><span class="cx">     {
</span><span class="cx">         var results = {};
</span><del>-         
-        results[Strings.json.score] = this.experiments[0].score(Experiment.defaults.CONCERN);
-           
-        if (statistics) {
-            this.experiments.forEach(function(experiment, index) {
-                var jsonExperiment = !index ? Strings.json.experiments.complexity : Strings.json.experiments.frameRate;
-                results[jsonExperiment] = {};
-                results[jsonExperiment][Strings.json.measurements.average] = experiment.mean();
-                results[jsonExperiment][Strings.json.measurements.concern] = experiment.concern(Experiment.defaults.CONCERN);
-                results[jsonExperiment][Strings.json.measurements.stdev] = experiment.standardDeviation();
-                results[jsonExperiment][Strings.json.measurements.percent] = experiment.percentage()
-            });
-        }
-        
-        if (graph) {
-            results[Strings.json.samples] = {};
-            results[Strings.json.samples][Strings.json.graph.points] = this.samples;
-            results[Strings.json.samples][Strings.json.graph.samplingTimeOffset] = this.samplingTimeOffset;
-        }
-        
</del><ins>+
+        if (options[&quot;adjustment&quot;] == &quot;adaptive&quot;)
+            results[Strings.json.targetFPS] = +options[&quot;frame-rate&quot;];
+
+        // Remove unused capacity
+        this.samples = this.samples.map(function(array) {
+            return array.slice(0, this.sampleCount);
+        }, this);
+
+        this._processor.processSamples(results);
+
</ins><span class="cx">         return results;
</span><span class="cx">     }
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkPerformanceTestsAnimometerresourcesstringsjs"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/Animometer/resources/strings.js (194522 => 194523)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/Animometer/resources/strings.js        2016-01-04 01:19:38 UTC (rev 194522)
+++ trunk/PerformanceTests/Animometer/resources/strings.js        2016-01-04 02:36:57 UTC (rev 194523)
</span><span class="lines">@@ -26,6 +26,9 @@
</span><span class="cx">         score: &quot;score&quot;,
</span><span class="cx">         samples: &quot;samples&quot;,
</span><span class="cx"> 
</span><ins>+        targetFPS: &quot;targetFPS&quot;,
+        samplingTimeOffset: &quot;samplingTimeOffset&quot;,
+
</ins><span class="cx">         experiments: {
</span><span class="cx">             complexity: &quot;complexity&quot;,
</span><span class="cx">             frameRate: &quot;frameRate&quot;
</span><span class="lines">@@ -42,11 +45,6 @@
</span><span class="cx">             iterations: &quot;iterationsResults&quot;,
</span><span class="cx">             suites: &quot;suitesResults&quot;,
</span><span class="cx">             tests: &quot;testsResults&quot;
</span><del>-        },
-
-        graph: {
-            points: &quot;points&quot;,
-            samplingTimeOffset: &quot;samplingTimeOffset&quot;
</del><span class="cx">         }
</span><span class="cx">     }
</span><span class="cx"> };
</span></span></pre></div>
<a id="trunkPerformanceTestsAnimometertestsbouncingparticlesresourcesbouncingparticlesjs"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/Animometer/tests/bouncing-particles/resources/bouncing-particles.js (194522 => 194523)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/Animometer/tests/bouncing-particles/resources/bouncing-particles.js        2016-01-04 01:19:38 UTC (rev 194522)
+++ trunk/PerformanceTests/Animometer/tests/bouncing-particles/resources/bouncing-particles.js        2016-01-04 02:36:57 UTC (rev 194523)
</span><span class="lines">@@ -113,5 +113,10 @@
</span><span class="cx"> 
</span><span class="cx">         this.particles.splice(-count, count);
</span><span class="cx">         return this.particles.length;
</span><ins>+    },
+
+    complexity: function()
+    {
+        return this.particles.length;
</ins><span class="cx">     }
</span><span class="cx"> });
</span></span></pre></div>
<a id="trunkPerformanceTestsAnimometertestsmiscresourcescanvaselectronsjs"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/Animometer/tests/misc/resources/canvas-electrons.js (194522 => 194523)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/Animometer/tests/misc/resources/canvas-electrons.js        2016-01-04 01:19:38 UTC (rev 194522)
+++ trunk/PerformanceTests/Animometer/tests/misc/resources/canvas-electrons.js        2016-01-04 02:36:57 UTC (rev 194523)
</span><span class="lines">@@ -95,6 +95,11 @@
</span><span class="cx">         this._electrons.forEach(function(electron) {
</span><span class="cx">             electron.animate(timeDelta);
</span><span class="cx">         });
</span><ins>+    },
+
+    complexity: function()
+    {
+        return this._electrons.length;
</ins><span class="cx">     }
</span><span class="cx"> });
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkPerformanceTestsAnimometertestsmiscresourcescanvasstarsjs"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/Animometer/tests/misc/resources/canvas-stars.js (194522 => 194523)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/Animometer/tests/misc/resources/canvas-stars.js        2016-01-04 01:19:38 UTC (rev 194522)
+++ trunk/PerformanceTests/Animometer/tests/misc/resources/canvas-stars.js        2016-01-04 02:36:57 UTC (rev 194523)
</span><span class="lines">@@ -91,6 +91,11 @@
</span><span class="cx">         this._objects.forEach(function(object) {
</span><span class="cx">             object.animate(timeDelta);
</span><span class="cx">         });
</span><ins>+    },
+
+    complexity: function()
+    {
+        return this._objects.length;
</ins><span class="cx">     }
</span><span class="cx"> });
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkPerformanceTestsAnimometertestsresourcesmainjs"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/Animometer/tests/resources/main.js (194522 => 194523)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/Animometer/tests/resources/main.js        2016-01-04 01:19:38 UTC (rev 194522)
+++ trunk/PerformanceTests/Animometer/tests/resources/main.js        2016-01-04 02:36:57 UTC (rev 194523)
</span><span class="lines">@@ -180,12 +180,10 @@
</span><span class="cx"> 
</span><span class="cx"> function Animator()
</span><span class="cx"> {
</span><del>-    this._frameCount = 0;
-    this._dropFrameCount = 1;
-    this._measureFrameCount = 3;
</del><ins>+    this._intervalFrameCount = 0;
+    this._numberOfFramesToMeasurePerInterval = 3;
</ins><span class="cx">     this._referenceTime = 0;
</span><span class="cx">     this._currentTimeOffset = 0;
</span><del>-    this._estimator = new KalmanEstimator(60);
</del><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> Animator.prototype =
</span><span class="lines">@@ -193,7 +191,12 @@
</span><span class="cx">     initialize: function(benchmark)
</span><span class="cx">     {
</span><span class="cx">         this._benchmark = benchmark;
</span><del>-        this._estimateFrameRate = benchmark.options[&quot;estimated-frame-rate&quot;];
</del><ins>+
+        // Use Kalman filter to get a more non-fluctuating frame rate.
+        if (benchmark.options[&quot;estimated-frame-rate&quot;])
+            this._estimator = new KalmanEstimator(60);
+        else
+            this._estimator = new IdentityEstimator;
</ins><span class="cx">     },
</span><span class="cx"> 
</span><span class="cx">     get benchmark()
</span><span class="lines">@@ -201,55 +204,55 @@
</span><span class="cx">         return this._benchmark;
</span><span class="cx">     },
</span><span class="cx"> 
</span><del>-    timeDelta: function()
</del><ins>+    _intervalTimeDelta: function()
</ins><span class="cx">     {
</span><span class="cx">         return this._currentTimeOffset - this._startTimeOffset;
</span><span class="cx">     },
</span><span class="cx"> 
</span><span class="cx">     _shouldRequestAnotherFrame: function()
</span><span class="cx">     {
</span><ins>+        // Cadence is number of frames to measure, then one more frame to adjust the scene, and drop
</ins><span class="cx">         var currentTime = performance.now();
</span><del>-        
</del><ins>+
</ins><span class="cx">         if (!this._referenceTime)
</span><span class="cx">             this._referenceTime = currentTime;
</span><del>-        else
-            this._currentTimeOffset = currentTime - this._referenceTime;
</del><span class="cx"> 
</span><del>-        if (!this._frameCount)
</del><ins>+        this._currentTimeOffset = currentTime - this._referenceTime;
+
+        if (!this._intervalFrameCount)
</ins><span class="cx">             this._startTimeOffset = this._currentTimeOffset;
</span><span class="cx"> 
</span><del>-        ++this._frameCount;
</del><ins>+        // Start the work for the next frame.
+        ++this._intervalFrameCount;
</ins><span class="cx"> 
</span><del>-        // Start measuring after dropping _dropFrameCount frames.
-        if (this._frameCount == this._dropFrameCount)
-            this._measureTimeOffset = this._currentTimeOffset;
-
</del><span class="cx">         // Drop _dropFrameCount frames and measure the average of _measureFrameCount frames.
</span><del>-        if (this._frameCount &lt; this._dropFrameCount + this._measureFrameCount)
</del><ins>+        if (this._intervalFrameCount &lt;= this._numberOfFramesToMeasurePerInterval) {
+            this._benchmark.record(this._currentTimeOffset, -1, -1);
</ins><span class="cx">             return true;
</span><ins>+        }
</ins><span class="cx"> 
</span><del>-        // Get the average FPS of _measureFrameCount frames over measureTimeDelta.
-        var measureTimeDelta = this._currentTimeOffset - this._measureTimeOffset;
-        var currentFrameRate = Math.floor(1000 / (measureTimeDelta / this._measureFrameCount));
</del><ins>+        // Get the average FPS of _measureFrameCount frames over intervalTimeDelta.
+        var intervalTimeDelta = this._intervalTimeDelta();
+        var intervalFrameRate = 1000 / (intervalTimeDelta / this._numberOfFramesToMeasurePerInterval);
+        var estimatedIntervalFrameRate = this._estimator.estimate(intervalFrameRate);
+        // Record the complexity of the frame we just rendered. The next frame's time respresents the adjusted
+        // complexity
+        this._benchmark.record(this._currentTimeOffset, estimatedIntervalFrameRate, intervalFrameRate);
</ins><span class="cx"> 
</span><del>-        // Use Kalman filter to get a more non-fluctuating frame rate.
-        if (this._estimateFrameRate)
-            currentFrameRate = this._estimator.estimate(currentFrameRate);
-
</del><span class="cx">         // Adjust the test to reach the desired FPS.
</span><del>-        var result = this._benchmark.update(this._currentTimeOffset, this.timeDelta(), currentFrameRate);
</del><ins>+        var shouldContinueRunning = this._benchmark.update(this._currentTimeOffset, intervalTimeDelta, estimatedIntervalFrameRate);
</ins><span class="cx"> 
</span><span class="cx">         // Start the next drop/measure cycle.
</span><del>-        this._frameCount = 0;
</del><ins>+        this._intervalFrameCount = 0;
</ins><span class="cx"> 
</span><del>-        // If result == 0, no more requestAnimationFrame() will be invoked.
-        return result;
</del><ins>+        // If result is false, no more requestAnimationFrame() will be invoked.
+        return shouldContinueRunning;
</ins><span class="cx">     },
</span><span class="cx"> 
</span><span class="cx">     animateLoop: function()
</span><span class="cx">     {
</span><span class="cx">         if (this._shouldRequestAnotherFrame()) {
</span><del>-            this._benchmark.stage.animate(this.timeDelta());
</del><ins>+            this._benchmark.stage.animate(this._intervalTimeDelta());
</ins><span class="cx">             requestAnimationFrame(this.animateLoop.bind(this));
</span><span class="cx">         }
</span><span class="cx">     }
</span><span class="lines">@@ -267,7 +270,7 @@
</span><span class="cx">     this._recordInterval = 200;
</span><span class="cx">     this._isSampling = false;
</span><span class="cx">     this._controller = new PIDController(this._options[&quot;frame-rate&quot;]);
</span><del>-    this._sampler = new Sampler(2);
</del><ins>+    this._sampler = new Sampler(4, 60 * this._options[&quot;test-interval&quot;], this);
</ins><span class="cx">     this._state = new BenchmarkState(this._options[&quot;test-interval&quot;] * 1000);
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="lines">@@ -295,7 +298,7 @@
</span><span class="cx">     },
</span><span class="cx"> 
</span><span class="cx">     // Called from the animator to adjust the complexity of the test.
</span><del>-    update: function(currentTimeOffset, timeDelta, currentFrameRate)
</del><ins>+    update: function(currentTimeOffset, intervalTimeDelta, estimatedIntervalFrameRate)
</ins><span class="cx">     {
</span><span class="cx">         this._state.update(currentTimeOffset);
</span><span class="cx"> 
</span><span class="lines">@@ -306,7 +309,9 @@
</span><span class="cx">         }
</span><span class="cx"> 
</span><span class="cx">         if (stage == BenchmarkState.stages.SAMPLING &amp;&amp; !this._isSampling) {
</span><del>-            this._sampler.startSampling(this._state.samplingTimeOffset());
</del><ins>+            this._sampler.mark(Strings.json.samplingTimeOffset, {
+                time: this._state.samplingTimeOffset() / 1000
+            });
</ins><span class="cx">             this._isSampling = true;
</span><span class="cx">         }
</span><span class="cx"> 
</span><span class="lines">@@ -320,29 +325,29 @@
</span><span class="cx">         else if (!(this._isSampling &amp;&amp; this._options[&quot;adjustment&quot;] == &quot;fixed-after-warmup&quot;)) {
</span><span class="cx">             // The relationship between frameRate and test complexity is inverse-proportional so we
</span><span class="cx">             // need to use the negative of PIDController.tune() to change the complexity of the test.
</span><del>-            tuneValue = -this._controller.tune(currentTimeOffset, timeDelta, currentFrameRate);
</del><ins>+            tuneValue = -this._controller.tune(currentTimeOffset, intervalTimeDelta, estimatedIntervalFrameRate);
</ins><span class="cx">             tuneValue = tuneValue &gt; 0 ? Math.floor(tuneValue) : Math.ceil(tuneValue);
</span><span class="cx">         }
</span><span class="cx"> 
</span><del>-        var currentComplexity = this._stage.tune(tuneValue);
-        this.record(currentTimeOffset, currentComplexity, currentFrameRate);
-        return true;
-    },
</del><ins>+        this._stage.tune(tuneValue);
</ins><span class="cx"> 
</span><del>-    record: function(currentTimeOffset, currentComplexity, currentFrameRate)
-    {
-        this._sampler.sample(currentTimeOffset, [currentComplexity, currentFrameRate]);
-
</del><span class="cx">         if (typeof this._recordTimeOffset == &quot;undefined&quot;)
</span><span class="cx">             this._recordTimeOffset = currentTimeOffset;
</span><span class="cx"> 
</span><span class="cx">         var stage = this._state.currentStage();
</span><span class="cx">         if (stage != BenchmarkState.stages.FINISHED &amp;&amp; currentTimeOffset &lt; this._recordTimeOffset + this._recordInterval)
</span><del>-            return;
</del><ins>+            return true;
</ins><span class="cx"> 
</span><span class="cx">         this._recordTimeOffset = currentTimeOffset;
</span><ins>+        return true;
</ins><span class="cx">     },
</span><span class="cx"> 
</span><ins>+    record: function(currentTimeOffset, estimatedFrameRate, intervalFrameRate)
+    {
+        // If the frame rate is -1 it means we are still recording for this sample
+        this._sampler.record(currentTimeOffset, this.stage.complexity(), estimatedFrameRate, intervalFrameRate);
+    },
+
</ins><span class="cx">     run: function()
</span><span class="cx">     {
</span><span class="cx">         this.start();
</span><span class="lines">@@ -357,5 +362,57 @@
</span><span class="cx"> 
</span><span class="cx">         resolveWhenFinished();
</span><span class="cx">         return promise;
</span><ins>+    },
+
+    processSamples: function(results)
+    {
+        var complexity = new Experiment;
+        var smoothedFPS = new Experiment;
+        var samplingIndex = 0;
+
+        var samplingMark = this._sampler.marks[Strings.json.samplingTimeOffset];
+        if (samplingMark) {
+            samplingIndex = samplingMark.index;
+            results[Strings.json.samplingTimeOffset] = samplingMark.time;
+        }
+
+        results[Strings.json.samples] = this._sampler.samples[0].map(function(d, i) {
+            var result = {
+                // time offsets represented as seconds
+                time: d/1000,
+                complexity: this._sampler.samples[1][i]
+            };
+
+            // time offsets represented as FPS
+            if (i == 0)
+                result.fps = 60;
+            else
+                result.fps = 1000 / (d - this._sampler.samples[0][i - 1]);
+
+            var smoothedFPSresult = this._sampler.samples[2][i];
+            if (smoothedFPSresult != -1) {
+                result.smoothedFPS = smoothedFPSresult;
+                result.intervalFPS = this._sampler.samples[3][i];
+            }
+
+            if (i &gt;= samplingIndex) {
+                complexity.sample(result.complexity);
+                if (smoothedFPSresult != -1) {
+                    smoothedFPS.sample(smoothedFPSresult);
+                }
+            }
+
+            return result;
+        }, this);
+
+        results[Strings.json.score] = complexity.score(Experiment.defaults.CONCERN);
+        [complexity, smoothedFPS].forEach(function(experiment, index) {
+            var jsonExperiment = !index ? Strings.json.experiments.complexity : Strings.json.experiments.frameRate;
+            results[jsonExperiment] = {};
+            results[jsonExperiment][Strings.json.measurements.average] = experiment.mean();
+            results[jsonExperiment][Strings.json.measurements.concern] = experiment.concern(Experiment.defaults.CONCERN);
+            results[jsonExperiment][Strings.json.measurements.stdev] = experiment.standardDeviation();
+            results[jsonExperiment][Strings.json.measurements.percent] = experiment.percentage();
+        });
</ins><span class="cx">     }
</span><span class="cx"> };
</span></span></pre></div>
<a id="trunkPerformanceTestsAnimometertestsresourcesmathjs"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/Animometer/tests/resources/math.js (194522 => 194523)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/Animometer/tests/resources/math.js        2016-01-04 01:19:38 UTC (rev 194522)
+++ trunk/PerformanceTests/Animometer/tests/resources/math.js        2016-01-04 02:36:57 UTC (rev 194523)
</span><span class="lines">@@ -30,7 +30,7 @@
</span><span class="cx">             out += A[i];
</span><span class="cx">             if (i &lt; n * m - 1)
</span><span class="cx">                 out += &quot;, &quot;;
</span><del>-        }       
</del><ins>+        }
</ins><span class="cx">         return out + &quot;]&quot;;
</span><span class="cx">     },
</span><span class="cx"> 
</span><span class="lines">@@ -193,7 +193,7 @@
</span><span class="cx"> 
</span><span class="cx">     this._Kp = 0;
</span><span class="cx">     this._stage = PIDController.stages.WARMING;
</span><del>-    
</del><ins>+
</ins><span class="cx">     this._eold = 0;
</span><span class="cx">     this._I = 0;
</span><span class="cx"> }
</span><span class="lines">@@ -208,12 +208,12 @@
</span><span class="cx"> // The Ziegler–Nichols method for is used tuning the PID controller. The workflow of
</span><span class="cx"> // the tuning is split into four stages. The first two stages determine the values
</span><span class="cx"> // of the PID controller gains. During these two stages we return the proportional
</span><del>-// term only. The third stage is used to determine the min-max values of the 
</del><ins>+// term only. The third stage is used to determine the min-max values of the
</ins><span class="cx"> // saturation actuator. In the last stage back-calculation and tracking are applied
</span><span class="cx"> // to avoid integrator windup. During the last two stages, we return a PID control
</span><span class="cx"> // value.
</span><span class="cx"> PIDController.stages = {
</span><del>-    WARMING: 0,         // Increase the value of the Kp until the system output reaches ysp. 
</del><ins>+    WARMING: 0,         // Increase the value of the Kp until the system output reaches ysp.
</ins><span class="cx">     OVERSHOOT: 1,       // Measure the oscillation period and the overshoot value
</span><span class="cx">     UNDERSHOOT: 2,      // Return PID value and measure the undershoot value
</span><span class="cx">     SATURATE: 3         // Return PID value and apply back-calculation and tracking.
</span><span class="lines">@@ -237,7 +237,7 @@
</span><span class="cx">     // proportional gain very small but achieves the desired progress. But if y does
</span><span class="cx">     // not change significantly after adding few items, that means we need a much
</span><span class="cx">     // bigger gain. So we need to move over a cubic curve which increases very
</span><del>-    // slowly with small t values but moves very fast with larger t values. 
</del><ins>+    // slowly with small t values but moves very fast with larger t values.
</ins><span class="cx">     // The basic formula is: y = t^3
</span><span class="cx">     // Change the formula to reach y=1 after 1000 ms: y = (t/1000)^3
</span><span class="cx">     // Change the formula to reach y=(ysp - y0) after 1000 ms: y = (ysp - y0) * (t/1000)^3
</span><span class="lines">@@ -257,7 +257,7 @@
</span><span class="cx"> 
</span><span class="cx">     // Decides how much the proportional gain should be increased during the manual
</span><span class="cx">     // gain stage. We choose to use the ratio of the ultimate distance to the current
</span><del>-    // distance as an indication of how much the system is responsive. We want 
</del><ins>+    // distance as an indication of how much the system is responsive. We want
</ins><span class="cx">     // to keep the increment under control so it does not cause the system instability
</span><span class="cx">     // So we choose to take the natural logarithm of this ratio.
</span><span class="cx">     _gainIncrement: function(t, y, e)
</span><span class="lines">@@ -277,7 +277,7 @@
</span><span class="cx">             if (yPosition == PIDController.yPositions.AFTER_SETPOINT)
</span><span class="cx">                 this._stage = PIDController.stages.OVERSHOOT;
</span><span class="cx">             break;
</span><del>-        
</del><ins>+
</ins><span class="cx">         case PIDController.stages.OVERSHOOT:
</span><span class="cx">             if (yPosition == PIDController.yPositions.BEFORE_SETPOINT)
</span><span class="cx">                 this._stage = PIDController.stages.UNDERSHOOT;
</span><span class="lines">@@ -313,7 +313,7 @@
</span><span class="cx">         // The ouput is a PID function.
</span><span class="cx">        return P + this._I + D;
</span><span class="cx">     },
</span><del>-    
</del><ins>+
</ins><span class="cx">     // Apply different strategies for the tuning based on the stage of the controller.
</span><span class="cx">     _tune: function(t, h, y, e)
</span><span class="cx">     {
</span><span class="lines">@@ -332,7 +332,7 @@
</span><span class="cx">                 // set-point yet
</span><span class="cx">                 this._Kp += this._gainIncrement(t, y, e);
</span><span class="cx">             }
</span><del>-        
</del><ins>+
</ins><span class="cx">             return this._tuneP(e);
</span><span class="cx"> 
</span><span class="cx">         case PIDController.stages.OVERSHOOT:
</span><span class="lines">@@ -343,41 +343,41 @@
</span><span class="cx">                 this._t0 = t;
</span><span class="cx">                 this._Kp /= 2;
</span><span class="cx">             }
</span><del>-        
</del><ins>+
</ins><span class="cx">             return this._tuneP(e);
</span><del>-    
</del><ins>+
</ins><span class="cx">         case PIDController.stages.UNDERSHOOT:
</span><span class="cx">             // This is the end of the Zieglerâ€Nichols method. We need to calculate the
</span><span class="cx">             // integral and derivative periods.
</span><span class="cx">             if (typeof this._Ti == &quot;undefined&quot;) {
</span><span class="cx">                 // t is the time of the end of the first overshot
</span><span class="cx">                 var Tu = t - this._t0;
</span><del>-        
</del><ins>+
</ins><span class="cx">                 // Calculate the system parameters from Kp and Tu assuming
</span><span class="cx">                 // a &quot;some overshoot&quot; control type. See:
</span><span class="cx">                 // https://en.wikipedia.org/wiki/Ziegler%E2%80%93Nichols_method
</span><span class="cx">                 this._Ti = Tu / 2;
</span><span class="cx">                 this._Td = Tu / 3;
</span><span class="cx">                 this._Kp = 0.33 * this._Kp;
</span><del>-        
</del><ins>+
</ins><span class="cx">                 // Calculate the tracking time.
</span><span class="cx">                 this._Tt = Math.sqrt(this._Ti * this._Td);
</span><span class="cx">             }
</span><del>-        
</del><ins>+
</ins><span class="cx">             return this._tunePID(h, y, e);
</span><del>-        
</del><ins>+
</ins><span class="cx">         case PIDController.stages.SATURATE:
</span><span class="cx">             return this._tunePID(h, y, e);
</span><span class="cx">         }
</span><del>-        
</del><ins>+
</ins><span class="cx">         return 0;
</span><span class="cx">     },
</span><del>-    
</del><ins>+
</ins><span class="cx">     // Ensures the system does not fluctuates.
</span><span class="cx">     _saturate: function(v, e)
</span><span class="cx">     {
</span><span class="cx">         var u = v;
</span><del>-        
</del><ins>+
</ins><span class="cx">         switch (this._stage) {
</span><span class="cx">         case PIDController.stages.OVERSHOOT:
</span><span class="cx">         case PIDController.stages.UNDERSHOOT:
</span><span class="lines">@@ -389,7 +389,7 @@
</span><span class="cx">                 this._max = Math.max(this._max, this._out);
</span><span class="cx">             }
</span><span class="cx">             break;
</span><del>-        
</del><ins>+
</ins><span class="cx">         case PIDController.stages.SATURATE:
</span><span class="cx">             const limitPercentage = 0.90;
</span><span class="cx">             var min = this._min &gt; 0 ? Math.min(this._min, this._max * limitPercentage) : this._min;
</span><span class="lines">@@ -399,13 +399,13 @@
</span><span class="cx">             // Clip the controller output to the min-max values
</span><span class="cx">             out = Math.max(Math.min(max, out), min);
</span><span class="cx">             u = out - this._out;
</span><del>-    
</del><ins>+
</ins><span class="cx">             // Apply the back-calculation and tracking
</span><span class="cx">             if (u != v)
</span><span class="cx">                 u += (this._Kp * this._Tt / this._Ti) * e;
</span><span class="cx">             break;
</span><span class="cx">         }
</span><del>-        
</del><ins>+
</ins><span class="cx">         this._out += u;
</span><span class="cx">         return u;
</span><span class="cx">     },
</span><span class="lines">@@ -415,14 +415,14 @@
</span><span class="cx">     tune: function(t, h, y)
</span><span class="cx">     {
</span><span class="cx">         this._updateStage(y);
</span><del>-        
</del><ins>+
</ins><span class="cx">         // Current error.
</span><span class="cx">         var e = this._ysp - y;
</span><span class="cx">         var v = this._tune(t, h, y, e);
</span><del>-        
</del><ins>+
</ins><span class="cx">         // Save e for the next call.
</span><span class="cx">         this._eold = e;
</span><del>-        
</del><ins>+
</ins><span class="cx">         // Apply back-calculation and tracking to avoid integrator windup
</span><span class="cx">         return this._saturate(v, e);
</span><span class="cx">     }
</span><span class="lines">@@ -480,3 +480,12 @@
</span><span class="cx">         return Vector3.multiplyVector3(this._vecH,  this._vecX_est);
</span><span class="cx">     }
</span><span class="cx"> }
</span><ins>+
+function IdentityEstimator() {}
+IdentityEstimator.prototype =
+{
+    estimate: function(current)
+    {
+        return current;
+    }
+};
</ins></span></pre></div>
<a id="trunkPerformanceTestsAnimometerteststextresourceslayeringtextjs"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/Animometer/tests/text/resources/layering-text.js (194522 => 194523)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/Animometer/tests/text/resources/layering-text.js        2016-01-04 01:19:38 UTC (rev 194522)
+++ trunk/PerformanceTests/Animometer/tests/text/resources/layering-text.js        2016-01-04 02:36:57 UTC (rev 194523)
</span><span class="lines">@@ -113,6 +113,11 @@
</span><span class="cx">             this._popTextElement();
</span><span class="cx"> 
</span><span class="cx">         return this._textElements.length;
</span><ins>+    },
+
+    complexity: function()
+    {
+        return this._textElements.length;
</ins><span class="cx">     }
</span><span class="cx"> });
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkPerformanceTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/ChangeLog (194522 => 194523)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/ChangeLog        2016-01-04 01:19:38 UTC (rev 194522)
+++ trunk/PerformanceTests/ChangeLog        2016-01-04 02:36:57 UTC (rev 194523)
</span><span class="lines">@@ -1,3 +1,130 @@
</span><ins>+2016-01-03  Jon Lee  &lt;jonlee@apple.com&gt;
+
+        Update data reporting and analysis
+        https://bugs.webkit.org/show_bug.cgi?id=152670
+
+        Reviewed by Simon Fraser.
+
+        Show new graph data. Provide controls to show different series data. Provide an
+        interactive cursor that shows the data at a given sample.
+
+        * Animometer/developer.html: Add a nav section in #results. Each part of the graph
+        has a checkbox for visual toggling, as well as companion spans to contain the data.
+                The numbers will always be shown even if the SVG isn't.
+        * Animometer/resources/debug-runner/animometer.css:
+        (#suites): Adjust spacing when doing fixed complexity.
+        (#test-graph nav): Place the nav in the upper right corner.
+        (#test-graph-data &gt; svg): Fix the FPS scale from 0-60. It makes the raw FPS goes past
+        that scale. Allow it to show.
+        (.target-fps): Add a dotted line for where the benchmark is supposed to settle for FPS.
+        (#cursor line): The cursor contains a line and highlight circles for the data being shown.
+        (#cursor circle):
+        (#complexity path): This and rules afterward are named by series type.
+        (#complexity circle):
+        (#filteredFPS path):
+        (#filteredFPS circle):
+        (#rawFPS path):
+        (#intervalFPS circle):
+        (.left-samples): Deleted.
+        (.right-samples): Deleted.
+        * Animometer/resources/debug-runner/animometer.js:
+        (initialize): Add a &quot;changed&quot; listener when the checkboxes change in the nav.
+        (onBenchmarkOptionsChanged): Renamed.
+        (showTestGraph): All graph data is passed in as graphData instead of as arguments.
+        * Animometer/resources/debug-runner/graph.js: Extend BenchmarkController. When showing
+        a new graph, call updateGraphData(). It creates all of the d3 graphs. onGraphOptionsChanged()
+        toggles the data on and off.
+        (updateGraphData): Add the axes. Add the average lines and markers for sample time and
+        target FPS. Add the cursor group. Use helper function addData() to add the data. On top of
+        everything add a transparent area which will catch all of the mouse events. When the mouse
+        moves in the graph, find the closest data point, show the data in the nav area, and highlight
+        the data points.
+        (addData): Adds a line and circle for each data point. Also adds a highlight cursor with a
+        size a little larger than the circle radius for the data points.
+        (onGraphOptionsChanged): Called when data is visually toggled.
+        (showOrHideNodes): Helper function to toggle the .hidden class.
+        * Animometer/resources/extensions.js:
+        (ResultsDashboard.prototype.get data): Get rid of the arguments for _processData.
+        (ResultsTable.prototype._addGraphButton): Shove all of the graph data into a singular object.
+
+        Producing the JSON can take a while with all of the data. Make it on-demand with a
+        button.
+
+        * Animometer/resources/debug-runner/animometer.js:
+        (showResults): When showing the results, don't serialize the JSON data. Move that to...
+        (showJSONResults): ...here. Remove the button.
+
+        * Animometer/developer.html: Add a button. The button will remove itself and populate
+        the textarea with the JSON data.
+        * Animometer/resources/debug-runner/animometer.css:
+        (.hidden): Add a universal hidden class.
+        (#results button.small-button): Promote the small-button styles to the whole results
+        section for use in the JSON button.
+        (#results button.small-button:active):
+        (#results-data button.small-button): Deleted.
+        (#results-data button.small-button:active): Deleted.
+
+        Refactor how Animator does its recording.
+
+        * Animometer/tests/resources/math.js: Create a new, simple estimator that just returns
+        the same interval frame rate for adjustment.
+        * Animometer/tests/resources/main.js:
+        (Animator): Remove _dropFrameCount, and make variables more accurate described.
+        (Animator.prototype.initialize): Use the identity estimator instead of using a bool.
+        (Animator.prototype._intervalTimeDelta): Rename, only used internally.
+        (Animator.prototype._shouldRequestAnotherFrame): Assume we drop one frame for adjustment
+        of the scene. If we are within the number of frames to measure for the interval, just
+        record the timestamp. Otherwise we are ready to evaluate and adjust the scene. Record
+        the interval frame rate and the estimator's frame rate.
+
+        Avoid processing the data through the Experiment while the test is running. Reconfigure
+        the sampler to just record the raw samples. After the test is done, run the samples through
+        the Experiment to get the score.
+
+        * Animometer/resources/sampler.js:
+        (Experiment): Fold _init() into the constructor since nobody else will call it. This is not
+        needed until the test concludes, so remove startSampling(). Clients should just call sample().
+        (Sampler): Pre-allocate arrays given the number of data points being recorded, and a capacity
+        of how many samples will be used. The processor is a called when it's time to process the data
+        since that is the client also telling the Sampler what to record.
+                Introduce the notion of marks as well, which allows the client to mark when an
+        event occurs. When we mark sample start, we can attach the timestamp there, instead of storing
+        it separately.
+        (Sampler.prototype.startSampling): Deleted. Clients should just call record().
+        (Sampler.prototype.record): The data to record is passed in as variable arguments.
+        (Sampler.prototype.mark): When adding a mark, a client needs to provide a unique string, and
+        can provide extra data object for later retrieval.
+        (Sampler.prototype.process): Renamed from toJSON. Trim the sampling arrays to what was used.
+        Call the processor to process the samples.
+        * Animometer/resources/debug-runner/benchmark-runner.js:
+        (BenchmarkRunner.prototype._runBenchmarkAndRecordResults): Call process().
+
+        * Animometer/resources/strings.js: Add some new strings, remove the graph ones since they are
+        not used.
+        * Animometer/tests/resources/main.js:
+        (Benchmark): Create a sampler with 4 series. The maximum number of points expected is the
+        number of seconds multiplied by 60 fps. Benchmark, as a client of the Sampler, knows about all
+        of the data being added to the Sampler. It is added through record(), and processed through
+        processSamples().
+        (Benchmark.prototype.update): Mark when we've passed warmup and are starting to sample. Include
+        the timestamp in the custom data for the mark. This avoids the need to store is separately in
+        the Sampler. Fold what was in record() here, since nothing else needs this functionality.
+        record() now just relays the information to the sampler.
+        (Benchmark.prototype.record): Called by Animator, which provides the data to the sampler.
+        Animator's calls to this is part of a later patch. Requires each stage to return its complexity.
+        (Benchmark.prototype.processSamples): If the sampling mark exists, add it to the results.
+                Go through all of the samples. All samples contain a timestamp and complexity. We
+        calculate &quot;raw FPS&quot; which is the time differential from the previous sample. At regular intervals
+        the Kalman-filtered FPS and the interval average FPS are also recorded. We also create two
+        experiments, to get the scores for the complexity and smoothed FPS, and add those samples to
+        the experiments. Grab those scores and add them into results also.
+
+        Add complexity() to the tests for Benchmark.record().
+        * Animometer/tests/bouncing-particles/resources/bouncing-particles.js:
+        * Animometer/tests/misc/resources/canvas-electrons.js:
+        * Animometer/tests/misc/resources/canvas-stars.js:
+        * Animometer/tests/text/resources/layering-text.js:
+
</ins><span class="cx"> 2015-12-27  Jon Lee  &lt;jonlee@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         Simplify the test harness
</span></span></pre>
</div>
</div>

</body>
</html>