| Index: tools/profview/profview.js
|
| diff --git a/tools/profview/profview.js b/tools/profview/profview.js
|
| index 9a42a68e63d4f54f79567a1f67ff3a2a6fca2168..79569080fceec344754dd656da49f27d4f97c7da 100644
|
| --- a/tools/profview/profview.js
|
| +++ b/tools/profview/profview.js
|
| @@ -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 / 8);
|
| @@ -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);
|
| @@ -629,6 +651,8 @@ class TimelineView {
|
| this.selecting = false;
|
|
|
| this.currentState = null;
|
| +
|
| + this.functionTimelineHeight = 12;
|
| }
|
|
|
| onMouseDown(e) {
|
| @@ -690,8 +714,9 @@ class TimelineView {
|
| ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
|
| let left = Math.min(this.selectionStart, this.selectionEnd);
|
| let right = Math.max(this.selectionStart, this.selectionEnd);
|
| - ctx.fillRect(0, 0, left, this.buffer.height);
|
| - ctx.fillRect(right, 0, this.buffer.width - right, this.buffer.height);
|
| + let height = this.buffer.height - this.functionTimelineHeight;
|
| + ctx.fillRect(0, 0, left, height);
|
| + ctx.fillRect(right, 0, this.buffer.width - right, height);
|
| }
|
| }
|
|
|
| @@ -709,6 +734,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.
|
| @@ -726,6 +752,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);
|
| @@ -743,6 +771,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");
|
|
|
| @@ -750,7 +782,7 @@ class TimelineView {
|
| buffer.height = this.canvas.height;
|
|
|
| // Calculate the bar heights for each bucket.
|
| - let graphHeight = buffer.height;
|
| + let graphHeight = buffer.height - this.functionTimelineHeight;
|
| let buckets = stackProcessor.buckets;
|
| let bucketsGraph = [];
|
| for (let i = 0; i < buckets.length; i++) {
|
| @@ -784,6 +816,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;
|
| @@ -813,6 +863,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>"));
|
| + }
|
| }
|
| }
|
|
|
|
|