Commit 65a07b7a authored by leszeks's avatar leszeks Committed by Commit bot

[tools/profview] Add individual function timeline view

Adds a bar below the current timeline view which can show the time
when an individual function was on the stack. Functions in the call
stack are now clickable to show them in this view.

Sections where the function was on the stack, but not at the top, are
displayed at half height.

Review-Url: https://codereview.chromium.org/2737083003
Cr-Commit-Position: refs/heads/master@{#43673}
parent fb887b81
......@@ -57,6 +57,9 @@ found in the LICENSE file. -->
<tr id="timeline-legend">
</tr>
</table>
<div>
Current code object: <span id="timeline-currentCode"></span>
</div>
</div>
<br>
......
......@@ -72,10 +72,10 @@ function resolveCodeKindAndVmState(code, vmState) {
return kind;
}
function createNodeFromStackEntry(code) {
function createNodeFromStackEntry(code, codeId) {
let name = code ? code.name : "UNKNOWN";
return { name, type : resolveCodeKind(code),
return { name, codeId, type : resolveCodeKind(code),
children : [], ownTicks : 0, ticks : 0 };
}
......@@ -143,7 +143,7 @@ function addOrUpdateChildNode(parent, file, stackIndex, stackPos, ascending) {
let childId = childIdFromCode(codeId, code);
let child = parent.children[childId];
if (!child) {
child = createNodeFromStackEntry(code);
child = createNodeFromStackEntry(code, codeId);
child.delayedExpansion = { frameList : [], ascending };
parent.children[childId] = child;
}
......@@ -177,6 +177,7 @@ function expandTreeNode(file, node, filter) {
function createEmptyNode(name) {
return {
name : name,
codeId: -1,
type : "CAT",
children : [],
ownTicks : 0,
......@@ -265,7 +266,13 @@ class FunctionListTree {
this.tree = root;
this.categories = categories;
} else {
this.tree = { name : "root", children : [], ownTicks : 0, ticks : 0 };
this.tree = {
name : "root",
codeId: -1,
children : [],
ownTicks : 0,
ticks : 0
};
this.categories = null;
}
......@@ -299,7 +306,7 @@ class FunctionListTree {
}
child = tree.children[childId];
if (!child) {
child = createNodeFromStackEntry(code);
child = createNodeFromStackEntry(code, codeId);
child.children[0] = createEmptyNode("Top-down tree");
child.children[0].delayedExpansion =
{ frameList : [], ascending : false };
......@@ -367,6 +374,54 @@ class CategorySampler {
}
}
class FunctionTimelineProcessor {
constructor(functionCodeId, filter) {
this.functionCodeId = functionCodeId;
this.filter = filter;
this.blocks = [];
this.currentBlock = null;
}
addStack(file, tickIndex) {
let { tm : timestamp, vm : vmState, s : stack } = file.ticks[tickIndex];
let codeInStack = stack.includes(this.functionCodeId);
if (codeInStack) {
let topOfStack = -1;
for (let i = 0; i < stack.length - 1; i += 2) {
let codeId = stack[i];
let code = codeId >= 0 ? file.code[codeId] : undefined;
let type = code ? code.type : undefined;
let kind = code ? code.kind : undefined;
if (!this.filter(type, kind)) continue;
topOfStack = i;
break;
}
let codeIsTopOfStack =
(topOfStack !== -1 && stack[topOfStack] === this.functionCodeId);
if (this.currentBlock !== null) {
this.currentBlock.end = timestamp;
if (codeIsTopOfStack === this.currentBlock.topOfStack) {
return;
}
}
this.currentBlock = {
start: timestamp,
end: timestamp,
topOfStack: codeIsTopOfStack
};
this.blocks.push(this.currentBlock);
} else {
this.currentBlock = null;
}
}
}
// Generates a tree out of a ticks sequence.
// {file} is the JSON files with the ticks and code objects.
// {startTime}, {endTime} is the interval.
......
......@@ -34,6 +34,11 @@ span.code-type-chip-space {
display : inline-block;
}
span.codeid-link {
text-decoration: underline;
cursor: pointer;
}
div.mode-button {
padding: 1em 3em;
display: inline-block;
......
......@@ -151,6 +151,14 @@ let main = {
}
},
setCurrentCode(codeId) {
if (codeId != main.currentState.currentCodeId) {
main.currentState = Object.assign({}, main.currentState);
main.currentState.currentCodeId = codeId;
main.delayRender();
}
},
onResize() {
main.setTimeLineDimensions(
window.innerWidth - 20, window.innerHeight / 5);
......@@ -241,6 +249,73 @@ function bucketFromKind(kind) {
return null;
}
function codeTypeToText(type) {
switch (type) {
case "UNKNOWN":
return "Unknown";
case "CPPCOMP":
return "C++ (compiler)";
case "CPPGC":
return "C++";
case "CPPEXT":
return "C++ External";
case "CPP":
return "C++";
case "LIB":
return "Library";
case "IC":
return "IC";
case "BC":
return "Bytecode";
case "STUB":
return "Stub";
case "BUILTIN":
return "Builtin";
case "REGEXP":
return "RegExp";
case "JSOPT":
return "JS opt";
case "JSUNOPT":
return "JS unopt";
}
console.error("Unknown type: " + type);
}
function createTypeDiv(type) {
if (type === "CAT") {
return document.createTextNode("");
}
let div = document.createElement("div");
div.classList.add("code-type-chip");
let span = document.createElement("span");
span.classList.add("code-type-chip");
span.textContent = codeTypeToText(type);
div.appendChild(span);
span = document.createElement("span");
span.classList.add("code-type-chip-space");
div.appendChild(span);
return div;
}
function isBytecodeHandler(kind) {
return kind === "BytecodeHandler";
}
function filterFromFilterId(id) {
switch (id) {
case "full-tree":
return (type, kind) => true;
case "js-funs":
return (type, kind) => type !== 'CODE';
case "js-exclude-bc":
return (type, kind) =>
type !== 'CODE' || !isBytecodeHandler(kind);
}
}
class CallTreeView {
constructor() {
this.element = $("calltree");
......@@ -264,18 +339,6 @@ class CallTreeView {
this.currentState = null;
}
filterFromFilterId(id) {
switch (id) {
case "full-tree":
return (type, kind) => true;
case "js-funs":
return (type, kind) => type !== 'CODE';
case "js-exclude-bc":
return (type, kind) =>
type !== 'CODE' || !CallTreeView.IsBytecodeHandler(kind);
}
}
sortFromId(id) {
switch (id) {
case "time":
......@@ -305,10 +368,6 @@ class CallTreeView {
}
}
static IsBytecodeHandler(kind) {
return kind === "BytecodeHandler";
}
createExpander(indent) {
let div = document.createElement("div");
div.style.width = (1 + indent) + "em";
......@@ -317,55 +376,17 @@ class CallTreeView {
return div;
}
codeTypeToText(type) {
switch (type) {
case "UNKNOWN":
return "Unknown";
case "CPPCOMP":
return "C++ (compiler)";
case "CPPGC":
return "C++";
case "CPPEXT":
return "C++ External";
case "CPP":
return "C++";
case "LIB":
return "Library";
case "IC":
return "IC";
case "BC":
return "Bytecode";
case "STUB":
return "Stub";
case "BUILTIN":
return "Builtin";
case "REGEXP":
return "RegExp";
case "JSOPT":
return "JS opt";
case "JSUNOPT":
return "JS unopt";
}
console.error("Unknown type: " + type);
}
createTypeDiv(type) {
if (type === "CAT") {
return document.createTextNode("");
createFunctionNode(name, codeId) {
if (codeId == -1) {
return document.createTextNode(name);
}
let div = document.createElement("div");
div.classList.add("code-type-chip");
let span = document.createElement("span");
span.classList.add("code-type-chip");
span.textContent = this.codeTypeToText(type);
div.appendChild(span);
span = document.createElement("span");
span.classList.add("code-type-chip-space");
div.appendChild(span);
return div;
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) {
......@@ -392,7 +413,7 @@ class CallTreeView {
// Collect the children, and sort them by ticks.
let children = [];
let filter =
this.filterFromFilterId(this.currentState.callTree.attribution);
filterFromFilterId(this.currentState.callTree.attribution);
for (let childId in tree.children) {
let child = tree.children[childId];
if (child.ticks > 0) {
......@@ -432,8 +453,8 @@ class CallTreeView {
let nameCell = row.insertCell();
let expander = this.createExpander(indent);
nameCell.appendChild(expander);
nameCell.appendChild(this.createTypeDiv(node.type));
nameCell.appendChild(document.createTextNode(node.name));
nameCell.appendChild(createTypeDiv(node.type));
nameCell.appendChild(this.createFunctionNode(node.name, node.codeId));
// Inclusive ticks cell.
c = row.insertCell();
......@@ -573,7 +594,7 @@ class CallTreeView {
// Build the tree.
let stackProcessor;
let filter = this.filterFromFilterId(this.currentState.callTree.attribution);
let filter = filterFromFilterId(this.currentState.callTree.attribution);
if (mode === "top-down") {
stackProcessor =
new PlainCallTreeProcessor(filter, false);
......@@ -619,6 +640,7 @@ class TimelineView {
this.element = $("timeline");
this.canvas = $("timeline-canvas");
this.legend = $("timeline-legend");
this.currentCode = $("timeline-currentCode");
this.canvas.onmousedown = this.onMouseDown.bind(this);
this.canvas.onmouseup = this.onMouseUp.bind(this);
......@@ -630,6 +652,7 @@ class TimelineView {
this.fontSize = 12;
this.imageOffset = this.fontSize * 1.2;
this.functionTimelineHeight = 12;
this.currentState = null;
}
......@@ -698,9 +721,9 @@ class TimelineView {
ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
left = Math.min(this.selectionStart, this.selectionEnd);
right = Math.max(this.selectionStart, this.selectionEnd);
ctx.fillRect(0, this.imageOffset, left, this.buffer.height);
ctx.fillRect(right, this.imageOffset, this.buffer.width - right,
this.buffer.height);
let height = this.buffer.height - this.functionTimelineHeight;
ctx.fillRect(0, this.imageOffset, left, height);
ctx.fillRect(right, this.imageOffset, this.buffer.width - right, height);
} else {
left = 0;
right = this.buffer.width;
......@@ -763,6 +786,7 @@ class TimelineView {
if (newState.timeLine.width === oldState.timeLine.width &&
newState.timeLine.height === oldState.timeLine.height &&
newState.file === oldState.file &&
newState.currentCodeId === oldState.currentCodeId &&
newState.start === oldState.start &&
newState.end === oldState.end) {
// No change, nothing to do.
......@@ -784,6 +808,8 @@ class TimelineView {
let file = this.currentState.file;
if (!file) return;
let currentCodeId = this.currentState.currentCodeId;
let firstTime = file.ticks[0].tm;
let lastTime = file.ticks[file.ticks.length - 1].tm;
let start = Math.max(this.currentState.start, firstTime);
......@@ -801,6 +827,10 @@ class TimelineView {
let stackProcessor = new CategorySampler(file, bucketCount);
generateTree(file, 0, Infinity, stackProcessor);
let codeIdProcessor = new FunctionTimelineProcessor(
currentCodeId,
filterFromFilterId(this.currentState.callTree.attribution));
generateTree(file, 0, Infinity, codeIdProcessor);
let buffer = document.createElement("canvas");
......@@ -808,7 +838,7 @@ class TimelineView {
buffer.height = height;
// Calculate the bar heights for each bucket.
let graphHeight = height;
let graphHeight = height - this.functionTimelineHeight;
let buckets = stackProcessor.buckets;
let bucketsGraph = [];
for (let i = 0; i < buckets.length; i++) {
......@@ -842,6 +872,24 @@ class TimelineView {
ctx.fill();
}
}
let functionTimelineYOffset = graphHeight;
let functionTimelineHeight = this.functionTimelineHeight;
let timestampScaler = width / (lastTime - firstTime);
ctx.fillStyle = "white";
ctx.fillRect(
0,
functionTimelineYOffset,
buffer.width,
functionTimelineHeight);
for (let i = 0; i < codeIdProcessor.blocks.length; i++) {
let block = codeIdProcessor.blocks[i];
ctx.fillStyle = "#000000";
ctx.fillRect(
Math.round((block.start - firstTime) * timestampScaler),
functionTimelineYOffset,
Math.max(1, Math.round((block.end - block.start) * timestampScaler)),
block.topOfStack ? functionTimelineHeight : functionTimelineHeight / 2);
}
// Remember stuff for later.
this.buffer = buffer;
......@@ -871,6 +919,18 @@ class TimelineView {
cell.appendChild(div);
cell.appendChild(document.createTextNode(" " + desc.text));
}
while (this.currentCode.firstChild) {
this.currentCode.removeChild(this.currentCode.firstChild);
}
if (currentCodeId) {
let currentCode = file.code[currentCodeId];
this.currentCode.appendChild(createTypeDiv(resolveCodeKind(currentCode)));
this.currentCode.appendChild(document.createTextNode(currentCode.name));
} else {
this.currentCode.appendChild(document.createTextNode("<none>"));
}
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment