d095be7fa661cfabe998d0ffe267ad7ccf08294d
[WebKit-https.git] / Tools / TestResultServer / static-dashboards / treemap.html
1 <!-- Copyright (C) 2011 Google Inc. All rights reserved.
2
3 Redistribution and use in source and binary forms, with or without
4 modification, are permitted provided that the following conditions are
5 met:
6
7     * Redistributions of source code must retain the above copyright
8 notice, this list of conditions and the following disclaimer.
9     * Redistributions in binary form must reproduce the above
10 copyright notice, this list of conditions and the following disclaimer
11 in the documentation and/or other materials provided with the
12 distribution.
13     * Neither the name of Google Inc. nor the names of its
14 contributors may be used to endorse or promote products derived from
15 this software without specific prior written permission.
16
17 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 -->
29 <!DOCTYPE html>
30 <title>Test Runtimes</title>
31 <link rel='stylesheet' href='webtreemap.css'></link>
32 <style>
33 body {
34     display: -moz-box;
35     display: -webkit-box;
36     display: box;
37     -moz-box-orient: vertical;
38     -webkit-box-orient: vertical;
39     box-orient: vertical;
40
41     position: absolute;
42     top: 0;
43     right: 0;
44     bottom: 0;
45     left: 0;
46 }
47
48 td:first-child {
49     text-align: left;
50 }
51
52 td {
53     text-align: right;
54 }
55
56 #map {
57     display: -moz-box;
58     display: -webkit-box;
59     display: box;
60
61     -moz-box-flex: 1;
62     -webkit-box-flex: 1;
63     box-flex: 1;
64
65     position: relative;
66     cursor: pointer;
67     -webkit-user-select: none;
68     -moz-user-select: none;
69 }
70
71 .extra-dom {
72     display: none;
73     border: none;
74     border-top: 1px dashed;
75     padding: 4px;
76     margin: 0;
77     overflow: auto;
78     cursor: auto;
79     -webkit-user-select: text;
80     -moz-user-select: text;
81 }
82
83 #focused-leaf {
84     display: -webkit-box;
85     display: -moz-box;
86     -webkit-box-orient: vertical;
87     -moz-box-orient: vertical;
88 }
89
90 #focused-leaf > .extra-dom {
91     display: -webkit-box;
92     display: -moz-box;
93     -webkit-box-flex: 1;
94     -moz-box-flex: 1;
95 }
96
97 #focused-leaf.webtreemap-node:hover {
98     background: white;
99 }
100
101 #focused-leaf .webtreemap-caption:hover {
102     background: #eee;
103 }
104
105 .error {
106     color: red;
107     font-style: italic;
108 }
109 </style>
110 <script src="builders.js"></script>
111 <script src="loader.js"></script>
112 <script src="string.js"></script>
113 <script src="dashboard_base.js"></script>
114 <script src="ui.js"></script>
115 <script src='webtreemap.js'></script>
116
117 <div id='header-container'></div>
118 <p>Click on a box to zoom in. Click on the outermost box to zoom out. <a href="" onclick="showAverages();return false;">Show averages</a></p>
119 <div id='map'></div>
120
121 <script>
122 var TEST_URL_BASE_PATH = "http://svn.webkit.org/repository/webkit/trunk/";
123
124 function humanReadableTime(milliseconds)
125 {
126     if (milliseconds < 1000)
127         return Math.floor(milliseconds) + 'ms';
128     else if (milliseconds < 60000)
129         return (milliseconds / 1000).toPrecision(2) + 's';
130
131     var minutes = Math.floor(milliseconds / 60000);
132     var seconds = Math.floor((milliseconds - minutes * 60000) / 1000);
133     return minutes + 'm' + seconds + 's';
134 }
135
136 // This looks like:
137 // { "data": {"$area": (sum of all timings)},
138 //   "name": (name of this node),
139 //   "children": [ (child nodes, in the same format as this) ] }
140 // childCount is added just to be includes in the node's name
141 function convertToWebTreemapFormat(treename, tree, path)
142 {
143     var total = 0;
144     var childCount = 0;
145     var children = [];
146     for (var name in tree) {
147         var treeNode = tree[name];
148         if (typeof treeNode == "number") {
149             var time = treeNode;
150             var node = {
151                 "data": {"$area": time},
152                 "name": name + " (" + humanReadableTime(time) + ")"
153             };
154             children.push(node);
155             total += time;
156             childCount++;
157         } else {
158             var newPath = path ? path + '/' + name : name;
159             var subtree = convertToWebTreemapFormat(name, treeNode, newPath);
160             children.push(subtree);
161             total += subtree["data"]["$area"];
162             childCount += subtree["childCount"];
163         }
164     }
165
166     children.sort(function(a, b) {
167         aTime = a.data["$area"]
168         bTime = b.data["$area"]
169         return bTime - aTime;
170     });
171
172     return {
173         "data": {"$area": total},
174         "name": treename + " (" + humanReadableTime(total) + " - " + childCount + " tests)",
175         "children": children,
176         "childCount": childCount,
177         "path": path
178     };
179 }
180
181 function listOfAllNonLeafNodes(tree, list)
182 {
183     if (!tree.children)
184         return;
185
186     if (!list)
187         list = [];
188     list.push(tree);
189
190     tree.children.forEach(function(child) {
191         listOfAllNonLeafNodes(child, list);
192     });
193     return list;
194 }
195
196 function reverseSortByAverage(list)
197 {
198     list.sort(function(a, b) {
199         var avgA = a.data['$area'] / a.childCount;
200         var avgB = b.data['$area'] / b.childCount;
201         return avgB - avgA;
202     });
203 }
204
205 function showAverages()
206 {
207     if (!document.getElementById('map'))
208         return;
209
210     var table = document.createElement('table');
211     table.innerHTML = '<th>directory</th><th># tests</th><th>avg time / test</th>';
212
213     var allNodes = listOfAllNonLeafNodes(g_webTree);
214     reverseSortByAverage(allNodes);
215     allNodes.forEach(function(node) {
216         var average = node.data['$area'] / node.childCount;
217         if (average > 100 && node.childCount != 1) {
218             var tr = document.createElement('tr');
219             tr.innerHTML = '<td></td><td>' + node.childCount + '</td><td>' + humanReadableTime(average) + '</td>';
220             tr.querySelector('td').innerText = node.path;
221             table.appendChild(tr);
222         }
223     });
224
225     var map = document.getElementById('map');
226     map.parentNode.replaceChild(table, map);
227 }
228
229 var g_isGeneratingPage = false;
230 var g_webTree;
231
232 function generatePage()
233 {
234     $('header-container').innerHTML = ui.html.testTypeSwitcher();
235
236     g_isGeneratingPage = true;
237
238     var rawTree = g_resultsByBuilder[g_currentState.builder || currentBuilderGroup().defaultBuilder()];
239     g_webTree = convertToWebTreemapFormat('LayoutTests', rawTree);
240     appendTreemap($('map'), g_webTree);
241
242     if (g_currentState.treemapfocus)
243         focusPath(g_webTree, g_currentState.treemapfocus)
244
245     g_isGeneratingPage = false;
246 }
247
248 function focusPath(tree, path)
249 {
250     var parts = decodeURIComponent(path).split('/');
251     if (extractName(tree) != parts[0]) {
252         console.error('Could not focus tree rooted at ' + parts[0]);
253         return;
254     }
255
256     for (var i = 1; i < parts.length; i++) {
257         var children = tree.children;
258         for (var j = 0; j < children.length; j++) {
259             var child = children[j];
260             if (extractName(child) == parts[i]) {
261                 tree = child;
262                 focus(tree);
263                 break;
264             }
265         }
266         if (j == children.length) {
267             console.error('Could not find tree at ' + parts[i]);
268             break;
269         }
270     }
271
272 }
273
274 function handleValidHashParameter(key, value)
275 {
276     switch(key) {
277     case 'builder':
278         validateParameter(g_currentState, key, value,
279             function() { return value in currentBuilders(); });
280         return true;
281
282     case 'treemapfocus':
283         validateParameter(g_currentState, key, value,
284             function() {
285                 // FIXME: There's probably a simpler regexp here. Just trying to match ascii + forward-slash.
286                 // e.g. LayoutTests/foo/bar.html
287                 return (value.match(/^(\w+\/\w*)*$/));
288             });
289         return true;
290
291     default:
292         return false;
293     }
294 }
295
296 g_defaultDashboardSpecificStateValues = {
297     builder: null,
298     treemapfocus: '',
299 };
300
301 DB_SPECIFIC_INVALIDATING_PARAMETERS = {
302     'testType': 'builder',
303     'group': 'builder'
304 };
305
306 function handleQueryParameterChange(params)
307 {
308     for (var param in params) {
309         if (param != 'treemapfocus') {
310             $('map').innerHTML = 'Loading...';
311             return true;
312         }
313     }
314     return false;
315 }
316
317 function extractName(node)
318 {
319     return node.name.split(' ')[0];
320 }
321
322 function fullName(node)
323 {
324     var buffer = [extractName(node)];
325     while (node.parent) {
326         node = node.parent;
327         buffer.unshift(extractName(node));
328     }
329     return buffer.join('/');
330 }
331
332 function handleFocus(tree)
333 {
334     var currentlyFocusedNode = $('focused-leaf');
335     if (currentlyFocusedNode)
336         currentlyFocusedNode.id = '';
337
338     if (!tree.children)
339         tree.dom.id = 'focused-leaf';
340
341     var name = fullName(tree);
342
343     if (!tree.children && !tree.extraDom && isLayoutTestResults()) {
344         tree.extraDom = document.createElement('pre');
345         tree.extraDom.className = 'extra-dom';
346         tree.dom.appendChild(tree.extraDom);
347
348         loader.request(TEST_URL_BASE_PATH + name,
349             function(xhr) {
350                 tree.extraDom.onmousedown = function(e) {
351                     e.stopPropagation();
352                 };
353                 tree.extraDom.textContent = xhr.responseText;
354             },
355             function (xhr) {
356                 tree.extraDom.textContent = "Could not load test."
357         });
358     }
359
360     // We don't want the focus calls during generatePage to try to modify the query state.
361     if (!g_isGeneratingPage)
362         setQueryParameter('treemapfocus', name);
363 }
364
365 window.addEventListener('load', function() {
366     var resourceLoader = new loader.Loader(intializeHistory);
367     resourceLoader.load();
368 }, false);
369 </script>