New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add import of v8 heap allocation profile #170
Changes from 1 commit
6ac972f
873d5d3
700a7ff
8021713
bbc6df8
b0d4589
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import {Profile, FrameInfo, StackListProfileBuilder} from '../lib/profile' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a URL you can add to a comment here that provides documentation on this file format? Or at least a link that explains how to record this kind of profile? |
||
import {getOrInsert} from '../lib/utils' | ||
import {ByteFormatter} from '../lib/value-formatters' | ||
|
||
interface HeapProfileCallFrame { | ||
columnNumber: number | ||
functionName: string | ||
lineNumber: number | ||
scriptId: string | ||
url: string | ||
} | ||
|
||
interface HeapProfileNode { | ||
callFrame: HeapProfileCallFrame | ||
selfSize: number | ||
children: HeapProfileNode[] | ||
id: number | ||
parent?: number | ||
totalSize: number | ||
} | ||
|
||
interface HeapProfile { | ||
head: HeapProfileNode | ||
} | ||
|
||
const callFrameToFrameInfo = new Map<HeapProfileCallFrame, FrameInfo>() | ||
function frameInfoForCallFrame(callFrame: HeapProfileCallFrame) { | ||
return getOrInsert(callFrameToFrameInfo, callFrame, callFrame => { | ||
const name = callFrame.functionName || '(anonymous)' | ||
const file = callFrame.url | ||
const line = callFrame.lineNumber | ||
const col = callFrame.columnNumber | ||
return { | ||
key: `${name}:${file}:${line}:${col}`, | ||
name, | ||
file, | ||
line, | ||
col, | ||
} | ||
}) | ||
} | ||
|
||
export function importFromChromeHeapProfile(chromeProfile: HeapProfile): Profile { | ||
const nodeById = new Map<number, HeapProfileNode>() | ||
let currentId = 0 | ||
const computeId = (node: HeapProfileNode, parent?: HeapProfileNode) => { | ||
node.id = currentId++ | ||
nodeById.set(node.id, node) | ||
if (parent) { | ||
node.parent = parent.id | ||
} | ||
|
||
node.children.forEach(children => computeId(children, node)) | ||
} | ||
computeId(chromeProfile.head) | ||
|
||
// compute the total size | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A small stylistic thing: I try to start all my comments with a capitalized letter |
||
const computeTotalSize = (node: HeapProfileNode): number => { | ||
if (node.children.length === 0) return node.selfSize || 0 | ||
const totalChild = node.children.reduce((total: number, children) => { | ||
total += computeTotalSize(children) | ||
return total | ||
}, node.selfSize) | ||
node.totalSize = totalChild | ||
return totalChild | ||
} | ||
const total = computeTotalSize(chromeProfile.head) | ||
|
||
// compute all stacks by taking each last node and going upward | ||
const stacks: HeapProfileNode[][] = Array.from(nodeById.values()).map(currentNode => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a pretty small thing, but I generally use for loops rather than map for a slight performance boost. (https://jsperf.com/map-vs-for-loop-performance/6) So something like:
If I'm misunderstanding the perf implications here, please let me know! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I personally prefer to use native methods of Array for readability but i switched the logic to use |
||
let stack: HeapProfileNode[] = [] | ||
stack.push(currentNode) | ||
// while we found a parent | ||
while (true) { | ||
if (typeof currentNode.parent !== 'number') break | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is correct, but I found it a bit misleading to read. I think what this is checking is if the parent is undefined or not, but it shouldn't be possible or parent to be any type other than number or undefined. I think the idiomatic way of doing that would be:
(intentionally using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. true, i switched to a null check but with a strict comparaison (see my comment bellow) |
||
const parent = nodeById.get(currentNode.parent) | ||
if (parent === undefined) break | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this case I would just do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Performance wise, it's better to check against the type you need than relying on V8 adding the check for 0/null/undefined for you |
||
// push the parent at the begining of the stack | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo: "beginning" |
||
stack.unshift(parent) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Weird! I would've expected doing repeated |
||
currentNode = parent | ||
} | ||
return stack | ||
}) | ||
|
||
const profile = new StackListProfileBuilder(total) | ||
|
||
for (let i = 0; i < stacks.length; i++) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you can just do |
||
const stack = stacks[i] | ||
const lastFrame = stack[stack.length - 1] | ||
profile.appendSampleWithWeight( | ||
stack.map(frame => frameInfoForCallFrame(frame.callFrame)), | ||
lastFrame.selfSize, | ||
) | ||
} | ||
profile.setValueFormatter(new ByteFormatter()) | ||
return profile.build() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I try to make every kind of profile import even if it has the incorrect extension by using heuristics. Can you please add a sensible heuristic to the list below for the second pass?
Maybe something like:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went for
'head' in parsed && 'selfSize' in parsed['head']
because it's more specific to the heap profile format