Chromium Code Reviews| Index: tools/callstats.html |
| diff --git a/tools/callstats.html b/tools/callstats.html |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..b02367cf1ed5e456fdf735b52de689a048543478 |
| --- /dev/null |
| +++ b/tools/callstats.html |
| @@ -0,0 +1,1284 @@ |
| +<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; |
| + } |
| + |
| + #popover table { |
| + position: relative; |
| + z-index: 1; |
| + text-align: right; |
| + } |
| + |
| + .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 |
|
nickie
2016/05/06 12:40:48
Comment line too long. Move "default" to next lin
|
| + // 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 |
|
nickie
2016/05/06 12:40:48
Same. Move "compare" to next line?
|
| + // 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 !== |
|
nickie
2016/05/06 12:40:48
Bad formatting. Break after last "&&"?
|
| + 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; |
|
nickie
2016/05/06 12:40:48
Bad formatting. Break after "+="?
|
| + entries = entries.filter((entry) => { |
| + return entry.name == selectedEntry.name || |
|
nickie
2016/05/06 12:40:48
"(entry) => { return ... }" can be written as "ent
|
| + (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) + |
|
nickie
2016/05/06 12:40:48
Bad formatting. You can skip the braces and the "
|
| + ')' |
| + }); |
| + 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); |
| + popover.querySelector(prefix + '.timeVariance').innerHTML = percent(entry |
| + .timeVariancePercent); |
| + popover.querySelector(prefix + '.percent').innerHTML = percent(entry.timePercent); |
|
nickie
2016/05/06 12:40:48
Bad formatting. Break after "="?
|
| + popover.querySelector(prefix + '.percentVariance').innerHTML = percent( |
| + entry.timePercentVariancePercent); |
| + popover.querySelector(prefix + '.count').innerHTML = count(entry._count); |
| + popover.querySelector(prefix + '.countVariance').innerHTML = percent( |
| + entry.timeVariancePercent); |
| + popover.querySelector(prefix + '.timeImpact').innerHTML = ms(entry.getTimeImpact()); |
|
nickie
2016/05/06 12:40:48
Bad formatting. Break after "="?
|
| + popover.querySelector(prefix + '.timePercentImpact').innerHTML = percent( |
| + entry.getTimePercentImpact()); |
| + } |
| + |
| + // =========================================================================== |
|
nickie
2016/05/06 12:40:48
Bad formatting. Fewer "="?
|
| + // 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) { |
| + if (value <= 0) return ''; |
| + if (baselineVersion == undefined) return ''; |
| + return '+'; |
| + } |
| + |
| + function ms(time) { |
| + return diffSign(time) + time.toFixed(1) + 'ms'; |
| + } |
| + |
| + function count(time) { |
| + return diffSign(time) + time.toFixed(0) + '#'; |
| + } |
| + |
| + function percent(time) { |
| + return diffSign(time) + time.toFixed(1) + '%'; |
| + } |
| + // =========================================================================== |
|
nickie
2016/05/06 12:40:48
Bad formatting. Fewer "="? Also, insert one blan
|
| + // 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); |
| + 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); |
| + } |
| + |
| + // =========================================================================== |
|
nickie
2016/05/06 12:40:48
Bad formatting. Fewer "="?
|
| + |
| + 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) { |
| + return this.getTotalValue(name, 'time'); |
| + } |
| + getTotalTimePercent(name) { |
| + return this.getTotalValue(name, 'timePercent'); |
| + } |
| + getTotalCount(name) { |
| + return this.getTotalValue(name, 'count'); |
| + } |
| + 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. |
|
nickie
2016/05/06 12:40:48
Bad formatting. Move two words to next line?
|
| + 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() { |
| + return this.page.version.getTotalTime(this.name); |
| + } |
| + getTimePercentImpact() { |
| + return this.page.version.getTotalTimePercent(this.name); |
| + } |
| + getCountImpact() { |
| + return this.page.version.getTotalCount(this.name); |
| + } |
| + 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 |
|
nickie
2016/05/06 12:40:48
Bad formatting. Move two words to next line?
|
| + // 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 |
| + } |
| + getVariancePercentForProperty(property) { |
| + var sum = 0; |
| + this.entries.forEach((entry) => { |
| + sum += entry[property + 'Variance'] * entry[property + |
| + 'Variance']; |
| + }); |
| + return Math.sqrt(sum) / this['_' + property] * 100; |
| + } |
| + get timeVariancePercent() { |
| + return this.getVariancePercentForProperty('time') |
| + } |
| + get timeVariance() { |
| + return this.getVariancePercentForProperty('timePercent') |
| + } |
| + } |
| + |
| + 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) { |
| + return this.getCompareWithBaseline(this._time, '_time'); |
| + } |
| + this._time = 0; |
| + var t = this.page.total.time; |
| + this.forEachPageGroup((group) => { |
| + t -= group.time; |
| + }); |
| + this._time = Math.round(t); |
| + return this._time; |
| + } |
| + get count() { |
| + if (this._count !== undefined) { |
| + return this.getCompareWithBaseline(this._count, '_count'); |
| + } |
| + this._count = 0; |
| + var c = this.page.total.count; |
| + this.forEachPageGroup((group) => { |
| + c -= group.count; |
| + }); |
| + this._count = Math.round(c); |
| + return this._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="2"></td> |
| + </tr> |
| + <tr> |
| + <td>Page:</td> |
| + <td class="page"></td> |
| + </tr> |
| + <tr> |
| + <td>Version:</td> |
| + <td><span class="version"></span></td> |
| + <td class="compare"><span class="version"></span></td> |
| + </tr> |
| + <tr> |
| + <td>Time:</td> |
| + <td><span class="time"></span> ± <span class="timeVariance"></span></td> |
| + <td class="compare"><span class="time"></span> ± <span class="timeVariance"></span></td> |
| + </tr> |
| + <tr> |
| + <td>Percent:</td> |
| + <td><span class="percent"></span> ± <span class="percentVariance"></span></td> |
| + <td class="compare"><span class="percent"></span> ± <span class="percentVariance"></span></td> |
| + </tr> |
| + <tr> |
| + <td>Count:</td> |
| + <td><span class="count"></span> ± <span class="countVariance"></span></td> |
| + <td class="compare"><span class="count"></span> ± <span class="countVariance"></span></td> |
| + </tr> |
| + <tr> |
| + <td>Overall Impact:</td> |
| + <td><span class="timeImpact"></span> / <span class="timePercentImpact"></span></td> |
| + <td class="compare"><span class="timeImpact"></span> / <span class="timePercentImpact"></span></td> |
| + </tr> |
| + </table> |
| + </div> |
| + |
| +</body> |
| + |
| +</html> |