| Index: tools/callstats.html
|
| diff --git a/tools/callstats.html b/tools/callstats.html
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..81d0459ad7e66539733dc7f24042ce5baee62a6c
|
| --- /dev/null
|
| +++ b/tools/callstats.html
|
| @@ -0,0 +1,1316 @@
|
| +<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;
|
| + }
|
| +
|
| + .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
|
| + }
|
| +
|
| + #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;
|
| + }
|
| + </style>
|
| + <script>
|
| + "use strict"
|
| +
|
| + // 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 selectedPage;
|
| + var baselineVersion;
|
| + var selectedEntry;
|
| +
|
| + function initialize() {
|
| + var original = $("column");
|
| + for (var i = 0; i < versions.length || i < 2; i++) {
|
| + // 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) => {
|
| + 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 select options
|
| + versions.forEach((version) => {
|
| + var optgroup = document.createElement("optgroup");
|
| + optgroup.label = version.name;
|
| + optgroup.version = version;
|
| + version.pages.forEach((page) => {
|
| + var option = document.createElement("option");
|
| + option.textContent = page.name;
|
| + option.page = page;
|
| + optgroup.appendChild(option);
|
| + });
|
| + select.appendChild(optgroup);
|
| + });
|
| + $("view").appendChild(column);
|
| + }
|
| + 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);
|
| + });
|
| + $('results').querySelectorAll('#results > .hidden').forEach((node) => {
|
| + toggleCssClass(node, 'hidden', false);
|
| + });
|
| + }
|
| +
|
| + function showPage(firstPage) {
|
| + selectedPage = firstPage;
|
| + selectedPage.sort();
|
| + showPageInColumn(firstPage, 0);
|
| + // Show the other versions of this page in the following columns.
|
| + var pageVersions = versions.pageVersions(firstPage.name);
|
| + var index = 1;
|
| + pageVersions.forEach((page) => {
|
| + if (page !== firstPage) {
|
| + showPageInColumn(page, index);
|
| + index++;
|
| + }
|
| + });
|
| + 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;
|
| + if (!parentEntry) {
|
| + tr.className = 'parent'
|
| + } else {
|
| + tr.className = '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');
|
| + }
|
| + 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 = referenceEntry;
|
| + 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
|
| + if (entry !== undefined) {
|
| + 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');
|
| + } 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;
|
| + 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) {
|
| + var table, tbody, entries;
|
| + table = $('detailView').querySelector('.versionDetailTable');
|
| + tbody = document.createElement('tbody');
|
| + if (entry !== undefined) {
|
| + $('detailView').querySelector('.versionDetail h3 span').innerHTML
|
| + = entry.name;
|
| + entries = versions.pageVersions(entry.page.name).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;
|
| + td(tr, pageEntry.page.version.name, 'version');
|
| + td(tr, pageEntry.position, 'value position');
|
| + td(tr, ms(pageEntry.time), 'value time');
|
| + td(tr, percent(pageEntry.timePercent), 'value time');
|
| + td(tr, count(pageEntry.count), 'value count');
|
| + tbody.appendChild(tr);
|
| + });
|
| + }
|
| + table.replaceChild(tbody, table.querySelector('tbody'));
|
| +
|
| + table = $('detailView').querySelector('.pageDetailTable');
|
| + tbody = document.createElement('tbody');
|
| + if (entry !== undefined) {
|
| + var version = entry.page.version;
|
| + $('detailView').querySelector('.pageDetail h3 span').innerHTML
|
| + = version.name;
|
| + entries = version.pages.map(
|
| + (page) => {
|
| + return page.get(entry.name)
|
| + });
|
| + entries.sort((a, b) => {
|
| + return b.timePercent - a.timePercent
|
| + });
|
| + 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, pageEntry.position, 'value position');
|
| + td(tr, ms(pageEntry.time), 'value time');
|
| + td(tr, percent(pageEntry.timePercent), 'value time');
|
| + td(tr, count(pageEntry.count), 'value count');
|
| + tbody.appendChild(tr);
|
| + });
|
| + // show the total for all pages
|
| + var tds = table.querySelectorAll('tfoot td');
|
| + tds[2].innerHTML = ms(entry.getTimeImpact());
|
| + // Only show the percentage total if we are in diff mode:
|
| + if (baselineVersion !== undefined) {
|
| + tds[3].innerHTML = percent(entry.getTimePercentImpact());
|
| + } else {
|
| + tds[3].innerHTML = ''
|
| + }
|
| + tds[4].innerHTML = count(entry.getCountImpact());
|
| + }
|
| + table.replaceChild(tbody, table.querySelector('tbody'));
|
| + showImpactList(entry.page);
|
| + }
|
| +
|
| + 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)
|
| + });
|
| + }
|
| + entries.sort((a, b) => {
|
| + return b.getTimePercentImpact() - a.getTimePercentImpact();
|
| + });
|
| + entries.forEach((entry) => {
|
| + var tr = document.createElement('tr');
|
| + tr.entry = entry;
|
| + td(tr, entry.name, 'name');
|
| + td(tr, ms(entry.getTimeImpact()), 'value time');
|
| + td(tr, percent(entry.getTimePercentImpact()), 'value time');
|
| + var topPages = entry.getPagesByPercentImpact().slice(0, 2)
|
| + .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 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);
|
| + if (entry === undefined) return;
|
| + setPopoverDetail(popover, entry, '.compare');
|
| + popover.querySelector('table').className = "compare";
|
| + }
|
| + }
|
| +
|
| + function setPopoverDetail(popover, entry, prefix) {
|
| + popover.querySelector(prefix + '.version').innerHTML
|
| + = entry.page.version.name;
|
| + popover.querySelector(prefix + '.time').innerHTML
|
| + = ms(entry._time, false);
|
| + popover.querySelector(prefix + '.timeVariance').innerHTML
|
| + = percent(entry.timeVariancePercent, false);
|
| + popover.querySelector(prefix + '.percent').innerHTML
|
| + = percent(entry.timePercent, false);
|
| + popover.querySelector(prefix + '.percentVariance').innerHTML
|
| + = percent(entry.timePercentVariancePercent, false);
|
| + popover.querySelector(prefix + '.count').innerHTML
|
| + = count(entry._count, false);
|
| + popover.querySelector(prefix + '.countVariance').innerHTML
|
| + = percent(entry.timeVariancePercent, false);
|
| + popover.querySelector(prefix + '.timeImpact').innerHTML
|
| + = ms(entry.getTimeImpact(false), false);
|
| + popover.querySelector(prefix + '.timePercentImpact').innerHTML
|
| + = percent(entry.getTimeImpactVariancePercent(false), false);
|
| + }
|
| +
|
| + // ========================================================================
|
| + // 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 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 diffSign(value, showDiff) {
|
| + if (value <= 0 || showDiff === false) return '';
|
| + if (baselineVersion == undefined) return '';
|
| + return '+';
|
| + }
|
| +
|
| + function ms(time, showDiff) {
|
| + return diffSign(time, showDiff) + time.toFixed(1) + 'ms';
|
| + }
|
| +
|
| + function count(time, showDiff) {
|
| + return diffSign(time, showDiff) + time.toFixed(0) + '#';
|
| + }
|
| +
|
| + function percent(time, showDiff) {
|
| + return diffSign(time, showDiff) + time.toFixed(1) + '%';
|
| + }
|
| + // ========================================================================
|
| + // EventHandlers
|
| + function handleLoadFile() {
|
| + var files = document.getElementById("uploadInput").files;
|
| + var file = files[0];
|
| + var reader = new FileReader();
|
| +
|
| + reader.onload = function(evt) {
|
| + 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[select.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
|
| + showPage(selectedPage);
|
| + if (selectedEntry === undefined) return;
|
| + selectEntry(selectedEntry, true);
|
| + }
|
| +
|
| + function handleUpdatePopover(event) {
|
| + var popover = $('popover');
|
| + popover.style.left = event.pageX + 'px';
|
| + popover.style.top = event.pageY + 'px';
|
| + popover.style.display = event.shiftKey ? 'block' : 'none';
|
| + var target = event.target;
|
| + while (target.entry === undefined) {
|
| + target = target.parentNode;
|
| + if (!target) return;
|
| + }
|
| + showPopover(target.entry);
|
| + }
|
| +
|
| + // ========================================================================
|
| +
|
| + class Versions {
|
| + constructor() {
|
| + this.versions = [];
|
| + }
|
| + add(version) {
|
| + this.versions.push(version)
|
| + }
|
| + pageVersions(name) {
|
| + var result = [];
|
| + this.versions.forEach((version) => {
|
| + var page = version.get(name);
|
| + if (page !== undefined) result.push(page);
|
| + });
|
| + return result;
|
| + }
|
| + get length() {
|
| + return this.versions.length
|
| + }
|
| + get(index) {
|
| + return this.versions[index]
|
| + };
|
| + forEach(f) {
|
| + this.versions.forEach(f);
|
| + }
|
| + sort() {
|
| + this.versions.sort((a, b) => {
|
| + if (a.name > b.name) return 1;
|
| + if (a.name < b.name) return -1;
|
| + return 0
|
| + })
|
| + }
|
| + }
|
| + 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.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.pages.forEach((page) => {
|
| + page.forEach(fun);
|
| + });
|
| + }
|
| + 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.pages.forEach((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) {
|
| + return this.getTotalValue(name, 'timePercent');
|
| + }
|
| + var baselineValue = baselineVersion.getTotalTime(name);
|
| + return this.getTotalValue(name, 'time') / baselineValue * 100;
|
| + }
|
| + getTotalTimeVariance(name, showDiff) {
|
| + // Calculate the overall error for a given entry name
|
| + var sum = 0;
|
| + this.pages.forEach((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) {
|
| + var property = showDiff === false ? '_count' : 'count';
|
| + return this.getTotalValue(name, property);
|
| + }
|
| + 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;
|
| + }
|
| + }
|
| + Version.fromJSON = function(name, data) {
|
| + var version = new Version(name);
|
| + for (var page in data) {
|
| + version.add(Page.fromJSON(version, page, data[page]));
|
| + }
|
| + return version;
|
| + }
|
| +
|
| +
|
| + class Page {
|
| + constructor(version, name) {
|
| + this.name = name;
|
| + this.total = new GroupedEntry('Total', /.*Total.*/);
|
| + this.unclassified = new UnclassifiedEntry(this)
|
| + this.groups = [
|
| + this.total,
|
| + new GroupedEntry('IC', /.*IC.*/),
|
| + new GroupedEntry('Optimize',
|
| + /StackGuard|.*Optimize.*|.*Deoptimize.*|Recompile.*/),
|
| + new GroupedEntry('Compile', /.*Compile.*|Parse.*/),
|
| + new GroupedEntry('Callback', /.*Callback$/),
|
| + new GroupedEntry('API', /.*API.*/),
|
| + new GroupedEntry('GC', /GC|AllocateInTargetSpace/),
|
| + new GroupedEntry('JavaScript', /JS_Execution/),
|
| + 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
|
| + }
|
| + 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()
|
| + });
|
| + }
|
| + }
|
| + Page.fromJSON = function(version, name, data) {
|
| + var page = new Page(version, 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;
|
| + }
|
| + 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;
|
| + return (this._time - baselineEntry._time) /
|
| + this.page.total._time * 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);
|
| + }
|
| + 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) {
|
| + super(0, 'Group-' + name, 0, 0, 0, 0, 0, 0);
|
| + this.regexp = regexp;
|
| + 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) {
|
| + super('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 onmousemove="handleUpdatePopover(event)">
|
| + <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();">
|
| + </p>
|
| + </form>
|
| + </div>
|
| + <div class="inline hidden">
|
| + <h2>Result</h2>
|
| + <div class="compareSelector">
|
| + 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="view">
|
| + </div>
|
| +
|
| + <div id="detailView" class="hidden">
|
| + <h2></h2>
|
| + <div class="versionDetail inline">
|
| + <h3><span></span></h3>
|
| + <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 class="pageDetail inline">
|
| + <h3><span></span></h3>
|
| + <table class="pageDetailTable" onclick="handleSelectDetailRow(this, event);">
|
| + <thead>
|
| + <tr>
|
| + <th class="page">Page </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>
|
| + <tfoot>
|
| + <tr>
|
| + <td class="page">Total:</td>
|
| + <td class="position"></td>
|
| + <td class="value time"></td>
|
| + <td class="value time"></td>
|
| + <td class="value count"></td>
|
| + </tr>
|
| + </tfoot>
|
| + <tbody></tbody>
|
| + </table>
|
| + </div>
|
| + <div class="impactView inline">
|
| + <h3>Impact list for <span></span></h3>
|
| + <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 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>Build chrome with the <a href="https://codereview.chromium.org/1923893002">extended runtime callstats</a>.</li>
|
| + <li>Run callstats.py with a web-page-replay archive:
|
| + <pre>./callstats.py run \
|
| + --replay-bin=$CHROME_SRC/third_party/webpagereplay/replay.py \
|
| + --replay-wpr=top25.wpr \
|
| + --js-flags="" \
|
| + --with-chrome=$CHROME_SRC/out/Release/chrome \
|
| + --sites-file=top25.json</pre>
|
| + </li>
|
| + <li>Move results file to a subdirectory: <code>mkdir $VERSION; mv *.txt $VERSION</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 $VERSION1 $VERSION2 > 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>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>
|
|
|