AnalysisResultsViewer never uses this._smallerIsBetter
[WebKit.git] / Websites / perf.webkit.org / public / v3 / components / analysis-results-viewer.js
1
2 class AnalysisResultsViewer extends ResultsTable {
3     constructor()
4     {
5         super('analysis-results-viewer');
6         this._startPoint = null;
7         this._endPoint = null;
8         this._testGroups = null;
9         this._currentTestGroup = null;
10         this._renderedCurrentTestGroup = null;
11         this._shouldRenderTable = true;
12         this._additionalHeading = null;
13         this._testGroupCallback = null;
14     }
15
16     setTestGroupCallback(callback) { this._testGroupCallback = callback; }
17
18     setCurrentTestGroup(testGroup)
19     {
20         this._currentTestGroup = testGroup;
21         this.render();
22     }
23
24     setPoints(startPoint, endPoint)
25     {
26         this._startPoint = startPoint;
27         this._endPoint = endPoint;
28         this._shouldRenderTable = true;
29     }
30
31     setTestGroups(testGroups)
32     {
33         this._testGroups = testGroups;
34         this._shouldRenderTable = true;
35     }
36
37     didUpdateResults()
38     {
39         this._shouldRenderTable = true;
40     }
41
42     render()
43     {
44         if (!this._valueFormatter || !this._startPoint)
45             return;
46
47         Instrumentation.startMeasuringTime('AnalysisResultsViewer', 'render');
48
49         if (this._shouldRenderTable) {
50             this._shouldRenderTable = false;
51             this._renderedCurrentTestGroup = null;
52
53             Instrumentation.startMeasuringTime('AnalysisResultsViewer', 'renderTable');
54             super.render();
55             Instrumentation.endMeasuringTime('AnalysisResultsViewer', 'renderTable');
56         }
57
58         if (this._currentTestGroup != this._renderedCurrentTestGroup) {
59             if (this._renderedCurrentTestGroup) {
60                 var className = this._classForTestGroup(this._renderedCurrentTestGroup);
61                 var element = this.content().querySelector('.' + className);
62                 if (element)
63                     element.classList.remove('selected');
64             }
65             if (this._currentTestGroup) {
66                 var className = this._classForTestGroup(this._currentTestGroup);
67                 var element = this.content().querySelector('.' + className);
68                 if (element)
69                     element.classList.add('selected');
70             }
71             this._renderedCurrentTestGroup = this._currentTestGroup;
72         }
73
74         Instrumentation.endMeasuringTime('AnalysisResultsViewer', 'render');
75     }
76
77     heading() { return [ComponentBase.createElement('th', 'Point')]; }
78     additionalHeading() { return this._additionalHeading; }
79
80     buildRowGroups()
81     {
82         Instrumentation.startMeasuringTime('AnalysisResultsViewer', 'buildRowGroups');
83
84         var testGroups = this._testGroups || [];
85         var rootSetsInTestGroups = this._collectRootSetsInTestGroups(testGroups);
86
87         var rowToMatchingRootSets = new Map;
88         var rowList = this._buildRowsForPointsAndTestGroups(rootSetsInTestGroups, rowToMatchingRootSets);
89
90         var testGroupLayoutMap = new Map;
91         var self = this;
92         rowList.forEach(function (row, rowIndex) {
93             var matchingRootSets = rowToMatchingRootSets.get(row);
94             for (var entry of matchingRootSets) {
95                 var testGroup = entry.testGroup();
96
97                 var block = testGroupLayoutMap.get(testGroup);
98                 if (!block) {
99                     block = new AnalysisResultsViewer.TestGroupStackingBlock(
100                         testGroup, self._classForTestGroup(testGroup), self._openStackingBlock.bind(self, testGroup));
101                     testGroupLayoutMap.set(testGroup, block);
102                 }
103                 block.addRowIndex(entry, rowIndex);
104             }
105         });
106
107         var grid = new AnalysisResultsViewer.TestGroupStackingGrid(rowList.length);
108         for (var testGroup of testGroups) {
109             var block = testGroupLayoutMap.get(testGroup);
110             if (block)
111                 grid.insertBlockToColumn(block);
112         }
113
114         grid.layout();
115         for (var rowIndex = 0; rowIndex < rowList.length; rowIndex++)
116             rowList[rowIndex].setAdditionalColumns(grid.createCellsForRow(rowIndex));
117
118         this._additionalHeading = grid._columns ? ComponentBase.createElement('td', {colspan: grid._columns.length + 1, class: 'stacking-block'}) : [];
119
120         Instrumentation.endMeasuringTime('AnalysisResultsViewer', 'buildRowGroups');
121
122         return [{rows: rowList}];
123     }
124
125     _collectRootSetsInTestGroups(testGroups)
126     {
127         if (!this._testGroups)
128             return [];
129
130         var rootSetsInTestGroups = [];
131         for (var group of this._testGroups) {
132             var sortedSets = group.requestedRootSets();
133             for (var i = 0; i < sortedSets.length; i++)
134                 rootSetsInTestGroups.push(new AnalysisResultsViewer.RootSetInTestGroup(group, sortedSets[i]));
135         }
136
137         return rootSetsInTestGroups;
138     }
139
140     _buildRowsForPointsAndTestGroups(rootSetsInTestGroups, rowToMatchingRootSets)
141     {
142         console.assert(this._startPoint.series == this._endPoint.series);
143         var rowList = [];
144         var pointAfterEnd = this._endPoint.series.nextPoint(this._endPoint);
145         var rootSetsWithPoints = new Set;
146         var pointIndex = 0;
147         for (var point = this._startPoint; point && point != pointAfterEnd; point = point.series.nextPoint(point), pointIndex++) {
148             var rootSetInPoint = point.rootSet();
149             var matchingRootSets = [];
150             for (var entry of rootSetsInTestGroups) {
151                 if (rootSetInPoint.equals(entry.rootSet()) && !rootSetsWithPoints.has(entry)) {
152                     matchingRootSets.push(entry);
153                     rootSetsWithPoints.add(entry);
154                 }
155             }
156
157             if (!matchingRootSets.length && point != this._startPoint && point != this._endPoint)
158                 continue;
159
160             var row = new ResultsTableRow(pointIndex.toString(), rootSetInPoint);
161             row.setResult(point);
162
163             rowToMatchingRootSets.set(row, matchingRootSets);
164             rowList.push(row);
165         }
166
167         rootSetsInTestGroups.forEach(function (entry) {
168             if (rootSetsWithPoints.has(entry))
169                 return;
170
171             for (var i = 0; i < rowList.length; i++) {
172                 var row = rowList[i];
173                 if (row.rootSet().equals(entry.rootSet())) {
174                     rowToMatchingRootSets.get(row).push(entry);
175                     return;
176                 }
177             }
178
179             var groupTime = entry.rootSet().latestCommitTime();
180             for (var i = 0; i < rowList.length; i++) {
181                 var rowTime = rowList[i].rootSet().latestCommitTime();
182                 if (rowTime > groupTime) {
183                     var newRow = new ResultsTableRow(null, entry.rootSet());
184                     rowToMatchingRootSets.set(newRow, [entry]);
185                     rowList.splice(i, 0, newRow);
186                     return;
187                 }
188
189                 if (rowTime == groupTime) {
190                     // Missing some commits. Do as best as we can to avoid going backwards in time.
191                     var repositoriesInNewRow = entry.rootSet().repositories();
192                     for (var j = i; j < rowList.length; j++) {
193                         for (var repository of repositoriesInNewRow) {
194                             var newCommit = entry.rootSet().commitForRepository(repository);
195                             var rowCommit = rowList[j].rootSet().commitForRepository(repository);
196                             if (!rowCommit || newCommit.time() < rowCommit.time()) {
197                                 var row = new ResultsTableRow(null, entry.rootSet());
198                                 rowToMatchingRootSets.set(row, [entry]);
199                                 rowList.splice(j, 0, row);
200                                 return;
201                             }
202                         }
203                     }
204                 }
205             }
206
207             var newRow = new ResultsTableRow(null, entry.rootSet());
208             rowToMatchingRootSets.set(newRow, [entry]);
209             rowList.push(newRow);
210         });
211
212         return rowList;
213     }
214
215     _classForTestGroup(testGroup)
216     {
217         return 'stacked-test-group-' + testGroup.id();
218     }
219
220     _openStackingBlock(testGroup)
221     {
222         if (this._testGroupCallback)
223             this._testGroupCallback(testGroup);
224     }
225
226     static htmlTemplate()
227     {
228         return `<section class="analysis-view">${ResultsTable.htmlTemplate()}</section>`;
229     }
230
231     static cssTemplate()
232     {
233         return ResultsTable.cssTemplate() + `
234             .analysis-view .stacking-block {
235                 position: relative;
236                 border: solid 1px #fff;
237                 cursor: pointer;
238             }
239
240             .analysis-view .stacking-block a {
241                 display: block;
242                 text-decoration: none;
243                 color: inherit;
244                 font-size: 0.8rem;
245                 padding: 0 0.1rem;
246                 max-width: 3rem;
247             }
248
249             .analysis-view .stacking-block:not(.failed) {
250                 color: black;
251                 opacity: 1;
252             }
253
254             .analysis-view .stacking-block.selected,
255             .analysis-view .stacking-block:hover {
256                 text-decoration: underline;
257             }
258
259             .analysis-view .stacking-block.selected:before {
260                 content: '';
261                 position: absolute;
262                 left: 0px;
263                 top: 0px;
264                 width: calc(100% - 2px);
265                 height: calc(100% - 2px);
266                 border: solid 1px #333;
267             }
268
269             .analysis-view .stacking-block.failed {
270                 background: rgba(128, 51, 128, 0.5);
271             }
272             .analysis-view .stacking-block.unchanged {
273                 background: rgba(128, 128, 128, 0.5);
274             }
275             .analysis-view .stacking-block.incomplete {
276                 background: rgba(204, 204, 51, 0.5);
277             }
278             .analysis-view .stacking-block.worse {
279                 background: rgba(255, 102, 102, 0.5);
280             }
281             .analysis-view .stacking-block.better {
282                 background: rgba(102, 102, 255, 0.5);
283             }
284         `;
285     }
286 }
287
288 ComponentBase.defineElement('analysis-results-viewer', AnalysisResultsViewer);
289
290 AnalysisResultsViewer.RootSetInTestGroup = class {
291     constructor(testGroup, rootSet)
292     {
293         console.assert(testGroup instanceof TestGroup);
294         console.assert(rootSet instanceof RootSet);
295         this._testGroup = testGroup;
296         this._rootSet = rootSet;
297     }
298
299     testGroup() { return this._testGroup; }
300     rootSet() { return this._rootSet; }
301 }
302
303 AnalysisResultsViewer.TestGroupStackingBlock = class {
304     constructor(testGroup, className, callback)
305     {
306         this._testGroup = testGroup;
307         this._rootSetIndexRowIndexMap = [];
308         this._className = className;
309         this._label = null;
310         this._title = null;
311         this._status = null;
312         this._callback = callback;
313     }
314
315     addRowIndex(rootSetInTestGroup, rowIndex)
316     {
317         console.assert(rootSetInTestGroup instanceof AnalysisResultsViewer.RootSetInTestGroup);
318         this._rootSetIndexRowIndexMap.push({rootSet: rootSetInTestGroup.rootSet(), rowIndex: rowIndex});
319     }
320
321     testGroup() { return this._testGroup; }
322
323     createStackingCell()
324     {
325         this._computeTestGroupStatus();
326
327         return ComponentBase.createElement('td', {
328             rowspan: this.endRowIndex() - this.startRowIndex() + 1,
329             title: this._title,
330             class: 'stacking-block ' + this._className + ' ' + this._status,
331             onclick: this._callback,
332         }, ComponentBase.createLink(this._label, this._title, this._callback));
333     }
334
335     isComplete() { return this._rootSetIndexRowIndexMap.length >= 2; }
336
337     startRowIndex() { return this._rootSetIndexRowIndexMap[0].rowIndex; }
338     endRowIndex() { return this._rootSetIndexRowIndexMap[this._rootSetIndexRowIndexMap.length - 1].rowIndex; }
339     isThin()
340     {
341         this._computeTestGroupStatus();
342         return this._status == 'failed';
343     }
344
345     _computeTestGroupStatus()
346     {
347         if (this._status || !this.isComplete())
348             return;
349
350         console.assert(this._rootSetIndexRowIndexMap.length <= 2); // FIXME: Support having more root sets.
351
352         var result = this._testGroup.compareTestResults(
353             this._rootSetIndexRowIndexMap[0].rootSet, this._rootSetIndexRowIndexMap[1].rootSet);
354
355         this._label = result.label;
356         this._title = result.fullLabel;
357         this._status = result.status;
358     }
359 }
360
361 AnalysisResultsViewer.TestGroupStackingGrid = class {
362     constructor(rowCount)
363     {
364         this._blocks = [];
365         this._columns = null;
366         this._rowCount = rowCount;
367     }
368
369     insertBlockToColumn(newBlock)
370     {
371         console.assert(newBlock instanceof AnalysisResultsViewer.TestGroupStackingBlock);
372         for (var i = this._blocks.length - 1; i >= 0; i--) {
373             var currentBlock = this._blocks[i];
374             if (currentBlock.startRowIndex() == newBlock.startRowIndex()
375                 && currentBlock.endRowIndex() == newBlock.endRowIndex()) {
376                 this._blocks.splice(i + 1, 0, newBlock);
377                 return;
378             }
379         }
380         this._blocks.push(newBlock);
381     }
382
383     layout()
384     {
385         this._columns = [];
386         for (var block of this._blocks)
387             this._layoutBlock(block);
388     }
389
390     _layoutBlock(newBlock)
391     {
392         for (var columnIndex = 0; columnIndex < this._columns.length; columnIndex++) {
393             var existingColumn = this._columns[columnIndex];
394             if (newBlock.isThin() != existingColumn[0].isThin())
395                 continue;
396
397             for (var i = 0; i < existingColumn.length; i++) {
398                 var currentBlock = existingColumn[i];
399                 if ((!i || existingColumn[i - 1].endRowIndex() < newBlock.startRowIndex())
400                     && newBlock.endRowIndex() < currentBlock.startRowIndex()) {
401                     existingColumn.splice(i, 0, newBlock);
402                     return;
403                 }
404             }
405
406             var lastBlock = existingColumn[existingColumn.length - 1];
407             if (lastBlock.endRowIndex() < newBlock.startRowIndex()) {
408                 existingColumn.push(newBlock);
409                 return;
410             }
411         }
412         this._columns.push([newBlock]);
413     }
414
415     createCellsForRow(rowIndex)
416     {
417         var element = ComponentBase.createElement;
418         var link = ComponentBase.createLink;
419
420         var cells = [element('td', {class: 'stacking-block'}, '')];
421         for (var columnIndex = 0; columnIndex < this._columns.length; columnIndex++) {
422             var blocksInColumn = this._columns[columnIndex];
423             if (!rowIndex && blocksInColumn[0].startRowIndex()) {
424                 cells.push(this._createEmptyStackingCell(blocksInColumn[0].startRowIndex()));
425                 continue;
426             }
427             for (var i = 0; i < blocksInColumn.length; i++) {
428                 var block = blocksInColumn[i];
429                 if (block.startRowIndex() == rowIndex) {
430                     cells.push(block.createStackingCell());
431                     break;
432                 }
433                 var rowCount = i + 1 < blocksInColumn.length ? blocksInColumn[i + 1].startRowIndex() : this._rowCount;
434                 var remainingRows = rowCount - block.endRowIndex() - 1;
435                 if (rowIndex == block.endRowIndex() + 1 && rowIndex < rowCount)
436                     cells.push(this._createEmptyStackingCell(remainingRows));
437             }
438         }
439
440         return cells;
441     }
442
443     _createEmptyStackingCell(rowspan, content)
444     {
445         return ComponentBase.createElement('td', {rowspan: rowspan, class: 'stacking-block'}, '');
446     }
447
448 }