<!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>[194788] trunk/Websites/perf.webkit.org</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/194788">194788</a></dd>
<dt>Author</dt> <dd>rniwa@webkit.org</dd>
<dt>Date</dt> <dd>2016-01-08 13:57:32 -0800 (Fri, 08 Jan 2016)</dd>
</dl>

<h3>Log Message</h3>
<pre>Make v3 UI analysis task page is hard to understand
https://bugs.webkit.org/show_bug.cgi?id=152917

Reviewed by Antti Koivisto.

Add a dark gray border around the selected block in the analysis results viewer instead of using darker
shades since that looks as if they were bigger regression/progression.

Explicitly show &quot;Failed&quot; as the label instead of omitting with &quot;-&quot; when all build requests in an A/B
testing group fails.

* public/v3/components/analysis-results-viewer.js:
(AnalysisResultsViewer.cssTemplate): Tweaked the style to underline text in the hovered blocks and the
selected blocks and show a dark gray border around the selected blocks.
(AnalysisResultsViewer.TestGroupStackingBlock):
(AnalysisResultsViewer.TestGroupStackingBlock.prototype.createStackingCell): Use this._title for title.
(AnalysisResultsViewer.TestGroupStackingBlock.prototype._computeTestGroupStatus):
(AnalysisResultsViewer.TestGroupStackingBlock.prototype._valuesForRootSet): Deleted.

* public/v3/components/results-table.js:
(ResultsTable.prototype.render):
(ResultsTable.prototype._createRevisionListCells): Extracted from ResultsTable.prototype.render.
(ResultsTable.cssTemplate): Tweaked the style.
(ResultsTableRow):
(ResultsTableRow.prototype.constructor): Added _labelForWholeRow to store the label for the entire row.
This is used to show the comparison result of two root sets (e.g. A vs B).
(ResultsTableRow.prototype.setLabelForWholeRow): Added.
(ResultsTableRow.prototype.labelForWholeRow): Added.
(ResultsTableRow.prototype.resultContent): Extracted from buildHeading. Creates a hyperlinked bar graph
used for each A/B testing result.
(ResultsTableRow.prototype.buildHeading): Deleted since we need to set colspan on the second table cell
when we're creating a row with _labelForWholeRow.

* public/v3/components/test-group-results-table.js:
(TestGroupResultsTable.prototype.buildRowGroups): Added rows to show relative differences and statistical
significance between root sets (e.g. A vs B).

* public/v3/models/build-request.js:
(BuildRequest.prototype.hasCompleted): Added.

* public/v3/models/test-group.js:
(TestGroup.prototype.compareTestResults): Extracted from AnalysisResultsViewer.TestGroupStackingBlock's
_computeTestGroupStatus and generalized to be reused in TestGroupResultsTable.
(TestGroup.prototype._valuesForRootSet): Moved from AnalysisResultsViewer.TestGroupStackingBlock.

* public/v3/pages/analysis-task-page.js:
(AnalysisTaskPage.cssTemplate): Tweaked the style.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkWebsitesperfwebkitorgChangeLog">trunk/Websites/perf.webkit.org/ChangeLog</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3componentsanalysisresultsviewerjs">trunk/Websites/perf.webkit.org/public/v3/components/analysis-results-viewer.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3componentsresultstablejs">trunk/Websites/perf.webkit.org/public/v3/components/results-table.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3componentstestgroupresultstablejs">trunk/Websites/perf.webkit.org/public/v3/components/test-group-results-table.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3modelsbuildrequestjs">trunk/Websites/perf.webkit.org/public/v3/models/build-request.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3modelstestgroupjs">trunk/Websites/perf.webkit.org/public/v3/models/test-group.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3pagesanalysistaskpagejs">trunk/Websites/perf.webkit.org/public/v3/pages/analysis-task-page.js</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkWebsitesperfwebkitorgChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/ChangeLog (194787 => 194788)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/ChangeLog        2016-01-08 21:42:25 UTC (rev 194787)
+++ trunk/Websites/perf.webkit.org/ChangeLog        2016-01-08 21:57:32 UTC (rev 194788)
</span><span class="lines">@@ -1,3 +1,53 @@
</span><ins>+2016-01-08  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
+
+        Make v3 UI analysis task page is hard to understand
+        https://bugs.webkit.org/show_bug.cgi?id=152917
+
+        Reviewed by Antti Koivisto.
+
+        Add a dark gray border around the selected block in the analysis results viewer instead of using darker
+        shades since that looks as if they were bigger regression/progression.
+
+        Explicitly show &quot;Failed&quot; as the label instead of omitting with &quot;-&quot; when all build requests in an A/B
+        testing group fails.
+
+        * public/v3/components/analysis-results-viewer.js:
+        (AnalysisResultsViewer.cssTemplate): Tweaked the style to underline text in the hovered blocks and the
+        selected blocks and show a dark gray border around the selected blocks.
+        (AnalysisResultsViewer.TestGroupStackingBlock):
+        (AnalysisResultsViewer.TestGroupStackingBlock.prototype.createStackingCell): Use this._title for title.
+        (AnalysisResultsViewer.TestGroupStackingBlock.prototype._computeTestGroupStatus):
+        (AnalysisResultsViewer.TestGroupStackingBlock.prototype._valuesForRootSet): Deleted.
+
+        * public/v3/components/results-table.js:
+        (ResultsTable.prototype.render):
+        (ResultsTable.prototype._createRevisionListCells): Extracted from ResultsTable.prototype.render.
+        (ResultsTable.cssTemplate): Tweaked the style.
+        (ResultsTableRow):
+        (ResultsTableRow.prototype.constructor): Added _labelForWholeRow to store the label for the entire row.
+        This is used to show the comparison result of two root sets (e.g. A vs B).
+        (ResultsTableRow.prototype.setLabelForWholeRow): Added.
+        (ResultsTableRow.prototype.labelForWholeRow): Added.
+        (ResultsTableRow.prototype.resultContent): Extracted from buildHeading. Creates a hyperlinked bar graph
+        used for each A/B testing result.
+        (ResultsTableRow.prototype.buildHeading): Deleted since we need to set colspan on the second table cell
+        when we're creating a row with _labelForWholeRow.
+
+        * public/v3/components/test-group-results-table.js:
+        (TestGroupResultsTable.prototype.buildRowGroups): Added rows to show relative differences and statistical
+        significance between root sets (e.g. A vs B).
+
+        * public/v3/models/build-request.js:
+        (BuildRequest.prototype.hasCompleted): Added.
+
+        * public/v3/models/test-group.js:
+        (TestGroup.prototype.compareTestResults): Extracted from AnalysisResultsViewer.TestGroupStackingBlock's
+        _computeTestGroupStatus and generalized to be reused in TestGroupResultsTable.
+        (TestGroup.prototype._valuesForRootSet): Moved from AnalysisResultsViewer.TestGroupStackingBlock.
+
+        * public/v3/pages/analysis-task-page.js:
+        (AnalysisTaskPage.cssTemplate): Tweaked the style.
+
</ins><span class="cx"> 2016-01-07  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
</span><span class="cx"> 
</span><span class="cx">         Perf dashboard should automatically add aggregators
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3componentsanalysisresultsviewerjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/components/analysis-results-viewer.js (194787 => 194788)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/components/analysis-results-viewer.js        2016-01-08 21:42:25 UTC (rev 194787)
+++ trunk/Websites/perf.webkit.org/public/v3/components/analysis-results-viewer.js        2016-01-08 21:57:32 UTC (rev 194788)
</span><span class="lines">@@ -239,6 +239,7 @@
</span><span class="cx">     {
</span><span class="cx">         return ResultsTable.cssTemplate() + `
</span><span class="cx">             .analysis-view .stacking-block {
</span><ins>+                position: relative;
</ins><span class="cx">                 border: solid 1px #fff;
</span><span class="cx">                 cursor: pointer;
</span><span class="cx">             }
</span><span class="lines">@@ -249,63 +250,44 @@
</span><span class="cx">                 color: inherit;
</span><span class="cx">                 font-size: 0.8rem;
</span><span class="cx">                 padding: 0 0.1rem;
</span><ins>+                max-width: 3rem;
</ins><span class="cx">             }
</span><span class="cx"> 
</span><span class="cx">             .analysis-view .stacking-block:not(.failed) {
</span><del>-                color: white;
</del><ins>+                color: black;
</ins><span class="cx">                 opacity: 1;
</span><span class="cx">             }
</span><span class="cx"> 
</span><del>-            .analysis-view .stacking-block.selected {
</del><ins>+            .analysis-view .stacking-block.selected,
+            .analysis-view .stacking-block:hover {
+                text-decoration: underline;
+            }
</ins><span class="cx"> 
</span><ins>+            .analysis-view .stacking-block.selected:before {
+                content: '';
+                position: absolute;
+                left: 0px;
+                top: 0px;
+                width: calc(100% - 2px);
+                height: calc(100% - 2px);
+                border: solid 1px #333;
</ins><span class="cx">             }
</span><span class="cx"> 
</span><span class="cx">             .analysis-view .stacking-block.failed {
</span><del>-                background: rgba(128, 51, 128, 0.4);
</del><ins>+                background: rgba(128, 51, 128, 0.5);
</ins><span class="cx">             }
</span><del>-            .analysis-view .stacking-block.failed:hover {
-                background: rgba(128, 51, 128, 0.6);
-            }
-            .analysis-view .stacking-block.failed.selected {
-                background: rgba(128, 51, 128, 1);
-                color: white;
-            }
-
</del><span class="cx">             .analysis-view .stacking-block.unchanged {
</span><del>-                background: rgba(128, 128, 128, 0.3);
-                color: black;
</del><ins>+                background: rgba(128, 128, 128, 0.5);
</ins><span class="cx">             }
</span><del>-            .analysis-view .stacking-block.unchanged:hover {
-                background: rgba(128, 128, 128, 0.6);
</del><ins>+            .analysis-view .stacking-block.incomplete {
+                background: rgba(204, 204, 51, 0.5);
</ins><span class="cx">             }
</span><del>-            .analysis-view .stacking-block.unchanged.selected {
-                background: rgba(128, 128, 128, 1);
-                color: white;
-            }
-
</del><span class="cx">             .analysis-view .stacking-block.worse {
</span><del>-                background: rgba(255, 102, 102, 0.4);
-                color: black;
</del><ins>+                background: rgba(255, 102, 102, 0.5);
</ins><span class="cx">             }
</span><del>-            .analysis-view .stacking-block.worse:hover {
-                background: rgba(255, 102, 102, 0.6);
-            }
-            .analysis-view .stacking-block.worse.selected {
-                background: rgba(255, 102, 102, 1);
-                color: white;
-            }
-
</del><span class="cx">             .analysis-view .stacking-block.better {
</span><del>-                background: rgba(102, 102, 255, 0.4);
-                color: black;
</del><ins>+                background: rgba(102, 102, 255, 0.5);
</ins><span class="cx">             }
</span><del>-            .analysis-view .stacking-block.better:hover {
-                background: rgba(102, 102, 255, 0.6);
-            }
-            .analysis-view .stacking-block.better.selected {
-                background: rgba(102, 102, 255, 1);
-                color: white;
-            }
</del><span class="cx">         `;
</span><span class="cx">     }
</span><span class="cx"> }
</span><span class="lines">@@ -332,7 +314,8 @@
</span><span class="cx">         this._smallerIsBetter = smallerIsBetter;
</span><span class="cx">         this._rootSetIndexRowIndexMap = [];
</span><span class="cx">         this._className = className;
</span><del>-        this._label = '-';
</del><ins>+        this._label = null;
+        this._title = null;
</ins><span class="cx">         this._status = null;
</span><span class="cx">         this._callback = callback;
</span><span class="cx">     }
</span><span class="lines">@@ -349,13 +332,12 @@
</span><span class="cx">     {
</span><span class="cx">         this._computeTestGroupStatus();
</span><span class="cx"> 
</span><del>-        var title = this._testGroup.label();
</del><span class="cx">         return ComponentBase.createElement('td', {
</span><span class="cx">             rowspan: this.endRowIndex() - this.startRowIndex() + 1,
</span><del>-            title: title,
</del><ins>+            title: this._title,
</ins><span class="cx">             class: 'stacking-block ' + this._className + ' ' + this._status,
</span><span class="cx">             onclick: this._callback,
</span><del>-        }, ComponentBase.createLink(this._label, title, this._callback));
</del><ins>+        }, ComponentBase.createLink(this._label, this._title, this._callback));
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     isComplete() { return this._rootSetIndexRowIndexMap.length &gt;= 2; }
</span><span class="lines">@@ -375,33 +357,13 @@
</span><span class="cx"> 
</span><span class="cx">         console.assert(this._rootSetIndexRowIndexMap.length &lt;= 2); // FIXME: Support having more root sets.
</span><span class="cx"> 
</span><del>-        var beforeValues = this._valuesForRootSet(this._rootSetIndexRowIndexMap[0].rootSet);
-        var afterValues = this._valuesForRootSet(this._rootSetIndexRowIndexMap[1].rootSet);
</del><ins>+        var result = this._testGroup.compareTestResults(
+            this._rootSetIndexRowIndexMap[0].rootSet, this._rootSetIndexRowIndexMap[1].rootSet);
</ins><span class="cx"> 
</span><del>-        var status = 'failed';
-        var beforeMean = Statistics.sum(beforeValues) / beforeValues.length;
-        var afterMean = Statistics.sum(afterValues) / afterValues.length;
-        if (beforeValues.length &amp;&amp; afterValues.length) {
-            var diff = afterMean - beforeMean;
-            this._label = (diff / beforeMean * 100).toFixed(2) + '%';
-            status = 'unchanged';
-            if (Statistics.testWelchsT(beforeValues, afterValues))
-                status = diff &lt; 0 == this._smallerIsBetter ? 'better' : 'worse';
-        }
-
-        this._status = status;
</del><ins>+        this._label = result.label;
+        this._title = result.fullLabel;
+        this._status = result.status;
</ins><span class="cx">     }
</span><del>-
-    _valuesForRootSet(rootSet)
-    {
-        var requests = this._testGroup.requestsForRootSet(rootSet);
-        var values = [];
-        for (var request of requests) {
-            if (request.result())
-                values.push(request.result().value);
-        }
-        return values;
-    }
</del><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> AnalysisResultsViewer.TestGroupStackingGrid = class {
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3componentsresultstablejs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/components/results-table.js (194787 => 194788)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/components/results-table.js        2016-01-08 21:42:25 UTC (rev 194787)
+++ trunk/Websites/perf.webkit.org/public/v3/components/results-table.js        2016-01-08 21:57:32 UTC (rev 194788)
</span><span class="lines">@@ -22,51 +22,25 @@
</span><span class="cx"> 
</span><span class="cx">         var barGraphGroup = new BarGraphGroup(this._valueFormatter);
</span><span class="cx">         var element = ComponentBase.createElement;
</span><del>-        var link = ComponentBase.createLink;
</del><ins>+        var self = this;
</ins><span class="cx">         var tableBodies = rowGroups.map(function (group) {
</span><span class="cx">             var groupHeading = group.heading;
</span><span class="cx">             var revisionSupressionCount = {};
</span><span class="cx"> 
</span><span class="cx">             return element('tbody', group.rows.map(function (row, rowIndex) {
</span><del>-                var cells = row.buildHeading(barGraphGroup);
</del><ins>+                var cells = [];
</ins><span class="cx"> 
</span><del>-                if (groupHeading &amp;&amp; !rowIndex)
-                    cells.unshift(element('th', {rowspan: group.rows.length}, groupHeading));
</del><ins>+                if (groupHeading !== undefined &amp;&amp; !rowIndex)
+                    cells.push(element('th', {rowspan: group.rows.length}, groupHeading));
+                cells.push(element('td', row.heading()));
</ins><span class="cx"> 
</span><del>-                var rootSet = row.rootSet();
-                repositoryList.forEach(function (repository) {
-                    var commit = rootSet ? rootSet.commitForRepository(repository) : null;
</del><ins>+                if (row.labelForWholeRow())
+                    cells.push(element('td', {class: 'whole-row-label', colspan: repositoryList.length + 1}, row.labelForWholeRow()));
+                else {
+                    cells.push(element('td', row.resultContent(barGraphGroup)));
+                    cells.push(self._createRevisionListCells(repositoryList, revisionSupressionCount, group, row.rootSet(), rowIndex));
+                }
</ins><span class="cx"> 
</span><del>-                    if (revisionSupressionCount[repository.id()]) {
-                        revisionSupressionCount[repository.id()]--;
-                        return;
-                    }
-
-                    var succeedingRowIndex = rowIndex + 1;
-                    while (succeedingRowIndex &lt; group.rows.length) {
-                        var succeedingRootSet = group.rows[succeedingRowIndex].rootSet();
-                        if (succeedingRootSet &amp;&amp; commit != succeedingRootSet.commitForRepository(repository))
-                            break;
-                        succeedingRowIndex++;
-                    }
-                    var rowSpan = succeedingRowIndex - rowIndex;
-                    var attributes = {class: 'revision'};
-                    if (rowSpan &gt; 1) {
-                        revisionSupressionCount[repository.id()] = rowSpan - 1;
-                        attributes['rowspan'] = rowSpan;                       
-                    }
-                    if (rowIndex + rowSpan &gt;= group.rows.length)
-                        attributes['class'] += ' lastRevision';
-
-                    var content = 'Missing';
-                    if (commit) {
-                        var url = commit.url();
-                        content = url ? link(commit.label(), url) : commit.label();
-                    }
-
-                    cells.push(element('td', attributes, content));
-                });
-
</del><span class="cx">                 return element('tr', [cells, row.additionalColumns()]);
</span><span class="cx">             }));
</span><span class="cx">         });
</span><span class="lines">@@ -89,6 +63,46 @@
</span><span class="cx">         Instrumentation.endMeasuringTime('ResultsTable', 'render');
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    _createRevisionListCells(repositoryList, revisionSupressionCount, testGroup, rootSet, rowIndex)
+    {
+        var element = ComponentBase.createElement;
+        var link = ComponentBase.createLink;
+        var cells = [];
+        for (var repository of repositoryList) {
+            var commit = rootSet ? rootSet.commitForRepository(repository) : null;
+
+            if (revisionSupressionCount[repository.id()]) {
+                revisionSupressionCount[repository.id()]--;
+                continue;
+            }
+
+            var succeedingRowIndex = rowIndex + 1;
+            while (succeedingRowIndex &lt; testGroup.rows.length) {
+                var succeedingRootSet = testGroup.rows[succeedingRowIndex].rootSet();
+                if (succeedingRootSet &amp;&amp; commit != succeedingRootSet.commitForRepository(repository))
+                    break;
+                succeedingRowIndex++;
+            }
+            var rowSpan = succeedingRowIndex - rowIndex;
+            var attributes = {class: 'revision'};
+            if (rowSpan &gt; 1) {
+                revisionSupressionCount[repository.id()] = rowSpan - 1;
+                attributes['rowspan'] = rowSpan;                       
+            }
+            if (rowIndex + rowSpan &gt;= testGroup.rows.length)
+                attributes['class'] += ' lastRevision';
+
+            var content = 'Missing';
+            if (commit) {
+                var url = commit.url();
+                content = url ? link(commit.label(), url) : commit.label();
+            }
+
+            cells.push(element('td', attributes, content));
+        }
+        return cells;
+    }
+
</ins><span class="cx">     heading() { throw 'NotImplemented'; }
</span><span class="cx">     additionalHeading() { return []; }
</span><span class="cx">     buildRowGroups() { throw 'NotImplemented'; }
</span><span class="lines">@@ -152,7 +166,11 @@
</span><span class="cx">                 height: 1.4rem;
</span><span class="cx">                 text-align: center;
</span><span class="cx">             }
</span><del>-            
</del><ins>+
+            .results-table td.whole-row-label {
+                text-align: left;
+            }
+
</ins><span class="cx">             .results-table thead {
</span><span class="cx">                 color: #c93;
</span><span class="cx">             }
</span><span class="lines">@@ -228,10 +246,11 @@
</span><span class="cx">         this._label = '-';
</span><span class="cx">         this._rootSet = rootSet;
</span><span class="cx">         this._additionalColumns = [];
</span><ins>+        this._labelForWholeRow = null;
</ins><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    heading() { return this._heading; }
</ins><span class="cx">     rootSet() { return this._rootSet; }
</span><del>-
</del><span class="cx">     setResult(result) { this._result = result; }
</span><span class="cx">     setLink(link, label)
</span><span class="cx">     {
</span><span class="lines">@@ -239,18 +258,15 @@
</span><span class="cx">         this._label = label;
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    setLabelForWholeRow(label) { this._labelForWholeRow = label; }
+    labelForWholeRow() { return this._labelForWholeRow; }
+
</ins><span class="cx">     additionalColumns() { return this._additionalColumns; }
</span><span class="cx">     setAdditionalColumns(additionalColumns) { this._additionalColumns = additionalColumns; }
</span><span class="cx"> 
</span><del>-    buildHeading(barGraphGroup)
</del><ins>+    resultContent(barGraphGroup)
</ins><span class="cx">     {
</span><del>-        var element = ComponentBase.createElement;
-        var link = ComponentBase.createLink;
-
</del><span class="cx">         var resultContent = this._result ? barGraphGroup.addBar(this._result.value, this._result.interval) : this._label;
</span><del>-        return [
-            element('th', this._heading),
-            element('td', this._link ? link(resultContent, this._label, this._link) : resultContent),
-        ];
</del><ins>+        return this._link ? ComponentBase.createLink(resultContent, this._label, this._link) : resultContent;
</ins><span class="cx">     }
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3componentstestgroupresultstablejs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/components/test-group-results-table.js (194787 => 194788)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/components/test-group-results-table.js        2016-01-08 21:42:25 UTC (rev 194787)
+++ trunk/Websites/perf.webkit.org/public/v3/components/test-group-results-table.js        2016-01-08 21:57:32 UTC (rev 194788)
</span><span class="lines">@@ -29,12 +29,14 @@
</span><span class="cx">         if (!testGroup)
</span><span class="cx">             return [];
</span><span class="cx"> 
</span><del>-        return this._testGroup.requestedRootSets().map(function (rootSet, setIndex) {
</del><ins>+        var rootSets = this._testGroup.requestedRootSets();
+        var groups = rootSets.map(function (rootSet, setIndex) {
</ins><span class="cx">             var rows = [new ResultsTableRow('Mean', rootSet)];
</span><span class="cx">             var results = [];
</span><span class="cx"> 
</span><span class="cx">             for (var request of testGroup.requestsForRootSet(rootSet)) {
</span><span class="cx">                 var result = request.result();
</span><ins>+                // Call result.rootSet() for each result since the set of revisions used in testing maybe different from requested ones.
</ins><span class="cx">                 var row = new ResultsTableRow(1 + +request.order(), result ? result.rootSet() : null);
</span><span class="cx">                 rows.push(row);
</span><span class="cx">                 if (result) {
</span><span class="lines">@@ -50,7 +52,27 @@
</span><span class="cx">                 rows[0].setResult(aggregatedResult);
</span><span class="cx"> 
</span><span class="cx">             return {heading: String.fromCharCode('A'.charCodeAt(0) + setIndex), rows:rows};
</span><del>-        })
</del><ins>+        });
+
+        var comparisonRows = [];
+        for (var i = 0; i &lt; rootSets.length; i++) {
+            for (var j = i + 1; j &lt; rootSets.length; j++) {
+                var startConfig = String.fromCharCode('A'.charCodeAt(0) + i);
+                var endConfig = String.fromCharCode('A'.charCodeAt(0) + j);
+
+                var result = this._testGroup.compareTestResults(rootSets[i], rootSets[j]);
+                if (result.status == 'incomplete' || result.status == 'failed')
+                    continue;
+
+                var row = new ResultsTableRow(`${startConfig} to ${endConfig}`, null);
+                row.setLabelForWholeRow(result.fullLabel);
+                comparisonRows.push(row);
+            }
+        }
+
+        groups.push({heading: '', rows: comparisonRows});
+
+        return groups;
</ins><span class="cx">     }
</span><span class="cx"> }
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3modelsbuildrequestjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/models/build-request.js (194787 => 194788)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/models/build-request.js        2016-01-08 21:42:25 UTC (rev 194787)
+++ trunk/Websites/perf.webkit.org/public/v3/models/build-request.js        2016-01-08 21:57:32 UTC (rev 194788)
</span><span class="lines">@@ -20,6 +20,7 @@
</span><span class="cx">     order() { return this._order; }
</span><span class="cx">     rootSet() { return this._rootSet; }
</span><span class="cx"> 
</span><ins>+    hasCompleted() { return this._status == 'failed' || this._status == 'completed'; }
</ins><span class="cx">     statusLabel()
</span><span class="cx">     {
</span><span class="cx">         switch (this._status) {
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3modelstestgroupjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/models/test-group.js (194787 => 194788)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/models/test-group.js        2016-01-08 21:42:25 UTC (rev 194787)
+++ trunk/Websites/perf.webkit.org/public/v3/models/test-group.js        2016-01-08 21:57:32 UTC (rev 194788)
</span><span class="lines">@@ -59,6 +59,50 @@
</span><span class="cx">         this._allRootSets = null;
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    hasCompleted()
+    {
+        return this._buildRequests.every(function (request) { return request.hasCompleted(); });
+    }
+
+    compareTestResults(rootSetA, rootSetB)
+    {
+        var beforeValues = this._valuesForRootSet(rootSetA);
+        var afterValues = this._valuesForRootSet(rootSetB);
+        var beforeMean = Statistics.sum(beforeValues) / beforeValues.length;
+        var afterMean = Statistics.sum(afterValues) / afterValues.length;
+
+        var result = {changeType: null, status: 'failed', label: 'Failed', fullLabel: 'Failed', isStatisticallySignificant: false};
+        if (beforeValues.length &amp;&amp; afterValues.length) {
+            var diff = afterMean - beforeMean;
+            result.changeType = diff &lt; 0 == this._smallerIsBetter ? 'better' : 'worse';
+            result.label = Math.abs(diff / beforeMean * 100).toFixed(2) + '% ' + result.changeType;
+            result.isStatisticallySignificant = Statistics.testWelchsT(beforeValues, afterValues);
+            result.status = result.isStatisticallySignificant ? result.changeType : 'unchanged';
+        }
+
+        if (!this.hasCompleted()) {
+            result.status = 'incomplete';
+            result.label = 'Running';
+            result.fullLabel = 'Running';
+        } else if (result.changeType) {
+            var significance = result.isStatisticallySignificant ? 'significant' : 'insignificant';
+            result.fullLabel = `${result.label} (statistically ${significance})`;
+        }
+
+        return result;
+    }
+
+    _valuesForRootSet(rootSet)
+    {
+        var requests = this.requestsForRootSet(rootSet);
+        var values = [];
+        for (var request of requests) {
+            if (request.result())
+                values.push(request.result().value);
+        }
+        return values;
+    }
+
</ins><span class="cx">     static fetchByTask(taskId)
</span><span class="cx">     {
</span><span class="cx">         return this.cachedFetch('../api/test-groups', {task: taskId}).then(function (data) {
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3pagesanalysistaskpagejs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/pages/analysis-task-page.js (194787 => 194788)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/pages/analysis-task-page.js        2016-01-08 21:42:25 UTC (rev 194787)
+++ trunk/Websites/perf.webkit.org/public/v3/pages/analysis-task-page.js        2016-01-08 21:57:32 UTC (rev 194788)
</span><span class="lines">@@ -204,11 +204,8 @@
</span><span class="cx">     {
</span><span class="cx">         return `
</span><span class="cx">             .analysis-tasl-page-container {
</span><del>-                text-align: center;
</del><span class="cx">             }
</span><span class="cx">             .analysis-tasl-page {
</span><del>-                display: inline-block;
-                text-align: left;
</del><span class="cx">             }
</span><span class="cx"> 
</span><span class="cx">             .analysis-task-name {
</span><span class="lines">@@ -245,34 +242,34 @@
</span><span class="cx">                 margin: 1rem;
</span><span class="cx">             }
</span><span class="cx"> 
</span><ins>+            .test-configuration h3 {
+                font-size: 1rem;
+                font-weight: inherit;
+                color: inherit;
+                margin: 0 1rem;
+                padding: 0;
+            }
+
</ins><span class="cx">             .test-group-view {
</span><del>-                display: flex;
-                flex-direction: row;
-                align-items: stretch;
-                align-content: stretch;
</del><ins>+                display: table;
</ins><span class="cx">                 margin: 0 1rem;
</span><span class="cx">             }
</span><span class="cx"> 
</span><span class="cx">             .test-group-details {
</span><del>-                display: flex;
-                flex-grow: 1;
</del><ins>+                display: table-cell;
</ins><span class="cx">                 margin-bottom: 1rem;
</span><span class="cx">             }
</span><span class="cx"> 
</span><del>-            .test-configuration h3 {
-                font-size: 1rem;
-                font-weight: inherit;
-                color: inherit;
-                margin: 0 1rem;
-                padding: 0;
</del><ins>+            .test-group-list {
+                display: table-cell;
</ins><span class="cx">             }
</span><span class="cx"> 
</span><span class="cx">             .test-group-list:not(:empty) {
</span><span class="cx">                 margin: 0;
</span><span class="cx">                 padding: 0.2rem 0;
</span><span class="cx">                 list-style: none;
</span><del>-                display: inline-block;
</del><span class="cx">                 border-right: solid 1px #ccc;
</span><ins>+                white-space: nowrap;
</ins><span class="cx">             }
</span><span class="cx"> 
</span><span class="cx">             .test-group-list li {
</span></span></pre>
</div>
</div>

</body>
</html>