|  | <html> | 
|  | <!-- | 
|  | Copyright 2016 the V8 project authors. All rights reserved.  Use of this source | 
|  | code is governed by a BSD-style license that can be found in the LICENSE file. | 
|  | --> | 
|  |  | 
|  | <head> | 
|  | <meta charset="UTF-8"> | 
|  | <style> | 
|  | body { | 
|  | font-family: arial; | 
|  | } | 
|  |  | 
|  | table { | 
|  | display: table; | 
|  | border-spacing: 0px; | 
|  | } | 
|  |  | 
|  | tr { | 
|  | border-spacing: 0px; | 
|  | padding: 10px; | 
|  | } | 
|  |  | 
|  | td, | 
|  | th { | 
|  | padding: 3px 10px 3px 5px; | 
|  | } | 
|  |  | 
|  | .inline { | 
|  | display: inline-block; | 
|  | vertical-align: top; | 
|  | } | 
|  |  | 
|  | h2, | 
|  | h3 { | 
|  | margin-bottom: 0px; | 
|  | } | 
|  |  | 
|  | .hidden { | 
|  | display: none; | 
|  | } | 
|  |  | 
|  | .view { | 
|  | display: table; | 
|  | } | 
|  |  | 
|  | .column { | 
|  | display: table-cell; | 
|  | border-right: 1px black dotted; | 
|  | min-width: 200px; | 
|  | } | 
|  |  | 
|  | .column .header { | 
|  | padding: 0 10px 0 10px | 
|  | } | 
|  |  | 
|  | #column { | 
|  | display: none; | 
|  | } | 
|  |  | 
|  | .list { | 
|  | width: 100%; | 
|  | } | 
|  |  | 
|  | select { | 
|  | width: 100% | 
|  | } | 
|  |  | 
|  | .list tbody { | 
|  | cursor: pointer; | 
|  | } | 
|  |  | 
|  | .list tr:nth-child(even) { | 
|  | background-color: #EFEFEF; | 
|  | } | 
|  |  | 
|  | .list tr:nth-child(even).selected { | 
|  | background-color: #DDD; | 
|  | } | 
|  |  | 
|  | .list tr.child { | 
|  | display: none; | 
|  | } | 
|  |  | 
|  | .list tr.child.visible { | 
|  | display: table-row; | 
|  | } | 
|  |  | 
|  | .list .child .name { | 
|  | padding-left: 20px; | 
|  | } | 
|  |  | 
|  | .list .parent td { | 
|  | border-top: 1px solid #AAA; | 
|  | } | 
|  |  | 
|  | .list .total { | 
|  | font-weight: bold | 
|  | } | 
|  |  | 
|  | .list tr.parent { | 
|  | background-color: #FFF; | 
|  | } | 
|  |  | 
|  | .list tr.parent.selected { | 
|  | background-color: #DDD; | 
|  | } | 
|  |  | 
|  | tr.selected { | 
|  | background-color: #DDD; | 
|  | } | 
|  |  | 
|  | .codeSearch { | 
|  | display: block-inline; | 
|  | float: right; | 
|  | border-radius: 5px; | 
|  | background-color: #EEE; | 
|  | width: 1em; | 
|  | text-align: center; | 
|  | } | 
|  |  | 
|  | .list .position { | 
|  | text-align: right; | 
|  | display: none; | 
|  | } | 
|  |  | 
|  | .list div.toggle { | 
|  | cursor: pointer; | 
|  | } | 
|  |  | 
|  | #column_0 .position { | 
|  | display: table-cell; | 
|  | } | 
|  |  | 
|  | #column_0 .name { | 
|  | display: table-cell; | 
|  | } | 
|  |  | 
|  | .list .name { | 
|  | display: none; | 
|  | white-space: nowrap; | 
|  | } | 
|  |  | 
|  | .value { | 
|  | text-align: right; | 
|  | } | 
|  |  | 
|  | .selectedVersion { | 
|  | font-weight: bold; | 
|  | } | 
|  |  | 
|  | #baseline { | 
|  | width: auto; | 
|  | } | 
|  |  | 
|  | .compareSelector { | 
|  | padding-bottom: 20px; | 
|  | } | 
|  |  | 
|  | .pageDetailTable tbody { | 
|  | cursor: pointer | 
|  | } | 
|  |  | 
|  | .pageDetailTable tfoot td { | 
|  | border-top: 1px grey solid; | 
|  | } | 
|  |  | 
|  | #popover { | 
|  | position: absolute; | 
|  | transform: translateY(-50%) translateX(40px); | 
|  | box-shadow: -2px 10px 44px -10px #000; | 
|  | border-radius: 5px; | 
|  | z-index: 1; | 
|  | background-color: #FFF; | 
|  | display: none; | 
|  | white-space: nowrap; | 
|  | } | 
|  |  | 
|  | #popover table { | 
|  | position: relative; | 
|  | z-index: 1; | 
|  | text-align: right; | 
|  | margin: 10px; | 
|  | } | 
|  | #popover td { | 
|  | padding: 3px 0px 3px 5px; | 
|  | white-space: nowrap; | 
|  | } | 
|  |  | 
|  | .popoverArrow { | 
|  | background-color: #FFF; | 
|  | position: absolute; | 
|  | width: 30px; | 
|  | height: 30px; | 
|  | transform: translateY(-50%)rotate(45deg); | 
|  | top: 50%; | 
|  | left: -10px; | 
|  | z-index: 0; | 
|  | } | 
|  |  | 
|  | #popover .name { | 
|  | padding: 5px; | 
|  | font-weight: bold; | 
|  | text-align: center; | 
|  | } | 
|  |  | 
|  | #popover table .compare { | 
|  | display: none | 
|  | } | 
|  |  | 
|  | #popover table.compare .compare { | 
|  | display: table-cell; | 
|  | } | 
|  |  | 
|  | #popover .compare .time, | 
|  | #popover .compare .version { | 
|  | padding-left: 10px; | 
|  | } | 
|  | .graph, | 
|  | .graph .content { | 
|  | width: 100%; | 
|  | } | 
|  |  | 
|  | .diff .hideDiff { | 
|  | display: none; | 
|  | } | 
|  | .noDiff .hideNoDiff { | 
|  | display: none; | 
|  | } | 
|  | </style> | 
|  | <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script> | 
|  | <script type="text/javascript"> | 
|  | "use strict" | 
|  | google.charts.load('current', {packages: ['corechart']}); | 
|  |  | 
|  | // Did anybody say monkeypatching? | 
|  | if (!NodeList.prototype.forEach) { | 
|  | NodeList.prototype.forEach = function(func) { | 
|  | for (var i = 0; i < this.length; i++) { | 
|  | func(this[i]); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | var versions; | 
|  | var pages; | 
|  | var selectedPage; | 
|  | var baselineVersion; | 
|  | var selectedEntry; | 
|  |  | 
|  | // Marker to programatically replace the defaultData. | 
|  | var defaultData = /*default-data-start*/undefined/*default-data-end*/; | 
|  |  | 
|  | function initialize() { | 
|  | // Initialize the stats table and toggle lists. | 
|  | var original = $("column"); | 
|  | var view = document.createElement('div'); | 
|  | view.id = 'view'; | 
|  | var i = 0; | 
|  | versions.forEach((version) =>  { | 
|  | if (!version.enabled) return; | 
|  | // add column | 
|  | var column = original.cloneNode(true); | 
|  | column.id = "column_" + i; | 
|  | // Fill in all versions | 
|  | var select = column.querySelector(".version"); | 
|  | select.id = "selectVersion_" + i; | 
|  | // add all select options | 
|  | versions.forEach((version) => { | 
|  | if (!version.enabled) return; | 
|  | var option = document.createElement("option"); | 
|  | option.textContent = version.name; | 
|  | option.version = version; | 
|  | select.appendChild(option); | 
|  | }); | 
|  | // Fill in all page versions | 
|  | select = column.querySelector(".pageVersion"); | 
|  | select.id = "select_" + i; | 
|  | // add all pages | 
|  | versions.forEach((version) => { | 
|  | if (!version.enabled) return; | 
|  | var optgroup = document.createElement("optgroup"); | 
|  | optgroup.label = version.name; | 
|  | optgroup.version = version; | 
|  | version.forEachPage((page) => { | 
|  | var option = document.createElement("option"); | 
|  | option.textContent = page.name; | 
|  | option.page = page; | 
|  | optgroup.appendChild(option); | 
|  | }); | 
|  | select.appendChild(optgroup); | 
|  | }); | 
|  | view.appendChild(column); | 
|  | i++; | 
|  | }); | 
|  | var oldView = $('view'); | 
|  | oldView.parentNode.replaceChild(view, oldView); | 
|  |  | 
|  | var select = $('baseline'); | 
|  | removeAllChildren(select); | 
|  | select.appendChild(document.createElement('option')); | 
|  | versions.forEach((version) => { | 
|  | var option = document.createElement("option"); | 
|  | option.textContent = version.name; | 
|  | option.version = version; | 
|  | select.appendChild(option); | 
|  | }); | 
|  | initializeToggleList(versions.versions, $('versionSelector')); | 
|  | initializeToggleList(pages.values(), $('pageSelector')); | 
|  | initializeToggleList(Group.groups.values(), $('groupSelector')); | 
|  | initializeToggleContentVisibility(); | 
|  | } | 
|  |  | 
|  | function initializeToggleList(items, node) { | 
|  | var list = node.querySelector('ul'); | 
|  | removeAllChildren(list); | 
|  | items = Array.from(items); | 
|  | items.sort(NameComparator); | 
|  | items.forEach((item) => { | 
|  | var li = document.createElement('li'); | 
|  | var checkbox = document.createElement('input'); | 
|  | checkbox.type = 'checkbox'; | 
|  | checkbox.checked = item.enabled; | 
|  | checkbox.item = item; | 
|  | checkbox.addEventListener('click', handleToggleVersionOrPageEnable); | 
|  | li.appendChild(checkbox); | 
|  | li.appendChild(document.createTextNode(item.name)); | 
|  | list.appendChild(li); | 
|  | }); | 
|  | $('results').querySelectorAll('#results > .hidden').forEach((node) => { | 
|  | toggleCssClass(node, 'hidden', false); | 
|  | }) | 
|  | } | 
|  |  | 
|  | function initializeToggleContentVisibility() { | 
|  | var nodes = document.querySelectorAll('.toggleContentVisibility'); | 
|  | nodes.forEach((node) => { | 
|  | var content = node.querySelector('.content'); | 
|  | var header = node.querySelector('h1,h2,h3'); | 
|  | if (content === undefined || header === undefined) return; | 
|  | if (header.querySelector('input') != undefined) return; | 
|  | var checkbox = document.createElement('input'); | 
|  | checkbox.type = 'checkbox'; | 
|  | checkbox.checked = content.className.indexOf('hidden') == -1; | 
|  | checkbox.contentNode = content; | 
|  | checkbox.addEventListener('click', handleToggleContentVisibility); | 
|  | header.insertBefore(checkbox, header.childNodes[0]); | 
|  | }); | 
|  | } | 
|  |  | 
|  | window.addEventListener('popstate', (event) => { | 
|  | popHistoryState(event.state); | 
|  | }); | 
|  |  | 
|  | function popHistoryState(state) { | 
|  | if (!state.version) return false; | 
|  | if (!versions) return false; | 
|  | var version = versions.getByName(state.version); | 
|  | if (!version) return false; | 
|  | var page = version.get(state.page); | 
|  | if (!page) return false; | 
|  | if (!state.entry) { | 
|  | showEntry(page.total); | 
|  | } else { | 
|  | var entry = page.get(state.entry); | 
|  | if (!entry) { | 
|  | showEntry(page.total); | 
|  | } else { | 
|  | showEntry(entry); | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | function pushHistoryState() { | 
|  | var selection = selectedEntry ? selectedEntry : selectedPage; | 
|  | if (!selection) return; | 
|  | var state = selection.urlParams(); | 
|  | // Don't push a history state if it didn't change. | 
|  | if (JSON.stringify(window.history.state) === JSON.stringify(state)) return; | 
|  | var params = "?"; | 
|  | for (var pairs of Object.entries(state)) { | 
|  | params += encodeURIComponent(pairs[0]) + "=" | 
|  | + encodeURIComponent(pairs[1]) + "&"; | 
|  | } | 
|  | window.history.pushState(state, selection.toString(), params); | 
|  | } | 
|  |  | 
|  | function showSelectedEntryInPage(page) { | 
|  | if (!selectedEntry) return showPage(page); | 
|  | var entry = page.get(selectedEntry.name); | 
|  | if (!entry) return showPage(page); | 
|  | selectEntry(entry); | 
|  | } | 
|  |  | 
|  | function showPage(firstPage) { | 
|  | var changeSelectedEntry = selectedEntry !== undefined | 
|  | && selectedEntry.page === selectedPage; | 
|  | selectedPage = firstPage; | 
|  | selectedPage.sort(); | 
|  | showPageInColumn(firstPage, 0); | 
|  | // Show the other versions of this page in the following columns. | 
|  | var pageVersions = versions.getPageVersions(firstPage); | 
|  | var index = 1; | 
|  | pageVersions.forEach((page) => { | 
|  | if (page !== firstPage) { | 
|  | showPageInColumn(page, index); | 
|  | index++; | 
|  | } | 
|  | }); | 
|  | if (changeSelectedEntry) { | 
|  | showEntryDetail(selectedPage.getEntry(selectedEntry)); | 
|  | } | 
|  | showImpactList(selectedPage); | 
|  | pushHistoryState(); | 
|  | } | 
|  |  | 
|  | function showPageInColumn(page, columnIndex) { | 
|  | page.sort(); | 
|  | var showDiff = (baselineVersion === undefined && columnIndex !== 0) || | 
|  | (baselineVersion !== undefined && page.version !== baselineVersion); | 
|  | var diffStatus = (td, a, b) => {}; | 
|  | if (showDiff) { | 
|  | if (baselineVersion !== undefined) { | 
|  | diffStatus = (td, a, b) => { | 
|  | if (a == 0) return; | 
|  | td.style.color = a < 0 ? '#FF0000' : '#00BB00'; | 
|  | }; | 
|  | } else { | 
|  | diffStatus = (td, a, b) => { | 
|  | if (a == b) return; | 
|  | var color; | 
|  | var ratio = a / b; | 
|  | if (ratio > 1) { | 
|  | ratio = Math.min(Math.round((ratio - 1) * 255 * 10), 200); | 
|  | color = '#' + ratio.toString(16) + "0000"; | 
|  | } else { | 
|  | ratio = Math.min(Math.round((1 - ratio) * 255 * 10), 200); | 
|  | color = '#00' + ratio.toString(16) + "00"; | 
|  | } | 
|  | td.style.color = color; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | var column = $('column_' + columnIndex); | 
|  | var select = $('select_' + columnIndex); | 
|  | // Find the matching option | 
|  | selectOption(select, (i, option) => { | 
|  | return option.page == page | 
|  | }); | 
|  | var table = column.querySelector("table"); | 
|  | var oldTbody = table.querySelector('tbody'); | 
|  | var tbody = document.createElement('tbody'); | 
|  | var referencePage = selectedPage; | 
|  | page.forEachSorted(selectedPage, (parentEntry, entry, referenceEntry) => { | 
|  | var tr = document.createElement('tr'); | 
|  | tbody.appendChild(tr); | 
|  | tr.entry = entry; | 
|  | tr.parentEntry = parentEntry; | 
|  | tr.className = parentEntry === undefined ? 'parent' : 'child'; | 
|  | // Don't show entries that do not exist on the current page or if we | 
|  | // compare against the current page | 
|  | if (entry !== undefined && page.version !== baselineVersion) { | 
|  | // If we show a diff, use the baselineVersion as the referenceEntry | 
|  | if (baselineVersion !== undefined) { | 
|  | var baselineEntry = baselineVersion.getEntry(entry); | 
|  | if (baselineEntry !== undefined) referenceEntry = baselineEntry | 
|  | } | 
|  | if (!parentEntry) { | 
|  | var node = td(tr, '<div class="toggle">►</div>', 'position'); | 
|  | node.firstChild.addEventListener('click', handleToggleGroup); | 
|  | } else { | 
|  | td(tr, entry.position == 0 ? '' : entry.position, 'position'); | 
|  | } | 
|  | addCodeSearchButton(entry, | 
|  | td(tr, entry.name, 'name ' + entry.cssClass())); | 
|  |  | 
|  | diffStatus( | 
|  | td(tr, ms(entry.time), 'value time'), | 
|  | entry.time, referenceEntry.time); | 
|  | diffStatus( | 
|  | td(tr, percent(entry.timePercent), 'value time'), | 
|  | entry.time, referenceEntry.time); | 
|  | diffStatus( | 
|  | td(tr, count(entry.count), 'value count'), | 
|  | entry.count, referenceEntry.count); | 
|  | } else if (baselineVersion !== undefined && referenceEntry | 
|  | && page.version !== baselineVersion) { | 
|  | // Show comparison of entry that does not exist on the current page. | 
|  | tr.entry = new Entry(0, referenceEntry.name); | 
|  | tr.entry.page = page; | 
|  | td(tr, '-', 'position'); | 
|  | td(tr, referenceEntry.name, 'name'); | 
|  | diffStatus( | 
|  | td(tr, ms(referenceEntry.time), 'value time'), | 
|  | referenceEntry.time, 0); | 
|  | diffStatus( | 
|  | td(tr, percent(referenceEntry.timePercent), 'value time'), | 
|  | referenceEntry.timePercent, 0); | 
|  | diffStatus( | 
|  | td(tr, count(referenceEntry.count), 'value count'), | 
|  | referenceEntry.count, 0); | 
|  | } else { | 
|  | // Display empty entry / baseline entry | 
|  | var showBaselineEntry = entry !== undefined; | 
|  | if (showBaselineEntry) { | 
|  | if (!parentEntry) { | 
|  | var node = td(tr, '<div class="toggle">►</div>', 'position'); | 
|  | node.firstChild.addEventListener('click', handleToggleGroup); | 
|  | } else { | 
|  | td(tr, entry.position == 0 ? '' : entry.position, 'position'); | 
|  | } | 
|  | td(tr, entry.name, 'name'); | 
|  | td(tr, ms(entry.time, false), 'value time'); | 
|  | td(tr, percent(entry.timePercent, false), 'value time'); | 
|  | td(tr, count(entry.count, false), 'value count'); | 
|  | } else { | 
|  | td(tr, '-', 'position'); | 
|  | td(tr, referenceEntry.name, 'name'); | 
|  | td(tr, '-', 'value time'); | 
|  | td(tr, '-', 'value time'); | 
|  | td(tr, '-', 'value count'); | 
|  | } | 
|  | } | 
|  | }); | 
|  | table.replaceChild(tbody, oldTbody); | 
|  | var versionSelect = column.querySelector('select.version'); | 
|  | selectOption(versionSelect, (index, option) => { | 
|  | return option.version == page.version | 
|  | }); | 
|  | } | 
|  |  | 
|  | function showEntry(entry) { | 
|  | selectEntry(entry, true); | 
|  | } | 
|  |  | 
|  | function selectEntry(entry, updateSelectedPage) { | 
|  | var needsPageSwitch = true; | 
|  | if (updateSelectedPage && selectedPage) { | 
|  | entry = selectedPage.version.getEntry(entry); | 
|  | needsPageSwitch = updateSelectedPage && entry.page != selectedPage; | 
|  | } | 
|  | var rowIndex = 0; | 
|  | // If clicked in the detail row change the first column to that page. | 
|  | if (needsPageSwitch) showPage(entry.page); | 
|  | var childNodes = $('column_0').querySelector('.list tbody').childNodes; | 
|  | for (var i = 0; i < childNodes.length; i++) { | 
|  | if (childNodes[i].entry !== undefined && | 
|  | childNodes[i].entry.name == entry.name) { | 
|  | rowIndex = i; | 
|  | break; | 
|  | } | 
|  | } | 
|  | var firstEntry = childNodes[rowIndex].entry; | 
|  | if (rowIndex) { | 
|  | if (firstEntry.parent) showGroup(firstEntry.parent); | 
|  | } | 
|  | // Deselect all | 
|  | $('view').querySelectorAll('.list tbody tr').forEach((tr) => { | 
|  | toggleCssClass(tr, 'selected', false); | 
|  | }); | 
|  | // Select the entry row | 
|  | $('view').querySelectorAll("tbody").forEach((body) => { | 
|  | var row = body.childNodes[rowIndex]; | 
|  | if (!row) return; | 
|  | toggleCssClass(row, 'selected', row.entry && row.entry.name == | 
|  | firstEntry.name); | 
|  | }); | 
|  | if (updateSelectedPage && selectedEntry) { | 
|  | entry = selectedEntry.page.version.getEntry(entry); | 
|  | } | 
|  | if (entry !== selectedEntry) { | 
|  | selectedEntry = entry; | 
|  | showEntryDetail(entry); | 
|  | } | 
|  | } | 
|  |  | 
|  | function showEntryDetail(entry) { | 
|  | showVersionDetails(entry); | 
|  | showPageDetails(entry); | 
|  | showImpactList(entry.page); | 
|  | showGraphs(entry.page); | 
|  | pushHistoryState(); | 
|  | } | 
|  |  | 
|  | function showVersionDetails(entry) { | 
|  | var table, tbody, entries; | 
|  | table = $('detailView').querySelector('.versionDetailTable'); | 
|  | tbody = document.createElement('tbody'); | 
|  | if (entry !== undefined) { | 
|  | $('detailView').querySelector('.versionDetail h3 span').textContent = | 
|  | entry.name + ' in ' + entry.page.name; | 
|  | entries = versions.getPageVersions(entry.page).map( | 
|  | (page) => { | 
|  | return page.get(entry.name) | 
|  | }); | 
|  | entries.sort((a, b) => { | 
|  | return a.time - b.time | 
|  | }); | 
|  | entries.forEach((pageEntry) => { | 
|  | if (pageEntry === undefined) return; | 
|  | var tr = document.createElement('tr'); | 
|  | if (pageEntry == entry) tr.className += 'selected'; | 
|  | tr.entry = pageEntry; | 
|  | var isBaselineEntry = pageEntry.page.version == baselineVersion; | 
|  | td(tr, pageEntry.page.version.name, 'version'); | 
|  | td(tr, ms(pageEntry.time, !isBaselineEntry), 'value time'); | 
|  | td(tr, percent(pageEntry.timePercent, !isBaselineEntry), 'value time'); | 
|  | td(tr, count(pageEntry.count, !isBaselineEntry), 'value count'); | 
|  | tbody.appendChild(tr); | 
|  | }); | 
|  | } | 
|  | table.replaceChild(tbody, table.querySelector('tbody')); | 
|  | } | 
|  |  | 
|  | function showPageDetails(entry) { | 
|  | var table, tbody, entries; | 
|  | table = $('detailView').querySelector('.pageDetailTable'); | 
|  | tbody = document.createElement('tbody'); | 
|  | if (entry === undefined) { | 
|  | table.replaceChild(tbody, table.querySelector('tbody')); | 
|  | return; | 
|  | } | 
|  | var version = entry.page.version; | 
|  | var showDiff = version !== baselineVersion; | 
|  | $('detailView').querySelector('.pageDetail h3 span').textContent = | 
|  | version.name; | 
|  | entries = version.pages.map((page) => { | 
|  | if (!page.enabled) return; | 
|  | return page.get(entry.name) | 
|  | }); | 
|  | entries.sort((a, b) => { | 
|  | var cmp = b.timePercent - a.timePercent; | 
|  | if (cmp.toFixed(1) == 0) return b.time - a.time; | 
|  | return cmp | 
|  | }); | 
|  | entries.forEach((pageEntry) => { | 
|  | if (pageEntry === undefined) return; | 
|  | var tr = document.createElement('tr'); | 
|  | if (pageEntry === entry) tr.className += 'selected'; | 
|  | tr.entry = pageEntry; | 
|  | td(tr, pageEntry.page.name, 'name'); | 
|  | td(tr, ms(pageEntry.time, showDiff), 'value time'); | 
|  | td(tr, percent(pageEntry.timePercent, showDiff), 'value time'); | 
|  | td(tr, percent(pageEntry.timePercentPerEntry, showDiff), | 
|  | 'value time hideNoDiff'); | 
|  | td(tr, count(pageEntry.count, showDiff), 'value count'); | 
|  | tbody.appendChild(tr); | 
|  | }); | 
|  | // show the total for all pages | 
|  | var tds = table.querySelectorAll('tfoot td'); | 
|  | tds[1].textContent = ms(entry.getTimeImpact(), showDiff); | 
|  | // Only show the percentage total if we are in diff mode: | 
|  | tds[2].textContent = percent(entry.getTimePercentImpact(), showDiff); | 
|  | tds[3].textContent = ''; | 
|  | tds[4].textContent = count(entry.getCountImpact(), showDiff); | 
|  | table.replaceChild(tbody, table.querySelector('tbody')); | 
|  | } | 
|  |  | 
|  | function showImpactList(page) { | 
|  | var impactView = $('detailView').querySelector('.impactView'); | 
|  | impactView.querySelector('h3 span').textContent = page.version.name; | 
|  |  | 
|  | var table = impactView.querySelector('table'); | 
|  | var tbody = document.createElement('tbody'); | 
|  | var version = page.version; | 
|  | var entries = version.allEntries(); | 
|  | if (selectedEntry !== undefined && selectedEntry.isGroup) { | 
|  | impactView.querySelector('h3 span').textContent += " " + selectedEntry.name; | 
|  | entries = entries.filter((entry) => { | 
|  | return entry.name == selectedEntry.name || | 
|  | (entry.parent && entry.parent.name == selectedEntry.name) | 
|  | }); | 
|  | } | 
|  | var isCompareView = baselineVersion !== undefined; | 
|  | entries = entries.filter((entry) => { | 
|  | if (isCompareView) { | 
|  | var impact = entry.getTimeImpact(); | 
|  | return impact < -1 || 1 < impact | 
|  | } | 
|  | return entry.getTimePercentImpact() > 0.01; | 
|  | }); | 
|  | entries = entries.slice(0, 50); | 
|  | entries.sort((a, b) => { | 
|  | var cmp = b.getTimePercentImpact() - a.getTimePercentImpact(); | 
|  | if (isCompareView || cmp.toFixed(1) == 0) { | 
|  | return b.getTimeImpact() - a.getTimeImpact(); | 
|  | } | 
|  | return cmp | 
|  | }); | 
|  | entries.forEach((entry) => { | 
|  | var tr = document.createElement('tr'); | 
|  | tr.entry = entry; | 
|  | td(tr, entry.name, 'name'); | 
|  | td(tr, ms(entry.getTimeImpact()), 'value time'); | 
|  | var percentImpact = entry.getTimePercentImpact(); | 
|  | td(tr, percentImpact > 1000 ? '-' : percent(percentImpact), 'value time'); | 
|  | var topPages = entry.getPagesByPercentImpact().slice(0, 3) | 
|  | .map((each) => { | 
|  | return each.name + ' (' + percent(each.getEntry(entry).timePercent) + | 
|  | ')' | 
|  | }); | 
|  | td(tr, topPages.join(', '), 'name'); | 
|  | tbody.appendChild(tr); | 
|  | }); | 
|  | table.replaceChild(tbody, table.querySelector('tbody')); | 
|  | } | 
|  |  | 
|  | function showGraphs(page) { | 
|  | var groups = page.groups.filter(each => each.enabled); | 
|  | // Sort groups by the biggest impact | 
|  | groups.sort((a, b) => { | 
|  | return b.getTimeImpact() - a.getTimeImpact(); | 
|  | }); | 
|  | if (selectedGroup == undefined) { | 
|  | selectedGroup = groups[0]; | 
|  | } else { | 
|  | groups = groups.filter(each => each.name != selectedGroup.name); | 
|  | groups.unshift(selectedGroup); | 
|  | } | 
|  | showPageGraph(groups, page); | 
|  | showVersionGraph(groups, page); | 
|  | showPageVersionGraph(groups, page); | 
|  | } | 
|  |  | 
|  | function getGraphDataTable(groups) { | 
|  | var dataTable = new google.visualization.DataTable(); | 
|  | dataTable.addColumn('string', 'Name'); | 
|  | groups.forEach(group => { | 
|  | var column = dataTable.addColumn('number', group.name.substring(6)); | 
|  | dataTable.setColumnProperty(column, 'group', group); | 
|  | }); | 
|  | return dataTable; | 
|  | } | 
|  |  | 
|  | var selectedGroup; | 
|  | function showPageGraph(groups, page) { | 
|  | var isDiffView = baselineVersion !== undefined; | 
|  | var dataTable = getGraphDataTable(groups); | 
|  | // Calculate the average row | 
|  | var row = ['Average']; | 
|  | groups.forEach((group) => { | 
|  | if (isDiffView) { | 
|  | row.push(group.isTotal ? 0 : group.getAverageTimeImpact()); | 
|  | } else { | 
|  | row.push(group.isTotal ? 0 : group.getTimeImpact()); | 
|  | } | 
|  | }); | 
|  | dataTable.addRow(row); | 
|  | // Sort the pages by the selected group. | 
|  | var pages = page.version.pages.filter(page => page.enabled); | 
|  | function sumDiff(page) { | 
|  | var sum = 0; | 
|  | groups.forEach(group => { | 
|  | var value = group.getTimePercentImpact() - | 
|  | page.getEntry(group).timePercent; | 
|  | sum += value * value; | 
|  | }); | 
|  | return sum; | 
|  | } | 
|  | if (isDiffView) { | 
|  | pages.sort((a, b) => { | 
|  | return b.getEntry(selectedGroup).time- | 
|  | a.getEntry(selectedGroup).time; | 
|  | }); | 
|  | } else { | 
|  | pages.sort((a, b) => { | 
|  | return b.getEntry(selectedGroup).timePercent - | 
|  | a.getEntry(selectedGroup).timePercent; | 
|  | }); | 
|  | } | 
|  | // Sort by sum of squared distance to the average. | 
|  | // pages.sort((a, b) => { | 
|  | //   return a.distanceFromTotalPercent() - b.distanceFromTotalPercent(); | 
|  | // }); | 
|  | // Calculate the entries for the pages | 
|  | pages.forEach((page) => { | 
|  | row = [page.name]; | 
|  | groups.forEach((group) => { | 
|  | row.push(group.isTotal ? 0 : page.getEntry(group).time); | 
|  | }); | 
|  | var rowIndex = dataTable.addRow(row); | 
|  | dataTable.setRowProperty(rowIndex, 'page', page); | 
|  | }); | 
|  | renderGraph('Pages for ' + page.version.name, groups, dataTable, | 
|  | 'pageGraph', isDiffView ? true : 'percent'); | 
|  | } | 
|  |  | 
|  | function showVersionGraph(groups, page) { | 
|  | var dataTable = getGraphDataTable(groups); | 
|  | var row; | 
|  | var vs = versions.versions.filter(version => version.enabled); | 
|  | vs.sort((a, b) => { | 
|  | return b.getEntry(selectedGroup).getTimeImpact() - | 
|  | a.getEntry(selectedGroup).getTimeImpact(); | 
|  | }); | 
|  | // Calculate the entries for the versions | 
|  | vs.forEach((version) => { | 
|  | row = [version.name]; | 
|  | groups.forEach((group) => { | 
|  | row.push(group.isTotal ? 0 : version.getEntry(group).getTimeImpact()); | 
|  | }); | 
|  | var rowIndex = dataTable.addRow(row); | 
|  | dataTable.setRowProperty(rowIndex, 'page', page); | 
|  | }); | 
|  | renderGraph('Versions Total Time over all Pages', groups, dataTable, | 
|  | 'versionGraph', true); | 
|  | } | 
|  |  | 
|  | function showPageVersionGraph(groups, page) { | 
|  | var dataTable = getGraphDataTable(groups); | 
|  | var row; | 
|  | var vs = versions.getPageVersions(page); | 
|  | vs.sort((a, b) => { | 
|  | return b.getEntry(selectedGroup).time - a.getEntry(selectedGroup).time; | 
|  | }); | 
|  | // Calculate the entries for the versions | 
|  | vs.forEach((page) => { | 
|  | row = [page.version.name]; | 
|  | groups.forEach((group) => { | 
|  | row.push(group.isTotal ? 0 : page.getEntry(group).time); | 
|  | }); | 
|  | var rowIndex = dataTable.addRow(row); | 
|  | dataTable.setRowProperty(rowIndex, 'page', page); | 
|  | }); | 
|  | renderGraph('Versions for ' + page.name, groups, dataTable, | 
|  | 'pageVersionGraph', true); | 
|  | } | 
|  |  | 
|  | function renderGraph(title, groups, dataTable, id, isStacked) { | 
|  | var isDiffView = baselineVersion !== undefined; | 
|  | var formatter = new google.visualization.NumberFormat({ | 
|  | suffix: (isDiffView ? 'msΔ' : 'ms'), | 
|  | negativeColor: 'red', | 
|  | groupingSymbol: "'" | 
|  | }); | 
|  | for (var i = 1; i < dataTable.getNumberOfColumns(); i++) { | 
|  | formatter.format(dataTable, i); | 
|  | } | 
|  | var height = 85 + 28 * dataTable.getNumberOfRows(); | 
|  | var options = { | 
|  | isStacked: isStacked, | 
|  | height: height, | 
|  | hAxis: { | 
|  | minValue: 0, | 
|  | textStyle: { fontSize: 14 } | 
|  | }, | 
|  | animation:{ | 
|  | duration: dataTable.getNumberOfRows() > 50 ? 0 : 500 , | 
|  | easing: 'out', | 
|  | }, | 
|  | vAxis: { | 
|  | textStyle: { fontSize: 14 } | 
|  | }, | 
|  | tooltip: { textStyle: { fontSize: 14 }}, | 
|  | explorer: { | 
|  | actions: ['dragToZoom', 'rightClickToReset'], | 
|  | maxZoomIn: 0.01 | 
|  | }, | 
|  | legend: {position:'top', maxLines: 1, textStyle: { fontSize: 14 }}, | 
|  | chartArea: {left:200, top:50, width:'98%', height:'80%'}, | 
|  | colors: groups.map(each => each.color) | 
|  | }; | 
|  | var parentNode = $(id); | 
|  | parentNode.querySelector('h2>span, h3>span').textContent = title; | 
|  | var graphNode = parentNode.querySelector('.content'); | 
|  |  | 
|  | var chart = graphNode.chart; | 
|  | if (chart === undefined) { | 
|  | chart = graphNode.chart = new google.visualization.BarChart(graphNode); | 
|  | } else { | 
|  | google.visualization.events.removeAllListeners(chart); | 
|  | } | 
|  | google.visualization.events.addListener(chart, 'select', selectHandler); | 
|  | function getChartEntry(selection) { | 
|  | if (!selection) return undefined; | 
|  | var column = selection.column; | 
|  | if (column == undefined) return undefined; | 
|  | var selectedGroup = dataTable.getColumnProperty(column, 'group'); | 
|  | var row = selection.row; | 
|  | if (row == null) return selectedGroup; | 
|  | var page = dataTable.getRowProperty(row, 'page'); | 
|  | if (!page) return selectedGroup; | 
|  | return page.getEntry(selectedGroup); | 
|  | } | 
|  | function selectHandler() { | 
|  | selectedGroup = getChartEntry(chart.getSelection()[0]) | 
|  | if (!selectedGroup) return; | 
|  | selectEntry(selectedGroup, true); | 
|  | } | 
|  |  | 
|  | // Make our global tooltips work | 
|  | google.visualization.events.addListener(chart, 'onmouseover', mouseOverHandler); | 
|  | function mouseOverHandler(selection) { | 
|  | graphNode.entry = getChartEntry(selection); | 
|  | } | 
|  | chart.draw(dataTable, options); | 
|  | } | 
|  |  | 
|  | function showGroup(entry) { | 
|  | toggleGroup(entry, true); | 
|  | } | 
|  |  | 
|  | function toggleGroup(group, show) { | 
|  | $('view').querySelectorAll(".child").forEach((tr) => { | 
|  | var entry = tr.parentEntry; | 
|  | if (!entry) return; | 
|  | if (entry.name !== group.name) return; | 
|  | toggleCssClass(tr, 'visible', show); | 
|  | }); | 
|  | } | 
|  |  | 
|  | function showPopover(entry) { | 
|  | var popover = $('popover'); | 
|  | popover.querySelector('td.name').textContent = entry.name; | 
|  | popover.querySelector('td.page').textContent = entry.page.name; | 
|  | setPopoverDetail(popover, entry, ''); | 
|  | popover.querySelector('table').className = ""; | 
|  | if (baselineVersion !== undefined) { | 
|  | entry = baselineVersion.getEntry(entry); | 
|  | setPopoverDetail(popover, entry, '.compare'); | 
|  | popover.querySelector('table').className = "compare"; | 
|  | } | 
|  | } | 
|  |  | 
|  | function setPopoverDetail(popover, entry, prefix) { | 
|  | var node = (name) => popover.querySelector(prefix + name); | 
|  | if (entry == undefined) { | 
|  | node('.version').textContent = baselineVersion.name; | 
|  | node('.time').textContent = '-'; | 
|  | node('.timeVariance').textContent = '-'; | 
|  | node('.percent').textContent = '-'; | 
|  | node('.percentPerEntry').textContent = '-'; | 
|  | node('.percentVariance').textContent  = '-'; | 
|  | node('.count').textContent =  '-'; | 
|  | node('.countVariance').textContent = '-'; | 
|  | node('.timeImpact').textContent = '-'; | 
|  | node('.timePercentImpact').textContent = '-'; | 
|  | } else { | 
|  | node('.version').textContent = entry.page.version.name; | 
|  | node('.time').textContent = ms(entry._time, false); | 
|  | node('.timeVariance').textContent | 
|  | = percent(entry.timeVariancePercent, false); | 
|  | node('.percent').textContent = percent(entry.timePercent, false); | 
|  | node('.percentPerEntry').textContent | 
|  | = percent(entry.timePercentPerEntry, false); | 
|  | node('.percentVariance').textContent | 
|  | = percent(entry.timePercentVariancePercent, false); | 
|  | node('.count').textContent = count(entry._count, false); | 
|  | node('.countVariance').textContent | 
|  | = percent(entry.timeVariancePercent, false); | 
|  | node('.timeImpact').textContent | 
|  | = ms(entry.getTimeImpact(false), false); | 
|  | node('.timePercentImpact').textContent | 
|  | = percent(entry.getTimeImpactVariancePercent(false), false); | 
|  | } | 
|  | } | 
|  | </script> | 
|  | <script type="text/javascript"> | 
|  | "use strict" | 
|  | // ========================================================================= | 
|  | // Helpers | 
|  | function $(id) { | 
|  | return document.getElementById(id) | 
|  | } | 
|  |  | 
|  | function removeAllChildren(node) { | 
|  | while (node.firstChild) { | 
|  | node.removeChild(node.firstChild); | 
|  | } | 
|  | } | 
|  |  | 
|  | function selectOption(select, match) { | 
|  | var options = select.options; | 
|  | for (var i = 0; i < options.length; i++) { | 
|  | if (match(i, options[i])) { | 
|  | select.selectedIndex = i; | 
|  | return; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | function addCodeSearchButton(entry, node) { | 
|  | if (entry.isGroup) return; | 
|  | var button = document.createElement("div"); | 
|  | button.textContent = '?' | 
|  | button.className = "codeSearch" | 
|  | button.addEventListener('click', handleCodeSearch); | 
|  | node.appendChild(button); | 
|  | return node; | 
|  | } | 
|  |  | 
|  | function td(tr, content, className) { | 
|  | var td = document.createElement("td"); | 
|  | if (content[0] == '<') { | 
|  | td.innerHTML = content; | 
|  | } else { | 
|  | td.textContent = content; | 
|  | } | 
|  | td.className = className | 
|  | tr.appendChild(td); | 
|  | return td | 
|  | } | 
|  |  | 
|  | function nodeIndex(node) { | 
|  | var children = node.parentNode.childNodes, | 
|  | i = 0; | 
|  | for (; i < children.length; i++) { | 
|  | if (children[i] == node) { | 
|  | return i; | 
|  | } | 
|  | } | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | function toggleCssClass(node, cssClass, toggleState) { | 
|  | var index = -1; | 
|  | var classes; | 
|  | if (node.className != undefined) { | 
|  | classes = node.className.split(' '); | 
|  | index = classes.indexOf(cssClass); | 
|  | } | 
|  | if (index == -1) { | 
|  | if (toggleState === false) return; | 
|  | node.className += ' ' + cssClass; | 
|  | return; | 
|  | } | 
|  | if (toggleState === true) return; | 
|  | classes.splice(index, 1); | 
|  | node.className = classes.join(' '); | 
|  | } | 
|  |  | 
|  | function NameComparator(a, b) { | 
|  | if (a.name > b.name) return 1; | 
|  | if (a.name < b.name) return -1; | 
|  | return 0 | 
|  | } | 
|  |  | 
|  | function diffSign(value, digits, unit, showDiff) { | 
|  | if (showDiff === false || baselineVersion == undefined) { | 
|  | if (value === undefined) return ''; | 
|  | return value.toFixed(digits) + unit; | 
|  | } | 
|  | return (value >= 0 ? '+' : '') + value.toFixed(digits) + unit + 'Δ'; | 
|  | } | 
|  |  | 
|  | function ms(value, showDiff) { | 
|  | return diffSign(value, 1, 'ms', showDiff); | 
|  | } | 
|  |  | 
|  | function count(value, showDiff) { | 
|  | return diffSign(value, 0, '#', showDiff); | 
|  | } | 
|  |  | 
|  | function percent(value, showDiff) { | 
|  | return diffSign(value, 1, '%', showDiff); | 
|  | } | 
|  |  | 
|  | </script> | 
|  | <script type="text/javascript"> | 
|  | "use strict" | 
|  | // ========================================================================= | 
|  | // EventHandlers | 
|  | function handleBodyLoad() { | 
|  | $('uploadInput').focus(); | 
|  | if (defaultData) { | 
|  | handleLoadJSON(defaultData); | 
|  | } else if (window.location.protocol !== 'file:') { | 
|  | tryLoadDefaultResults(); | 
|  | } | 
|  | } | 
|  |  | 
|  | function tryLoadDefaultResults() { | 
|  | // Try to load a results.json file adjacent to this day. | 
|  | var xhr = new XMLHttpRequest(); | 
|  | // The markers on the following line can be used to replace the url easily | 
|  | // with scripts. | 
|  | xhr.open('GET', /*results-url-start*/'results.json'/*results-url-end*/, true); | 
|  | xhr.onreadystatechange = function(e) { | 
|  | if(this.readyState !== XMLHttpRequest.DONE || this.status !== 200) return; | 
|  | handleLoadText(this.responseText); | 
|  | }; | 
|  | xhr.send(); | 
|  | } | 
|  |  | 
|  | function handleAppendFile() { | 
|  | var files = document.getElementById("appendInput").files; | 
|  | loadFiles(files, true); | 
|  | } | 
|  |  | 
|  | function handleLoadFile() { | 
|  | var files = document.getElementById("uploadInput").files; | 
|  | loadFiles(files, false) | 
|  | } | 
|  |  | 
|  | function loadFiles(files, append) { | 
|  | var file = files[0]; | 
|  | var reader = new FileReader(); | 
|  |  | 
|  | reader.onload = function(evt) { | 
|  | handleLoadText(this.result, append); | 
|  | } | 
|  | reader.readAsText(file); | 
|  | } | 
|  |  | 
|  | function handleLoadText(text, append) { | 
|  | handleLoadJSON(JSON.parse(text), append); | 
|  | } | 
|  |  | 
|  | function getStateFromParams() { | 
|  | var query = window.location.search.substr(1); | 
|  | var result = {}; | 
|  | query.split("&").forEach((part) => { | 
|  | var item = part.split("="); | 
|  | var key = decodeURIComponent(item[0]) | 
|  | result[key] = decodeURIComponent(item[1]); | 
|  | }); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | function handleLoadJSON(json, append) { | 
|  | let isFirstLoad = pages === undefined; | 
|  | json = fixClusterTelemetryResults(json); | 
|  | json = fixSinglePageJSON(json); | 
|  | if (append && !isFirstLoad) { | 
|  | json = createUniqueVersions(json) | 
|  | } | 
|  | var state = getStateFromParams(); | 
|  | if (!append || isFirstLoad) { | 
|  | pages = new Pages(); | 
|  | versions = Versions.fromJSON(json); | 
|  | } else { | 
|  | Versions.fromJSON(json).forEach(e => versions.add(e)) | 
|  | } | 
|  | initialize() | 
|  | if (isFirstLoad && !popHistoryState(state)) { | 
|  | showEntry(selectedPage.total); | 
|  | } | 
|  | } | 
|  |  | 
|  | function fixClusterTelemetryResults(json) { | 
|  | // Convert CT results to callstats compatible JSON | 
|  | // Input: | 
|  | // { PATH: { "pairs": { METRIC: { "count": XX, "time": XX }.. }}.. } | 
|  | let firstEntry; | 
|  | for (let key in json) { | 
|  | firstEntry = json[key]; | 
|  | break; | 
|  | } | 
|  | // Return the original JSON if it is not a CT result. | 
|  | if (firstEntry.pairs === undefined) return json; | 
|  | // The results include already the group totals, remove them by filtering. | 
|  | let groupNames = new Set(Array.from(Group.groups.values()).map(e => e.name)); | 
|  | let result = Object.create(null); | 
|  | for (let file_name in json) { | 
|  | let entries = []; | 
|  | let file_data = json[file_name].pairs; | 
|  | for (let name in file_data) { | 
|  | if(name != "Total" && groupNames.has(name)) continue; | 
|  | let entry = file_data[name]; | 
|  | let count = entry.count; | 
|  | let time = entry.time; | 
|  | entries.push([name, time, 0, 0, count, 0, 0]); | 
|  | } | 
|  | let domain = file_name.split("/").slice(-1)[0]; | 
|  | result[domain] = entries; | 
|  | } | 
|  | return {__proto__:null, ClusterTelemetry: result}; | 
|  | } | 
|  |  | 
|  | function fixSinglePageJSON(json) { | 
|  | // Try to detect the single-version case, where we're missing the toplevel | 
|  | // version object. The incoming JSON is of the form: | 
|  | //    {"Page 1": [... data points ... ], "Page 2": [...], ...} | 
|  | // Instead of the default multi-page JSON: | 
|  | //    {"Version 1": { "Page 1": ..., ...}, "Version 2": {...}, ...} | 
|  | // In this case insert a single "Default" version as top-level entry. | 
|  | var firstProperty = (object) => { | 
|  | for (var key in object) return key; | 
|  | }; | 
|  | var maybePage = json[firstProperty(json)]; | 
|  | if (!Array.isArray(maybePage)) return json; | 
|  | return {"Default": json} | 
|  | } | 
|  |  | 
|  | var appendIndex = 0; | 
|  | function createUniqueVersions(json) { | 
|  | // Make sure all toplevel entries are unique namaes and added properly | 
|  | appendIndex++; | 
|  | let result = {__proto__:null} | 
|  | for (let key in json) { | 
|  | result[key+"_"+appendIndex] = json[key]; | 
|  | } | 
|  | return result | 
|  | } | 
|  |  | 
|  | function handleToggleGroup(event) { | 
|  | var group = event.target.parentNode.parentNode.entry; | 
|  | toggleGroup(selectedPage.get(group.name)); | 
|  | } | 
|  |  | 
|  | function handleSelectPage(select, event) { | 
|  | var option = select.options[select.selectedIndex]; | 
|  | if (select.id == "select_0") { | 
|  | showSelectedEntryInPage(option.page); | 
|  | } else { | 
|  | var columnIndex = select.id.split('_')[1]; | 
|  | showPageInColumn(option.page, columnIndex); | 
|  | } | 
|  | } | 
|  |  | 
|  | function handleSelectVersion(select, event) { | 
|  | var option = select.options[select.selectedIndex]; | 
|  | var version = option.version; | 
|  | if (select.id == "selectVersion_0") { | 
|  | var page = version.get(selectedPage.name); | 
|  | showSelectedEntryInPage(page); | 
|  | } else { | 
|  | var columnIndex = select.id.split('_')[1]; | 
|  | var pageSelect = $('select_' + columnIndex); | 
|  | var page = pageSelect.options[pageSelect.selectedIndex].page; | 
|  | page = version.get(page.name); | 
|  | showPageInColumn(page, columnIndex); | 
|  | } | 
|  | } | 
|  |  | 
|  | function handleSelectDetailRow(table, event) { | 
|  | if (event.target.tagName != 'TD') return; | 
|  | var tr = event.target.parentNode; | 
|  | if (tr.tagName != 'TR') return; | 
|  | if (tr.entry === undefined) return; | 
|  | selectEntry(tr.entry, true); | 
|  | } | 
|  |  | 
|  | function handleSelectRow(table, event, fromDetail) { | 
|  | if (event.target.tagName != 'TD') return; | 
|  | var tr = event.target.parentNode; | 
|  | if (tr.tagName != 'TR') return; | 
|  | if (tr.entry === undefined) return; | 
|  | selectEntry(tr.entry, false); | 
|  | } | 
|  |  | 
|  | function handleSelectBaseline(select, event) { | 
|  | var option = select.options[select.selectedIndex]; | 
|  | baselineVersion = option.version; | 
|  | var showingDiff = baselineVersion !== undefined; | 
|  | var body = $('body'); | 
|  | toggleCssClass(body, 'diff', showingDiff); | 
|  | toggleCssClass(body, 'noDiff', !showingDiff); | 
|  | showPage(selectedPage); | 
|  | if (selectedEntry === undefined) return; | 
|  | selectEntry(selectedEntry, true); | 
|  | } | 
|  |  | 
|  | function findEntry(event) { | 
|  | var target = event.target; | 
|  | while (target.entry === undefined) { | 
|  | target = target.parentNode; | 
|  | if (!target) return undefined; | 
|  | } | 
|  | return target.entry; | 
|  | } | 
|  |  | 
|  | function handleUpdatePopover(event) { | 
|  | var popover = $('popover'); | 
|  | popover.style.left = event.pageX + 'px'; | 
|  | popover.style.top = event.pageY + 'px'; | 
|  | popover.style.display = 'none'; | 
|  | popover.style.display = event.shiftKey ? 'block' : 'none'; | 
|  | var entry = findEntry(event); | 
|  | if (entry === undefined) return; | 
|  | showPopover(entry); | 
|  | } | 
|  |  | 
|  | function handleToggleVersionOrPageEnable(event) { | 
|  | var item = this.item ; | 
|  | if (item  === undefined) return; | 
|  | item .enabled = this.checked; | 
|  | initialize(); | 
|  | var page = selectedPage; | 
|  | if (page === undefined || !page.version.enabled) { | 
|  | page = versions.getEnabledPage(page.name); | 
|  | } | 
|  | if (!page.enabled) { | 
|  | page = page.getNextPage(); | 
|  | } | 
|  | showPage(page); | 
|  | } | 
|  |  | 
|  | function handleToggleContentVisibility(event) { | 
|  | var content = event.target.contentNode; | 
|  | toggleCssClass(content, 'hidden'); | 
|  | } | 
|  |  | 
|  | function handleCodeSearch(event) { | 
|  | var entry = findEntry(event); | 
|  | if (entry === undefined) return; | 
|  | var url = "https://cs.chromium.org/search/?sq=package:chromium&type=cs&q="; | 
|  | name = entry.name; | 
|  | if (name.startsWith("API_")) { | 
|  | name = name.substring(4); | 
|  | } | 
|  | url += encodeURIComponent(name) + "+file:src/v8/src"; | 
|  | window.open(url,'_blank'); | 
|  | } | 
|  | </script> | 
|  | <script type="text/javascript"> | 
|  | "use strict" | 
|  | // ========================================================================= | 
|  | class Versions { | 
|  | constructor() { | 
|  | this.versions = []; | 
|  | } | 
|  | add(version) { | 
|  | this.versions.push(version) | 
|  | } | 
|  | getPageVersions(page) { | 
|  | var result = []; | 
|  | this.versions.forEach((version) => { | 
|  | if (!version.enabled) return; | 
|  | var versionPage = version.get(page.name); | 
|  | if (versionPage  !== undefined) result.push(versionPage); | 
|  | }); | 
|  | return result; | 
|  | } | 
|  | get length() { | 
|  | return this.versions.length | 
|  | } | 
|  | get(index) { | 
|  | return this.versions[index] | 
|  | } | 
|  | getByName(name) { | 
|  | return this.versions.find((each) => each.name == name); | 
|  | } | 
|  | forEach(f) { | 
|  | this.versions.forEach(f); | 
|  | } | 
|  | sort() { | 
|  | this.versions.sort(NameComparator); | 
|  | } | 
|  | getEnabledPage(name) { | 
|  | for (var i = 0; i < this.versions.length; i++) { | 
|  | var version = this.versions[i]; | 
|  | if (!version.enabled) continue; | 
|  | var page = version.get(name); | 
|  | if (page !== undefined) return page; | 
|  | } | 
|  | } | 
|  | } | 
|  | Versions.fromJSON = function(json) { | 
|  | var versions = new Versions(); | 
|  | for (var version in json) { | 
|  | versions.add(Version.fromJSON(version, json[version])); | 
|  | } | 
|  | versions.sort(); | 
|  | return versions; | 
|  | } | 
|  |  | 
|  | class Version { | 
|  | constructor(name) { | 
|  | this.name = name; | 
|  | this.enabled = true; | 
|  | this.pages = []; | 
|  | } | 
|  | add(page) { | 
|  | this.pages.push(page); | 
|  | } | 
|  | indexOf(name) { | 
|  | for (var i = 0; i < this.pages.length; i++) { | 
|  | if (this.pages[i].name == name) return i; | 
|  | } | 
|  | return -1; | 
|  | } | 
|  | getNextPage(page) { | 
|  | if (this.length == 0) return undefined; | 
|  | return this.pages[(this.indexOf(page.name) + 1) % this.length]; | 
|  | } | 
|  | get(name) { | 
|  | var index = this.indexOf(name); | 
|  | if (0 <= index) return this.pages[index]; | 
|  | return undefined | 
|  | } | 
|  | get length() { | 
|  | return this.pages.length | 
|  | } | 
|  | getEntry(entry) { | 
|  | if (entry === undefined) return undefined; | 
|  | var page = this.get(entry.page.name); | 
|  | if (page === undefined) return undefined; | 
|  | return page.get(entry.name); | 
|  | } | 
|  | forEachEntry(fun) { | 
|  | this.forEachPage((page) => { | 
|  | page.forEach(fun); | 
|  | }); | 
|  | } | 
|  | forEachPage(fun) { | 
|  | this.pages.forEach((page) => { | 
|  | if (!page.enabled) return; | 
|  | fun(page); | 
|  | }) | 
|  | } | 
|  | allEntries() { | 
|  | var map = new Map(); | 
|  | this.forEachEntry((group, entry) => { | 
|  | if (!map.has(entry.name)) map.set(entry.name, entry); | 
|  | }); | 
|  | return Array.from(map.values()); | 
|  | } | 
|  | getTotalValue(name, property) { | 
|  | if (name === undefined) name = this.pages[0].total.name; | 
|  | var sum = 0; | 
|  | this.forEachPage((page) => { | 
|  | var entry = page.get(name); | 
|  | if (entry !== undefined) sum += entry[property]; | 
|  | }); | 
|  | return sum; | 
|  | } | 
|  | getTotalTime(name, showDiff) { | 
|  | return this.getTotalValue(name, showDiff === false ? '_time' : 'time'); | 
|  | } | 
|  | getTotalTimePercent(name, showDiff) { | 
|  | if (baselineVersion === undefined || showDiff === false) { | 
|  | // Return the overall average percent of the given entry name. | 
|  | return this.getTotalValue(name, 'time') / | 
|  | this.getTotalTime('Group-Total') * 100; | 
|  | } | 
|  | // Otherwise return the difference to the sum of the baseline version. | 
|  | var baselineValue = baselineVersion.getTotalTime(name, false); | 
|  | var total = this.getTotalValue(name, '_time'); | 
|  | return (total / baselineValue - 1)  * 100; | 
|  | } | 
|  | getTotalTimeVariance(name, showDiff) { | 
|  | // Calculate the overall error for a given entry name | 
|  | var sum = 0; | 
|  | this.forEachPage((page) => { | 
|  | var entry = page.get(name); | 
|  | if (entry === undefined) return; | 
|  | sum += entry.timeVariance * entry.timeVariance; | 
|  | }); | 
|  | return Math.sqrt(sum); | 
|  | } | 
|  | getTotalTimeVariancePercent(name, showDiff) { | 
|  | return this.getTotalTimeVariance(name, showDiff) / | 
|  | this.getTotalTime(name, showDiff) * 100; | 
|  | } | 
|  | getTotalCount(name, showDiff) { | 
|  | return this.getTotalValue(name, showDiff === false ? '_count' : 'count'); | 
|  | } | 
|  | getAverageTimeImpact(name, showDiff) { | 
|  | return this.getTotalTime(name, showDiff) / this.pages.length; | 
|  | } | 
|  | getPagesByPercentImpact(name) { | 
|  | var sortedPages = | 
|  | this.pages.filter((each) => { | 
|  | return each.get(name) !== undefined | 
|  | }); | 
|  | sortedPages.sort((a, b) => { | 
|  | return b.get(name).timePercent - a.get(name).timePercent; | 
|  | }); | 
|  | return sortedPages; | 
|  | } | 
|  | sort() { | 
|  | this.pages.sort(NameComparator) | 
|  | } | 
|  | } | 
|  | Version.fromJSON = function(name, data) { | 
|  | var version = new Version(name); | 
|  | for (var pageName in data) { | 
|  | version.add(PageVersion.fromJSON(version, pageName, data[pageName])); | 
|  | } | 
|  | version.sort(); | 
|  | return version; | 
|  | } | 
|  |  | 
|  | class Pages extends Map { | 
|  | get(name) { | 
|  | if (name.indexOf('www.') == 0) { | 
|  | name = name.substring(4); | 
|  | } | 
|  | if (!this.has(name)) { | 
|  | this.set(name, new Page(name)); | 
|  | } | 
|  | return super.get(name); | 
|  | } | 
|  | } | 
|  |  | 
|  | class Page { | 
|  | constructor(name) { | 
|  | this.name = name; | 
|  | this.enabled = true; | 
|  | this.versions = []; | 
|  | } | 
|  | add(page) { | 
|  | this.versions.push(page); | 
|  | } | 
|  | } | 
|  |  | 
|  | class PageVersion { | 
|  | constructor(version, page) { | 
|  | this.page = page; | 
|  | this.page.add(this); | 
|  | this.total = Group.groups.get('total').entry(); | 
|  | this.total.isTotal = true; | 
|  | this.unclassified = new UnclassifiedEntry(this) | 
|  | this.groups = [ | 
|  | this.total, | 
|  | Group.groups.get('ic').entry(), | 
|  | Group.groups.get('optimize').entry(), | 
|  | Group.groups.get('compile-background').entry(), | 
|  | Group.groups.get('compile').entry(), | 
|  | Group.groups.get('parse-background').entry(), | 
|  | Group.groups.get('parse').entry(), | 
|  | Group.groups.get('callback').entry(), | 
|  | Group.groups.get('api').entry(), | 
|  | Group.groups.get('gc').entry(), | 
|  | Group.groups.get('javascript').entry(), | 
|  | Group.groups.get('runtime').entry(), | 
|  | this.unclassified | 
|  | ]; | 
|  | this.entryDict = new Map(); | 
|  | this.groups.forEach((entry) => { | 
|  | entry.page = this; | 
|  | this.entryDict.set(entry.name, entry); | 
|  | }); | 
|  | this.version = version; | 
|  | } | 
|  | toString() { | 
|  | return this.version.name + ": " + this.name; | 
|  | } | 
|  | urlParams() { | 
|  | return { version: this.version.name, page: this.name}; | 
|  | } | 
|  | add(entry) { | 
|  | // Ignore accidentally added Group entries. | 
|  | if (entry.name.startsWith(GroupedEntry.prefix)) return; | 
|  | entry.page = this; | 
|  | this.entryDict.set(entry.name, entry); | 
|  | var added = false; | 
|  | this.groups.forEach((group) => { | 
|  | if (!added) added = group.add(entry); | 
|  | }); | 
|  | if (added) return; | 
|  | this.unclassified.push(entry); | 
|  | } | 
|  | get(name) { | 
|  | return this.entryDict.get(name) | 
|  | } | 
|  | getEntry(entry) { | 
|  | if (entry === undefined) return undefined; | 
|  | return this.get(entry.name); | 
|  | } | 
|  | get length() { | 
|  | return this.versions.length | 
|  | } | 
|  | get name() { return this.page.name } | 
|  | get enabled() { return this.page.enabled } | 
|  | forEachSorted(referencePage, func) { | 
|  | // Iterate over all the entries in the order they appear on the | 
|  | // reference page. | 
|  | referencePage.forEach((parent, referenceEntry) => { | 
|  | var entry; | 
|  | if (parent) parent = this.entryDict.get(parent.name); | 
|  | if (referenceEntry) entry = this.entryDict.get(referenceEntry.name); | 
|  | func(parent, entry, referenceEntry); | 
|  | }); | 
|  | } | 
|  | forEach(fun) { | 
|  | this.forEachGroup((group) => { | 
|  | fun(undefined, group); | 
|  | group.forEach((entry) => { | 
|  | fun(group, entry) | 
|  | }); | 
|  | }); | 
|  | } | 
|  | forEachGroup(fun) { | 
|  | this.groups.forEach(fun) | 
|  | } | 
|  | sort() { | 
|  | this.groups.sort((a, b) => { | 
|  | return b.time - a.time; | 
|  | }); | 
|  | this.groups.forEach((group) => { | 
|  | group.sort() | 
|  | }); | 
|  | } | 
|  | distanceFromTotalPercent() { | 
|  | var sum = 0; | 
|  | this.groups.forEach(group => { | 
|  | if (group == this.total) return; | 
|  | var value = group.getTimePercentImpact() - | 
|  | this.getEntry(group).timePercent; | 
|  | sum += value * value; | 
|  | }); | 
|  | return sum; | 
|  | } | 
|  | getNextPage() { | 
|  | return this.version.getNextPage(this); | 
|  | } | 
|  | } | 
|  | PageVersion.fromJSON = function(version, name, data) { | 
|  | var page = new PageVersion(version, pages.get(name)); | 
|  | for (var i = 0; i < data.length; i++) { | 
|  | page.add(Entry.fromJSON(i, data[data.length - i - 1])); | 
|  | } | 
|  | page.sort(); | 
|  | return page | 
|  | } | 
|  |  | 
|  |  | 
|  | class Entry { | 
|  | constructor(position, name, time, timeVariance, timeVariancePercent, | 
|  | count, | 
|  | countVariance, countVariancePercent) { | 
|  | this.position = position; | 
|  | this.name = name; | 
|  | this._time = time; | 
|  | this._timeVariance = timeVariance; | 
|  | this._timeVariancePercent = timeVariancePercent; | 
|  | this._count = count; | 
|  | this.countVariance = countVariance; | 
|  | this.countVariancePercent = countVariancePercent; | 
|  | this.page = undefined; | 
|  | this.parent = undefined; | 
|  | this.isTotal = false; | 
|  | } | 
|  | urlParams() { | 
|  | var params = this.page.urlParams(); | 
|  | params.entry = this.name; | 
|  | return params; | 
|  | } | 
|  | getCompareWithBaseline(value, property) { | 
|  | if (baselineVersion == undefined) return value; | 
|  | var baselineEntry = baselineVersion.getEntry(this); | 
|  | if (!baselineEntry) return value; | 
|  | if (baselineVersion === this.page.version) return value; | 
|  | return value - baselineEntry[property]; | 
|  | } | 
|  | cssClass() { | 
|  | return '' | 
|  | } | 
|  | get time() { | 
|  | return this.getCompareWithBaseline(this._time, '_time'); | 
|  | } | 
|  | get count() { | 
|  | return this.getCompareWithBaseline(this._count, '_count'); | 
|  | } | 
|  | get timePercent() { | 
|  | var value = this._time / this.page.total._time * 100; | 
|  | if (baselineVersion == undefined) return value; | 
|  | var baselineEntry = baselineVersion.getEntry(this); | 
|  | if (!baselineEntry) return value; | 
|  | if (baselineVersion === this.page.version) return value; | 
|  | return (this._time - baselineEntry._time) / this.page.total._time * | 
|  | 100; | 
|  | } | 
|  | get timePercentPerEntry() { | 
|  | var value = this._time / this.page.total._time * 100; | 
|  | if (baselineVersion == undefined) return value; | 
|  | var baselineEntry = baselineVersion.getEntry(this); | 
|  | if (!baselineEntry) return value; | 
|  | if (baselineVersion === this.page.version) return value; | 
|  | return (this._time / baselineEntry._time - 1) * 100; | 
|  | } | 
|  | get timePercentVariancePercent() { | 
|  | // Get the absolute values for the percentages | 
|  | return this.timeVariance / this.page.total._time * 100; | 
|  | } | 
|  | getTimeImpact(showDiff) { | 
|  | return this.page.version.getTotalTime(this.name, showDiff); | 
|  | } | 
|  | getTimeImpactVariancePercent(showDiff) { | 
|  | return this.page.version.getTotalTimeVariancePercent(this.name, showDiff); | 
|  | } | 
|  | getTimePercentImpact(showDiff) { | 
|  | return this.page.version.getTotalTimePercent(this.name, showDiff); | 
|  | } | 
|  | getCountImpact(showDiff) { | 
|  | return this.page.version.getTotalCount(this.name, showDiff); | 
|  | } | 
|  | getAverageTimeImpact(showDiff) { | 
|  | return this.page.version.getAverageTimeImpact(this.name, showDiff); | 
|  | } | 
|  | getPagesByPercentImpact() { | 
|  | return this.page.version.getPagesByPercentImpact(this.name); | 
|  | } | 
|  | get isGroup() { | 
|  | return false | 
|  | } | 
|  | get timeVariance() { | 
|  | return this._timeVariance | 
|  | } | 
|  | get timeVariancePercent() { | 
|  | return this._timeVariancePercent | 
|  | } | 
|  | } | 
|  | Entry.fromJSON = function(position, data) { | 
|  | return new Entry(position, ...data); | 
|  | } | 
|  |  | 
|  | class Group { | 
|  | constructor(name, regexp, color) { | 
|  | this.name = name; | 
|  | this.regexp = regexp; | 
|  | this.color = color; | 
|  | this.enabled = true; | 
|  | } | 
|  | entry() { return new GroupedEntry(this) }; | 
|  | } | 
|  | Group.groups = new Map(); | 
|  | Group.add = function(name, group) { | 
|  | this.groups.set(name, group); | 
|  | return group; | 
|  | } | 
|  | Group.add('total', new Group('Total', /.*Total.*/, '#BBB')); | 
|  | Group.add('ic', new Group('IC', /.*IC_.*/, "#3366CC")); | 
|  | Group.add('optimize', new Group('Optimize', | 
|  | /StackGuard|.*Optimize.*|.*Deoptimize.*|Recompile.*/, "#DC3912")); | 
|  | Group.add('compile-background', new Group('Compile-Background', | 
|  | /(.*CompileBackground.*)/, "#b9a720")); | 
|  | Group.add('compile', new Group('Compile', | 
|  | /(^Compile.*)|(.*_Compile.*)/, "#FFAA00")); | 
|  | Group.add('parse-background', | 
|  | new Group('Parse-Background', /.*ParseBackground.*/, "#af744d")); | 
|  | Group.add('parse', new Group('Parse', /.*Parse.*/, "#FF6600")); | 
|  | Group.add('callback', new Group('Blink C++', /.*Callback.*/, "#109618")); | 
|  | Group.add('api', new Group('API', /.*API.*/, "#990099")); | 
|  | Group.add('gc-custom', new Group('GC-Custom', /GC_Custom_.*/, "#0099C6")); | 
|  | Group.add('gc', new Group('GC', /GC_.*|AllocateInTargetSpace/, "#00799c")); | 
|  | Group.add('javascript', new Group('JavaScript', /JS_Execution/, "#DD4477")); | 
|  | Group.add('runtime', new Group('V8 C++', /.*/, "#88BB00")); | 
|  | var group = | 
|  | Group.add('unclassified', new Group('Unclassified', /.*/, "#000")); | 
|  | group.enabled = false; | 
|  |  | 
|  | class GroupedEntry extends Entry { | 
|  | constructor(group) { | 
|  | super(0, GroupedEntry.prefix + group.name, 0, 0, 0, 0, 0, 0); | 
|  | this.group = group; | 
|  | this.entries = []; | 
|  | this.missingEntries = null; | 
|  | } | 
|  | get regexp() { return this.group.regexp } | 
|  | get color() { return this.group.color } | 
|  | get enabled() { return this.group.enabled } | 
|  | add(entry) { | 
|  | if (!this.regexp.test(entry.name)) return false; | 
|  | this._time += entry.time; | 
|  | this._count += entry.count; | 
|  | // TODO: sum up variance | 
|  | this.entries.push(entry); | 
|  | entry.parent = this; | 
|  | return true; | 
|  | } | 
|  | _initializeMissingEntries() { | 
|  | var dummyEntryNames = new Set(); | 
|  | versions.forEach((version) => { | 
|  | var groupEntry = version.getEntry(this); | 
|  | if (groupEntry != this) { | 
|  | for (var entry of groupEntry.entries) { | 
|  | if (this.page.get(entry.name) == undefined) { | 
|  | dummyEntryNames.add(entry.name); | 
|  | } | 
|  | } | 
|  | } | 
|  | }); | 
|  | this.missingEntries  = []; | 
|  | for (var name of dummyEntryNames) { | 
|  | var tmpEntry = new Entry(0, name, 0, 0, 0, 0, 0, 0); | 
|  | tmpEntry.page = this.page; | 
|  | this.missingEntries.push(tmpEntry); | 
|  | }; | 
|  | } | 
|  |  | 
|  | forEach(fun) { | 
|  | // Show also all entries which are in at least one version. | 
|  | // Concatenate our real entries. | 
|  | if (this.missingEntries == null) { | 
|  | this._initializeMissingEntries(); | 
|  | } | 
|  | var tmpEntries = this.missingEntries.concat(this.entries); | 
|  |  | 
|  | // The compared entries are sorted by absolute impact. | 
|  | tmpEntries.sort((a, b) => { | 
|  | return b.time - a.time | 
|  | }); | 
|  | tmpEntries.forEach(fun); | 
|  | } | 
|  | sort() { | 
|  | this.entries.sort((a, b) => { | 
|  | return b.time - a.time; | 
|  | }); | 
|  | } | 
|  | cssClass() { | 
|  | if (this.page.total == this) return 'total'; | 
|  | return ''; | 
|  | } | 
|  | get isGroup() { | 
|  | return true | 
|  | } | 
|  | getVarianceForProperty(property) { | 
|  | var sum = 0; | 
|  | this.entries.forEach((entry) => { | 
|  | sum += entry[property + 'Variance'] * entry[property + | 
|  | 'Variance']; | 
|  | }); | 
|  | return Math.sqrt(sum); | 
|  | } | 
|  | get timeVariancePercent() { | 
|  | if (this._time == 0) return 0; | 
|  | return this.getVarianceForProperty('time')  / this._time * 100 | 
|  | } | 
|  | get timeVariance() { | 
|  | return this.getVarianceForProperty('time') | 
|  | } | 
|  | } | 
|  | GroupedEntry.prefix = 'Group-'; | 
|  |  | 
|  | class UnclassifiedEntry extends GroupedEntry { | 
|  | constructor(page) { | 
|  | super(Group.groups.get('unclassified')); | 
|  | this.page = page; | 
|  | this._time = undefined; | 
|  | this._count = undefined; | 
|  | } | 
|  | add(entry) { | 
|  | this.entries.push(entry); | 
|  | entry.parent = this; | 
|  | return true; | 
|  | } | 
|  | forEachPageGroup(fun) { | 
|  | this.page.forEachGroup((group) => { | 
|  | if (group == this) return; | 
|  | if (group == this.page.total) return; | 
|  | fun(group); | 
|  | }); | 
|  | } | 
|  | get time() { | 
|  | if (this._time === undefined) { | 
|  | this._time = this.page.total._time; | 
|  | this.forEachPageGroup((group) => { | 
|  | this._time -= group._time; | 
|  | }); | 
|  | } | 
|  | return this.getCompareWithBaseline(this._time, '_time'); | 
|  | } | 
|  | get count() { | 
|  | if (this._count === undefined) { | 
|  | this._count = this.page.total._count; | 
|  | this.forEachPageGroup((group) => { | 
|  | this._count -= group._count; | 
|  | }); | 
|  | } | 
|  | return this.getCompareWithBaseline(this._count, '_count'); | 
|  | } | 
|  | } | 
|  | </script> | 
|  | </head> | 
|  |  | 
|  | <body id="body" onmousemove="handleUpdatePopover(event)" onload="handleBodyLoad()" class="noDiff"> | 
|  | <h1>Runtime Stats Komparator</h1> | 
|  |  | 
|  | <div id="results"> | 
|  | <div class="inline"> | 
|  | <h2>Data</h2> | 
|  | <form name="fileForm"> | 
|  | <p> | 
|  | <label for="uploadInput">Load File:</label> | 
|  | <input id="uploadInput" type="file" name="files" onchange="handleLoadFile();" accept=".json"> | 
|  | </p> | 
|  | <p> | 
|  | <label for="appendInput">Append File:</label> | 
|  | <input id="appendInput" type="file" name="files" onchange="handleAppendFile();" accept=".json"> | 
|  | </p> | 
|  | </form> | 
|  | </div> | 
|  |  | 
|  | <div class="inline hidden"> | 
|  | <h2>Result</h2> | 
|  | <div class="compareSelector inline"> | 
|  | Compare against: <select id="baseline" onchange="handleSelectBaseline(this, event)"></select><br/> | 
|  | <span style="color: #060">Green</span> the selected version above performs | 
|  | better on this measurement. | 
|  | </div> | 
|  | </div> | 
|  |  | 
|  | <div id="versionSelector" class="inline toggleContentVisibility"> | 
|  | <h2>Versions</h2> | 
|  | <div class="content hidden"> | 
|  | <ul></ul> | 
|  | </div> | 
|  | </div> | 
|  |  | 
|  | <div id="pageSelector" class="inline toggleContentVisibility"> | 
|  | <h2>Pages</h2> | 
|  | <div class="content hidden"> | 
|  | <ul></ul> | 
|  | </div> | 
|  | </div> | 
|  |  | 
|  | <div id="groupSelector" class="inline toggleContentVisibility"> | 
|  | <h2>Groups</h2> | 
|  | <div class="content hidden"> | 
|  | <ul></ul> | 
|  | </div> | 
|  | </div> | 
|  |  | 
|  | <div id="view"> | 
|  | </div> | 
|  |  | 
|  | <div id="detailView" class="hidden"> | 
|  | <div class="versionDetail inline toggleContentVisibility"> | 
|  | <h3><span></span></h3> | 
|  | <div class="content"> | 
|  | <table class="versionDetailTable" onclick="handleSelectDetailRow(this, event);"> | 
|  | <thead> | 
|  | <tr> | 
|  | <th class="version">Version </th> | 
|  | <th class="position">Pos. </th> | 
|  | <th class="value time">Time▴ </th> | 
|  | <th class="value time">Percent </th> | 
|  | <th class="value count">Count </th> | 
|  | </tr> | 
|  | </thead> | 
|  | <tbody></tbody> | 
|  | </table> | 
|  | </div> | 
|  | </div> | 
|  | <div class="pageDetail inline toggleContentVisibility"> | 
|  | <h3>Page Comparison for <span></span></h3> | 
|  | <div class="content"> | 
|  | <table class="pageDetailTable" onclick="handleSelectDetailRow(this, event);"> | 
|  | <thead> | 
|  | <tr> | 
|  | <th class="page">Page </th> | 
|  | <th class="value time">Time </th> | 
|  | <th class="value time">Percent▾ </th> | 
|  | <th class="value time hideNoDiff">%/Entry </th> | 
|  | <th class="value count">Count </th> | 
|  | </tr> | 
|  | </thead> | 
|  | <tfoot> | 
|  | <tr> | 
|  | <td class="page">Total:</td> | 
|  | <td class="value time"></td> | 
|  | <td class="value time"></td> | 
|  | <td class="value time hideNoDiff"></td> | 
|  | <td class="value count"></td> | 
|  | </tr> | 
|  | </tfoot> | 
|  | <tbody></tbody> | 
|  | </table> | 
|  | </div> | 
|  | </div> | 
|  | <div class="impactView inline toggleContentVisibility"> | 
|  | <h3>Impact list for <span></span></h3> | 
|  | <div class="content"> | 
|  | <table class="pageDetailTable" onclick="handleSelectDetailRow(this, event);"> | 
|  | <thead> | 
|  | <tr> | 
|  | <th class="page">Name </th> | 
|  | <th class="value time">Time </th> | 
|  | <th class="value time">Percent▾ </th> | 
|  | <th class="">Top Pages</th> | 
|  | </tr> | 
|  | </thead> | 
|  | <tbody></tbody> | 
|  | </table> | 
|  | </div> | 
|  | </div> | 
|  | </div> | 
|  | <div id="pageVersionGraph" class="graph hidden toggleContentVisibility"> | 
|  | <h3><span></span></h3> | 
|  | <div class="content"></div> | 
|  | </div> | 
|  | <div id="pageGraph" class="graph hidden toggleContentVisibility"> | 
|  | <h3><span></span></h3> | 
|  | <div class="content"></div> | 
|  | </div> | 
|  | <div id="versionGraph" class="graph hidden toggleContentVisibility"> | 
|  | <h3><span></span></h3> | 
|  | <div class="content"></div> | 
|  | </div> | 
|  |  | 
|  | <div id="column" class="column"> | 
|  | <div class="header"> | 
|  | <select class="version" onchange="handleSelectVersion(this, event);"></select> | 
|  | <select class="pageVersion" onchange="handleSelectPage(this, event);"></select> | 
|  | </div> | 
|  | <table class="list" onclick="handleSelectRow(this, event);"> | 
|  | <thead> | 
|  | <tr> | 
|  | <th class="position">Pos. </th> | 
|  | <th class="name">Name </th> | 
|  | <th class="value time">Time </th> | 
|  | <th class="value time">Percent </th> | 
|  | <th class="value count">Count </th> | 
|  | </tr> | 
|  | </thead> | 
|  | <tbody></tbody> | 
|  | </table> | 
|  | </div> | 
|  | </div> | 
|  |  | 
|  | <div class="inline"> | 
|  | <h2>Usage</h2> | 
|  | <ol> | 
|  | <li>Install scipy, e.g. <code>sudo aptitude install python-scipy</code> | 
|  | <li>Build chrome.</li> | 
|  | <li>Check out a known working version of webpagereply: | 
|  | <pre>git -C $CHROME_DIR/third_party/webpagereplay checkout 7dbd94752d1cde5536ffc623a9e10a51721eff1d</pre> | 
|  | </li> | 
|  | <li>Run <code>callstats.py</code> with a web-page-replay archive: | 
|  | <pre>$V8_DIR/tools/callstats.py run \ | 
|  | --replay-bin=$CHROME_SRC/third_party/webpagereplay/replay.py \ | 
|  | --replay-wpr=$INPUT_DIR/top25.wpr \ | 
|  | --js-flags="" \ | 
|  | --with-chrome=$CHROME_SRC/out/Release/chrome \ | 
|  | --sites-file=$INPUT_DIR/top25.json</pre> | 
|  | </li> | 
|  | <li>Move results file to a subdirectory: <code>mkdir $VERSION_DIR; mv *.txt $VERSION_DIR</code></li> | 
|  | <li>Repeat from step 1 with a different configuration (e.g. <code>--js-flags="--nolazy"</code>).</li> | 
|  | <li>Create the final results file: <code>./callstats.py json $VERSION_DIR1 $VERSION_DIR2 > result.json</code></li> | 
|  | <li>Use <code>results.json</code> on this site.</code> | 
|  | </ol> | 
|  | </div> | 
|  |  | 
|  | <div id="popover"> | 
|  | <div class="popoverArrow"></div> | 
|  | <table> | 
|  | <tr> | 
|  | <td class="name" colspan="6"></td> | 
|  | </tr> | 
|  | <tr> | 
|  | <td>Page:</td> | 
|  | <td class="page name" colspan="6"></td> | 
|  | </tr> | 
|  | <tr> | 
|  | <td>Version:</td> | 
|  | <td class="version name" colspan="3"></td> | 
|  | <td class="compare version name" colspan="3"></td> | 
|  | </tr> | 
|  | <tr> | 
|  | <td>Time:</td> | 
|  | <td class="time"></td><td>±</td><td class="timeVariance"></td> | 
|  | <td class="compare time"></td><td class="compare"> ± </td><td class="compare timeVariance"></td> | 
|  | </tr> | 
|  | <tr> | 
|  | <td>Percent:</td> | 
|  | <td class="percent"></td><td>±</td><td class="percentVariance"></td> | 
|  | <td class="compare percent"></td><td class="compare"> ± </td><td class="compare percentVariance"></td> | 
|  | </tr> | 
|  | <tr> | 
|  | <td>Percent per Entry:</td> | 
|  | <td class="percentPerEntry"></td><td colspan=2></td> | 
|  | <td class="compare percentPerEntry"></td><td colspan=2></td> | 
|  | </tr> | 
|  | <tr> | 
|  | <td>Count:</td> | 
|  | <td class="count"></td><td>±</td><td class="countVariance"></td> | 
|  | <td class="compare count"></td><td class="compare"> ± </td><td class="compare countVariance"></td> | 
|  | </tr> | 
|  | <tr> | 
|  | <td>Overall Impact:</td> | 
|  | <td class="timeImpact"></td><td>±</td><td class="timePercentImpact"></td> | 
|  | <td class="compare timeImpact"></td><td class="compare"> ± </td><td class="compare timePercentImpact"></td> | 
|  | </tr> | 
|  | </table> | 
|  | </div> | 
|  | </body> | 
|  | </html> |