[profview] Show all variants of a function in the timeline

When displaying a single function's timeline, display all its variants
(colour-coded by kind) instead of just the ones with the same code-id.
This allows us to see all optimised versions of a function, as well as
changes between optimised and unoptimised.

Drive-by -- Do some rounding to get rendering pixel-perfect.

Change-Id: I385c83b39414ac5e59208b7a25b488d6a283e2b0

NOTRY=true

Change-Id: I385c83b39414ac5e59208b7a25b488d6a283e2b0
Reviewed-on: https://chromium-review.googlesource.com/455833
Commit-Queue: Leszek Swirski <leszeks@chromium.org>
Reviewed-by: Jaroslav Sevcik <jarin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#43894}
This commit is contained in:
Leszek Swirski 2017-03-17 12:06:09 +00:00 committed by Commit Bot
parent 29877f5ae0
commit 64746e5fed
2 changed files with 84 additions and 27 deletions

View File

@ -72,6 +72,19 @@ function resolveCodeKindAndVmState(code, vmState) {
return kind;
}
function codeEquals(code1, code2, allowDifferentKinds = false) {
if (!code1 || !code2) return false;
if (code1.name != code2.name || code1.type != code2.type) return false;
if (code1.type == 'CODE') {
if (!allowDifferentKinds && code1.kind != code2.kind) return false;
} else if (code1.type == 'JS') {
if (!allowDifferentKinds && code1.kind != code2.kind) return false;
if (code1.func != code2.func) return false;
}
return true;
}
function createNodeFromStackEntry(code, codeId) {
let name = code ? code.name : "UNKNOWN";
@ -383,36 +396,56 @@ class FunctionTimelineProcessor {
}
addStack(file, tickIndex) {
if (!this.functionCodeId) return;
let { tm : timestamp, vm : vmState, s : stack } = file.ticks[tickIndex];
let functionCode = file.code[this.functionCodeId];
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;
// Find if the function is on the stack, and its position on the stack,
// ignoring any filtered entries.
let stackCode = undefined;
let functionPosInStack = -1;
let filteredI = 0
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;
// Match other instances of the same function (e.g. unoptimised, various
// different optimised versions).
if (codeEquals(code, functionCode, true)) {
functionPosInStack = filteredI;
stackCode = code;
break;
}
filteredI++;
}
let codeIsTopOfStack =
(topOfStack !== -1 && stack[topOfStack] === this.functionCodeId);
if (functionPosInStack >= 0) {
let stackKind = resolveCodeKindAndVmState(stackCode, vmState);
let codeIsTopOfStack = (functionPosInStack == 0);
if (this.currentBlock !== null) {
this.currentBlock.end = timestamp;
if (codeIsTopOfStack === this.currentBlock.topOfStack) {
if (codeIsTopOfStack === this.currentBlock.topOfStack
&& stackKind === this.currentBlock.kind) {
// If we haven't changed the stack top or the function kind, then
// we're happy just extending the current block and not starting
// a new one.
return;
}
}
// Start a new block at the current timestamp.
this.currentBlock = {
start: timestamp,
end: timestamp,
code: stackCode,
kind: stackKind,
topOfStack: codeIsTopOfStack
};
this.blocks.push(this.currentBlock);

View File

@ -161,7 +161,8 @@ let main = {
onResize() {
main.setTimeLineDimensions(
window.innerWidth - 20, window.innerHeight / 5);
Math.round(window.innerWidth - 20),
Math.round(window.innerHeight / 5));
},
onLoad() {
@ -237,6 +238,14 @@ let bucketDescriptors =
text : "Unknown" }
];
let kindToBucketDescriptor = {}
for (let i = 0; i < bucketDescriptors.length; i++) {
let bucket = bucketDescriptors[i];
for (let j = 0; j < bucket.kinds.length; j++) {
kindToBucketDescriptor[bucket.kinds[j]] = bucket;
}
}
function bucketFromKind(kind) {
for (let i = 0; i < bucketDescriptors.length; i++) {
let bucket = bucketDescriptors[i];
@ -651,8 +660,8 @@ class TimelineView {
this.selecting = false;
this.fontSize = 12;
this.imageOffset = this.fontSize * 1.2;
this.functionTimelineHeight = 12;
this.imageOffset = Math.round(this.fontSize * 1.2);
this.functionTimelineHeight = 16;
this.currentState = null;
}
@ -679,7 +688,7 @@ class TimelineView {
this.selectionStart = null;
this.selectionEnd = null;
let ctx = this.canvas.getContext("2d");
ctx.drawImage(this.buffer, 0, 0);
ctx.drawImage(this.buffer, 0, this.imageOffset);
} else {
this.selectionEnd = x;
this.drawSelection();
@ -850,23 +859,25 @@ class TimelineView {
for (let k = 0; k < desc.kinds.length; k++) {
sum += buckets[i][desc.kinds[k]];
}
bucketData.push(graphHeight * sum / total);
bucketData.push(Math.round(graphHeight * sum / total));
}
bucketsGraph.push(bucketData);
}
// Draw the graph into the buffer.
let bucketWidth = width / bucketCount;
let bucketWidth = width / (bucketsGraph.length - 1);
let ctx = buffer.getContext('2d');
for (let i = 0; i < bucketsGraph.length - 1; i++) {
let bucketData = bucketsGraph[i];
let nextBucketData = bucketsGraph[i + 1];
for (let j = 0; j < bucketData.length; j++) {
let x1 = Math.round(i * bucketWidth);
let x2 = Math.round((i + 1) * bucketWidth);
ctx.beginPath();
ctx.moveTo(i * bucketWidth, j && bucketData[j - 1]);
ctx.lineTo((i + 1) * bucketWidth, j && nextBucketData[j - 1]);
ctx.lineTo((i + 1) * bucketWidth, nextBucketData[j]);
ctx.lineTo(i * bucketWidth, bucketData[j]);
ctx.moveTo(x1, j && bucketData[j - 1]);
ctx.lineTo(x2, j && nextBucketData[j - 1]);
ctx.lineTo(x2, nextBucketData[j]);
ctx.lineTo(x1, bucketData[j]);
ctx.closePath();
ctx.fillStyle = bucketDescriptors[j].color;
ctx.fill();
@ -874,6 +885,7 @@ class TimelineView {
}
let functionTimelineYOffset = graphHeight;
let functionTimelineHeight = this.functionTimelineHeight;
let functionTimelineHalfHeight = Math.round(functionTimelineHeight / 2);
let timestampScaler = width / (lastTime - firstTime);
ctx.fillStyle = "white";
ctx.fillRect(
@ -883,13 +895,27 @@ class TimelineView {
functionTimelineHeight);
for (let i = 0; i < codeIdProcessor.blocks.length; i++) {
let block = codeIdProcessor.blocks[i];
ctx.fillStyle = "#000000";
let bucket = kindToBucketDescriptor[block.kind];
ctx.fillStyle = bucket.color;
ctx.fillRect(
Math.round((block.start - firstTime) * timestampScaler),
functionTimelineYOffset,
Math.max(1, Math.round((block.end - block.start) * timestampScaler)),
block.topOfStack ? functionTimelineHeight : functionTimelineHeight / 2);
block.topOfStack ? functionTimelineHeight : functionTimelineHalfHeight);
}
ctx.strokeStyle = "black";
ctx.lineWidth = "1";
ctx.beginPath();
ctx.moveTo(0, functionTimelineYOffset + 0.5);
ctx.lineTo(buffer.width, functionTimelineYOffset + 0.5);
ctx.stroke();
ctx.strokeStyle = "rgba(0,0,0,0.2)";
ctx.lineWidth = "1";
ctx.beginPath();
ctx.moveTo(0, functionTimelineYOffset + functionTimelineHalfHeight - 0.5);
ctx.lineTo(buffer.width,
functionTimelineYOffset + functionTimelineHalfHeight - 0.5);
ctx.stroke();
// Remember stuff for later.
this.buffer = buffer;
@ -925,9 +951,7 @@ class TimelineView {
}
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>"));
}