2 class SummaryPage extends PageWithHeading {
4 constructor(summarySettings)
6 super('Summary', null);
9 heading: summarySettings.platformGroups.map(function (platformGroup) { return platformGroup.name; }),
12 this._shouldConstructTable = true;
13 this._renderQueue = [];
14 this._configGroups = [];
15 this._excludedConfigurations = summarySettings.excludedConfigurations;
17 for (var metricGroup of summarySettings.metricGroups) {
18 var group = {name: metricGroup.name, rows: []};
19 this._table.groups.push(group);
20 for (var subMetricGroup of metricGroup.subgroups) {
21 var row = {name: subMetricGroup.name, cells: []};
23 for (var platformGroup of summarySettings.platformGroups)
24 row.cells.push(this._createConfigurationGroup(platformGroup.platforms, subMetricGroup.metrics));
29 routeName() { return 'summary'; }
35 var current = Date.now();
36 var timeRange = [current - 7 * 24 * 3600 * 1000, current];
37 for (var group of this._configGroups)
38 group.fetchAndComputeSummary(timeRange).then(this.render.bind(this));
45 if (this._shouldConstructTable)
46 this.renderReplace(this.content().querySelector('.summary-table'), this._constructTable());
48 for (var render of this._renderQueue)
52 _createConfigurationGroup(platformIdList, metricIdList)
54 var platforms = platformIdList.map(function (id) { return Platform.findById(id); }).filter(function (obj) { return !!obj; });
55 var metrics = metricIdList.map(function (id) { return Metric.findById(id); }).filter(function (obj) { return !!obj; });
56 var configGroup = new SummaryPageConfigurationGroup(platforms, metrics, this._excludedConfigurations);
57 this._configGroups.push(configGroup);
63 var element = ComponentBase.createElement;
67 this._shouldConstructTable = false;
68 this._renderQueue = [];
73 element('td', {colspan: 2}),
74 this._table.heading.map(function (label) { return element('td', label); }),
76 this._table.groups.map(function (rowGroup) {
77 return rowGroup.rows.map(function (row, rowIndex) {
79 headings = [element('th', {class: 'minorHeader'}, row.name)];
81 headings.unshift(element('th', {class: 'majorHeader', rowspan: rowGroup.rows.length}, rowGroup.name));
82 return element('tr', [headings, row.cells.map(self._constructRatioGraph.bind(self))]);
88 _constructRatioGraph(configurationGroup)
90 var element = ComponentBase.createElement;
91 var link = ComponentBase.createLink;
92 var configurationList = configurationGroup.configurationList();
94 var ratioGraph = new RatioBarGraph();
96 var state = ChartsPage.createStateForConfigurationList(configurationList);
97 var anchor = link(ratioGraph, this.router().url('charts', state));
98 this._renderQueue.push(function () {
99 var warnings = configurationGroup.warnings();
100 var warningText = '';
101 for (var type in warnings) {
102 var platformString = Array.from(warnings[type]).map(function (platform) { return platform.name(); }).join(', ');
103 warningText += `Missing ${type} for following platform(s): ${platformString}`;
106 anchor.title = warningText || 'Open charts';
107 ratioGraph.update(configurationGroup.ratio(), configurationGroup.label(), !!warningText);
110 if (configurationList.length == 0)
111 return element('td', ratioGraph);
113 return element('td', anchor);
116 static htmlTemplate()
118 return `<section class="page-with-heading"><table class="summary-table"></table></section>`;
125 border-collapse: collapse;
128 width: calc(100% - 2rem - 2px);
137 .summary-table .majorHeader {
141 .summary-table .minorHeader {
145 .summary-table .unifiedHeader {
149 .summary-table > tr:nth-child(even) > *:not(.majorHeader) {
154 .summary-table thead td {
156 font-weight: inherit;
158 padding: 0.2rem 0.4rem;
161 .summary-table thead td {
165 .summary-table tbody td {
166 font-weight: inherit;
171 .summary-table td > * {
178 class SummaryPageConfigurationGroup {
179 constructor(platforms, metrics, excludedConfigurations)
181 this._measurementSets = [];
182 this._configurationList = [];
183 this._setToRatio = new Map;
187 this._smallerIsBetter = metrics.length ? metrics[0].isSmallerBetter() : null;
189 for (var platform of platforms) {
190 console.assert(platform instanceof Platform);
191 for (var metric of metrics) {
192 console.assert(metric instanceof Metric);
193 console.assert(this._smallerIsBetter == metric.isSmallerBetter());
194 metric.isSmallerBetter();
196 if (excludedConfigurations && platform.id() in excludedConfigurations && excludedConfigurations[platform.id()].includes(+metric.id()))
198 if (platform.hasMetric(metric)) {
199 this._measurementSets.push(MeasurementSet.findSet(platform.id(), metric.id(), platform.lastModified(metric)));
200 this._configurationList.push([platform.id(), metric.id()]);
206 ratio() { return this._ratio; }
207 label() { return this._label; }
208 warnings() { return this._warnings; }
209 changeType() { return this._changeType; }
210 configurationList() { return this._configurationList; }
212 fetchAndComputeSummary(timeRange)
214 console.assert(timeRange instanceof Array);
215 console.assert(typeof(timeRange[0]) == 'number');
216 console.assert(typeof(timeRange[1]) == 'number');
219 for (var set of this._measurementSets)
220 promises.push(this._fetchAndComputeRatio(set, timeRange));
222 return Promise.all(promises).then(this._computeSummary.bind(this));
228 for (var set of this._measurementSets) {
229 var ratio = this._setToRatio.get(set);
234 var averageRatio = Statistics.mean(ratios);
235 if (isNaN(averageRatio))
238 var currentIsSmallerThanBaseline = averageRatio < 1;
239 var changeType = this._smallerIsBetter == currentIsSmallerThanBaseline ? 'better' : 'worse';
240 averageRatio = Math.abs(averageRatio - 1);
242 this._ratio = averageRatio * (changeType == 'better' ? 1 : -1);
243 this._label = (averageRatio * 100).toFixed(1) + '%';
244 this._changeType = changeType;
247 _fetchAndComputeRatio(set, timeRange)
249 var setToRatio = this._setToRatio;
251 return set.fetchBetween(timeRange[0], timeRange[1]).then(function () {
252 var baselineTimeSeries = set.fetchedTimeSeries('baseline', false, false);
253 var currentTimeSeries = set.fetchedTimeSeries('current', false, false);
255 var baselineMedian = SummaryPageConfigurationGroup._medianForTimeRange(baselineTimeSeries, timeRange);
256 var currentMedian = SummaryPageConfigurationGroup._medianForTimeRange(currentTimeSeries, timeRange);
257 var platform = Platform.findById(set.platformId());
258 if (!baselineMedian) {
259 if(!('baseline' in self._warnings))
260 self._warnings['baseline'] = new Set;
261 self._warnings['baseline'].add(platform);
263 if (!currentMedian) {
264 if(!('current' in self._warnings))
265 self._warnings['current'] = new Set;
266 self._warnings['current'].add(platform);
269 setToRatio.set(set, currentMedian / baselineMedian);
270 }).catch(function () {
271 setToRatio.set(set, NaN);
275 static _medianForTimeRange(timeSeries, timeRange)
277 if (!timeSeries.firstPoint())
280 var startPoint = timeSeries.findPointAfterTime(timeRange[0]) || timeSeries.lastPoint();
281 var afterEndPoint = timeSeries.findPointAfterTime(timeRange[1]) || timeSeries.lastPoint();
282 var endPoint = timeSeries.previousPoint(afterEndPoint);
283 if (!endPoint || startPoint == afterEndPoint)
284 endPoint = afterEndPoint;
286 var points = timeSeries.dataBetweenPoints(startPoint, endPoint).map(function (point) { return point.value; });
287 return Statistics.median(points);