Diff
Modified: trunk/Websites/perf.webkit.org/ChangeLog (199443 => 199444)
--- trunk/Websites/perf.webkit.org/ChangeLog 2016-04-13 09:20:58 UTC (rev 199443)
+++ trunk/Websites/perf.webkit.org/ChangeLog 2016-04-13 09:26:28 UTC (rev 199444)
@@ -1,5 +1,62 @@
2016-04-12 Ryosuke Niwa <[email protected]>
+ Add a summary page to v3 UI
+ https://bugs.webkit.org/show_bug.cgi?id=156531
+
+ Reviewed by Stephanie Lewis.
+
+ Add new "Summary" page, which shows the average difference (better or worse) from the baseline across
+ multiple platforms and tests by a single number.
+
+ * public/include/manifest.php:
+ (ManifestGenerator::generate): Include "summary" in manifest.json.
+ * public/shared/statistics.js:
+ (Statistics.mean): Added.
+ (Statistics.median): Added.
+ * public/v3/components/ratio-bar-graph.js: Added.
+ (RatioBarGraph): Shows a horizontal bar graph that visualizes the relative difference (e.g. 3% better).
+ (RatioBarGraph.prototype.update):
+ (RatioBarGraph.prototype.render):
+ (RatioBarGraph.cssTemplate):
+ (RatioBarGraph.htmlTemplate):
+ * public/v3/index.html:
+ * public/v3/main.js:
+ (main): Instantiate SummaryPage and add it to the navigation bar and the router.
+ * public/v3/models/manifest.js:
+ (Manifest._didFetchManifest): Let "summary" pass through from manifest.json to main().
+ * public/v3/models/measurement-set.js:
+ (MeasurementSet.prototype._failedToFetchJSON): Invoke the callback with an error or true in order for
+ the callback can detect a failure.
+ (MeasurementSet.prototype._invokeCallbacks): Ditto.
+ * public/v3/pages/charts-page.js:
+ (ChartsPage.createStateForConfigurationList): Added to add a hyperlink from summary page to charts page.
+ * public/v3/pages/summary-page.js: Added.
+ (SummaryPage): Added.
+ (SummaryPage.prototype.routeName): Added.
+ (SummaryPage.prototype.open): Added.
+ (SummaryPage.prototype.render): Added.
+ (SummaryPage.prototype._createConfigurationGroupAndStartFetchingData): Added.
+ (SummaryPage.prototype._constructTable): Added.
+ (SummaryPage.prototype._constructRatioGraph): Added.
+ (SummaryPage.htmlTemplate): Added.
+ (SummaryPage.cssTemplate): Added.
+ (SummaryPageConfigurationGroup): Added. Represents a set of platforms and tests shown in a single cell.
+ (SummaryPageConfigurationGroup.prototype.ratio): Added.
+ (SummaryPageConfigurationGroup.prototype.label): Added.
+ (SummaryPageConfigurationGroup.prototype.changeType): Added.
+ (SummaryPageConfigurationGroup.prototype.configurationList): Added.
+ (SummaryPageConfigurationGroup.prototype.fetchAndComputeSummary): Added.
+ (SummaryPageConfigurationGroup.prototype._computeSummary): Added.
+ (SummaryPageConfigurationGroup.prototype._fetchAndComputeRatio): Added. Invoked for each time series in
+ the set, and stores the computed ratio of the current values to the baseline in this._setToRatio.
+ The results are aggregated by _computeSummary as a single number later.
+ (SummaryPageConfigurationGroup._medianForTimeRange): Added.
+ (SummaryPageConfigurationGroup._fetchData): A thin wrapper to make MeasurementSet.fetchBetween promise
+ friendly since MeasurementSet doesn't support Promise at the moment (but it should!).
+ * server-tests/api-manifest.js: Updated a test case.
+
+2016-04-12 Ryosuke Niwa <[email protected]>
+
Make sync-buildbot.js fault safe
https://bugs.webkit.org/show_bug.cgi?id=156498
Modified: trunk/Websites/perf.webkit.org/public/include/manifest.php (199443 => 199444)
--- trunk/Websites/perf.webkit.org/public/include/manifest.php 2016-04-13 09:20:58 UTC (rev 199443)
+++ trunk/Websites/perf.webkit.org/public/include/manifest.php 2016-04-13 09:26:28 UTC (rev 199444)
@@ -36,6 +36,7 @@
'builders' => (object)$this->builders(),
'bugTrackers' => (object)$this->bug_trackers($repositories_table),
'dashboards' => (object)config('dashboards'),
+ 'summary' => (object)config('summary'),
);
$this->manifest['elapsedTime'] = (microtime(true) - $start_time) * 1000;
Modified: trunk/Websites/perf.webkit.org/public/shared/statistics.js (199443 => 199444)
--- trunk/Websites/perf.webkit.org/public/shared/statistics.js 2016-04-13 09:20:58 UTC (rev 199443)
+++ trunk/Websites/perf.webkit.org/public/shared/statistics.js 2016-04-13 09:26:28 UTC (rev 199444)
@@ -12,6 +12,14 @@
return values.length ? values.reduce(function (a, b) { return a + b; }) : 0;
}
+ this.mean = function (values) {
+ return this.sum(values) / values.length;
+ }
+
+ this.median = function (values) {
+ return values.sort(function (a, b) { return a - b; })[Math.floor(values.length / 2)];
+ }
+
this.squareSum = function (values) {
return values.length ? values.reduce(function (sum, value) { return sum + value * value;}, 0) : 0;
}
Added: trunk/Websites/perf.webkit.org/public/v3/components/ratio-bar-graph.js (0 => 199444)
--- trunk/Websites/perf.webkit.org/public/v3/components/ratio-bar-graph.js (rev 0)
+++ trunk/Websites/perf.webkit.org/public/v3/components/ratio-bar-graph.js 2016-04-13 09:26:28 UTC (rev 199444)
@@ -0,0 +1,89 @@
+class RatioBarGraph extends ComponentBase {
+
+ constructor()
+ {
+ super('ratio-bar-graph');
+ this._ratio = 0;
+ this._label = null;
+ this._shouldRender = true;
+ this._bar = this.content().querySelector('.bar');
+ this._labelContainer = this.content().querySelector('.label');
+ }
+
+ update(ratio, label)
+ {
+ console.assert(ratio >= -1 && ratio <= 1);
+ this._ratio = ratio;
+ this._label = label;
+ this._shouldRender = true;
+ }
+
+ render()
+ {
+ if (!this._shouldRender)
+ return;
+
+ var percent = Math.abs(this._ratio * 100);
+ this._labelContainer.textContent = this._label;
+ this._bar.style.width = Math.min(percent, 50) + '%';
+ this._bar.parentNode.className = 'ratio-bar-graph ' + (this._ratio > 0 ? 'better' : 'worse');
+
+ this._shouldRender = false;
+ }
+
+ static htmlTemplate()
+ {
+ return `<div class="ratio-bar-graph"><div class="seperator"></div><div class="bar"></div><div class="label"></div></div>`;
+ }
+
+ static cssTemplate()
+ {
+ return `
+ .ratio-bar-graph {
+ position: relative;
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+ width: 10rem;
+ height: 2.5rem;
+ overflow: hidden;
+ text-decoration: none;
+ color: black;
+ }
+ .ratio-bar-graph .seperator {
+ position: absolute;
+ left: 50%;
+ width: 0px;
+ height: 100%;
+ border-left: solid 1px #ccc;
+ }
+ .ratio-bar-graph .bar {
+ position: absolute;
+ left: 50%;
+ top: 0.5rem;
+ height: calc(100% - 1rem);
+ background: #ccc;
+ }
+ .ratio-bar-graph.worse .bar {
+ transform: translateX(-100%);
+ background: #c33;
+ }
+ .ratio-bar-graph.better .bar {
+ background: #3c3;
+ }
+ .ratio-bar-graph .label {
+ position: absolute;
+ line-height: 2.5rem;
+ }
+ .ratio-bar-graph.worse .label {
+ text-align: left;
+ left: calc(50% + 0.2rem);
+ }
+ .ratio-bar-graph.better .label {
+ text-align: right;
+ right: calc(50% + 0.2rem);
+ }
+ `;
+ }
+
+}
\ No newline at end of file
Modified: trunk/Websites/perf.webkit.org/public/v3/index.html (199443 => 199444)
--- trunk/Websites/perf.webkit.org/public/v3/index.html 2016-04-13 09:20:58 UTC (rev 199443)
+++ trunk/Websites/perf.webkit.org/public/v3/index.html 2016-04-13 09:26:28 UTC (rev 199444)
@@ -82,6 +82,7 @@
<script src=""
<script src=""
<script src=""
+ <script src=""
<script src=""
<script src=""
<script src=""
@@ -98,6 +99,7 @@
<script src=""
<script src=""
<script src=""
+ <script src=""
<script src=""
</template>
Modified: trunk/Websites/perf.webkit.org/public/v3/main.js (199443 => 199444)
--- trunk/Websites/perf.webkit.org/public/v3/main.js 2016-04-13 09:20:58 UTC (rev 199443)
+++ trunk/Websites/perf.webkit.org/public/v3/main.js 2016-04-13 09:26:28 UTC (rev 199444)
@@ -18,10 +18,10 @@
}
var router = new PageRouter();
+ var chartsToolbar = new ChartsToolbar;
- var chartsToolbar = new ChartsToolbar;
+ var summaryPage = new SummaryPage(manifest.summary);
var chartsPage = new ChartsPage(chartsToolbar);
-
var analysisCategoryPage = new AnalysisCategoryPage();
var createAnalysisTaskPage = new CreateAnalysisTaskPage();
@@ -31,12 +31,13 @@
analysisTaskPage.setParentPage(analysisCategoryPage);
var heading = new Heading(manifest.siteTitle);
- heading.addPageGroup([chartsPage, analysisCategoryPage]);
+ heading.addPageGroup([summaryPage, chartsPage, analysisCategoryPage]);
heading.setTitle(manifest.siteTitle);
heading.addPageGroup(dashboardPages);
var router = new PageRouter();
+ router.addPage(summaryPage);
router.addPage(chartsPage);
router.addPage(createAnalysisTaskPage);
router.addPage(analysisTaskPage);
Modified: trunk/Websites/perf.webkit.org/public/v3/models/manifest.js (199443 => 199444)
--- trunk/Websites/perf.webkit.org/public/v3/models/manifest.js 2016-04-13 09:20:58 UTC (rev 199443)
+++ trunk/Websites/perf.webkit.org/public/v3/models/manifest.js 2016-04-13 09:26:28 UTC (rev 199444)
@@ -48,6 +48,7 @@
return {
siteTitle: rawResponse.siteTitle,
dashboards: rawResponse.dashboards, // FIXME: Add an abstraction around dashboards.
+ summary: rawResponse.summary,
}
}
}
Modified: trunk/Websites/perf.webkit.org/public/v3/models/measurement-set.js (199443 => 199444)
--- trunk/Websites/perf.webkit.org/public/v3/models/measurement-set.js 2016-04-13 09:20:58 UTC (rev 199443)
+++ trunk/Websites/perf.webkit.org/public/v3/models/measurement-set.js 2016-04-13 09:26:28 UTC (rev 199444)
@@ -166,22 +166,22 @@
_failedToFetchJSON(clusterEndTime, error)
{
if (clusterEndTime) {
- this._invokeCallbacks(clusterEndTime);
+ this._invokeCallbacks(clusterEndTime, error || true);
return;
}
console.assert(!this._fetchedPrimary);
console.assert(this._waitingForPrimaryCluster instanceof Array);
for (var entry of this._waitingForPrimaryCluster)
- entry.callback();
+ entry.callback(error || true);
this._waitingForPrimaryCluster = false;
}
- _invokeCallbacks(clusterEndTime)
+ _invokeCallbacks(clusterEndTime, error)
{
var callbackList = this._endTimeToCallback[clusterEndTime];
for (var callback of callbackList)
- callback();
+ callback(error);
this._endTimeToCallback[clusterEndTime] = true;
}
Modified: trunk/Websites/perf.webkit.org/public/v3/pages/charts-page.js (199443 => 199444)
--- trunk/Websites/perf.webkit.org/public/v3/pages/charts-page.js 2016-04-13 09:20:58 UTC (rev 199443)
+++ trunk/Websites/perf.webkit.org/public/v3/pages/charts-page.js 2016-04-13 09:26:28 UTC (rev 199444)
@@ -37,6 +37,13 @@
return state;
}
+ static createStateForConfigurationList(configurationList, startTime)
+ {
+ console.assert(configurationList instanceof Array);
+ var state = {paneList: configurationList};
+ return state;
+ }
+
open(state)
{
this.toolbar().setNumberOfDaysCallback(this.setNumberOfDaysFromToolbar.bind(this));
Added: trunk/Websites/perf.webkit.org/public/v3/pages/summary-page.js (0 => 199444)
--- trunk/Websites/perf.webkit.org/public/v3/pages/summary-page.js (rev 0)
+++ trunk/Websites/perf.webkit.org/public/v3/pages/summary-page.js 2016-04-13 09:26:28 UTC (rev 199444)
@@ -0,0 +1,286 @@
+
+class SummaryPage extends PageWithHeading {
+
+ constructor(summarySettings)
+ {
+ super('Summary', null);
+
+ this._table = {
+ heading: summarySettings.platformGroups.map(function (platformGroup) { return platformGroup.name; }),
+ groups: [],
+ };
+ this._shouldConstructTable = true;
+ this._renderQueue = [];
+
+ var current = Date.now();
+ var timeRange = [current - 7 * 24 * 3600 * 1000, current];
+ for (var metricGroup of summarySettings.metricGroups) {
+ var group = {name: metricGroup.name, rows: []};
+ this._table.groups.push(group);
+ for (var subMetricGroup of metricGroup.subgroups) {
+ var row = {name: subMetricGroup.name, cells: []};
+ group.rows.push(row);
+ for (var platformGroup of summarySettings.platformGroups)
+ row.cells.push(this._createConfigurationGroupAndStartFetchingData(platformGroup.platforms, subMetricGroup.metrics, timeRange));
+ }
+ }
+ }
+
+ routeName() { return 'summary'; }
+
+ open(state)
+ {
+ super.open(state);
+ }
+
+ render()
+ {
+ super.render();
+
+ if (this._shouldConstructTable)
+ this.renderReplace(this.content().querySelector('.summary-table'), this._constructTable());
+
+ for (var render of this._renderQueue)
+ render();
+ }
+
+ _createConfigurationGroupAndStartFetchingData(platformIdList, metricIdList, timeRange)
+ {
+ var platforms = platformIdList.map(function (id) { return Platform.findById(id); }).filter(function (obj) { return !!obj; });
+ var metrics = metricIdList.map(function (id) { return Metric.findById(id); }).filter(function (obj) { return !!obj; });
+ var configGroup = new SummaryPageConfigurationGroup(platforms, metrics);
+ configGroup.fetchAndComputeSummary(timeRange).then(this.render.bind(this));
+ return configGroup;
+ }
+
+ _constructTable()
+ {
+ var element = ComponentBase.createElement;
+
+ var self = this;
+
+ this._shouldConstructTable = false;
+ this._renderQueue = [];
+
+ return [
+ element('thead',
+ element('tr', [
+ element('td', {colspan: 2}),
+ this._table.heading.map(function (label) { return element('td', label); }),
+ ])),
+ this._table.groups.map(function (rowGroup) {
+ return rowGroup.rows.map(function (row, rowIndex) {
+ var headings;
+ if (rowGroup.rows.length == 1)
+ headings = [element('th', {class: 'unifiedHeader', colspan: 2}, row.name)];
+ else {
+ headings = [element('th', {class: 'minorHeader'}, row.name)];
+ if (!rowIndex)
+ headings.unshift(element('th', {class: 'majorHeader', rowspan: rowGroup.rows.length}, rowGroup.name));
+ }
+ return element('tr', [headings, row.cells.map(self._constructRatioGraph.bind(self))]);
+ });
+ }),
+ ];
+ }
+
+ _constructRatioGraph(configurationGroup)
+ {
+ var element = ComponentBase.createElement;
+ var link = ComponentBase.createLink;
+
+ var ratioGraph = new RatioBarGraph();
+
+ this._renderQueue.push(function () {
+ ratioGraph.update(configurationGroup.ratio(), configurationGroup.label());
+ ratioGraph.render();
+ });
+
+ var state = ChartsPage.createStateForConfigurationList(configurationGroup.configurationList());
+ return element('td', link(ratioGraph, 'Open charts', this.router().url('charts', state)));
+ }
+
+ static htmlTemplate()
+ {
+ return `<section class="page-with-heading"><table class="summary-table"></table></section>`;
+ }
+
+ static cssTemplate()
+ {
+ return `
+ .summary-table {
+ border-collapse: collapse;
+ border: none;
+ margin: 0 1rem;
+ width: calc(100% - 2rem - 2px);
+ }
+
+ .summary-table td,
+ .summary-table th {
+ text-align: center;
+ padding: 0px;
+ }
+
+ .summary-table .majorHeader {
+ width: 5rem;
+ }
+
+ .summary-table .minorHeader {
+ width: 7rem;
+ }
+
+ .summary-table .unifiedHeader {
+ padding-left: 5rem;
+ }
+
+ .summary-table > tr:nth-child(even) > *:not(.majorHeader) {
+ background: #f9f9f9;
+ }
+
+ .summary-table th,
+ .summary-table thead td {
+ color: #333;
+ font-weight: inherit;
+ font-size: 1rem;
+ padding: 0.2rem 0.4rem;
+ }
+
+ .summary-table thead td {
+ font-size: 1.2rem;
+ }
+
+ .summary-table tbody td {
+ font-weight: inherit;
+ font-size: 0.9rem;
+ padding: 0;
+ }
+
+ .summary-table td > * {
+ height: 100%;
+ }
+ `;
+ }
+}
+
+class SummaryPageConfigurationGroup {
+ constructor(platforms, metrics)
+ {
+ this._measurementSets = [];
+ this._configurationList = [];
+ this._setToRatio = new Map;
+ this._ratio = null;
+ this._label = null;
+ this._changeType = null;
+ this._smallerIsBetter = metrics.length ? metrics[0].isSmallerBetter() : null;
+
+ for (var platform of platforms) {
+ console.assert(platform instanceof Platform);
+ for (var metric of metrics) {
+ console.assert(metric instanceof Metric);
+ console.assert(this._smallerIsBetter == metric.isSmallerBetter());
+ metric.isSmallerBetter();
+ if (platform.hasMetric(metric)) {
+ this._measurementSets.push(MeasurementSet.findSet(platform.id(), metric.id(), platform.lastModified(metric)));
+ this._configurationList.push([platform.id(), metric.id()]);
+ }
+ }
+ }
+ }
+
+ ratio() { return this._ratio; }
+ label() { return this._label; }
+ changeType() { return this._changeType; }
+ configurationList() { return this._configurationList; }
+
+ fetchAndComputeSummary(timeRange)
+ {
+ console.assert(timeRange instanceof Array);
+ console.assert(typeof(timeRange[0]) == 'number');
+ console.assert(typeof(timeRange[1]) == 'number');
+
+ var promises = [];
+ for (var set of this._measurementSets)
+ promises.push(this._fetchAndComputeRatio(set, timeRange));
+
+ return Promise.all(promises).then(this._computeSummary.bind(this));
+ }
+
+ _computeSummary()
+ {
+ var ratios = [];
+ for (var set of this._measurementSets) {
+ var ratio = this._setToRatio.get(set);
+ if (!isNaN(ratio))
+ ratios.push(ratio);
+ }
+
+ var averageRatio = Statistics.mean(ratios);
+ if (isNaN(averageRatio)) {
+ this._summary = '-';
+ this._changeType = null;
+ return;
+ }
+
+ if (Math.abs(averageRatio - 1) < 0.001) { // Less than 0.1% difference.
+ this._summary = 'No change';
+ this._changeType = null;
+ return;
+ }
+
+ var currentIsSmallerThanBaseline = averageRatio < 1;
+ var changeType = this._smallerIsBetter == currentIsSmallerThanBaseline ? 'better' : 'worse';
+ if (currentIsSmallerThanBaseline)
+ averageRatio = 1 / averageRatio;
+
+ this._ratio = (averageRatio - 1) * (changeType == 'better' ? 1 : -1);
+ this._label = ((averageRatio - 1) * 100).toFixed(1) + '%';
+ this._changeType = changeType;
+ }
+
+ _fetchAndComputeRatio(set, timeRange)
+ {
+ var setToRatio = this._setToRatio;
+ return SummaryPageConfigurationGroup._fetchData(set, timeRange).then(function () {
+ var baselineTimeSeries = set.fetchedTimeSeries('baseline', false, false);
+ var currentTimeSeries = set.fetchedTimeSeries('current', false, false);
+
+ var baselineMedian = SummaryPageConfigurationGroup._medianForTimeRange(baselineTimeSeries, timeRange);
+ var currentMedian = SummaryPageConfigurationGroup._medianForTimeRange(currentTimeSeries, timeRange);
+ setToRatio.set(set, currentMedian / baselineMedian);
+ }).catch(function () {
+ setToRatio.set(set, NaN);
+ });
+ }
+
+ static _medianForTimeRange(timeSeries, timeRange)
+ {
+ if (!timeSeries.firstPoint())
+ return NaN;
+
+ var startPoint = timeSeries.findPointAfterTime(timeRange[0]) || timeSeries.lastPoint();
+ var afterEndPoint = timeSeries.findPointAfterTime(timeRange[1]) || timeSeries.lastPoint();
+ var endPoint = timeSeries.previousPoint(afterEndPoint);
+ if (!endPoint || startPoint == afterEndPoint)
+ endPoint = afterEndPoint;
+
+ var points = timeSeries.dataBetweenPoints(startPoint, endPoint).map(function (point) { return point.value; });
+ return Statistics.median(points);
+ }
+
+ static _fetchData(set, timeRange)
+ {
+ // FIXME: Make fetchBetween return a promise.
+ var done = false;
+ return new Promise(function (resolve, reject) {
+ set.fetchBetween(timeRange[0], timeRange[1], function (error) {
+ if (done)
+ return;
+ if (error)
+ reject(null);
+ else if (set.hasFetchedRange(timeRange[0], timeRange[1]))
+ resolve();
+ done = true;
+ });
+ });
+ }
+}
Modified: trunk/Websites/perf.webkit.org/server-tests/api-manifest.js (199443 => 199444)
--- trunk/Websites/perf.webkit.org/server-tests/api-manifest.js 2016-04-13 09:20:58 UTC (rev 199443)
+++ trunk/Websites/perf.webkit.org/server-tests/api-manifest.js 2016-04-13 09:26:28 UTC (rev 199444)
@@ -17,7 +17,7 @@
it("should generate an empty manifest when database is empty", function (done) {
TestServer.remoteAPI().getJSON('/api/manifest').then(function (manifest) {
assert.deepEqual(Object.keys(manifest).sort(), ['all', 'bugTrackers', 'builders', 'dashboard', 'dashboards',
- 'elapsedTime', 'metrics', 'repositories', 'siteTitle', 'status', 'tests']);
+ 'elapsedTime', 'metrics', 'repositories', 'siteTitle', 'status', 'summary', 'tests']);
assert.equal(typeof(manifest.elapsedTime), 'number');
delete manifest.elapsedTime;
@@ -32,6 +32,7 @@
metrics: {},
repositories: {},
tests: {},
+ summary: {},
status: 'OK'
});
done();