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