1 // Copyright (C) 2013 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 var defaultDashboardSpecificStateValues = { 30 builder: null, 31 treemapfocus: '', 32 }; 33 34 var DB_SPECIFIC_INVALIDATING_PARAMETERS = { 35 'testType': 'builder', 36 'group': 'builder' 37 }; 38 39 var g_haveEverGeneratedPage = false; 40 41 function generatePage(historyInstance) 42 { 43 g_haveEverGeneratedPage = true; 44 $('header-container').innerHTML = ui.html.testTypeSwitcher(); 45 46 g_isGeneratingPage = true; 47 48 var rawTree = g_resultsByBuilder[historyInstance.dashboardSpecificState.builder || currentBuilderGroup().defaultBuilder()]; 49 g_webTree = convertToWebTreemapFormat('AllTests', rawTree); 50 appendTreemap($('map'), g_webTree); 51 52 if (historyInstance.dashboardSpecificState.treemapfocus) 53 focusPath(g_webTree, historyInstance.dashboardSpecificState.treemapfocus) 54 55 g_isGeneratingPage = false; 56 } 57 58 function handleValidHashParameter(historyInstance, key, value) 59 { 60 switch(key) { 61 case 'builder': 62 history.validateParameter(historyInstance.dashboardSpecificState, key, value, 63 function() { return value in currentBuilders(); }); 64 return true; 65 66 case 'treemapfocus': 67 history.validateParameter(historyInstance.dashboardSpecificState, key, value, 68 function() { 69 return value.match(/^[\w./]+$/); 70 }); 71 return true; 72 73 default: 74 return false; 75 } 76 } 77 78 function handleQueryParameterChange(historyInstance, params) 79 { 80 for (var param in params) { 81 // When we're first loading the page, if there is a treemapfocus parameter, 82 // it will show up here. After we've generated the page, treemapfocus parameter 83 // changes should just be handled by the treemap code instead of calling through 84 // to generatePage. 85 if (!g_haveEverGeneratedPage || param != 'treemapfocus') { 86 $('map').innerHTML = 'Loading...'; 87 return true; 88 } 89 } 90 return false; 91 } 92 93 var treemapConfig = { 94 defaultStateValues: defaultDashboardSpecificStateValues, 95 generatePage: generatePage, 96 handleValidHashParameter: handleValidHashParameter, 97 handleQueryParameterChange: handleQueryParameterChange, 98 invalidatingHashParameters: DB_SPECIFIC_INVALIDATING_PARAMETERS 99 }; 100 101 // FIXME(jparent): Eventually remove all usage of global history object. 102 var g_history = new history.History(treemapConfig); 103 g_history.parseCrossDashboardParameters(); 104 105 var TEST_URL_BASE_PATH = "http://src.chromium.org/blink/trunk/"; 106 107 function humanReadableTime(milliseconds) 108 { 109 if (milliseconds < 1000) 110 return Math.floor(milliseconds) + 'ms'; 111 else if (milliseconds < 60000) 112 return (milliseconds / 1000).toPrecision(2) + 's'; 113 114 var minutes = Math.floor(milliseconds / 60000); 115 var seconds = Math.floor((milliseconds - minutes * 60000) / 1000); 116 return minutes + 'm' + seconds + 's'; 117 } 118 119 // This looks like: 120 // { "data": {"$area": (sum of all timings)}, 121 // "name": (name of this node), 122 // "children": [ (child nodes, in the same format as this) ] } 123 // childCount is added just to be includes in the node's name 124 function convertToWebTreemapFormat(treename, tree, path) 125 { 126 var total = 0; 127 var childCount = 0; 128 var children = []; 129 for (var name in tree) { 130 var treeNode = tree[name]; 131 if (typeof treeNode == "number") { 132 var time = treeNode; 133 var node = { 134 "data": {"$area": time}, 135 "name": name + " (" + humanReadableTime(time) + ")" 136 }; 137 children.push(node); 138 total += time; 139 childCount++; 140 } else { 141 var newPath = path ? path + '/' + name : name; 142 var subtree = convertToWebTreemapFormat(name, treeNode, newPath); 143 children.push(subtree); 144 total += subtree["data"]["$area"]; 145 childCount += subtree["childCount"]; 146 } 147 } 148 149 children.sort(function(a, b) { 150 aTime = a.data["$area"] 151 bTime = b.data["$area"] 152 return bTime - aTime; 153 }); 154 155 return { 156 "data": {"$area": total}, 157 "name": treename + " (" + humanReadableTime(total) + " - " + childCount + " tests)", 158 "children": children, 159 "childCount": childCount, 160 "path": path 161 }; 162 } 163 164 function listOfAllNonLeafNodes(tree, list) 165 { 166 if (!tree.children) 167 return; 168 169 if (!list) 170 list = []; 171 list.push(tree); 172 173 tree.children.forEach(function(child) { 174 listOfAllNonLeafNodes(child, list); 175 }); 176 return list; 177 } 178 179 function reverseSortByAverage(list) 180 { 181 list.sort(function(a, b) { 182 var avgA = a.data['$area'] / a.childCount; 183 var avgB = b.data['$area'] / b.childCount; 184 return avgB - avgA; 185 }); 186 } 187 188 function showAverages() 189 { 190 if (!document.getElementById('map')) 191 return; 192 193 var table = document.createElement('table'); 194 table.innerHTML = '<th>directory</th><th># tests</th><th>avg time / test</th>'; 195 196 var allNodes = listOfAllNonLeafNodes(g_webTree); 197 reverseSortByAverage(allNodes); 198 allNodes.forEach(function(node) { 199 var average = node.data['$area'] / node.childCount; 200 if (average > 100 && node.childCount != 1) { 201 var tr = document.createElement('tr'); 202 tr.innerHTML = '<td></td><td>' + node.childCount + '</td><td>' + humanReadableTime(average) + '</td>'; 203 tr.querySelector('td').innerText = node.path; 204 table.appendChild(tr); 205 } 206 }); 207 208 var map = document.getElementById('map'); 209 map.parentNode.replaceChild(table, map); 210 } 211 212 var g_isGeneratingPage = false; 213 var g_webTree; 214 215 function focusPath(tree, path) 216 { 217 var parts = decodeURIComponent(path).split('/'); 218 if (extractName(tree) != parts[0]) { 219 console.error('Could not focus tree rooted at ' + parts[0]); 220 return; 221 } 222 223 for (var i = 1; i < parts.length; i++) { 224 var children = tree.children; 225 for (var j = 0; j < children.length; j++) { 226 var child = children[j]; 227 if (extractName(child) == parts[i]) { 228 tree = child; 229 focus(tree); 230 break; 231 } 232 } 233 if (j == children.length) { 234 console.error('Could not find tree at ' + parts[i]); 235 break; 236 } 237 } 238 239 } 240 241 function extractName(node) 242 { 243 return node.name.split(' ')[0]; 244 } 245 246 function fullName(node) 247 { 248 var buffer = [extractName(node)]; 249 while (node.parent) { 250 node = node.parent; 251 buffer.unshift(extractName(node)); 252 } 253 return buffer.join('/'); 254 } 255 256 function handleFocus(tree) 257 { 258 var currentlyFocusedNode = $('focused-leaf'); 259 if (currentlyFocusedNode) 260 currentlyFocusedNode.id = ''; 261 262 if (!tree.children) 263 tree.dom.id = 'focused-leaf'; 264 265 var name = fullName(tree); 266 267 if (!tree.children && !tree.extraDom && g_history.isLayoutTestResults()) { 268 tree.extraDom = document.createElement('pre'); 269 tree.extraDom.className = 'extra-dom'; 270 tree.dom.appendChild(tree.extraDom); 271 272 loader.request(TEST_URL_BASE_PATH + name, 273 function(xhr) { 274 tree.extraDom.onmousedown = function(e) { 275 e.stopPropagation(); 276 }; 277 tree.extraDom.textContent = xhr.responseText; 278 }, 279 function (xhr) { 280 tree.extraDom.textContent = "Could not load test." 281 }); 282 } 283 284 // We don't want the focus calls during generatePage to try to modify the query state. 285 if (!g_isGeneratingPage) 286 g_history.setQueryParameter('treemapfocus', name); 287 } 288 289 window.addEventListener('load', function() { 290 var resourceLoader = new loader.Loader(); 291 resourceLoader.load(); 292 }, false); 293