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: add process.takeHeapSnapshot() / webContents.takeHeapSnapshot() #14456

Merged
merged 1 commit into from Sep 18, 2018

Conversation

miniak
Copy link
Contributor

@miniak miniak commented Sep 4, 2018

Description of Change

There is a node module for getting V8 heap snapshots https://github.com/bnoordhuis/node-heapdump, but it cannot be used in sandboxed renderers due to node not being available. Currently the only way to do this programmatically is to expose this API in Electron.

This PR is adding these new methods:

  • process.takeHeapSnapshot() - allows the main process and non-sandboxed renderers to take its own V8 heap snapshot
  • webContents.takeHeapSnapshot() - allows the main process to ask any renderer (including sandboxed ones) to create its V8 heap snapshot
Checklist
  • PR description included and stakeholders cc'd
  • npm test passes
  • tests are changed or added
  • relevant documentation is changed or added
  • PR title follows semantic commit guidelines
Release Notes

Notes: Added process.getHeapSnapshot() / webContents.takeHeapSnapshot() for taking V8 heap snapshots.

@miniak miniak requested a review from a team September 4, 2018 22:20
@miniak miniak force-pushed the miniak/heap-snapshot branch 4 times, most recently from 7d30e10 to ab6a9fe Compare September 4, 2018 22:46
@miniak miniak force-pushed the miniak/heap-snapshot branch 4 times, most recently from 58ec892 to a238aa2 Compare September 5, 2018 18:19
@alexeykuzmin
Copy link
Contributor

there is a bug causing boolean return types for processobject methods to be incorrectly generated as Electron.Boolean, which causes failures when validating electron.d.ts.

@MarshallOfSound FYI

@miniak miniak force-pushed the miniak/heap-snapshot branch 2 times, most recently from e662710 to 139eb32 Compare September 5, 2018 19:11
@MarshallOfSound
Copy link
Member

@miniak The bug to which you refer was fixed in electron/typescript-definitions#107 if you are up to date with latest master and have the latest version of electron-typescript-definitions installed primitives should work fine in process modifications 🤔

@miniak miniak force-pushed the miniak/heap-snapshot branch 3 times, most recently from 0a9532f to a2fa4cf Compare September 6, 2018 19:01
@miniak miniak requested a review from a team September 6, 2018 19:01
@miniak miniak changed the title [wip] feat: add process.getHeapSnapshot() [wip] feat: add process.takeHeapSnapshot() Sep 6, 2018
@miniak miniak force-pushed the miniak/heap-snapshot branch 4 times, most recently from e4ca655 to 174dd5a Compare September 12, 2018 21:52
@alexeykuzmin
Copy link
Contributor

@miniak

  1. Please make sure that "electron-lint" passes. You might need to rebase this PR onto the latest master to fix that check.
  2. If you're done with the implementation, please remove the [wip] suffix.

Copy link
Contributor

@alexeykuzmin alexeykuzmin left a comment

Choose a reason for hiding this comment

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

👍

@@ -171,6 +171,10 @@ Returns `Object`:
Returns an object giving memory usage statistics about the entire system. Note
that all statistics are reported in Kilobytes.

### `process.takeHeapSnapshot()`

Returns `String` - The file path if the heap snapshot has been created successfully.
Copy link
Contributor

Choose a reason for hiding this comment

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

The method has to be synchronous, right?

What is returned if there is an error?
If a method throws an exception, it has to be documented.

}

if (!file.IsValid()) {
args->ThrowError("takeHeapSnapshot failed - cannot create file");
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you please move all error messages to constants in an anonymous namespace on the top of the file?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

why? we're not doing that it other places

atom/common/api/atom_bindings.cc Outdated Show resolved Hide resolved
@miniak miniak changed the title [wip] feat: add process.takeHeapSnapshot() [wip] feat: add process.takeHeapSnapshot() / webContents.takeHeapSnapshot() Sep 15, 2018
Copy link
Member

@deepak1556 deepak1556 left a comment

Choose a reason for hiding this comment

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

The implementation looks good wrt to file handling for a sandboxed process, Is there any reason this api has to be a synchronous version ? Making it async would allow us to avoid sync IPC as well avoid file operations on the UI task runner, which would be a good thing.

@miniak
Copy link
Contributor Author

miniak commented Sep 16, 2018

@deepak1556, @alexeykuzmin I've refactored the implementation significantly, the file handle is now pushed from the main process when webContents.takeHeapSnapshot() is used. process.takeHeapSnapshot() can only be used in non-sandboxed processes. It's more secure like that.

@miniak
Copy link
Contributor Author

miniak commented Sep 16, 2018

@deepak1556 I am not sure if it is possible to offload this to a background thread. I've tried taking the snapshot on the main thread and then saving it on IO thread, it didn't work.

const filePath = path.join(remote.app.getPath('userData'), 'test.heapsnapshot')
const success = process.takeHeapSnapshot(filePath)
expect(success).to.be.true()
fs.unlink(filePath, () => done())
Copy link
Contributor

Choose a reason for hiding this comment

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

Please call expect after the unlink.
And it would be better to call it sync version fs.unlinkSync()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

refactored a bit differently

spec/api-process-spec.js Outdated Show resolved Hide resolved
spec/api-web-contents-spec.js Outdated Show resolved Hide resolved
const filePath = path.join(remote.app.getPath('userData'), 'test.heapsnapshot')

afterEach(done => {
fs.unlink(filePath, () => done())
Copy link
Contributor

Choose a reason for hiding this comment

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

Please call in a beforeEach hook.
afterEach won't be called for failed tests =/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

refactored a bit differently

Copy link
Contributor

@alexeykuzmin alexeykuzmin left a comment

Choose a reason for hiding this comment

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

Please replace "it('returns false on failure'" with "it('throws an exception on failure'" in the tests.
👍

@miniak miniak force-pushed the miniak/heap-snapshot branch 2 times, most recently from d4305d6 to 83b7f23 Compare September 17, 2018 21:35
@miniak
Copy link
Contributor Author

miniak commented Sep 17, 2018

@MarshallOfSound I've changed the implementation a bit and I don't need to return a string anymore. The filePath is now passed to the method. I just need to return whether it succeeded or failed. Boolean also has issues so I just throw an exception when it fails.

* `filePath` String - Path to the output file.

Takes a V8 heap snapshot and saves it to `filePath`.
Throws an exception in case of failure.
Copy link
Member

Choose a reason for hiding this comment

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

We shouldn't make a worse API because the docs generator is breaking the PR. This should be returning a success state not throwing an error. Working around a docs generator bug by changing the API is a bad idea.

Requesting changes and as mentioned I'll look into the docs generator issue

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@MarshallOfSound I get your point, but is it actually worse? An exception can give you potentially much more information why something failed than just a false return value.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

@miniak It was published when that github release went out an hour or so ago --> https://www.npmjs.com/package/electron-typescript-definitions/v/2.0.1

It's an automated pipeline

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The npmjs webpage took a while to reflect the update.

Copy link
Contributor

Choose a reason for hiding this comment

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

Working around a docs generator bug by changing the API is a bad idea.

I agree that generally it is a bad idea.

This should be returning a success state not throwing an error.

But in this case I would argue.
In most cases an exception will be thrown because of a user "error", like an invalid file path or lack of permission to write to a specified location.
Returning just a boolean "state" value simply doesn't provide any useful feedback to a user.

Copy link
Member

Choose a reason for hiding this comment

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

@alexeykuzmin Sure if we have things like EACCESS, EPERM and other such standard file errors then throwing them to be consistent with things like node fs makes sense. From what I saw this was just throwing a generic "something went wrong" error which is just as helpful as returning false but harder / more annoying to catch.

@miniak
Copy link
Contributor Author

miniak commented Sep 18, 2018

@MarshallOfSound electron-typescript-definitions bumped to 2.0.1, boolean return value restored.

@miniak
Copy link
Contributor Author

miniak commented Sep 18, 2018

@MarshallOfSound I am going to push a small fix

diff --git a/atom/common/heap_snapshot.cc b/atom/common/heap_snapshot.cc
index d66ec474c..bb36e98f5 100644
--- a/atom/common/heap_snapshot.cc
+++ b/atom/common/heap_snapshot.cc
@@ -42,6 +42,8 @@ bool TakeHeapSnapshot(v8::Isolate* isolate, base::File* file) {
     return false;
 
   auto* snapshot = isolate->GetHeapProfiler()->TakeHeapSnapshot();
+  if (!snapshot)
+    return false;
 
   HeapSnapshotOutputStream stream(file);
   snapshot->Serialize(&stream, v8::HeapSnapshot::kJSON);

@codebytere codebytere merged commit e22142e into master Sep 18, 2018
@release-clerk
Copy link

release-clerk bot commented Sep 18, 2018

Release Notes Persisted

Added process.getHeapSnapshot() / webContents.takeHeapSnapshot() for taking V8 heap snapshots.

@deepak1556 deepak1556 deleted the miniak/heap-snapshot branch September 18, 2018 18:02
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.

None yet

5 participants