diff options
Diffstat (limited to 'deps/v8/tools/callstats.html')
-rw-r--r-- | deps/v8/tools/callstats.html | 1809 |
1 files changed, 1809 insertions, 0 deletions
diff --git a/deps/v8/tools/callstats.html b/deps/v8/tools/callstats.html new file mode 100644 index 0000000000..76cc8c686d --- /dev/null +++ b/deps/v8/tools/callstats.html @@ -0,0 +1,1809 @@ +<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; + + function initialize() { + 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')); + 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', handleToggleVersionEnable); + 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]); + }); + } + + 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)); + } else { + showImpactList(selectedPage); + } + } + + 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) => { + // Filter out entries that do not exist in the first column for the default + // view. + if (baselineVersion === undefined && referenceEntry && + referenceEntry.time == 0) { + return; + } + 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, '-', '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 selectEntry(entry, updateSelectedPage) { + if (updateSelectedPage) { + entry = selectedPage.version.getEntry(entry); + } + var rowIndex = 0; + var needsPageSwitch = updateSelectedPage && entry.page != selectedPage; + // 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.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) { + entry = selectedEntry.page.version.getEntry(entry); + } + selectedEntry = entry; + showEntryDetail(entry); + } + + function showEntryDetail(entry) { + showVersionDetails(entry); + showPageDetails(entry); + showImpactList(entry.page); + showGraphs(entry.page); + } + + function showVersionDetails(entry) { + var table, tbody, entries; + table = $('detailView').querySelector('.versionDetailTable'); + tbody = document.createElement('tbody'); + if (entry !== undefined) { + $('detailView').querySelector('.versionDetail h3 span').innerHTML = + 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').innerHTML = + 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].innerHTML = ms(entry.getTimeImpact(), showDiff); + // Only show the percentage total if we are in diff mode: + tds[2].innerHTML = percent(entry.getTimePercentImpact(), showDiff); + tds[3].innerHTML = ''; + tds[4].innerHTML = count(entry.getCountImpact(), showDiff); + table.replaceChild(tbody, table.querySelector('tbody')); + } + + function showImpactList(page) { + var impactView = $('detailView').querySelector('.impactView'); + impactView.querySelector('h3 span').innerHTML = 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').innerHTML += " " + 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.1; + }); + 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.slice(); + // 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, + }, + animation:{ + duration: 500, + easing: 'out', + }, + vAxis: { + }, + explorer: { + actions: ['dragToZoom', 'rightClickToReset'], + maxZoomIn: 0.01 + }, + legend: {position:'top', textStyle:{fontSize: '16px'}}, + chartArea: {left:200, top:50, width:'98%', height:'80%'}, + colors: groups.map(each => each.color) + }; + var parentNode = $(id); + parentNode.querySelector('h2>span, h3>span').innerHTML = 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').innerHTML = entry.name; + popover.querySelector('td.page').innerHTML = 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').innerHTML = baselineVersion.name; + node('.time').innerHTML = '-'; + node('.timeVariance').innerHTML = '-'; + node('.percent').innerHTML = '-'; + node('.percentPerEntry').innerHTML = '-'; + node('.percentVariance').innerHTML = '-'; + node('.count').innerHTML = '-'; + node('.countVariance').innerHTML = '-'; + node('.timeImpact').innerHTML = '-'; + node('.timePercentImpact').innerHTML = '-'; + } else { + node('.version').innerHTML = entry.page.version.name; + node('.time').innerHTML = ms(entry._time, false); + node('.timeVariance').innerHTML + = percent(entry.timeVariancePercent, false); + node('.percent').innerHTML = percent(entry.timePercent, false); + node('.percentPerEntry').innerHTML + = percent(entry.timePercentPerEntry, false); + node('.percentVariance').innerHTML + = percent(entry.timePercentVariancePercent, false); + node('.count').innerHTML = count(entry._count, false); + node('.countVariance').innerHTML + = percent(entry.timeVariancePercent, false); + node('.timeImpact').innerHTML + = ms(entry.getTimeImpact(false), false); + node('.timePercentImpact').innerHTML + = 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.innerHTML = '?' + button.className = "codeSearch" + button.addEventListener('click', handleCodeSearch); + node.appendChild(button); + return node; + } + + function td(tr, content, className) { + var td = document.createElement("td"); + td.innerHTML = 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(); + } + + function handleLoadFile() { + var files = document.getElementById("uploadInput").files; + var file = files[0]; + var reader = new FileReader(); + + reader.onload = function(evt) { + pages = new Pages(); + versions = Versions.fromJSON(JSON.parse(this.result)); + initialize() + showPage(versions.versions[0].pages[0]); + } + reader.readAsText(file); + } + + 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") { + showPage(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); + showPage(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 handleToggleVersionEnable(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); + } + 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] + }; + 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; + } + get(name) { + var index = this.indexOf(name); + if (0 <= index) return this.pages[index]; + return undefined + } + get length() { + return this.versions.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 = new GroupedEntry('Total', /.*Total.*/, '#BBB'); + this.total.isTotal = true; + this.unclassified = new UnclassifiedEntry(this, "#000") + this.groups = [ + this.total, + new GroupedEntry('IC', /.*IC.*/, "#3366CC"), + new GroupedEntry('Optimize', + /StackGuard|.*Optimize.*|.*Deoptimize.*|Recompile.*/, "#DC3912"), + new GroupedEntry('Compile', /.*Compile.*/, "#FFAA00"), + new GroupedEntry('Parse', /.*Parse.*/, "#FF6600"), + new GroupedEntry('Callback', /.*Callback$/, "#109618"), + new GroupedEntry('API', /.*API.*/, "#990099"), + new GroupedEntry('GC', /GC|AllocateInTargetSpace/, "#0099C6"), + new GroupedEntry('JavaScript', /JS_Execution/, "#DD4477"), + new GroupedEntry('Runtime', /.*/, "#88BB00"), + this.unclassified + ]; + this.entryDict = new Map(); + this.groups.forEach((entry) => { + entry.page = this; + this.entryDict.set(entry.name, entry); + }); + this.version = version; + } + add(entry) { + 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; + } + } + 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; + } + 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 GroupedEntry extends Entry { + constructor(name, regexp, color) { + super(0, 'Group-' + name, 0, 0, 0, 0, 0, 0); + this.regexp = regexp; + this.color = color; + this.entries = []; + } + add(entry) { + if (!entry.name.match(this.regexp)) return false; + this._time += entry.time; + this._count += entry.count; + // TODO: sum up variance + this.entries.push(entry); + entry.parent = this; + return true; + } + forEach(fun) { + if (baselineVersion === undefined) { + this.entries.forEach(fun); + return; + } + // If we have a baslineVersion to compare against show also all entries + // from the other group. + var tmpEntries = baselineVersion.getEntry(this) + .entries.filter((entry) => { + return this.page.get(entry.name) == undefined + }); + + // The compared entries are sorted by absolute impact. + tmpEntries = tmpEntries.map((entry) => { + var tmpEntry = new Entry(0, entry.name, 0, 0, 0, 0, 0, 0); + tmpEntry.page = this.page; + return tmpEntry; + }); + tmpEntries = tmpEntries.concat(this.entries); + tmpEntries.sort((a, b) => { + return a.time - b.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') + } + } + + class UnclassifiedEntry extends GroupedEntry { + constructor(page, color) { + super('Unclassified', undefined, color); + 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> + <input id="uploadInput" type="file" name="files" onchange="handleLoadFile();" 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>Version Selector</h2> + <div class="content hidden"> + <ul></ul> + </div> + </div> + + <div id="pageSelector" class="inline toggleContentVisibility"> + <h2>Page Selector</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> |