Skip to content
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

feat: use inspector for heap profiles #236

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

psx95
Copy link
Contributor

@psx95 psx95 commented Nov 26, 2022

For heap profiling, replaces the native C++ addon with calls to Node's inspector module. This uses the same V8::CpuProfiler under the hood but communicates over the chrome devtools protocol.

A similar PR has been raised to determine viability of using Node's inspector module for time profiling.

Testing

  • Able to produce heap profiles which can be viewed using the pprof tool.
  • All unit tests are passing.

Caveats

  • Although no the stackDepth parameter to the heap profile is still preserved here to avoid breaking changes, this parameter is a no-op now. Stack depth will default to 128. This is based on the native source code of the V8 heap profiler.
  • The memory usage number seem a bit off when compared to what was generated by the native C++ addon. There are images of flame graphs attached to highlight this difference.

Flame graph generated using the in-built inspector

in-built-inspector

Flame graph generated using the C++ addon

original-implementation

The graphs look similar, but there are differences, mostly due to the serializeHeapProfile task. When using the in-built inspector, this process takes about 1.55MB, while in the implementation that used C++ addon, this process took 0.81MB.

This accounts for most difference b/w the memory usage reported by the different profilers.

@psx95 psx95 self-assigned this Nov 26, 2022
@conventional-commit-lint-gcf
Copy link

🤖 I detect that the PR title and the commit message differ and there's only one commit. To use the PR title for the commit history, you can use Github's automerge feature with squashing, or use automerge label. Good luck human!

-- conventional-commit-lint bot
https://conventionalcommits.org/


export function startSamplingHeapProfiler(
heapIntervalBytes: number,
stackDepth: number
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove "stack depth", since it's no longer in use.

{heapIntervalBytes},
(err: Error | null): void => {
if (err !== null) {
console.error(`Error starting heap sampling: ${err}`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing should be logged within this repo (cloud-profiler-nodejs will log errors occurring; logging anything directly here will either result in things being double logged, or prevent users from being able to control what is logged)

@@ -84,17 +83,17 @@ export function profile(
* started with different parameters, this throws an error.
*
* @param intervalBytes - average number of bytes between samples.
* @param stackDepth - maximum stack depth for samples collected.
* @param stackDepth - maximum stack depth for samples collected. This is currently no-op.
* Default stack depth of 128 will be used. Kept to avoid making breaking change.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd favor making the breaking change here (over keeping behavior which no longer works).

@nolanmar511
Copy link
Contributor

Kokoro system tests have failed with the following (suggesting that maybe the heap profile isn't being written out successfully any more):

+ pprof -filefunctions -top -nodecount=2 heap.pb.gz
+ grep 'busyLoop.*src/busybench.js'
+ tee
heap.pb.gz: parsing profile: empty input file
pprof: failed to fetch any source profiles


/**
* Need to create this interface since the type definitions file for node inspector
* at https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/node/inspector.d.ts
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might open an issue in that repo and link it here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, will do that.

result.profile as CompatibleSamplingHeapProfile;
resolve(
translateToAllocationProfileNode(
compatibleHeapProfile.head,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit since these are the only two fields in compatibleHeapProfile, just pass it directly here

children: [],
};

const children: AllocationProfileNode[] = new Array<AllocationProfileNode>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

node.children.map() should give the same result without all the boilerplate?

nodeId: number,
samples: SamplingHeapProfileSample[]
): SamplingHeapProfileSample[] {
const filtered = samples.filter((sample: SamplingHeapProfileSample) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd create a map in the outer scope or closure of the recursive function to do this lookup in constant time as this is n^2 now. Check out my PR for cpu time if you don't what i mean

return filtered;
}

function createAllocationsFromSamplesForNode(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldnt this be done once in linear time for all nodes, since the samples already have node ID? Group by node ID then for each group, group by byte sizes being equal?

);
}
heapIntervalBytes = intervalBytes;
heapStackDepth = stackDepth;
startSamplingHeapProfiler(heapIntervalBytes, heapStackDepth);
startSamplingHeapProfiler(heapIntervalBytes, stackDepth);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function will return before the promise completes as it's fire and forget with this promise, might be why the test is failing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants