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