• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1<html>
2<!--
3Copyright 2016 the V8 project authors. All rights reserved.  Use of this source
4code 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