// Copyright (C) 2013 Google Inc. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. var defaultDashboardSpecificStateValues = { builder: null, treemapfocus: '', }; var DB_SPECIFIC_INVALIDATING_PARAMETERS = { 'testType': 'builder', 'group': 'builder' }; var g_haveEverGeneratedPage = false; function generatePage(historyInstance) { g_haveEverGeneratedPage = true; $('header-container').innerHTML = ui.html.testTypeSwitcher(); g_isGeneratingPage = true; var rawTree = g_resultsByBuilder[historyInstance.dashboardSpecificState.builder || currentBuilderGroup().defaultBuilder()]; g_webTree = convertToWebTreemapFormat('AllTests', rawTree); appendTreemap($('map'), g_webTree); if (historyInstance.dashboardSpecificState.treemapfocus) focusPath(g_webTree, historyInstance.dashboardSpecificState.treemapfocus) g_isGeneratingPage = false; } function handleValidHashParameter(historyInstance, key, value) { switch(key) { case 'builder': history.validateParameter(historyInstance.dashboardSpecificState, key, value, function() { return value in currentBuilders(); }); return true; case 'treemapfocus': history.validateParameter(historyInstance.dashboardSpecificState, key, value, function() { return value.match(/^[\w./]+$/); }); return true; default: return false; } } function handleQueryParameterChange(historyInstance, params) { for (var param in params) { // When we're first loading the page, if there is a treemapfocus parameter, // it will show up here. After we've generated the page, treemapfocus parameter // changes should just be handled by the treemap code instead of calling through // to generatePage. if (!g_haveEverGeneratedPage || param != 'treemapfocus') { $('map').innerHTML = 'Loading...'; return true; } } return false; } var treemapConfig = { defaultStateValues: defaultDashboardSpecificStateValues, generatePage: generatePage, handleValidHashParameter: handleValidHashParameter, handleQueryParameterChange: handleQueryParameterChange, invalidatingHashParameters: DB_SPECIFIC_INVALIDATING_PARAMETERS }; // FIXME(jparent): Eventually remove all usage of global history object. var g_history = new history.History(treemapConfig); g_history.parseCrossDashboardParameters(); var TEST_URL_BASE_PATH = "http://src.chromium.org/blink/trunk/"; function humanReadableTime(milliseconds) { if (milliseconds < 1000) return Math.floor(milliseconds) + 'ms'; else if (milliseconds < 60000) return (milliseconds / 1000).toPrecision(2) + 's'; var minutes = Math.floor(milliseconds / 60000); var seconds = Math.floor((milliseconds - minutes * 60000) / 1000); return minutes + 'm' + seconds + 's'; } // This looks like: // { "data": {"$area": (sum of all timings)}, // "name": (name of this node), // "children": [ (child nodes, in the same format as this) ] } // childCount is added just to be includes in the node's name function convertToWebTreemapFormat(treename, tree, path) { var total = 0; var childCount = 0; var children = []; for (var name in tree) { var treeNode = tree[name]; if (typeof treeNode == "number") { var time = treeNode; var node = { "data": {"$area": time}, "name": name + " (" + humanReadableTime(time) + ")" }; children.push(node); total += time; childCount++; } else { var newPath = path ? path + '/' + name : name; var subtree = convertToWebTreemapFormat(name, treeNode, newPath); children.push(subtree); total += subtree["data"]["$area"]; childCount += subtree["childCount"]; } } children.sort(function(a, b) { aTime = a.data["$area"] bTime = b.data["$area"] return bTime - aTime; }); return { "data": {"$area": total}, "name": treename + " (" + humanReadableTime(total) + " - " + childCount + " tests)", "children": children, "childCount": childCount, "path": path }; } function listOfAllNonLeafNodes(tree, list) { if (!tree.children) return; if (!list) list = []; list.push(tree); tree.children.forEach(function(child) { listOfAllNonLeafNodes(child, list); }); return list; } function reverseSortByAverage(list) { list.sort(function(a, b) { var avgA = a.data['$area'] / a.childCount; var avgB = b.data['$area'] / b.childCount; return avgB - avgA; }); } function showAverages() { if (!document.getElementById('map')) return; var table = document.createElement('table'); table.innerHTML = 'directory# testsavg time / test'; var allNodes = listOfAllNonLeafNodes(g_webTree); reverseSortByAverage(allNodes); allNodes.forEach(function(node) { var average = node.data['$area'] / node.childCount; if (average > 100 && node.childCount != 1) { var tr = document.createElement('tr'); tr.innerHTML = '' + node.childCount + '' + humanReadableTime(average) + ''; tr.querySelector('td').innerText = node.path; table.appendChild(tr); } }); var map = document.getElementById('map'); map.parentNode.replaceChild(table, map); } var g_isGeneratingPage = false; var g_webTree; function focusPath(tree, path) { var parts = decodeURIComponent(path).split('/'); if (extractName(tree) != parts[0]) { console.error('Could not focus tree rooted at ' + parts[0]); return; } for (var i = 1; i < parts.length; i++) { var children = tree.children; for (var j = 0; j < children.length; j++) { var child = children[j]; if (extractName(child) == parts[i]) { tree = child; focus(tree); break; } } if (j == children.length) { console.error('Could not find tree at ' + parts[i]); break; } } } function extractName(node) { return node.name.split(' ')[0]; } function fullName(node) { var buffer = [extractName(node)]; while (node.parent) { node = node.parent; buffer.unshift(extractName(node)); } return buffer.join('/'); } function handleFocus(tree) { var currentlyFocusedNode = $('focused-leaf'); if (currentlyFocusedNode) currentlyFocusedNode.id = ''; if (!tree.children) tree.dom.id = 'focused-leaf'; var name = fullName(tree); if (!tree.children && !tree.extraDom && g_history.isLayoutTestResults()) { tree.extraDom = document.createElement('pre'); tree.extraDom.className = 'extra-dom'; tree.dom.appendChild(tree.extraDom); loader.request(TEST_URL_BASE_PATH + name, function(xhr) { tree.extraDom.onmousedown = function(e) { e.stopPropagation(); }; tree.extraDom.textContent = xhr.responseText; }, function (xhr) { tree.extraDom.textContent = "Could not load test." }); } // We don't want the focus calls during generatePage to try to modify the query state. if (!g_isGeneratingPage) g_history.setQueryParameter('treemapfocus', name); } window.addEventListener('load', function() { var resourceLoader = new loader.Loader(); resourceLoader.load(); }, false);