Index: tools/profview/profile-utils.js |
diff --git a/tools/profview/profile-utils.js b/tools/profview/profile-utils.js |
index e6fb58c5646a9dd173c1cba85531dc791cc76cb2..02e7483fb8f8ba9009fab61523ee94c03d601876 100644 |
--- a/tools/profview/profile-utils.js |
+++ b/tools/profview/profile-utils.js |
@@ -79,6 +79,89 @@ function createNodeFromStackEntry(code) { |
children : [], ownTicks : 0, ticks : 0 }; |
} |
+function childIdFromCode(codeId, code) { |
+ // For JavaScript function, pretend there is one instance of optimized |
+ // function and one instance of unoptimized function per SFI. |
+ // Otherwise, just compute the id from code id. |
+ let type = resolveCodeKind(code); |
+ if (type === "JSOPT") { |
+ return code.func * 4 + 1; |
+ } else if (type === "JSUNOPT") { |
+ return code.func * 4 + 2; |
+ } else { |
+ return codeId * 4; |
+ } |
+} |
+ |
+// We store list of ticks and positions within the ticks stack by |
+// storing flattened triplets of { tickIndex, depth, count }. |
+// Triplet { 123, 2, 3 } encodes positions in ticks 123, 124, 125, |
+// all of them at depth 2. The flattened array is used to encode |
+// position within the call-tree. |
+ |
+// The following function helps to encode such triplets. |
+function addFrameToFrameList(paths, pathIndex, depth) { |
+ // Try to combine with the previous code run. |
+ if (paths.length > 0 && |
+ paths[paths.length - 3] + 1 === pathIndex && |
+ paths[paths.length - 2] === depth) { |
+ paths[paths.length - 1]++; |
+ } else { |
+ paths.push(pathIndex, depth, 1); |
+ } |
+} |
+ |
+// This expands a tree node (direct children only). |
+function expandTreeNode(file, node, filter) { |
+ let { frameList, ascending } = node.delayedExpansion; |
+ |
+ let step = ascending ? 2 : -2; |
+ |
+ for (let i = 0; i < frameList.length; i+= 3) { |
+ let stackIndex = frameList[i]; |
+ let depth = frameList[i + 1]; |
+ let count = frameList[i + 2]; |
+ for (let j = 0; j < count; j++) { |
+ let tick = file.ticks[stackIndex + j]; |
+ let stack = tick.s; |
+ |
+ // Get to the next frame that has not been filtered out. |
+ let k = depth + step; |
+ let codeId = -1; |
+ let code = null; |
+ while (k >= 0 && k < stack.length) { |
+ codeId = stack[k]; |
+ code = codeId >= 0 ? file.code[codeId] : undefined; |
+ if (filter) { |
+ let type = code ? code.type : undefined; |
+ let kind = code ? code.kind : undefined; |
+ if (filter(type, kind)) break; |
+ } |
+ k += step; |
+ } |
+ if (k < 0 || k >= stack.length) { |
+ // We reached the end without finding the next step. |
+ // If we are doing top-down call tree, update own ticks. |
+ if (!ascending) { |
+ node.ownTicks++; |
+ } |
+ } else { |
+ // We found a child node. |
+ let childId = childIdFromCode(codeId, code); |
+ let child = node.children[childId]; |
+ if (!child) { |
+ child = createNodeFromStackEntry(code); |
+ child.delayedExpansion = { frameList : [], ascending }; |
+ node.children[childId] = child; |
+ } |
+ child.ticks++; |
+ addFrameToFrameList(child.delayedExpansion.frameList, stackIndex, k); |
+ } |
+ } |
+ } |
+ node.delayedExpansion = null; |
+} |
+ |
function addStackToTree(file, stack, tree, filter, ascending, start) { |
if (start === undefined) { |
start = ascending ? 0 : stack.length - 2; |
@@ -97,15 +180,7 @@ function addStackToTree(file, stack, tree, filter, ascending, start) { |
// For JavaScript function, pretend there is one instance of optimized |
// function and one instance of unoptimized function per SFI. |
- let type = resolveCodeKind(code); |
- let childId; |
- if (type === "JSOPT") { |
- childId = code.func * 4 + 1; |
- } else if (type === "JSUNOPT") { |
- childId = code.func * 4 + 2; |
- } else { |
- childId = codeId * 4; |
- } |
+ let childId = childIdFromCode(codeId, code); |
let child = tree.children[childId]; |
if (!child) { |
child = createNodeFromStackEntry(code); |
@@ -134,40 +209,49 @@ class PlainCallTreeProcessor { |
this.isBottomUp = isBottomUp; |
} |
- addStack(file, timestamp, vmState, stack) { |
+ addStack(file, tickIndex) { |
+ let stack = file.ticks[tickIndex].s; |
addStackToTree(file, stack, this.tree, this.filter, this.isBottomUp); |
} |
} |
+function buildCategoryTreeAndLookup() { |
+ let root = createEmptyNode("root"); |
+ let categories = {}; |
+ function addCategory(name, types) { |
+ let n = createEmptyNode(name); |
+ for (let i = 0; i < types.length; i++) { |
+ categories[types[i]] = n; |
+ } |
+ root.children.push(n); |
+ } |
+ addCategory("JS Optimized", [ "JSOPT" ]); |
+ addCategory("JS Unoptimized", [ "JSUNOPT", "BC" ]); |
+ addCategory("IC", [ "IC" ]); |
+ addCategory("RegExp", [ "REGEXP" ]); |
+ addCategory("Other generated", [ "STUB", "BUILTIN" ]); |
+ addCategory("C++", [ "CPP", "LIB" ]); |
+ addCategory("C++/GC", [ "CPPGC" ]); |
+ addCategory("C++/Compiler", [ "CPPCOMP" ]); |
+ addCategory("C++/External", [ "CPPEXT" ]); |
+ addCategory("Unknown", [ "UNKNOWN" ]); |
+ |
+ return { categories, root }; |
+} |
+ |
class CategorizedCallTreeProcessor { |
constructor(filter, isBottomUp) { |
this.filter = filter; |
- let root = createEmptyNode("root"); |
- let categories = {}; |
- function addCategory(name, types) { |
- let n = createEmptyNode(name); |
- for (let i = 0; i < types.length; i++) { |
- categories[types[i]] = n; |
- } |
- root.children.push(n); |
- } |
- addCategory("JS Optimized", [ "JSOPT" ]); |
- addCategory("JS Unoptimized", [ "JSUNOPT", "BC" ]); |
- addCategory("IC", [ "IC" ]); |
- addCategory("RegExp", [ "REGEXP" ]); |
- addCategory("Other generated", [ "STUB", "BUILTIN" ]); |
- addCategory("C++", [ "CPP", "LIB" ]); |
- addCategory("C++/GC", [ "CPPGC" ]); |
- addCategory("C++/Compiler", [ "CPPCOMP" ]); |
- addCategory("C++/External", [ "CPPEXT" ]); |
- addCategory("Unknown", [ "UNKNOWN" ]); |
+ let { categories, root } = buildCategoryTreeAndLookup(); |
this.tree = root; |
this.categories = categories; |
this.isBottomUp = isBottomUp; |
} |
- addStack(file, timestamp, vmState, stack) { |
+ addStack(file, tickIndex) { |
+ let stack = file.ticks[tickIndex].s; |
+ let vmState = file.ticks[tickIndex].vm; |
if (stack.length === 0) return; |
let codeId = stack[0]; |
let code = codeId >= 0 ? file.code[codeId] : undefined; |
@@ -183,15 +267,27 @@ class CategorizedCallTreeProcessor { |
} |
class FunctionListTree { |
- constructor(filter) { |
- this.tree = { name : "root", children : [], ownTicks : 0, ticks : 0 }; |
+ constructor(filter, withCategories) { |
+ if (withCategories) { |
+ let { categories, root } = buildCategoryTreeAndLookup(); |
+ this.tree = root; |
+ this.categories = categories; |
+ } else { |
+ this.tree = { name : "root", children : [], ownTicks : 0, ticks : 0 }; |
+ this.categories = null; |
+ } |
+ |
this.codeVisited = []; |
this.filter = filter; |
} |
- addStack(file, timestamp, vmState, stack) { |
+ addStack(file, tickIndex) { |
+ let stack = file.ticks[tickIndex].s; |
+ let vmState = file.ticks[tickIndex].vm; |
+ |
this.tree.ticks++; |
let child = null; |
+ let tree = null; |
for (let i = stack.length - 2; i >= 0; i -= 2) { |
let codeId = stack[i]; |
if (codeId < 0 || this.codeVisited[codeId]) continue; |
@@ -202,16 +298,38 @@ class FunctionListTree { |
let kind = code ? code.kind : undefined; |
if (!this.filter(type, kind)) continue; |
} |
- child = this.tree.children[codeId]; |
+ let childId = childIdFromCode(codeId, code); |
+ if (this.categories) { |
+ let kind = resolveCodeKindAndVmState(code, vmState); |
+ tree = this.categories[kind]; |
+ } else { |
+ tree = this.tree; |
+ } |
+ child = tree.children[childId]; |
if (!child) { |
child = createNodeFromStackEntry(code); |
- this.tree.children[codeId] = child; |
+ child.children[0] = createEmptyNode("Top-down tree"); |
+ child.children[0].delayedExpansion = |
+ { frameList : [], ascending : false }; |
+ child.children[1] = createEmptyNode("Bottom-up tree"); |
+ child.children[1].delayedExpansion = |
+ { frameList : [], ascending : true }; |
+ tree.children[childId] = child; |
} |
child.ticks++; |
+ child.children[0].ticks++; |
+ addFrameToFrameList( |
+ child.children[0].delayedExpansion.frameList, tickIndex, i); |
+ child.children[1].ticks++; |
+ addFrameToFrameList( |
+ child.children[1].delayedExpansion.frameList, tickIndex, i); |
this.codeVisited[codeId] = true; |
} |
if (child) { |
child.ownTicks++; |
+ console.assert(tree !== null); |
+ tree.ticks++; |
+ console.assert(tree.type === "CAT"); |
} |
for (let i = 0; i < stack.length; i += 2) { |
@@ -240,7 +358,9 @@ class CategorySampler { |
} |
} |
- addStack(file, timestamp, vmState, stack) { |
+ addStack(file, tickIndex) { |
+ let { tm : timestamp, vm : vmState, s : stack } = file.ticks[tickIndex]; |
+ |
let i = Math.floor((timestamp - this.firstTime) / this.step); |
if (i == this.buckets.length) i--; |
console.assert(i >= 0 && i < this.buckets.length); |
@@ -269,7 +389,7 @@ function generateTree( |
let tickCount = 0; |
while (i < ticks.length && ticks[i].tm < endTime) { |
- tree.addStack(file, ticks[i].tm, ticks[i].vm, ticks[i].s); |
+ tree.addStack(file, i); |
i++; |
tickCount++; |
} |