Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1942)

Unified Diff: tools/profview/profview.js

Issue 2753543006: [profiler] Web UI: add summary of opts/deopts. (Closed)
Patch Set: Address reviewer comments Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « tools/profview/profile-utils.js ('k') | tools/tickprocessor.js » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/profview/profview.js
diff --git a/tools/profview/profview.js b/tools/profview/profview.js
index da80385de6889289b9ed3742f94c3fc6ce5b5b95..aefdcd6e0cd446b949ed35bf79fbf545f39b1938 100644
--- a/tools/profview/profview.js
+++ b/tools/profview/profview.js
@@ -14,34 +14,17 @@ function createViews() {
components.push(new CallTreeView());
components.push(new TimelineView());
components.push(new HelpView());
+ components.push(new SummaryView());
+ components.push(new ModeBarView());
- let modeBar = $("mode-bar");
-
- function addMode(id, text, active) {
- let div = document.createElement("div");
- div.classList = "mode-button" + (active ? " active-mode-button" : "");
- div.id = "mode-" + id;
- div.textContent = text;
- div.onclick = () => {
- if (main.currentState.callTree.mode === id) return;
- let old = $("mode-" + main.currentState.callTree.mode);
- old.classList = "mode-button";
- div.classList = "mode-button active-mode-button";
- main.setMode(id);
- };
- modeBar.appendChild(div);
- }
-
- addMode("bottom-up", "Bottom up", true);
- addMode("top-down", "Top down");
- addMode("function-list", "Functions");
-
- main.setMode("bottom-up");
+ main.setMode("summary");
}
function emptyState() {
return {
file : null,
+ mode : "none",
+ currentCodeId : null,
start : 0,
end : Infinity,
timeLine : {
@@ -49,7 +32,6 @@ function emptyState() {
height : 100
},
callTree : {
- mode : "none",
attribution : "js-exclude-bc",
categories : "code-type",
sort : "time"
@@ -68,28 +50,35 @@ let main = {
setMode(mode) {
if (mode != main.currentState.mode) {
- let callTreeState = Object.assign({}, main.currentState.callTree);
- callTreeState.mode = mode;
+
+ function setCallTreeModifiers(attribution, categories, sort) {
+ let callTreeState = Object.assign({}, main.currentState.callTree);
+ callTreeState.attribution = attribution;
+ callTreeState.categories = categories;
+ callTreeState.sort = sort;
+ return callTreeState;
+ }
+
+ let state = Object.assign({}, main.currentState);
+
switch (mode) {
case "bottom-up":
- callTreeState.attribution = "js-exclude-bc";
- callTreeState.categories = "code-type";
- callTreeState.sort = "time";
+ state.callTree =
+ setCallTreeModifiers("js-exclude-bc", "code-type", "time");
break;
case "top-down":
- callTreeState.attribution = "js-exclude-bc";
- callTreeState.categories = "none";
- callTreeState.sort = "time";
+ state.callTree =
+ setCallTreeModifiers("js-exclude-bc", "none", "time");
break;
case "function-list":
- callTreeState.attribution = "js-exclude-bc";
- callTreeState.categories = "code-type";
- callTreeState.sort = "own-time";
+ state.callTree =
+ setCallTreeModifiers("js-exclude-bc", "code-type", "own-time");
break;
- default:
- console.error("Invalid mode");
}
- main.currentState = setCallTreeState(main.currentState, callTreeState);
+
+ state.mode = mode;
+
+ main.currentState = state;
main.delayRender();
}
},
@@ -201,12 +190,12 @@ let main = {
let bucketDescriptors =
[ { kinds : [ "JSOPT" ],
- color : "#ffb000",
- backgroundColor : "#ffe0c0",
- text : "JS Optimized" },
- { kinds : [ "JSUNOPT", "BC" ],
color : "#00ff00",
backgroundColor : "#c0ffc0",
+ text : "JS Optimized" },
+ { kinds : [ "JSUNOPT", "BC" ],
+ color : "#ffb000",
+ backgroundColor : "#ffe0c0",
text : "JS Unoptimized" },
{ kinds : [ "IC" ],
color : "#ffff00",
@@ -325,6 +314,27 @@ function filterFromFilterId(id) {
}
}
+function createTableExpander(indent) {
+ let div = document.createElement("div");
+ div.style.width = (indent + 0.5) + "em";
+ div.style.display = "inline-block";
+ div.style.textAlign = "right";
+ return div;
+}
+
+function createFunctionNode(name, codeId) {
+ if (codeId == -1) {
+ return document.createTextNode(name);
+ }
+ let nameElement = document.createElement("span");
+ nameElement.classList.add("codeid-link")
+ nameElement.onclick = function() {
+ main.setCurrentCode(codeId);
+ };
+ nameElement.appendChild(document.createTextNode(name));
+ return nameElement;
+}
+
class CallTreeView {
constructor() {
this.element = $("calltree");
@@ -377,27 +387,6 @@ class CallTreeView {
}
}
- createExpander(indent) {
- let div = document.createElement("div");
- div.style.width = (1 + indent) + "em";
- div.style.display = "inline-block";
- div.style.textAlign = "right";
- return div;
- }
-
- createFunctionNode(name, codeId) {
- if (codeId == -1) {
- return document.createTextNode(name);
- }
- let nameElement = document.createElement("span");
- nameElement.classList.add("codeid-link")
- nameElement.onclick = function() {
- main.setCurrentCode(codeId);
- };
- nameElement.appendChild(document.createTextNode(name));
- return nameElement;
- }
-
expandTree(tree, indent) {
let that = this;
let index = 0;
@@ -406,7 +395,6 @@ class CallTreeView {
let expander = tree.expander;
if (row) {
- console.assert("expander");
index = row.rowIndex;
id = row.id;
@@ -452,7 +440,7 @@ class CallTreeView {
c.textContent = (node.ticks * 100 / tree.ticks).toFixed(2) + "%";
c.style.textAlign = "right";
// Exclusive time % cell.
- if (this.currentState.callTree.mode !== "bottom-up") {
+ if (this.currentState.mode !== "bottom-up") {
c = row.insertCell(-1);
c.textContent = (node.ownTicks * 100 / this.tickCount).toFixed(2) + "%";
c.style.textAlign = "right";
@@ -460,16 +448,16 @@ class CallTreeView {
// Create the name cell.
let nameCell = row.insertCell();
- let expander = this.createExpander(indent);
+ let expander = createTableExpander(indent + 1);
nameCell.appendChild(expander);
nameCell.appendChild(createTypeDiv(node.type));
- nameCell.appendChild(this.createFunctionNode(node.name, node.codeId));
+ nameCell.appendChild(createFunctionNode(node.name, node.codeId));
// Inclusive ticks cell.
c = row.insertCell();
c.textContent = node.ticks;
c.style.textAlign = "right";
- if (this.currentState.callTree.mode !== "bottom-up") {
+ if (this.currentState.mode !== "bottom-up") {
// Exclusive ticks cell.
c = row.insertCell(-1);
c.textContent = node.ownTicks;
@@ -500,7 +488,7 @@ class CallTreeView {
expander.onclick = expandHandler;
}
- fillSelects(calltree) {
+ fillSelects(mode, calltree) {
function addOptions(e, values, current) {
while (e.options.length > 0) {
e.remove(0);
@@ -523,7 +511,7 @@ class CallTreeView {
text : "Attribute non-functions to JS functions" }
];
- switch (calltree.mode) {
+ switch (mode) {
case "bottom-up":
addOptions(this.selectAttribution, attributions, calltree.attribution);
addOptions(this.selectCategories, [
@@ -564,10 +552,22 @@ class CallTreeView {
console.error("Unexpected mode");
}
+ static isCallTreeMode(mode) {
+ switch (mode) {
+ case "bottom-up":
+ case "top-down":
+ case "function-list":
+ return true;
+ default:
+ return false;
+ }
+ }
+
render(newState) {
let oldState = this.currentState;
- if (!newState.file) {
+ if (!newState.file || !CallTreeView.isCallTreeMode(newState.mode)) {
this.element.style.display = "none";
+ this.currentState = null;
return;
}
@@ -576,7 +576,7 @@ class CallTreeView {
if (newState.file === oldState.file &&
newState.start === oldState.start &&
newState.end === oldState.end &&
- newState.callTree.mode === oldState.callTree.mode &&
+ newState.mode === oldState.mode &&
newState.callTree.attribution === oldState.callTree.attribution &&
newState.callTree.categories === oldState.callTree.categories &&
newState.callTree.sort === oldState.callTree.sort) {
@@ -587,12 +587,12 @@ class CallTreeView {
this.element.style.display = "inherit";
- let mode = this.currentState.callTree.mode;
- if (!oldState || mode !== oldState.callTree.mode) {
+ let mode = this.currentState.mode;
+ if (!oldState || mode !== oldState.mode) {
// Technically, we should also call this if attribution, categories or
// sort change, but the selection is already highlighted by the combobox
// itself, so we do need to do anything here.
- this.fillSelects(newState.callTree);
+ this.fillSelects(newState.mode, newState.callTree);
}
let ownTimeClass = (mode === "bottom-up") ? "numeric-hidden" : "numeric";
@@ -661,7 +661,8 @@ class TimelineView {
this.fontSize = 12;
this.imageOffset = Math.round(this.fontSize * 1.2);
- this.functionTimelineHeight = 16;
+ this.functionTimelineHeight = 24;
+ this.functionTimelineTickHeight = 16;
this.currentState = null;
}
@@ -864,8 +865,8 @@ class TimelineView {
bucketsGraph.push(bucketData);
}
- // Draw the graph into the buffer.
- let bucketWidth = width / (bucketsGraph.length - 1);
+ // Draw the category graph into the buffer.
+ let bucketWidth = width / bucketsGraph.length;
let ctx = buffer.getContext('2d');
for (let i = 0; i < bucketsGraph.length - 1; i++) {
let bucketData = bucketsGraph[i];
@@ -883,25 +884,30 @@ class TimelineView {
ctx.fill();
}
}
+
+ // Draw the function ticks.
let functionTimelineYOffset = graphHeight;
- let functionTimelineHeight = this.functionTimelineHeight;
- let functionTimelineHalfHeight = Math.round(functionTimelineHeight / 2);
+ let functionTimelineTickHeight = this.functionTimelineTickHeight;
+ let functionTimelineHalfHeight =
+ Math.round(functionTimelineTickHeight / 2);
let timestampScaler = width / (lastTime - firstTime);
+ let timestampToX = (t) => Math.round((t - firstTime) * timestampScaler);
ctx.fillStyle = "white";
ctx.fillRect(
0,
functionTimelineYOffset,
buffer.width,
- functionTimelineHeight);
+ this.functionTimelineHeight);
for (let i = 0; i < codeIdProcessor.blocks.length; i++) {
let block = codeIdProcessor.blocks[i];
let bucket = kindToBucketDescriptor[block.kind];
ctx.fillStyle = bucket.color;
ctx.fillRect(
- Math.round((block.start - firstTime) * timestampScaler),
+ timestampToX(block.start),
functionTimelineYOffset,
Math.max(1, Math.round((block.end - block.start) * timestampScaler)),
- block.topOfStack ? functionTimelineHeight : functionTimelineHalfHeight);
+ block.topOfStack ?
+ functionTimelineTickHeight : functionTimelineHalfHeight);
}
ctx.strokeStyle = "black";
ctx.lineWidth = "1";
@@ -917,6 +923,53 @@ class TimelineView {
functionTimelineYOffset + functionTimelineHalfHeight - 0.5);
ctx.stroke();
+ // Draw marks for optimizations and deoptimizations in the function
+ // timeline.
+ if (currentCodeId && currentCodeId >= 0 &&
+ file.code[currentCodeId].func) {
+ let y = Math.round(functionTimelineYOffset + functionTimelineTickHeight +
+ (this.functionTimelineHeight - functionTimelineTickHeight) / 2);
+ let func = file.functions[file.code[currentCodeId].func];
+ for (let i = 0; i < func.codes.length; i++) {
+ let code = file.code[func.codes[i]];
+ if (code.kind === "Opt") {
+ if (code.deopt) {
+ // Draw deoptimization mark.
+ let x = timestampToX(code.deopt.tm);
+ ctx.lineWidth = 0.7;
+ ctx.strokeStyle = "red";
+ ctx.beginPath();
+ ctx.moveTo(x - 3, y - 3);
+ ctx.lineTo(x + 3, y + 3);
+ ctx.stroke();
+ ctx.beginPath();
+ ctx.moveTo(x - 3, y + 3);
+ ctx.lineTo(x + 3, y - 3);
+ ctx.stroke();
+ }
+ // Draw optimization mark.
+ let x = timestampToX(code.tm);
+ ctx.lineWidth = 0.7;
+ ctx.strokeStyle = "blue";
+ ctx.beginPath();
+ ctx.moveTo(x - 3, y - 3);
+ ctx.lineTo(x, y);
+ ctx.stroke();
+ ctx.beginPath();
+ ctx.moveTo(x - 3, y + 3);
+ ctx.lineTo(x, y);
+ ctx.stroke();
+ } else {
+ // Draw code creation mark.
+ let x = Math.round(timestampToX(code.tm));
+ ctx.beginPath();
+ ctx.fillStyle = "black";
+ ctx.arc(x, y, 3, 0, 2 * Math.PI);
+ ctx.fill();
+ }
+ }
+ }
+
// Remember stuff for later.
this.buffer = buffer;
@@ -958,6 +1011,218 @@ class TimelineView {
}
}
+class ModeBarView {
+ constructor() {
+ let modeBar = this.element = $("mode-bar");
+
+ function addMode(id, text, active) {
+ let div = document.createElement("div");
+ div.classList = "mode-button" + (active ? " active-mode-button" : "");
+ div.id = "mode-" + id;
+ div.textContent = text;
+ div.onclick = () => {
+ if (main.currentState.mode === id) return;
+ let old = $("mode-" + main.currentState.mode);
+ old.classList = "mode-button";
+ div.classList = "mode-button active-mode-button";
+ main.setMode(id);
+ };
+ modeBar.appendChild(div);
+ }
+
+ addMode("summary", "Summary", true);
+ addMode("bottom-up", "Bottom up");
+ addMode("top-down", "Top down");
+ addMode("function-list", "Functions");
+ }
+
+ render(newState) {
+ if (!newState.file) {
+ this.element.style.display = "none";
+ return;
+ }
+
+ this.element.style.display = "inherit";
+ }
+}
+
+class SummaryView {
+ constructor() {
+ this.element = $("summary");
+ this.currentState = null;
+ }
+
+ render(newState) {
+ let oldState = this.currentState;
+
+ if (!newState.file || newState.mode !== "summary") {
+ this.element.style.display = "none";
+ this.currentState = null;
+ return;
+ }
+
+ this.currentState = newState;
+ if (oldState) {
+ if (newState.file === oldState.file &&
+ newState.start === oldState.start &&
+ newState.end === oldState.end) {
+ // No change, nothing to do.
+ return;
+ }
+ }
+
+ this.element.style.display = "inherit";
+
+ while (this.element.firstChild) {
+ this.element.removeChild(this.element.firstChild);
+ }
+
+ let stats = computeOptimizationStats(
+ this.currentState.file, newState.start, newState.end);
+
+ let table = document.createElement("table");
+ let rows = document.createElement("tbody");
+
+ function addRow(text, number, indent) {
+ let row = rows.insertRow(-1);
+ let textCell = row.insertCell(-1);
+ textCell.textContent = text;
+ let numberCell = row.insertCell(-1);
+ numberCell.textContent = number;
+ if (indent) {
+ textCell.style.textIndent = indent + "em";
+ numberCell.style.textIndent = indent + "em";
+ }
+ return row;
+ }
+
+ function makeCollapsible(row, expander) {
+ expander.textContent = "\u25BE";
+ let expandHandler = expander.onclick;
+ expander.onclick = () => {
+ let id = row.id;
+ let index = row.rowIndex + 1;
+ while (index < rows.rows.length &&
+ rows.rows[index].id.startsWith(id)) {
+ rows.deleteRow(index);
+ }
+ expander.textContent = "\u25B8";
+ expander.onclick = expandHandler;
+ }
+ }
+
+ function expandDeoptInstances(row, expander, instances, indent, kind) {
+ let index = row.rowIndex;
+ for (let i = 0; i < instances.length; i++) {
+ let childRow = rows.insertRow(index + 1);
+ childRow.id = row.id + i + "/";
+
+ let deopt = instances[i].deopt;
+
+ let textCell = childRow.insertCell(-1);
+ textCell.appendChild(document.createTextNode(deopt.posText));
+ textCell.style.textIndent = indent + "em";
+ let reasonCell = childRow.insertCell(-1);
+ reasonCell.appendChild(
+ document.createTextNode("Reason: " + deopt.reason));
+ reasonCell.style.textIndent = indent + "em";
+ }
+ makeCollapsible(row, expander);
+ }
+
+ function expandDeoptFunctionList(row, expander, list, indent, kind) {
+ let index = row.rowIndex;
+ for (let i = 0; i < list.length; i++) {
+ let childRow = rows.insertRow(index + 1);
+ childRow.id = row.id + i + "/";
+
+ let textCell = childRow.insertCell(-1);
+ let expander = createTableExpander(indent);
+ textCell.appendChild(expander);
+ textCell.appendChild(
+ createFunctionNode(list[i].f.name, list[i].f.codes[0]));
+
+ let numberCell = childRow.insertCell(-1);
+ numberCell.textContent = list[i].instances.length;
+ numberCell.style.textIndent = indent + "em";
+
+ expander.textContent = "\u25B8";
+ expander.onclick = () => {
+ expandDeoptInstances(
+ childRow, expander, list[i].instances, indent + 1);
+ };
+ }
+ makeCollapsible(row, expander);
+ }
+
+ function expandOptimizedFunctionList(row, expander, list, indent, kind) {
+ let index = row.rowIndex;
+ for (let i = 0; i < list.length; i++) {
+ let childRow = rows.insertRow(index + 1);
+ childRow.id = row.id + i + "/";
+
+ let textCell = childRow.insertCell(-1);
+ textCell.appendChild(
+ createFunctionNode(list[i].f.name, list[i].f.codes[0]));
+ textCell.style.textIndent = indent + "em";
+
+ let numberCell = childRow.insertCell(-1);
+ numberCell.textContent = list[i].instances.length;
+ numberCell.style.textIndent = indent + "em";
+ }
+ makeCollapsible(row, expander);
+ }
+
+ function addExpandableRow(text, list, indent, kind) {
+ let row = rows.insertRow(-1);
+
+ row.id = "opt-table/" + kind + "/";
+
+ let textCell = row.insertCell(-1);
+ let expander = createTableExpander(indent);
+ textCell.appendChild(expander);
+ textCell.appendChild(document.createTextNode(text));
+
+ let numberCell = row.insertCell(-1);
+ numberCell.textContent = list.count;
+ if (indent) {
+ numberCell.style.textIndent = indent + "em";
+ }
+
+ if (list.count > 0) {
+ expander.textContent = "\u25B8";
+ if (kind === "opt") {
+ expander.onclick = () => {
+ expandOptimizedFunctionList(
+ row, expander, list.functions, indent + 1, kind);
+ };
+ } else {
+ expander.onclick = () => {
+ expandDeoptFunctionList(
+ row, expander, list.functions, indent + 1, kind);
+ };
+ }
+ }
+ return row;
+ }
+
+ addRow("Total function count:", stats.functionCount);
+ addRow("Optimized function count:", stats.optimizedFunctionCount, 1);
+ addRow("Deoptimized function count:", stats.deoptimizedFunctionCount, 2);
+
+ addExpandableRow("Optimization count:", stats.optimizations, 0, "opt");
+ let deoptCount = stats.eagerDeoptimizations.count +
+ stats.softDeoptimizations.count + stats.lazyDeoptimizations.count;
+ addRow("Deoptimization count:", deoptCount);
+ addExpandableRow("Eager:", stats.eagerDeoptimizations, 1, "eager");
+ addExpandableRow("Lazy:", stats.lazyDeoptimizations, 1, "lazy");
+ addExpandableRow("Soft:", stats.softDeoptimizations, 1, "soft");
+
+ table.appendChild(rows);
+ this.element.appendChild(table);
+ }
+}
+
class HelpView {
constructor() {
this.element = $("help");
« no previous file with comments | « tools/profview/profile-utils.js ('k') | tools/tickprocessor.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698