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> |