Home | History | Annotate | Download | only in static-dashboards
      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