Home | History | Annotate | Download | only in tools
      1 <html>
      2 <!--
      3 Copyright 2016 the V8 project authors. All rights reserved.  Use of this source
      4 code is governed by a BSD-style license that can be found in the LICENSE file.
      5 -->
      6 
      7 <head>
      8   <meta charset="UTF-8">
      9   <style>
     10     body {
     11       font-family: arial;
     12     }
     13     
     14     table {
     15       display: table;
     16       border-spacing: 0px;
     17     }
     18     
     19     tr {
     20       border-spacing: 0px;
     21       padding: 10px;
     22     }
     23     
     24     td,
     25     th {
     26       padding: 3px 10px 3px 5px;
     27     }
     28     
     29     .inline {
     30       display: inline-block;
     31       vertical-align: top;
     32     }
     33     
     34     h2,
     35     h3 {
     36       margin-bottom: 0px;
     37     }
     38     
     39     .hidden {
     40       display: none;
     41     }
     42     
     43     .view {
     44       display: table;
     45     }
     46     
     47     .column {
     48       display: table-cell;
     49       border-right: 1px black dotted;
     50       min-width: 200px;
     51     }
     52     
     53     .column .header {
     54       padding: 0 10px 0 10px
     55     }
     56     
     57     #column {
     58       display: none;
     59     }
     60    
     61     .list {
     62       width: 100%;
     63     }
     64     
     65     select {
     66       width: 100%
     67     }
     68     
     69     .list tbody {
     70       cursor: pointer;
     71     }
     72     
     73     .list tr:nth-child(even) {
     74       background-color: #EFEFEF;
     75     }
     76     
     77     .list tr:nth-child(even).selected {
     78       background-color: #DDD;
     79     }
     80     
     81     .list tr.child {
     82       display: none;
     83     }
     84     
     85     .list tr.child.visible {
     86       display: table-row;
     87     }
     88     
     89     .list .child .name {
     90       padding-left: 20px;
     91     }
     92     
     93     .list .parent td {
     94       border-top: 1px solid #AAA;
     95     }
     96     
     97     .list .total {
     98       font-weight: bold
     99     }
    100     
    101     .list tr.parent {
    102       background-color: #FFF;
    103     }
    104     
    105     .list tr.parent.selected {
    106       background-color: #DDD;
    107     }
    108     
    109     tr.selected {
    110       background-color: #DDD;
    111     }
    112     
    113     .codeSearch {
    114       display: block-inline;
    115       float: right;
    116       border-radius: 5px;
    117       background-color: #EEE;
    118       width: 1em;
    119       text-align: center;
    120     }
    121     
    122     .list .position {
    123       text-align: right;
    124       display: none;
    125     }
    126     
    127     .list div.toggle {
    128       cursor: pointer;
    129     }
    130     
    131     #column_0 .position {
    132       display: table-cell;
    133     }
    134     
    135     #column_0 .name {
    136       display: table-cell;
    137     }
    138     
    139     .list .name {
    140       display: none;
    141       white-space: nowrap;
    142     }
    143     
    144     .value {
    145       text-align: right;
    146     }
    147     
    148     .selectedVersion {
    149       font-weight: bold;
    150     }
    151     
    152     #baseline {
    153       width: auto;
    154     }
    155     
    156     .compareSelector {
    157       padding-bottom: 20px;
    158     }
    159     
    160     .pageDetailTable tbody {
    161       cursor: pointer
    162     }
    163     
    164     .pageDetailTable tfoot td {
    165       border-top: 1px grey solid;
    166     }
    167     
    168     #popover {
    169       position: absolute;
    170       transform: translateY(-50%) translateX(40px);
    171       box-shadow: -2px 10px 44px -10px #000;
    172       border-radius: 5px;
    173       z-index: 1;
    174       background-color: #FFF;
    175       display: none;
    176       white-space: nowrap;
    177     }
    178     
    179     #popover table {
    180       position: relative;
    181       z-index: 1;
    182       text-align: right;
    183       margin: 10px;
    184     }
    185     #popover td {
    186       padding: 3px 0px 3px 5px;
    187       white-space: nowrap;
    188     }
    189     
    190     .popoverArrow {
    191       background-color: #FFF;
    192       position: absolute;
    193       width: 30px;
    194       height: 30px;
    195       transform: translateY(-50%)rotate(45deg);
    196       top: 50%;
    197       left: -10px;
    198       z-index: 0;
    199     }
    200     
    201     #popover .name {
    202       padding: 5px;
    203       font-weight: bold;
    204       text-align: center;
    205     }
    206     
    207     #popover table .compare {
    208       display: none
    209     }
    210     
    211     #popover table.compare .compare {
    212       display: table-cell;
    213     }
    214 
    215     #popover .compare .time,
    216     #popover .compare .version {
    217       padding-left: 10px;
    218     }
    219     .graph,
    220     .graph .content {
    221       width: 100%;
    222     }
    223 
    224     .diff .hideDiff {
    225       display: none;
    226     }
    227     .noDiff .hideNoDiff {
    228       display: none;
    229     }
    230   </style>
    231   <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    232   <script type="text/javascript">
    233     "use strict"
    234     google.charts.load('current', {packages: ['corechart']});
    235 
    236     // Did anybody say monkeypatching?
    237     if (!NodeList.prototype.forEach) {
    238       NodeList.prototype.forEach = function(func) {
    239         for (var i = 0; i < this.length; i++) {
    240           func(this[i]);
    241         }
    242       }
    243     }
    244 
    245     var versions;
    246     var pages;
    247     var selectedPage;
    248     var baselineVersion;
    249     var selectedEntry;
    250 
    251     function initialize() {
    252       var original = $("column");
    253       var view = document.createElement('div');
    254       view.id = 'view';
    255       var i = 0;
    256       versions.forEach((version) =>  {
    257         if (!version.enabled) return;
    258         // add column
    259         var column = original.cloneNode(true);
    260         column.id = "column_" + i;
    261         // Fill in all versions
    262         var select = column.querySelector(".version");
    263         select.id = "selectVersion_" + i;
    264         // add all select options
    265         versions.forEach((version) => {
    266           if (!version.enabled) return;
    267           var option = document.createElement("option");
    268           option.textContent = version.name;
    269           option.version = version;
    270           select.appendChild(option);
    271         });
    272         // Fill in all page versions
    273         select = column.querySelector(".pageVersion");
    274         select.id = "select_" + i;
    275         // add all pages
    276         versions.forEach((version) => {
    277           if (!version.enabled) return;
    278           var optgroup = document.createElement("optgroup");
    279           optgroup.label = version.name;
    280           optgroup.version = version;
    281           version.forEachPage((page) => {
    282             var option = document.createElement("option");
    283             option.textContent = page.name;
    284             option.page = page;
    285             optgroup.appendChild(option);
    286           });
    287           select.appendChild(optgroup);
    288         });
    289         view.appendChild(column);
    290         i++;
    291       });
    292       var oldView = $('view');
    293       oldView.parentNode.replaceChild(view, oldView);
    294       
    295       var select = $('baseline');
    296       removeAllChildren(select);
    297       select.appendChild(document.createElement('option'));
    298       versions.forEach((version) => {
    299         var option = document.createElement("option");
    300         option.textContent = version.name;
    301         option.version = version;
    302         select.appendChild(option);
    303       });
    304       initializeToggleList(versions.versions, $('versionSelector'));
    305       initializeToggleList(pages.values(), $('pageSelector'));
    306       initializeToggleContentVisibility();
    307     }
    308 
    309     function initializeToggleList(items, node) {
    310       var list = node.querySelector('ul');
    311       removeAllChildren(list);
    312       items = Array.from(items);
    313       items.sort(NameComparator);
    314       items.forEach((item) => {
    315         var li = document.createElement('li');
    316         var checkbox = document.createElement('input');
    317         checkbox.type = 'checkbox';
    318         checkbox.checked = item.enabled;
    319         checkbox.item = item;
    320         checkbox.addEventListener('click', handleToggleVersionEnable);
    321         li.appendChild(checkbox);
    322         li.appendChild(document.createTextNode(item.name));
    323         list.appendChild(li);
    324       });
    325       $('results').querySelectorAll('#results > .hidden').forEach((node) => {
    326         toggleCssClass(node, 'hidden', false);
    327       })
    328     }
    329 
    330     function initializeToggleContentVisibility() {
    331       var nodes = document.querySelectorAll('.toggleContentVisibility');
    332       nodes.forEach((node) => {
    333         var content = node.querySelector('.content');
    334         var header = node.querySelector('h1,h2,h3');
    335         if (content === undefined || header === undefined) return;
    336         if (header.querySelector('input') != undefined) return;
    337         var checkbox = document.createElement('input');
    338         checkbox.type = 'checkbox';
    339         checkbox.checked = content.className.indexOf('hidden') == -1;
    340         checkbox.contentNode = content;
    341         checkbox.addEventListener('click', handleToggleContentVisibility);
    342         header.insertBefore(checkbox, header.childNodes[0]);
    343       });
    344     }
    345 
    346     function showPage(firstPage) {
    347       var changeSelectedEntry = selectedEntry !== undefined 
    348           && selectedEntry.page === selectedPage;
    349       selectedPage = firstPage;
    350       selectedPage.sort();
    351       showPageInColumn(firstPage, 0);
    352       // Show the other versions of this page in the following columns.
    353       var pageVersions = versions.getPageVersions(firstPage);
    354       var index = 1;
    355       pageVersions.forEach((page) => {
    356         if (page !== firstPage) {
    357           showPageInColumn(page, index);
    358           index++;
    359         }
    360       });
    361       if (changeSelectedEntry) {
    362         showEntryDetail(selectedPage.getEntry(selectedEntry));
    363       } else {
    364         showImpactList(selectedPage);
    365       }
    366     }
    367 
    368     function showPageInColumn(page, columnIndex) {
    369       page.sort();
    370       var showDiff = (baselineVersion === undefined && columnIndex !== 0) ||
    371         (baselineVersion !== undefined && page.version !== baselineVersion);
    372       var diffStatus = (td, a, b) => {};
    373       if (showDiff) {
    374         if (baselineVersion !== undefined) {
    375           diffStatus = (td, a, b) => {
    376             if (a == 0) return;
    377             td.style.color = a < 0 ? '#FF0000' : '#00BB00';
    378           };
    379         } else {
    380           diffStatus = (td, a, b) => {
    381             if (a == b) return;
    382             var color;
    383             var ratio = a / b;
    384             if (ratio > 1) {
    385               ratio = Math.min(Math.round((ratio - 1) * 255 * 10), 200);
    386               color = '#' + ratio.toString(16) + "0000";
    387             } else {
    388               ratio = Math.min(Math.round((1 - ratio) * 255 * 10), 200);
    389               color = '#00' + ratio.toString(16) + "00";
    390             }
    391             td.style.color = color;
    392           }
    393         }
    394       }
    395 
    396       var column = $('column_' + columnIndex);
    397       var select = $('select_' + columnIndex);
    398       // Find the matching option
    399       selectOption(select, (i, option) => {
    400         return option.page == page
    401       });
    402       var table = column.querySelector("table");
    403       var oldTbody = table.querySelector('tbody');
    404       var tbody = document.createElement('tbody');
    405       var referencePage = selectedPage;
    406       page.forEachSorted(selectedPage, (parentEntry, entry, referenceEntry) => {
    407         // Filter out entries that do not exist in the first column for the default
    408         // view.
    409         if (baselineVersion === undefined && referenceEntry &&
    410           referenceEntry.time == 0) {
    411           return;
    412         }
    413         var tr = document.createElement('tr');
    414         tbody.appendChild(tr);
    415         tr.entry = entry;
    416         tr.parentEntry = parentEntry;
    417         tr.className = parentEntry === undefined ? 'parent' : 'child';
    418         // Don't show entries that do not exist on the current page or if we
    419         // compare against the current page
    420         if (entry !== undefined && page.version !== baselineVersion) {
    421           // If we show a diff, use the baselineVersion as the referenceEntry
    422           if (baselineVersion !== undefined) {
    423             var baselineEntry = baselineVersion.getEntry(entry);
    424             if (baselineEntry !== undefined) referenceEntry = baselineEntry
    425           }
    426           if (!parentEntry) {
    427             var node = td(tr, '<div class="toggle"></div>', 'position');
    428             node.firstChild.addEventListener('click', handleToggleGroup);
    429           } else {
    430             td(tr, entry.position == 0 ? '' : entry.position, 'position');
    431           }
    432           addCodeSearchButton(entry,
    433               td(tr, entry.name, 'name ' + entry.cssClass()));
    434           
    435           diffStatus(
    436             td(tr, ms(entry.time), 'value time'),
    437             entry.time, referenceEntry.time);
    438           diffStatus(
    439             td(tr, percent(entry.timePercent), 'value time'),
    440             entry.time, referenceEntry.time);
    441           diffStatus(
    442             td(tr, count(entry.count), 'value count'),
    443             entry.count, referenceEntry.count);
    444         } else if (baselineVersion !== undefined && referenceEntry 
    445             && page.version !== baselineVersion) {
    446           // Show comparison of entry that does not exist on the current page.
    447           tr.entry = new Entry(0, referenceEntry.name);
    448           tr.entry.page = page;
    449           td(tr, '-', 'position');
    450           td(tr, referenceEntry.name, 'name');
    451           diffStatus(
    452             td(tr, ms(-referenceEntry.time), 'value time'),
    453             -referenceEntry.time, 0);
    454           diffStatus(
    455             td(tr, percent(-referenceEntry.timePercent), 'value time'),
    456             -referenceEntry.timePercent, 0);
    457           diffStatus(
    458             td(tr, count(-referenceEntry.count), 'value count'),
    459             -referenceEntry.count, 0);
    460         } else {
    461           // Display empty entry / baseline entry
    462           var showBaselineEntry = entry !== undefined;
    463           if (showBaselineEntry) {
    464             if (!parentEntry) {
    465               var node = td(tr, '<div class="toggle"></div>', 'position');
    466               node.firstChild.addEventListener('click', handleToggleGroup);
    467             } else {
    468               td(tr, entry.position == 0 ? '' : entry.position, 'position');
    469             }
    470             td(tr, entry.name, 'name');
    471             td(tr, ms(entry.time, false), 'value time');
    472             td(tr, percent(entry.timePercent, false), 'value time');
    473             td(tr, count(entry.count, false), 'value count');
    474           } else {
    475             td(tr, '-', 'position');
    476             td(tr, '-', 'name');
    477             td(tr, '-', 'value time');
    478             td(tr, '-', 'value time');
    479             td(tr, '-', 'value count');
    480           }
    481         }
    482       });
    483       table.replaceChild(tbody, oldTbody);
    484       var versionSelect = column.querySelector('select.version');
    485       selectOption(versionSelect, (index, option) => {
    486         return option.version == page.version
    487       });
    488     }
    489 
    490     function selectEntry(entry, updateSelectedPage) {
    491       if (updateSelectedPage) {
    492         entry = selectedPage.version.getEntry(entry);
    493       }
    494       var rowIndex = 0;
    495       var needsPageSwitch = updateSelectedPage && entry.page != selectedPage;
    496       // If clicked in the detail row change the first column to that page.
    497       if (needsPageSwitch) showPage(entry.page);
    498       var childNodes = $('column_0').querySelector('.list tbody').childNodes;
    499       for (var i = 0; i < childNodes.length; i++) {
    500         if (childNodes[i].entry.name == entry.name) {
    501           rowIndex = i;
    502           break;
    503         }
    504       }
    505       var firstEntry = childNodes[rowIndex].entry;
    506       if (rowIndex) {
    507         if (firstEntry.parent) showGroup(firstEntry.parent);
    508       }
    509       // Deselect all
    510       $('view').querySelectorAll('.list tbody tr').forEach((tr) => {
    511         toggleCssClass(tr, 'selected', false);
    512       });
    513       // Select the entry row
    514       $('view').querySelectorAll("tbody").forEach((body) => {
    515         var row = body.childNodes[rowIndex];
    516         if (!row) return;
    517         toggleCssClass(row, 'selected', row.entry && row.entry.name ==
    518           firstEntry.name);
    519       });
    520       if (updateSelectedPage) {
    521         entry = selectedEntry.page.version.getEntry(entry);
    522       }
    523       selectedEntry = entry;
    524       showEntryDetail(entry);
    525     }
    526 
    527     function showEntryDetail(entry) {
    528       showVersionDetails(entry);
    529       showPageDetails(entry);
    530       showImpactList(entry.page);
    531       showGraphs(entry.page);
    532     }
    533     
    534     function showVersionDetails(entry) {
    535       var table, tbody, entries;
    536       table = $('detailView').querySelector('.versionDetailTable');
    537       tbody = document.createElement('tbody');
    538       if (entry !== undefined) {
    539         $('detailView').querySelector('.versionDetail h3 span').innerHTML =
    540           entry.name + ' in ' + entry.page.name;
    541         entries = versions.getPageVersions(entry.page).map(
    542           (page) => {
    543             return page.get(entry.name)
    544           });
    545         entries.sort((a, b) => {
    546           return a.time - b.time
    547         });
    548         entries.forEach((pageEntry) => {
    549           if (pageEntry === undefined) return;
    550           var tr = document.createElement('tr');
    551           if (pageEntry == entry) tr.className += 'selected';
    552           tr.entry = pageEntry;
    553           var isBaselineEntry = pageEntry.page.version == baselineVersion;
    554           td(tr, pageEntry.page.version.name, 'version');
    555           td(tr, ms(pageEntry.time, !isBaselineEntry), 'value time');
    556           td(tr, percent(pageEntry.timePercent, !isBaselineEntry), 'value time');
    557           td(tr, count(pageEntry.count, !isBaselineEntry), 'value count');
    558           tbody.appendChild(tr);
    559         });
    560       }
    561       table.replaceChild(tbody, table.querySelector('tbody'));
    562     }
    563 
    564     function showPageDetails(entry) {
    565       var table, tbody, entries;
    566       table = $('detailView').querySelector('.pageDetailTable');
    567       tbody = document.createElement('tbody');
    568       if (entry === undefined) {
    569         table.replaceChild(tbody, table.querySelector('tbody'));
    570         return;
    571       }
    572       var version = entry.page.version;
    573       var showDiff = version !== baselineVersion;
    574       $('detailView').querySelector('.pageDetail h3 span').innerHTML =
    575         version.name;
    576       entries = version.pages.map((page) => {
    577           if (!page.enabled) return;
    578           return page.get(entry.name)
    579         });
    580       entries.sort((a, b) => {
    581         var cmp = b.timePercent - a.timePercent;
    582         if (cmp.toFixed(1) == 0) return b.time - a.time;
    583         return cmp
    584       });
    585       entries.forEach((pageEntry) => {
    586         if (pageEntry === undefined) return;
    587         var tr = document.createElement('tr');
    588         if (pageEntry === entry) tr.className += 'selected';
    589         tr.entry = pageEntry;
    590         td(tr, pageEntry.page.name, 'name');
    591         td(tr, ms(pageEntry.time, showDiff), 'value time');
    592         td(tr, percent(pageEntry.timePercent, showDiff), 'value time');
    593         td(tr, percent(pageEntry.timePercentPerEntry, showDiff),
    594             'value time hideNoDiff');
    595         td(tr, count(pageEntry.count, showDiff), 'value count');
    596         tbody.appendChild(tr);
    597       });
    598       // show the total for all pages
    599       var tds = table.querySelectorAll('tfoot td');
    600       tds[1].innerHTML = ms(entry.getTimeImpact(), showDiff);
    601       // Only show the percentage total if we are in diff mode:
    602       tds[2].innerHTML = percent(entry.getTimePercentImpact(), showDiff);
    603       tds[3].innerHTML = '';
    604       tds[4].innerHTML = count(entry.getCountImpact(), showDiff);
    605       table.replaceChild(tbody, table.querySelector('tbody'));
    606     }
    607 
    608     function showImpactList(page) {
    609       var impactView = $('detailView').querySelector('.impactView');
    610       impactView.querySelector('h3 span').innerHTML = page.version.name;
    611 
    612       var table = impactView.querySelector('table');
    613       var tbody = document.createElement('tbody');
    614       var version = page.version;
    615       var entries = version.allEntries();
    616       if (selectedEntry !== undefined && selectedEntry.isGroup) {
    617         impactView.querySelector('h3 span').innerHTML += " " + selectedEntry.name;
    618         entries = entries.filter((entry) => {
    619           return entry.name == selectedEntry.name ||
    620             (entry.parent && entry.parent.name == selectedEntry.name)
    621         });
    622       }
    623       var isCompareView = baselineVersion !== undefined;
    624       entries = entries.filter((entry) => {
    625         if (isCompareView) {
    626           var impact = entry.getTimeImpact();
    627           return impact < -1 || 1 < impact
    628         }
    629         return entry.getTimePercentImpact() > 0.1;
    630       });
    631       entries.sort((a, b) => {
    632         var cmp = b.getTimePercentImpact() - a.getTimePercentImpact(); 
    633         if (isCompareView || cmp.toFixed(1) == 0) {
    634           return b.getTimeImpact() - a.getTimeImpact();
    635         }
    636         return cmp
    637       });
    638       entries.forEach((entry) => {
    639         var tr = document.createElement('tr');
    640         tr.entry = entry;
    641         td(tr, entry.name, 'name');
    642         td(tr, ms(entry.getTimeImpact()), 'value time');
    643         var percentImpact = entry.getTimePercentImpact();
    644         td(tr, percentImpact > 1000 ? '-' : percent(percentImpact), 'value time');
    645         var topPages = entry.getPagesByPercentImpact().slice(0, 3)
    646           .map((each) => {
    647             return each.name + ' (' + percent(each.getEntry(entry).timePercent) +
    648               ')'
    649           });
    650         td(tr, topPages.join(', '), 'name');
    651         tbody.appendChild(tr);
    652       });
    653       table.replaceChild(tbody, table.querySelector('tbody'));
    654     }
    655     
    656     function showGraphs(page) {
    657       var groups = page.groups.slice(); 
    658       // Sort groups by the biggest impact
    659       groups.sort((a, b) => {
    660         return b.getTimeImpact() - a.getTimeImpact();
    661       });
    662       if (selectedGroup == undefined) {
    663         selectedGroup = groups[0];
    664       } else {
    665         groups = groups.filter(each => each.name != selectedGroup.name);
    666         groups.unshift(selectedGroup);
    667       }
    668       showPageGraph(groups, page);
    669       showVersionGraph(groups, page);
    670       showPageVersionGraph(groups, page);
    671     }
    672     
    673     function getGraphDataTable(groups) {
    674       var dataTable = new google.visualization.DataTable();
    675       dataTable.addColumn('string', 'Name');
    676       groups.forEach(group => {
    677         var column = dataTable.addColumn('number', group.name.substring(6));
    678         dataTable.setColumnProperty(column, 'group', group);
    679       });
    680       return dataTable;
    681     }
    682 
    683     var selectedGroup;
    684     function showPageGraph(groups, page) {
    685       var isDiffView = baselineVersion !== undefined;
    686       var dataTable = getGraphDataTable(groups);
    687       // Calculate the average row
    688       var row = ['Average'];
    689       groups.forEach((group) => {
    690         if (isDiffView) {
    691           row.push(group.isTotal ? 0 : group.getAverageTimeImpact());
    692         } else {
    693           row.push(group.isTotal ? 0 : group.getTimeImpact());
    694         }
    695       });
    696       dataTable.addRow(row);
    697       // Sort the pages by the selected group.
    698       var pages = page.version.pages.filter(page => page.enabled);
    699       function sumDiff(page) {
    700         var sum = 0;
    701         groups.forEach(group => {
    702           var value = group.getTimePercentImpact() -
    703             page.getEntry(group).timePercent;
    704           sum += value * value;
    705         });
    706         return sum;
    707       }
    708       if (isDiffView) {
    709         pages.sort((a, b) => {
    710           return b.getEntry(selectedGroup).time-
    711             a.getEntry(selectedGroup).time; 
    712         });
    713       } else {
    714         pages.sort((a, b) => {
    715           return b.getEntry(selectedGroup).timePercent -
    716             a.getEntry(selectedGroup).timePercent; 
    717         });
    718       }
    719       // Sort by sum of squared distance to the average.
    720       // pages.sort((a, b) => {
    721       //   return a.distanceFromTotalPercent() - b.distanceFromTotalPercent(); 
    722       // });
    723       // Calculate the entries for the pages
    724       pages.forEach((page) => { 
    725         row = [page.name];
    726         groups.forEach((group) => {
    727           row.push(group.isTotal ? 0 : page.getEntry(group).time);
    728         });
    729         var rowIndex = dataTable.addRow(row);
    730         dataTable.setRowProperty(rowIndex, 'page', page);
    731       });
    732       renderGraph('Pages for ' + page.version.name, groups, dataTable,
    733           'pageGraph', isDiffView ? true : 'percent');
    734     }
    735 
    736     function showVersionGraph(groups, page) {
    737       var dataTable = getGraphDataTable(groups);
    738       var row;
    739       var vs = versions.versions.filter(version => version.enabled);
    740       vs.sort((a, b) => {
    741         return b.getEntry(selectedGroup).getTimeImpact() -
    742           a.getEntry(selectedGroup).getTimeImpact(); 
    743       });
    744       // Calculate the entries for the versions 
    745       vs.forEach((version) => { 
    746         row = [version.name];
    747         groups.forEach((group) => {
    748           row.push(group.isTotal ? 0 : version.getEntry(group).getTimeImpact());
    749         });
    750         var rowIndex = dataTable.addRow(row);
    751         dataTable.setRowProperty(rowIndex, 'page', page);
    752       });
    753       renderGraph('Versions Total Time over all Pages', groups, dataTable,
    754           'versionGraph', true);
    755     }
    756 
    757     function showPageVersionGraph(groups, page) {
    758       var dataTable = getGraphDataTable(groups);
    759       var row;
    760       var vs = versions.getPageVersions(page);
    761       vs.sort((a, b) => {
    762         return b.getEntry(selectedGroup).time - a.getEntry(selectedGroup).time; 
    763       });
    764       // Calculate the entries for the versions 
    765       vs.forEach((page) => { 
    766         row = [page.version.name];
    767         groups.forEach((group) => {
    768           row.push(group.isTotal ? 0 : page.getEntry(group).time);
    769         });
    770         var rowIndex = dataTable.addRow(row);
    771         dataTable.setRowProperty(rowIndex, 'page', page);
    772       });
    773       renderGraph('Versions for ' + page.name, groups, dataTable,
    774           'pageVersionGraph', true);
    775     }
    776 
    777     function renderGraph(title, groups, dataTable, id, isStacked) {
    778       var isDiffView = baselineVersion !== undefined;
    779       var formatter = new google.visualization.NumberFormat({
    780         suffix: (isDiffView ? 'ms' : 'ms'), 
    781         negativeColor: 'red', 
    782         groupingSymbol: "'"
    783       });
    784       for (var i = 1; i < dataTable.getNumberOfColumns(); i++) {
    785         formatter.format(dataTable, i);
    786       }
    787       var height = 85 + 28 * dataTable.getNumberOfRows();
    788       var options = {
    789         isStacked: isStacked,
    790         height: height,
    791         hAxis: {
    792           minValue: 0,
    793         },
    794         animation:{
    795           duration: 500,
    796           easing: 'out',
    797         },
    798         vAxis: {
    799         },
    800         explorer: {
    801           actions: ['dragToZoom', 'rightClickToReset'],
    802           maxZoomIn: 0.01
    803         },
    804         legend: {position:'top', textStyle:{fontSize: '16px'}},
    805         chartArea: {left:200, top:50, width:'98%', height:'80%'},
    806         colors: groups.map(each => each.color)
    807       };
    808       var parentNode = $(id);
    809       parentNode.querySelector('h2>span, h3>span').innerHTML = title;
    810       var graphNode = parentNode.querySelector('.content');
    811 
    812       var chart = graphNode.chart;
    813       if (chart === undefined) {
    814         chart = graphNode.chart = new google.visualization.BarChart(graphNode);
    815       } else {
    816         google.visualization.events.removeAllListeners(chart);
    817       }
    818       google.visualization.events.addListener(chart, 'select', selectHandler);
    819       function getChartEntry(selection) {
    820         if (!selection) return undefined;
    821         var column = selection.column;
    822         if (column == undefined) return undefined;
    823         var selectedGroup = dataTable.getColumnProperty(column, 'group');
    824         var row = selection.row;
    825         if (row == null) return selectedGroup;
    826         var page = dataTable.getRowProperty(row, 'page');
    827         if (!page) return selectedGroup;
    828         return page.getEntry(selectedGroup);
    829       }
    830       function selectHandler() {
    831         selectedGroup = getChartEntry(chart.getSelection()[0])
    832         if (!selectedGroup) return;
    833         selectEntry(selectedGroup, true);
    834       }
    835 
    836       // Make our global tooltips work
    837       google.visualization.events.addListener(chart, 'onmouseover', mouseOverHandler);
    838       function mouseOverHandler(selection) {
    839         graphNode.entry = getChartEntry(selection);
    840       }
    841       chart.draw(dataTable, options);
    842     }
    843 
    844     function showGroup(entry) {
    845       toggleGroup(entry, true);
    846     }
    847 
    848     function toggleGroup(group, show) {
    849       $('view').querySelectorAll(".child").forEach((tr) => {
    850         var entry = tr.parentEntry;
    851         if (!entry) return;
    852         if (entry.name !== group.name) return;
    853         toggleCssClass(tr, 'visible', show);
    854       });
    855     }
    856 
    857     function showPopover(entry) {
    858       var popover = $('popover');
    859       popover.querySelector('td.name').innerHTML = entry.name;
    860       popover.querySelector('td.page').innerHTML = entry.page.name;
    861       setPopoverDetail(popover, entry, '');
    862       popover.querySelector('table').className = "";
    863       if (baselineVersion !== undefined) {
    864         entry = baselineVersion.getEntry(entry);
    865         setPopoverDetail(popover, entry, '.compare');
    866         popover.querySelector('table').className = "compare";
    867       }
    868     }
    869 
    870     function setPopoverDetail(popover, entry, prefix) {
    871       var node = (name) => popover.querySelector(prefix + name);
    872       if (entry == undefined) {
    873         node('.version').innerHTML = baselineVersion.name;
    874         node('.time').innerHTML = '-';
    875         node('.timeVariance').innerHTML = '-';
    876         node('.percent').innerHTML = '-';
    877         node('.percentPerEntry').innerHTML = '-';
    878         node('.percentVariance').innerHTML  = '-';
    879         node('.count').innerHTML =  '-';
    880         node('.countVariance').innerHTML = '-';
    881         node('.timeImpact').innerHTML = '-';
    882         node('.timePercentImpact').innerHTML = '-';
    883       } else {
    884         node('.version').innerHTML = entry.page.version.name;
    885         node('.time').innerHTML = ms(entry._time, false);
    886         node('.timeVariance').innerHTML
    887             = percent(entry.timeVariancePercent, false);
    888         node('.percent').innerHTML = percent(entry.timePercent, false);
    889         node('.percentPerEntry').innerHTML
    890             = percent(entry.timePercentPerEntry, false);
    891         node('.percentVariance').innerHTML 
    892             = percent(entry.timePercentVariancePercent, false);
    893         node('.count').innerHTML = count(entry._count, false);
    894         node('.countVariance').innerHTML
    895             = percent(entry.timeVariancePercent, false);
    896         node('.timeImpact').innerHTML
    897             = ms(entry.getTimeImpact(false), false);
    898         node('.timePercentImpact').innerHTML
    899             = percent(entry.getTimeImpactVariancePercent(false), false);
    900       }
    901     }
    902   </script>
    903   <script type="text/javascript">
    904   "use strict"
    905     // =========================================================================
    906     // Helpers
    907     function $(id) {
    908       return document.getElementById(id)
    909     }
    910 
    911     function removeAllChildren(node) {
    912       while (node.firstChild) {
    913         node.removeChild(node.firstChild);
    914       }
    915     }
    916 
    917     function selectOption(select, match) {
    918       var options = select.options;
    919       for (var i = 0; i < options.length; i++) {
    920         if (match(i, options[i])) {
    921           select.selectedIndex = i;
    922           return;
    923         }
    924       }
    925     }
    926 
    927     function addCodeSearchButton(entry, node) {
    928       if (entry.isGroup) return;
    929       var button = document.createElement("div");
    930       button.innerHTML = '?'
    931       button.className = "codeSearch"
    932       button.addEventListener('click', handleCodeSearch);
    933       node.appendChild(button);
    934       return node;
    935     }
    936 
    937     function td(tr, content, className) {
    938       var td = document.createElement("td");
    939       td.innerHTML = content;
    940       td.className = className
    941       tr.appendChild(td);
    942       return td
    943     }
    944 
    945     function nodeIndex(node) {
    946       var children = node.parentNode.childNodes,
    947         i = 0;
    948       for (; i < children.length; i++) {
    949         if (children[i] == node) {
    950           return i;
    951         }
    952       }
    953       return -1;
    954     }
    955 
    956     function toggleCssClass(node, cssClass, toggleState) {
    957       var index = -1;
    958       var classes;
    959       if (node.className != undefined) {
    960         classes = node.className.split(' ');
    961         index = classes.indexOf(cssClass);
    962       }
    963       if (index == -1) {
    964         if (toggleState === false) return;
    965         node.className += ' ' + cssClass;
    966         return;
    967       }
    968       if (toggleState === true) return;
    969       classes.splice(index, 1);
    970       node.className = classes.join(' ');
    971     }
    972 
    973     function NameComparator(a, b) {
    974       if (a.name > b.name) return 1;
    975       if (a.name < b.name) return -1;
    976       return 0
    977     }
    978 
    979     function diffSign(value, digits, unit, showDiff) {
    980       if (showDiff === false || baselineVersion == undefined) {
    981         if (value === undefined) return '';
    982         return value.toFixed(digits) + unit;
    983       }
    984       return (value >= 0 ? '+' : '') + value.toFixed(digits) + unit + '';
    985     }
    986 
    987     function ms(value, showDiff) {
    988       return diffSign(value, 1, 'ms', showDiff);
    989     }
    990 
    991     function count(value, showDiff) {
    992       return diffSign(value, 0, '#', showDiff);
    993     }
    994 
    995     function percent(value, showDiff) {
    996       return diffSign(value, 1, '%', showDiff);
    997     }
    998 
    999   </script>
   1000   <script type="text/javascript">
   1001   "use strict"
   1002     // =========================================================================
   1003     // EventHandlers
   1004     function handleBodyLoad() {
   1005       $('uploadInput').focus(); 
   1006     }
   1007 
   1008     function handleLoadFile() {
   1009       var files = document.getElementById("uploadInput").files;
   1010       var file = files[0];
   1011       var reader = new FileReader();
   1012 
   1013       reader.onload = function(evt) {
   1014         pages = new Pages();
   1015         versions = Versions.fromJSON(JSON.parse(this.result));
   1016         initialize()
   1017         showPage(versions.versions[0].pages[0]);
   1018       }
   1019       reader.readAsText(file);
   1020     }
   1021 
   1022     function handleToggleGroup(event) {
   1023       var group = event.target.parentNode.parentNode.entry;
   1024       toggleGroup(selectedPage.get(group.name));
   1025     }
   1026 
   1027     function handleSelectPage(select, event) {
   1028       var option = select.options[select.selectedIndex];
   1029       if (select.id == "select_0") {
   1030         showPage(option.page);
   1031       } else {
   1032         var columnIndex = select.id.split('_')[1];
   1033         showPageInColumn(option.page, columnIndex);
   1034       }
   1035     }
   1036 
   1037     function handleSelectVersion(select, event) {
   1038       var option = select.options[select.selectedIndex];
   1039       var version = option.version;
   1040       if (select.id == "selectVersion_0") {
   1041         var page = version.get(selectedPage.name);
   1042         showPage(page);
   1043       } else {
   1044         var columnIndex = select.id.split('_')[1];
   1045         var pageSelect = $('select_' + columnIndex);
   1046         var page = pageSelect.options[pageSelect.selectedIndex].page;
   1047         page = version.get(page.name);
   1048         showPageInColumn(page, columnIndex);
   1049       }
   1050     }
   1051 
   1052     function handleSelectDetailRow(table, event) {
   1053       if (event.target.tagName != 'TD') return;
   1054       var tr = event.target.parentNode;
   1055       if (tr.tagName != 'TR') return;
   1056       if (tr.entry === undefined) return;
   1057       selectEntry(tr.entry, true);
   1058     }
   1059 
   1060     function handleSelectRow(table, event, fromDetail) {
   1061       if (event.target.tagName != 'TD') return;
   1062       var tr = event.target.parentNode;
   1063       if (tr.tagName != 'TR') return;
   1064       if (tr.entry === undefined) return;
   1065       selectEntry(tr.entry, false);
   1066     }
   1067 
   1068     function handleSelectBaseline(select, event) {
   1069       var option = select.options[select.selectedIndex];
   1070       baselineVersion = option.version;
   1071       var showingDiff = baselineVersion !== undefined;
   1072       var body = $('body');
   1073       toggleCssClass(body, 'diff', showingDiff);
   1074       toggleCssClass(body, 'noDiff', !showingDiff);
   1075       showPage(selectedPage);
   1076       if (selectedEntry === undefined) return;
   1077       selectEntry(selectedEntry, true);
   1078     }
   1079 
   1080     function findEntry(event) {
   1081       var target = event.target;
   1082       while (target.entry === undefined) {
   1083         target = target.parentNode;
   1084         if (!target) return undefined;
   1085       }
   1086       return target.entry;
   1087     }
   1088 
   1089     function handleUpdatePopover(event) {
   1090       var popover = $('popover');
   1091       popover.style.left = event.pageX + 'px';
   1092       popover.style.top = event.pageY + 'px';
   1093       popover.style.display = 'none';
   1094       popover.style.display = event.shiftKey ? 'block' : 'none';
   1095       var entry = findEntry(event);
   1096       if (entry === undefined) return;
   1097       showPopover(entry);
   1098     }
   1099 
   1100     function handleToggleVersionEnable(event) {
   1101       var item = this.item ;
   1102       if (item  === undefined) return;
   1103       item .enabled = this.checked;
   1104       initialize();
   1105       var page = selectedPage;
   1106       if (page === undefined || !page.version.enabled) {
   1107         page = versions.getEnabledPage(page.name);
   1108       }
   1109       showPage(page);
   1110     }
   1111 
   1112     function handleToggleContentVisibility(event) {
   1113       var content = event.target.contentNode;
   1114       toggleCssClass(content, 'hidden');
   1115     }
   1116 
   1117     function handleCodeSearch(event) {
   1118       var entry = findEntry(event);
   1119       if (entry === undefined) return;
   1120       var url = "https://cs.chromium.org/search/?sq=package:chromium&type=cs&q=";
   1121       name = entry.name;
   1122       if (name.startsWith("API_")) {
   1123         name = name.substring(4);
   1124       }
   1125       url += encodeURIComponent(name) + "+file:src/v8/src";
   1126       window.open(url,'_blank');
   1127     }
   1128   </script>
   1129   <script type="text/javascript">
   1130   "use strict"
   1131     // =========================================================================
   1132     class Versions {
   1133       constructor() {
   1134         this.versions = [];
   1135       }
   1136       add(version) {
   1137         this.versions.push(version)
   1138       }
   1139       getPageVersions(page) {
   1140         var result = [];
   1141         this.versions.forEach((version) => {
   1142           if (!version.enabled) return;
   1143           var versionPage = version.get(page.name);
   1144           if (versionPage  !== undefined) result.push(versionPage);
   1145         });
   1146         return result;
   1147       }
   1148       get length() {
   1149         return this.versions.length
   1150       }
   1151       get(index) {
   1152         return this.versions[index]
   1153       };
   1154       forEach(f) {
   1155         this.versions.forEach(f);
   1156       }
   1157       sort() {
   1158         this.versions.sort(NameComparator);
   1159       }
   1160       getEnabledPage(name) {
   1161         for (var i = 0; i < this.versions.length; i++) {
   1162           var version = this.versions[i];
   1163           if (!version.enabled) continue;
   1164           var page = version.get(name);
   1165           if (page !== undefined) return page;
   1166         }
   1167       }
   1168     }
   1169     Versions.fromJSON = function(json) {
   1170       var versions = new Versions();
   1171       for (var version in json) {
   1172         versions.add(Version.fromJSON(version, json[version]));
   1173       }
   1174       versions.sort();
   1175       return versions;
   1176     }
   1177 
   1178     class Version {
   1179       constructor(name) {
   1180         this.name = name;
   1181         this.enabled = true;
   1182         this.pages = [];
   1183       }
   1184       add(page) {
   1185         this.pages.push(page);
   1186       }
   1187       indexOf(name) {
   1188         for (var i = 0; i < this.pages.length; i++) {
   1189           if (this.pages[i].name == name) return i;
   1190         }
   1191         return -1;
   1192       }
   1193       get(name) {
   1194         var index = this.indexOf(name);
   1195         if (0 <= index) return this.pages[index];
   1196         return undefined
   1197       }
   1198       get length() {
   1199         return this.versions.length
   1200       }
   1201       getEntry(entry) {
   1202         if (entry === undefined) return undefined;
   1203         var page = this.get(entry.page.name);
   1204         if (page === undefined) return undefined;
   1205         return page.get(entry.name);
   1206       }
   1207       forEachEntry(fun) {
   1208         this.forEachPage((page) => {
   1209           page.forEach(fun);
   1210         });
   1211       }
   1212       forEachPage(fun) {
   1213         this.pages.forEach((page) => {
   1214           if (!page.enabled) return;
   1215           fun(page);
   1216         })
   1217       }
   1218       allEntries() {
   1219         var map = new Map();
   1220         this.forEachEntry((group, entry) => {
   1221           if (!map.has(entry.name)) map.set(entry.name, entry);
   1222         });
   1223         return Array.from(map.values());
   1224       }
   1225       getTotalValue(name, property) {
   1226         if (name === undefined) name = this.pages[0].total.name;
   1227         var sum = 0;
   1228         this.forEachPage((page) => {
   1229           var entry = page.get(name);
   1230           if (entry !== undefined) sum += entry[property];
   1231         });
   1232         return sum;
   1233       }
   1234       getTotalTime(name, showDiff) {
   1235         return this.getTotalValue(name, showDiff === false ? '_time' : 'time');
   1236       }
   1237       getTotalTimePercent(name, showDiff) {
   1238         if (baselineVersion === undefined || showDiff === false) {
   1239           // Return the overall average percent of the given entry name.
   1240           return this.getTotalValue(name, 'time') /
   1241             this.getTotalTime('Group-Total') * 100;
   1242         }
   1243         // Otherwise return the difference to the sum of the baseline version.
   1244         var baselineValue = baselineVersion.getTotalTime(name, false);
   1245         var total = this.getTotalValue(name, '_time');
   1246         return (total / baselineValue - 1)  * 100;
   1247       }
   1248       getTotalTimeVariance(name, showDiff) {
   1249         // Calculate the overall error for a given entry name
   1250         var sum = 0;
   1251         this.forEachPage((page) => {
   1252           var entry = page.get(name);
   1253           if (entry === undefined) return;
   1254           sum += entry.timeVariance * entry.timeVariance;
   1255         });
   1256         return Math.sqrt(sum);
   1257       }
   1258       getTotalTimeVariancePercent(name, showDiff) {
   1259         return this.getTotalTimeVariance(name, showDiff) / 
   1260           this.getTotalTime(name, showDiff) * 100;
   1261       }
   1262       getTotalCount(name, showDiff) {
   1263         return this.getTotalValue(name, showDiff === false ? '_count' : 'count');
   1264       }
   1265       getAverageTimeImpact(name, showDiff) {
   1266         return this.getTotalTime(name, showDiff) / this.pages.length;
   1267       }
   1268       getPagesByPercentImpact(name) {
   1269         var sortedPages =
   1270           this.pages.filter((each) => {
   1271             return each.get(name) !== undefined
   1272           });
   1273         sortedPages.sort((a, b) => {
   1274           return b.get(name).timePercent - a.get(name).timePercent;
   1275         });
   1276         return sortedPages;
   1277       }
   1278       sort() {
   1279         this.pages.sort(NameComparator)
   1280       }
   1281     }
   1282     Version.fromJSON = function(name, data) {
   1283       var version = new Version(name);
   1284       for (var pageName in data) {
   1285         version.add(PageVersion.fromJSON(version, pageName, data[pageName]));
   1286       }
   1287       version.sort();
   1288       return version;
   1289     }
   1290     
   1291     class Pages extends Map {
   1292       get(name) {
   1293         if (name.indexOf('www.') == 0) {
   1294           name = name.substring(4);
   1295         }
   1296         if (!this.has(name)) {
   1297           this.set(name, new Page(name));
   1298         }
   1299         return super.get(name);
   1300       }
   1301     }
   1302 
   1303     class Page {
   1304       constructor(name) {
   1305         this.name = name;
   1306         this.enabled = true;
   1307         this.versions = [];
   1308       }
   1309       add(page) {
   1310         this.versions.push(page);
   1311       }
   1312     }
   1313 
   1314     class PageVersion {
   1315       constructor(version, page) {
   1316         this.page = page;
   1317         this.page.add(this);
   1318         this.total = new GroupedEntry('Total', /.*Total.*/, '#BBB');
   1319         this.total.isTotal = true;
   1320         this.unclassified = new UnclassifiedEntry(this, "#000")
   1321         this.groups = [
   1322           this.total,
   1323           new GroupedEntry('IC', /.*IC.*/, "#3366CC"),
   1324           new GroupedEntry('Optimize',
   1325             /StackGuard|.*Optimize.*|.*Deoptimize.*|Recompile.*/, "#DC3912"),
   1326           new GroupedEntry('Compile', /.*Compile.*/, "#FFAA00"),
   1327           new GroupedEntry('Parse', /.*Parse.*/, "#FF6600"),
   1328           new GroupedEntry('Callback', /.*Callback$/, "#109618"),
   1329           new GroupedEntry('API', /.*API.*/, "#990099"),
   1330           new GroupedEntry('GC', /GC|AllocateInTargetSpace/, "#0099C6"),
   1331           new GroupedEntry('JavaScript', /JS_Execution/, "#DD4477"),
   1332           new GroupedEntry('Runtime', /.*/, "#88BB00"),
   1333           this.unclassified
   1334         ];
   1335         this.entryDict = new Map();
   1336         this.groups.forEach((entry) => {
   1337           entry.page = this;
   1338           this.entryDict.set(entry.name, entry);
   1339         });
   1340         this.version = version;
   1341       }
   1342       add(entry) {
   1343         entry.page = this;
   1344         this.entryDict.set(entry.name, entry);
   1345         var added = false;
   1346         this.groups.forEach((group) => {
   1347           if (!added) added = group.add(entry);
   1348         });
   1349         if (added) return;
   1350         this.unclassified.push(entry);
   1351       }
   1352       get(name) {
   1353         return this.entryDict.get(name)
   1354       }
   1355       getEntry(entry) {
   1356         if (entry === undefined) return undefined;
   1357         return this.get(entry.name);
   1358       }
   1359       get length() {
   1360         return this.versions.length
   1361       }
   1362       get name() { return this.page.name }
   1363       get enabled() { return this.page.enabled }
   1364       forEachSorted(referencePage, func) {
   1365         // Iterate over all the entries in the order they appear on the
   1366         // reference page.
   1367         referencePage.forEach((parent, referenceEntry) => {
   1368           var entry;
   1369           if (parent) parent = this.entryDict.get(parent.name);
   1370           if (referenceEntry) entry = this.entryDict.get(referenceEntry.name);
   1371           func(parent, entry, referenceEntry);
   1372         });
   1373       }
   1374       forEach(fun) {
   1375         this.forEachGroup((group) => {
   1376           fun(undefined, group);
   1377           group.forEach((entry) => {
   1378             fun(group, entry)
   1379           });
   1380         });
   1381       }
   1382       forEachGroup(fun) {
   1383         this.groups.forEach(fun)
   1384       }
   1385       sort() {
   1386         this.groups.sort((a, b) => {
   1387           return b.time - a.time;
   1388         });
   1389         this.groups.forEach((group) => {
   1390           group.sort()
   1391         });
   1392       }
   1393       distanceFromTotalPercent() {
   1394         var sum = 0;
   1395         this.groups.forEach(group => {
   1396           if (group == this.total) return;
   1397           var value = group.getTimePercentImpact() - 
   1398               this.getEntry(group).timePercent;
   1399           sum += value * value;
   1400         });
   1401         return sum;
   1402       }
   1403     }
   1404     PageVersion.fromJSON = function(version, name, data) {
   1405       var page = new PageVersion(version, pages.get(name));
   1406       for (var i = 0; i < data.length; i++) {
   1407         page.add(Entry.fromJSON(i, data[data.length - i - 1]));
   1408       }
   1409       page.sort();
   1410       return page
   1411     }
   1412 
   1413 
   1414     class Entry {
   1415       constructor(position, name, time, timeVariance, timeVariancePercent,
   1416         count,
   1417         countVariance, countVariancePercent) {
   1418         this.position = position;
   1419         this.name = name;
   1420         this._time = time;
   1421         this._timeVariance = timeVariance;
   1422         this._timeVariancePercent = timeVariancePercent;
   1423         this._count = count;
   1424         this.countVariance = countVariance;
   1425         this.countVariancePercent = countVariancePercent;
   1426         this.page = undefined;
   1427         this.parent = undefined;
   1428         this.isTotal = false;
   1429       }
   1430       getCompareWithBaseline(value, property) {
   1431         if (baselineVersion == undefined) return value;
   1432         var baselineEntry = baselineVersion.getEntry(this);
   1433         if (!baselineEntry) return value;
   1434         if (baselineVersion === this.page.version) return value;
   1435         return value - baselineEntry[property];
   1436       }
   1437       cssClass() {
   1438         return ''
   1439       }
   1440       get time() {
   1441         return this.getCompareWithBaseline(this._time, '_time');
   1442       }
   1443       get count() {
   1444         return this.getCompareWithBaseline(this._count, '_count');
   1445       }
   1446       get timePercent() {
   1447         var value = this._time / this.page.total._time * 100;
   1448         if (baselineVersion == undefined) return value;
   1449         var baselineEntry = baselineVersion.getEntry(this);
   1450         if (!baselineEntry) return value;
   1451         if (baselineVersion === this.page.version) return value;
   1452         return (this._time - baselineEntry._time) / this.page.total._time *
   1453           100;
   1454       }
   1455       get timePercentPerEntry() {
   1456         var value = this._time / this.page.total._time * 100;
   1457         if (baselineVersion == undefined) return value;
   1458         var baselineEntry = baselineVersion.getEntry(this);
   1459         if (!baselineEntry) return value;
   1460         if (baselineVersion === this.page.version) return value;
   1461         return (this._time / baselineEntry._time - 1) * 100;
   1462       }
   1463       get timePercentVariancePercent() {
   1464         // Get the absolute values for the percentages
   1465         return this.timeVariance / this.page.total._time * 100;
   1466       }
   1467       getTimeImpact(showDiff) {
   1468         return this.page.version.getTotalTime(this.name, showDiff);
   1469       }
   1470       getTimeImpactVariancePercent(showDiff) {
   1471         return this.page.version.getTotalTimeVariancePercent(this.name, showDiff);
   1472       }
   1473       getTimePercentImpact(showDiff) {
   1474         return this.page.version.getTotalTimePercent(this.name, showDiff);
   1475       }
   1476       getCountImpact(showDiff) {
   1477         return this.page.version.getTotalCount(this.name, showDiff);
   1478       }
   1479       getAverageTimeImpact(showDiff) {
   1480         return this.page.version.getAverageTimeImpact(this.name, showDiff);
   1481       }
   1482       getPagesByPercentImpact() {
   1483         return this.page.version.getPagesByPercentImpact(this.name);
   1484       }
   1485       get isGroup() {
   1486         return false
   1487       }
   1488       get timeVariance() {
   1489         return this._timeVariance
   1490       }
   1491       get timeVariancePercent() {
   1492         return this._timeVariancePercent
   1493       }
   1494     }
   1495     Entry.fromJSON = function(position, data) {
   1496       return new Entry(position, ...data);
   1497     }
   1498 
   1499 
   1500     class GroupedEntry extends Entry {
   1501       constructor(name, regexp, color) {
   1502         super(0, 'Group-' + name, 0, 0, 0, 0, 0, 0);
   1503         this.regexp = regexp;
   1504         this.color = color;
   1505         this.entries = [];
   1506       }
   1507       add(entry) {
   1508         if (!entry.name.match(this.regexp)) return false;
   1509         this._time += entry.time;
   1510         this._count += entry.count;
   1511         // TODO: sum up variance
   1512         this.entries.push(entry);
   1513         entry.parent = this;
   1514         return true;
   1515       }
   1516       forEach(fun) {
   1517         if (baselineVersion === undefined) {
   1518           this.entries.forEach(fun);
   1519           return;
   1520         }
   1521         // If we have a baslineVersion to compare against show also all entries
   1522         // from the other group.
   1523         var tmpEntries = baselineVersion.getEntry(this)
   1524           .entries.filter((entry) => {
   1525             return this.page.get(entry.name) == undefined
   1526           });
   1527 
   1528         // The compared entries are sorted by absolute impact.
   1529         tmpEntries = tmpEntries.map((entry) => {
   1530           var tmpEntry = new Entry(0, entry.name, 0, 0, 0, 0, 0, 0);
   1531           tmpEntry.page = this.page;
   1532           return tmpEntry;
   1533         });
   1534         tmpEntries = tmpEntries.concat(this.entries);
   1535         tmpEntries.sort((a, b) => {
   1536           return a.time - b.time
   1537         });
   1538         tmpEntries.forEach(fun);
   1539       }
   1540       sort() {
   1541         this.entries.sort((a, b) => {
   1542           return b.time - a.time;
   1543         });
   1544       }
   1545       cssClass() {
   1546         if (this.page.total == this) return 'total';
   1547         return '';
   1548       }
   1549       get isGroup() {
   1550         return true
   1551       }
   1552       getVarianceForProperty(property) {
   1553         var sum = 0;
   1554         this.entries.forEach((entry) => {
   1555           sum += entry[property + 'Variance'] * entry[property +
   1556             'Variance'];
   1557         });
   1558         return Math.sqrt(sum);
   1559       }
   1560       get timeVariancePercent() {
   1561         if (this._time == 0) return 0;
   1562         return this.getVarianceForProperty('time')  / this._time * 100
   1563       }
   1564       get timeVariance() {
   1565         return this.getVarianceForProperty('time')
   1566       }
   1567     }
   1568 
   1569     class UnclassifiedEntry extends GroupedEntry {
   1570       constructor(page, color) {
   1571         super('Unclassified', undefined, color);
   1572         this.page = page;
   1573         this._time = undefined;
   1574         this._count = undefined;
   1575       }
   1576       add(entry) {
   1577         this.entries.push(entry);
   1578         entry.parent = this;
   1579         return true;
   1580       }
   1581       forEachPageGroup(fun) {
   1582         this.page.forEachGroup((group) => {
   1583           if (group == this) return;
   1584           if (group == this.page.total) return;
   1585           fun(group);
   1586         });
   1587       }
   1588       get time() {
   1589         if (this._time === undefined) {
   1590           this._time = this.page.total._time;
   1591           this.forEachPageGroup((group) => {
   1592             this._time -= group._time;
   1593           });
   1594         }
   1595         return this.getCompareWithBaseline(this._time, '_time');
   1596       }
   1597       get count() {
   1598         if (this._count === undefined) {
   1599           this._count = this.page.total._count;
   1600           this.forEachPageGroup((group) => {
   1601             this._count -= group._count;
   1602           });
   1603         }
   1604         return this.getCompareWithBaseline(this._count, '_count');
   1605       }
   1606     }
   1607   </script>
   1608 </head>
   1609 
   1610 <body id="body" onmousemove="handleUpdatePopover(event)" onload="handleBodyLoad()" class="noDiff">
   1611   <h1>Runtime Stats Komparator</h1>
   1612 
   1613   <div id="results">
   1614     <div class="inline">
   1615       <h2>Data</h2>
   1616       <form name="fileForm">
   1617         <p>
   1618           <input id="uploadInput" type="file" name="files" onchange="handleLoadFile();" accept=".json">
   1619         </p>
   1620       </form>
   1621     </div>
   1622 
   1623     <div class="inline hidden">
   1624       <h2>Result</h2>
   1625       <div class="compareSelector inline">
   1626         Compare against:&nbsp;<select id="baseline" onchange="handleSelectBaseline(this, event)"></select><br/>
   1627         <span style="color: #060">Green</span> the selected version above performs
   1628         better on this measurement.
   1629       </div>
   1630     </div>
   1631     
   1632     <div id="versionSelector" class="inline toggleContentVisibility">
   1633       <h2>Version Selector</h2>
   1634       <div class="content hidden">
   1635         <ul></ul>
   1636       </div>
   1637     </div>
   1638     
   1639     <div id="pageSelector" class="inline toggleContentVisibility">
   1640       <h2>Page Selector</h2>
   1641       <div class="content hidden">
   1642         <ul></ul>
   1643       </div>
   1644     </div>
   1645 
   1646     <div id="view">
   1647     </div>
   1648 
   1649     <div id="detailView" class="hidden">
   1650       <div class="versionDetail inline toggleContentVisibility">
   1651         <h3><span></span></h3>
   1652         <div class="content">
   1653           <table class="versionDetailTable" onclick="handleSelectDetailRow(this, event);">
   1654             <thead>
   1655               <tr>
   1656                 <th class="version">Version&nbsp;</th>
   1657                 <th class="position">Pos.&nbsp;</th>
   1658                 <th class="value time">Time&nbsp;</th>
   1659                 <th class="value time">Percent&nbsp;</th>
   1660                 <th class="value count">Count&nbsp;</th>
   1661               </tr>
   1662             </thead>
   1663             <tbody></tbody>
   1664           </table>
   1665         </div>
   1666       </div>
   1667       <div class="pageDetail inline toggleContentVisibility">
   1668         <h3>Page Comparison for <span></span></h3>
   1669         <div class="content">
   1670           <table class="pageDetailTable" onclick="handleSelectDetailRow(this, event);">
   1671             <thead>
   1672               <tr>
   1673                 <th class="page">Page&nbsp;</th>
   1674                 <th class="value time">Time&nbsp;</th>
   1675                 <th class="value time">Percent&nbsp;</th>
   1676                 <th class="value time hideNoDiff">%/Entry&nbsp;</th>
   1677                 <th class="value count">Count&nbsp;</th>
   1678               </tr>
   1679             </thead>
   1680             <tfoot>
   1681               <tr>
   1682                 <td class="page">Total:</td>
   1683                 <td class="value time"></td>
   1684                 <td class="value time"></td>
   1685                 <td class="value time hideNoDiff"></td>
   1686                 <td class="value count"></td>
   1687               </tr>
   1688             </tfoot>
   1689             <tbody></tbody>
   1690           </table>
   1691         </div>
   1692       </div>
   1693       <div class="impactView inline toggleContentVisibility">
   1694         <h3>Impact list for <span></span></h3>
   1695         <div class="content">
   1696           <table class="pageDetailTable" onclick="handleSelectDetailRow(this, event);">
   1697             <thead>
   1698               <tr>
   1699                 <th class="page">Name&nbsp;</th>
   1700                 <th class="value time">Time&nbsp;</th>
   1701                 <th class="value time">Percent&nbsp;</th>
   1702                 <th class="">Top Pages</th>
   1703               </tr>
   1704             </thead>
   1705             <tbody></tbody>
   1706           </table>
   1707         </div>
   1708       </div>
   1709     </div>
   1710     <div id="pageVersionGraph" class="graph hidden toggleContentVisibility">
   1711       <h3><span></span></h3>
   1712       <div class="content"></div>
   1713     </div>
   1714     <div id="pageGraph" class="graph hidden toggleContentVisibility">
   1715       <h3><span></span></h3>
   1716       <div class="content"></div>
   1717     </div>
   1718     <div id="versionGraph" class="graph hidden toggleContentVisibility">
   1719       <h3><span></span></h3>
   1720       <div class="content"></div>
   1721     </div>
   1722 
   1723     <div id="column" class="column">
   1724       <div class="header">
   1725         <select class="version" onchange="handleSelectVersion(this, event);"></select>
   1726         <select class="pageVersion" onchange="handleSelectPage(this, event);"></select>
   1727       </div>
   1728       <table class="list" onclick="handleSelectRow(this, event);">
   1729         <thead>
   1730           <tr>
   1731             <th class="position">Pos.&nbsp;</th>
   1732             <th class="name">Name&nbsp;</th>
   1733             <th class="value time">Time&nbsp;</th>
   1734             <th class="value time">Percent&nbsp;</th>
   1735             <th class="value count">Count&nbsp;</th>
   1736           </tr>
   1737         </thead>
   1738         <tbody></tbody>
   1739       </table>
   1740     </div>
   1741   </div>
   1742 
   1743   <div class="inline">
   1744     <h2>Usage</h2>
   1745     <ol>
   1746       <li>Install scipy, e.g. <code>sudo aptitude install python-scipy</code>
   1747       <li>Build chrome with the <a href="https://codereview.chromium.org/1923893002">extended runtime callstats</a>.</li>
   1748       <li>Run <code>callstats.py</code> with a web-page-replay archive:
   1749         <pre>$V8_DIR/tools/callstats.py run \
   1750         --replay-bin=$CHROME_SRC/third_party/webpagereplay/replay.py \
   1751         --replay-wpr=$INPUT_DIR/top25.wpr \
   1752         --js-flags="" \
   1753         --with-chrome=$CHROME_SRC/out/Release/chrome \
   1754         --sites-file=$INPUT_DIR/top25.json</pre>
   1755       </li>
   1756       <li>Move results file to a subdirectory: <code>mkdir $VERSION_DIR; mv *.txt $VERSION_DIR</code></li>
   1757       <li>Repeat from step 1 with a different configuration (e.g. <code>--js-flags="--nolazy"</code>).</li>
   1758       <li>Create the final results file: <code>./callstats.py json $VERSION_DIR1 $VERSION_DIR2 > result.json</code></li>
   1759       <li>Use <code>results.json</code> on this site.</code>
   1760     </ol>
   1761   </div>
   1762 
   1763   <div id="popover">
   1764     <div class="popoverArrow"></div>
   1765     <table>
   1766       <tr>
   1767         <td class="name" colspan="6"></td>
   1768       </tr>
   1769       <tr>
   1770         <td>Page:</td>
   1771         <td class="page name" colspan="6"></td>
   1772       </tr>
   1773       <tr>
   1774         <td>Version:</td>
   1775         <td class="version name" colspan="3"></td>
   1776         <td class="compare version name" colspan="3"></td>
   1777       </tr>
   1778       <tr>
   1779         <td>Time:</td>
   1780         <td class="time"></td><td></td><td class="timeVariance"></td>
   1781         <td class="compare time"></td><td class="compare">  </td><td class="compare timeVariance"></td>
   1782       </tr>
   1783       <tr>
   1784         <td>Percent:</td>
   1785         <td class="percent"></td><td></td><td class="percentVariance"></td>
   1786         <td class="compare percent"></td><td class="compare">  </td><td class="compare percentVariance"></td>
   1787       </tr>
   1788       <tr>
   1789         <td>Percent per Entry:</td>
   1790         <td class="percentPerEntry"></td><td colspan=2></td>
   1791         <td class="compare percentPerEntry"></td><td colspan=2></td>
   1792       </tr>
   1793       <tr>
   1794         <td>Count:</td>
   1795         <td class="count"></td><td></td><td class="countVariance"></td>
   1796         <td class="compare count"></td><td class="compare">  </td><td class="compare countVariance"></td>
   1797       </tr>
   1798       <tr>
   1799         <td>Overall Impact:</td>
   1800         <td class="timeImpact"></td><td></td><td class="timePercentImpact"></td>
   1801         <td class="compare timeImpact"></td><td class="compare">  </td><td class="compare timePercentImpact"></td>
   1802       </tr>
   1803     </table>
   1804   </div>
   1805 </body>
   1806 </html>
   1807