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