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