-
Notifications
You must be signed in to change notification settings - Fork 244
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
Conversation
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.
Wooooaaaah cool! Thanks for doing this and nice work figuring out where to put the necessary code!
Before this can land, in addition to the code review comments that need resolving, I need a few things:
- Instructions for how to record a heap allocation profile like this to be placed into the PR description (I'll use that to update the wiki page: https://github.com/jlfwong/speedscope/wiki/Importing-from-Node.js)
- A minimal checked in example of a heap allocation profile placed in this directory within a subdirectory for the relevant node version https://github.com/jlfwong/speedscope/tree/master/sample/profiles/node
- Leveraging the checked in allocation profile in 2, a test which imports the profile and asserts a snapshot of the contents, like this: https://github.com/jlfwong/speedscope/blob/master/src/import/v8proflog.test.ts
I also don't use yarn for this project, so if you could please remove the yarn.lock file from this PR, that would be much appreciated!
@@ -103,6 +104,9 @@ async function _importProfileGroup(dataSource: ProfileDataSource): Promise<Profi | |||
} else if (fileName.endsWith('.v8log.json')) { | |||
console.log('Importing as --prof-process v8 log') | |||
return toGroup(importFromV8ProfLog(JSON.parse(contents))) | |||
} else if (fileName.endsWith('.heapprofile')) { |
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:
'head' in parsed && 'callFrame' in parsed['head']
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
src/import/v8heapalloc.ts
Outdated
} | ||
computeId(chromeProfile.head) | ||
|
||
// compute the total size |
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.
A small stylistic thing: I try to start all my comments with a capitalized letter
@@ -0,0 +1,97 @@ | |||
import {Profile, FrameInfo, StackListProfileBuilder} from '../lib/profile' |
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.
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?
src/import/v8heapalloc.ts
Outdated
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 comment
The 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:
const stacks: HeapProfileNode[][] = []
for (let currentNode of nodeById.values()) {
...
}
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 comment
The 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 for
as requested
src/import/v8heapalloc.ts
Outdated
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 comment
The 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:
if (currentParent.parent == null) break
(intentionally using ==
so that it captures both undefined
and null
, but still not 0
, because JavaScript is a craaazy language)
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.
true, i switched to a null check but with a strict comparaison (see my comment bellow)
while (true) { | ||
if (typeof currentNode.parent !== 'number') break | ||
const parent = nodeById.get(currentNode.parent) | ||
if (parent === undefined) break |
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.
In this case I would just do if (!parent) break
, since any node returned by nodeById.get(...)
will not be falsy. if (parent != null)
would also be fine.
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.
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
Since any Map return undefined
if no entry is found, i believe we can check only for undefined
const parent = nodeById.get(currentNode.parent) | ||
if (parent === undefined) break | ||
// push the parent at the begining of the stack | ||
stack.unshift(parent) |
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.
Weird! I would've expected doing repeated stack.push(parent)
followed by a stack.reverse()
at the end to be more efficient than doing repeated unshift
calls, but it looks like that's wrong? https://jsperf.com/array-unshift-vs-array-push-and-array-reverse. I would've thought that arrays would be implemented as vectors, making unshift
an O(n) operation, but it looks like it's optimized really well somehow.
src/import/v8heapalloc.ts
Outdated
if (typeof currentNode.parent !== 'number') break | ||
const parent = nodeById.get(currentNode.parent) | ||
if (parent === undefined) break | ||
// push the parent at the begining of the stack |
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.
typo: "beginning"
src/import/v8heapalloc.ts
Outdated
|
||
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 comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can just do for (let stack of stacks) { ... }
here. TypeScript will compile it down to roughly the same thing, and it doesn't look like you need the index i
anywhere else in the loop.
First round of fix, i will the documentation/test later in the day |
Importing a Heap Profile from the Chrome inspectorLike the CPU Profiling, you can use the Chrome Inspector to record an heap allocation profile, just launch your process with the Click the start button to start recording a profile. Now, use your application like you want to and click on the Click on "save" to save the recorded profile. Save it with the default You should be able to open that file in https://www.speedscope.app/. To view it offline, you'll need to install speedscope via
Then you should be able to open it like so:
|
I'm not sure to understand why the test are failing, the CI only log :
Do you have any idea why ? |
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.
Sweet! I think all that's left is to run npm run jest -- -u
on this branch & push the result. Once that's done, CI should pass, and I can merge and deploy the result. Thanks for following through with PR feedback so quickly
import {checkProfileSnapshot} from '../lib/test-utils' | ||
|
||
test('importV8HeapAlloc from Chrome', async () => { | ||
await checkProfileSnapshot('./sample/profiles/Chrome/69/Heap-20181005T144546.heapprofile') |
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.
The way these tests work is by generating output, then comparing against output that's checked into the repository. To seed the initial data, you'll need to run npm run jest -- -u
, which should create local snapshot files that you'll need to check into the repository. I believe that should result in CI passing.
CI is currently failing because jest is run in a mode which does not write snapshots to disk, it only compares them. It doesn't find a corresponding snapshot on disk to compare against since none was checked in, resulting in a test failure.
import {getOrInsert} from '../lib/utils' | ||
import {ByteFormatter} from '../lib/value-formatters' | ||
|
||
/** |
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.
This is perfect! Thanks for writing this :)
And done ! thanks for the help for generating the snapshot. Just to tell you also, i'm working to support an old cpu profile format of v8 (mainly outputted by https://github.com/hyj1991/v8-profiler-node8), which is the current format we use ourselves, so an PR will arrive this week i believe ;) |
@vmarchaud thanks for the hard work! This is now merged, published to npm, and deployed to https://www.speedscope.app/. I also updated the documentation here: https://github.com/jlfwong/speedscope/wiki/Importing-from-Node.js#importing-a-heap-profile
Sounds good to me 😄 |
As said on #170, i added the support for the old format used by https://github.com/hyj1991/v8-profiler-node8 (which is currently used for pm2.io).
First PR here, but first thanks for the awesome tool !
We plan to integrate it into our product (https://pm2.io/) to visualize all kind of profile that we can take out of NodeJS, for this we needed to be able to render heap allocation that are done by V8 so here it is !
Sorry for not opening an issue first, we just wanted to see if we were able to implement it before bothering you
Regards