[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:
parent
29877f5ae0
commit
64746e5fed
@ -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);
|
||||
|
@ -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>"));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user