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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 20 additions & 0 deletions atom/browser/api/atom_api_web_contents.cc
Expand Up @@ -50,6 +50,7 @@
#include "atom/common/options_switches.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_restrictions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/values.h"
#include "brightray/browser/inspectable_web_contents.h"
Expand Down Expand Up @@ -1985,6 +1986,24 @@ void WebContents::GrantOriginAccess(const GURL& url) {
url::Origin::Create(url));
}

bool WebContents::TakeHeapSnapshot(const base::FilePath& file_path,
const std::string& channel) {
base::ThreadRestrictions::ScopedAllowIO allow_io;

base::File file(file_path,
base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
if (!file.IsValid())
return false;

auto* frame_host = web_contents()->GetMainFrame();
if (!frame_host)
return false;

return frame_host->Send(new AtomFrameMsg_TakeHeapSnapshot(
frame_host->GetRoutingID(),
IPC::TakePlatformFileForTransit(std::move(file)), channel));
}

// static
void WebContents::BuildPrototype(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> prototype) {
Expand Down Expand Up @@ -2081,6 +2100,7 @@ void WebContents::BuildPrototype(v8::Isolate* isolate,
.SetMethod("getWebRTCIPHandlingPolicy",
&WebContents::GetWebRTCIPHandlingPolicy)
.SetMethod("_grantOriginAccess", &WebContents::GrantOriginAccess)
.SetMethod("_takeHeapSnapshot", &WebContents::TakeHeapSnapshot)
.SetProperty("id", &WebContents::ID)
.SetProperty("session", &WebContents::Session)
.SetProperty("hostWebContents", &WebContents::HostWebContents)
Expand Down
3 changes: 3 additions & 0 deletions atom/browser/api/atom_api_web_contents.h
Expand Up @@ -250,6 +250,9 @@ class WebContents : public mate::TrackableObject<WebContents>,
// the specified URL.
void GrantOriginAccess(const GURL& url);

bool TakeHeapSnapshot(const base::FilePath& file_path,
const std::string& channel);

// Properties.
int32_t ID() const;
v8::Local<v8::Value> Session(v8::Isolate* isolate);
Expand Down
5 changes: 5 additions & 0 deletions atom/common/api/api_messages.h
Expand Up @@ -10,6 +10,7 @@
#include "content/public/common/common_param_traits.h"
#include "content/public/common/referrer.h"
#include "ipc/ipc_message_macros.h"
#include "ipc/ipc_platform_file.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/ipc/gfx_param_traits.h"
#include "url/gurl.h"
Expand Down Expand Up @@ -76,3 +77,7 @@ IPC_SYNC_MESSAGE_ROUTED0_1(AtomFrameHostMsg_GetZoomLevel, double /* result */)
IPC_MESSAGE_ROUTED2(AtomFrameHostMsg_PDFSaveURLAs,
GURL /* url */,
content::Referrer /* referrer */)

IPC_MESSAGE_ROUTED2(AtomFrameMsg_TakeHeapSnapshot,
IPC::PlatformFileForTransit /* file_handle */,
std::string /* channel */)
15 changes: 15 additions & 0 deletions atom/common/api/atom_bindings.cc
Expand Up @@ -11,12 +11,15 @@
#include "atom/common/api/locker.h"
#include "atom/common/atom_version.h"
#include "atom/common/chrome_version.h"
#include "atom/common/heap_snapshot.h"
#include "atom/common/native_mate_converters/file_path_converter.h"
#include "atom/common/native_mate_converters/string16_converter.h"
#include "atom/common/node_includes.h"
#include "base/logging.h"
#include "base/process/process_info.h"
#include "base/process/process_metrics_iocounters.h"
#include "base/sys_info.h"
#include "base/threading/thread_restrictions.h"
#include "native_mate/dictionary.h"

namespace atom {
Expand Down Expand Up @@ -60,6 +63,7 @@ void AtomBindings::BindTo(v8::Isolate* isolate, v8::Local<v8::Object> process) {
dict.SetMethod("getCPUUsage", base::Bind(&AtomBindings::GetCPUUsage,
base::Unretained(metrics_.get())));
dict.SetMethod("getIOCounters", &GetIOCounters);
dict.SetMethod("takeHeapSnapshot", &TakeHeapSnapshot);
#if defined(OS_POSIX)
dict.SetMethod("setFdLimit", &base::SetFdLimit);
#endif
Expand Down Expand Up @@ -238,4 +242,15 @@ v8::Local<v8::Value> AtomBindings::GetIOCounters(v8::Isolate* isolate) {
return dict.GetHandle();
}

// static
bool AtomBindings::TakeHeapSnapshot(v8::Isolate* isolate,
const base::FilePath& file_path) {
base::ThreadRestrictions::ScopedAllowIO allow_io;

base::File file(file_path,
base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);

return atom::TakeHeapSnapshot(isolate, &file);
}

} // namespace atom
3 changes: 3 additions & 0 deletions atom/common/api/atom_bindings.h
Expand Up @@ -8,6 +8,7 @@
#include <list>
#include <memory>

#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/process/process_metrics.h"
#include "base/strings/string16.h"
Expand Down Expand Up @@ -43,6 +44,8 @@ class AtomBindings {
static v8::Local<v8::Value> GetCPUUsage(base::ProcessMetrics* metrics,
v8::Isolate* isolate);
static v8::Local<v8::Value> GetIOCounters(v8::Isolate* isolate);
static bool TakeHeapSnapshot(v8::Isolate* isolate,
const base::FilePath& file_path);

private:
void ActivateUVLoop(v8::Isolate* isolate);
Expand Down
56 changes: 56 additions & 0 deletions atom/common/heap_snapshot.cc
@@ -0,0 +1,56 @@
// Copyright (c) 2018 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.

#include "atom/common/heap_snapshot.h"

#include "v8/include/v8-profiler.h"

namespace {

class HeapSnapshotOutputStream : public v8::OutputStream {
public:
explicit HeapSnapshotOutputStream(base::File* file) : file_(file) {
DCHECK(file_);
}

bool IsComplete() const { return is_complete_; }

// v8::OutputStream
int GetChunkSize() override { return 65536; }
void EndOfStream() override { is_complete_ = true; }

v8::OutputStream::WriteResult WriteAsciiChunk(char* data, int size) override {
auto bytes_written = file_->WriteAtCurrentPos(data, size);
return bytes_written == size ? kContinue : kAbort;
}

private:
base::File* file_ = nullptr;
bool is_complete_ = false;
};

} // namespace

namespace atom {

bool TakeHeapSnapshot(v8::Isolate* isolate, base::File* file) {
DCHECK(isolate);
DCHECK(file);

if (!file->IsValid())
return false;

auto* snapshot = isolate->GetHeapProfiler()->TakeHeapSnapshot();
if (!snapshot)
return false;

HeapSnapshotOutputStream stream(file);
snapshot->Serialize(&stream, v8::HeapSnapshot::kJSON);

const_cast<v8::HeapSnapshot*>(snapshot)->Delete();

return stream.IsComplete();
}

} // namespace atom
17 changes: 17 additions & 0 deletions atom/common/heap_snapshot.h
@@ -0,0 +1,17 @@
// Copyright (c) 2018 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.

#ifndef ATOM_COMMON_HEAP_SNAPSHOT_H_
#define ATOM_COMMON_HEAP_SNAPSHOT_H_

#include "base/files/file.h"
#include "v8/include/v8.h"

namespace atom {

bool TakeHeapSnapshot(v8::Isolate* isolate, base::File* file);

} // namespace atom

#endif // ATOM_COMMON_HEAP_SNAPSHOT_H_
21 changes: 20 additions & 1 deletion atom/renderer/atom_render_frame_observer.cc
Expand Up @@ -9,20 +9,22 @@

#include "atom/common/api/api_messages.h"
#include "atom/common/api/event_emitter_caller.h"
#include "atom/common/heap_snapshot.h"
#include "atom/common/native_mate_converters/value_converter.h"
#include "atom/common/node_includes.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/thread_restrictions.h"
#include "base/trace_event/trace_event.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_view.h"
#include "ipc/ipc_message_macros.h"
#include "native_mate/dictionary.h"
#include "net/base/net_module.h"
#include "net/grit/net_resources.h"
#include "third_party/blink/public/web/blink.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_draggable_region.h"
#include "third_party/blink/public/web/web_element.h"
#include "third_party/blink/public/web/blink.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/public/web/web_script_source.h"
#include "ui/base/resource/resource_bundle.h"
Expand Down Expand Up @@ -161,6 +163,7 @@ bool AtomRenderFrameObserver::OnMessageReceived(const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(AtomRenderFrameObserver, message)
IPC_MESSAGE_HANDLER(AtomFrameMsg_Message, OnBrowserMessage)
IPC_MESSAGE_HANDLER(AtomFrameMsg_TakeHeapSnapshot, OnTakeHeapSnapshot)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()

Expand Down Expand Up @@ -195,6 +198,22 @@ void AtomRenderFrameObserver::OnBrowserMessage(bool send_to_all,
}
}

void AtomRenderFrameObserver::OnTakeHeapSnapshot(
IPC::PlatformFileForTransit file_handle,
const std::string& channel) {
base::ThreadRestrictions::ScopedAllowIO allow_io;

auto file = IPC::PlatformFileForTransitToFile(file_handle);
bool success = TakeHeapSnapshot(blink::MainThreadIsolate(), &file);

base::ListValue args;
args.AppendString(channel);
args.AppendBoolean(success);

render_frame_->Send(new AtomFrameHostMsg_Message(
render_frame_->GetRoutingID(), "ipc-message", args));
}

void AtomRenderFrameObserver::EmitIPCEvent(blink::WebLocalFrame* frame,
const std::string& channel,
const base::ListValue& args,
Expand Down
3 changes: 3 additions & 0 deletions atom/renderer/atom_render_frame_observer.h
Expand Up @@ -10,6 +10,7 @@
#include "atom/renderer/renderer_client_base.h"
#include "base/strings/string16.h"
#include "content/public/renderer/render_frame_observer.h"
#include "ipc/ipc_platform_file.h"
#include "third_party/blink/public/web/web_local_frame.h"

namespace base {
Expand Down Expand Up @@ -57,6 +58,8 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver {
const std::string& channel,
const base::ListValue& args,
int32_t sender_id);
void OnTakeHeapSnapshot(IPC::PlatformFileForTransit file_handle,
const std::string& channel);

content::RenderFrame* render_frame_;
RendererClientBase* renderer_client_;
Expand Down
8 changes: 8 additions & 0 deletions docs/api/process.md
Expand Up @@ -171,6 +171,14 @@ Returns `Object`:
Returns an object giving memory usage statistics about the entire system. Note
that all statistics are reported in Kilobytes.

### `process.takeHeapSnapshot(filePath)`

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

Returns `Boolean` - Indicates whether the snapshot has been created successfully.

Takes a V8 heap snapshot and saves it to `filePath`.

### `process.hang()`

Causes the main thread of the current process hang.
Expand Down
8 changes: 8 additions & 0 deletions docs/api/web-contents.md
Expand Up @@ -1497,6 +1497,14 @@ Returns `Integer` - The Chromium internal `pid` of the associated renderer. Can
be compared to the `frameProcessId` passed by frame specific navigation events
(e.g. `did-frame-navigate`)

#### `contents.takeHeapSnapshot(filePath)`

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

Returns `Promise` - Indicates whether the snapshot has been created successfully.

Takes a V8 heap snapshot and saves it to `filePath`.

### Instance Properties

#### `contents.id`
Expand Down
2 changes: 2 additions & 0 deletions filenames.gni
Expand Up @@ -486,6 +486,8 @@ filenames = {
"atom/common/draggable_region.cc",
"atom/common/draggable_region.h",
"atom/common/google_api_key.h",
"atom/common/heap_snapshot.cc",
"atom/common/heap_snapshot.h",
"atom/common/key_weak_map.h",
"atom/common/keyboard_util.cc",
"atom/common/keyboard_util.h",
Expand Down
16 changes: 16 additions & 0 deletions lib/browser/api/web-contents.js
Expand Up @@ -160,6 +160,22 @@ WebContents.prototype.executeJavaScript = function (code, hasUserGesture, callba
}
}

WebContents.prototype.takeHeapSnapshot = function (filePath) {
return new Promise((resolve, reject) => {
const channel = `ELECTRON_TAKE_HEAP_SNAPSHOT_RESULT_${getNextId()}`
ipcMain.once(channel, (event, success) => {
if (success) {
resolve()
} else {
reject(new Error('takeHeapSnapshot failed'))
}
})
if (!this._takeHeapSnapshot(filePath, channel)) {
ipcMain.emit(channel, false)
}
})
}

// Translate the options of printToPDF.
WebContents.prototype.printToPDF = function (options, callback) {
const printingSetting = Object.assign({}, defaultPrintingSetting)
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -13,7 +13,7 @@
"dugite": "^1.45.0",
"electabul": "~0.0.4",
"electron-docs-linter": "^2.3.4",
"electron-typescript-definitions": "^2.0.0",
"electron-typescript-definitions": "^2.0.1",
"eslint": "^5.6.0",
"eslint-config-standard": "^12.0.0",
"eslint-plugin-mocha": "^5.2.0",
Expand Down
32 changes: 32 additions & 0 deletions spec/api-process-spec.js
@@ -1,3 +1,7 @@
const { remote } = require('electron')
const fs = require('fs')
const path = require('path')

const { expect } = require('chai')

describe('process module', () => {
Expand Down Expand Up @@ -67,4 +71,32 @@ describe('process module', () => {
expect(heapStats.doesZapGarbage).to.be.a('boolean')
})
})

describe('process.takeHeapSnapshot()', () => {
it('returns true on success', () => {
const filePath = path.join(remote.app.getPath('temp'), 'test.heapsnapshot')

const cleanup = () => {
try {
fs.unlinkSync(filePath)
} catch (e) {
// ignore error
}
}

try {
const success = process.takeHeapSnapshot(filePath)
expect(success).to.be.true()
const stats = fs.statSync(filePath)
expect(stats.size).not.to.be.equal(0)
} finally {
cleanup()
}
})

it('returns false on failure', () => {
miniak marked this conversation as resolved.
Show resolved Hide resolved
const success = process.takeHeapSnapshot('')
expect(success).to.be.false()
})
})
})