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 29var defaultDashboardSpecificStateValues = { 30 builder: null, 31 treemapfocus: '', 32}; 33 34var DB_SPECIFIC_INVALIDATING_PARAMETERS = { 35 'testType': 'builder', 36 'group': 'builder' 37}; 38 39var g_haveEverGeneratedPage = false; 40 41function 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 58function 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 78function 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 93var 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. 102var g_history = new history.History(treemapConfig); 103g_history.parseCrossDashboardParameters(); 104 105var TEST_URL_BASE_PATH = "http://src.chromium.org/blink/trunk/"; 106 107function 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 124function 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 164function 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 179function 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 188function 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 212var g_isGeneratingPage = false; 213var g_webTree; 214 215function 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 241function extractName(node) 242{ 243 return node.name.split(' ')[0]; 244} 245 246function 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 256function 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 289window.addEventListener('load', function() { 290 var resourceLoader = new loader.Loader(); 291 resourceLoader.load(); 292}, false); 293