Skip to content

Commit

Permalink
feat: add process.takeHeapSnapshot() / webContents.takeHeapSnapshot()
Browse files Browse the repository at this point in the history
  • Loading branch information
miniak committed Sep 17, 2018
1 parent ab47ad7 commit 83b7f23
Show file tree
Hide file tree
Showing 15 changed files with 259 additions and 1 deletion.
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 */)
18 changes: 18 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,18 @@ v8::Local<v8::Value> AtomBindings::GetIOCounters(v8::Isolate* isolate) {
return dict.GetHandle();
}

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

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

if (!atom::TakeHeapSnapshot(isolate, &file)) {
args->ThrowError("takeHeapSnapshot failed");
}
}

} // namespace atom
4 changes: 4 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,9 @@ class AtomBindings {
static v8::Local<v8::Value> GetCPUUsage(base::ProcessMetrics* metrics,
v8::Isolate* isolate);
static v8::Local<v8::Value> GetIOCounters(v8::Isolate* isolate);
static void TakeHeapSnapshot(v8::Isolate* isolate,
mate::Arguments* args,
const base::FilePath& file_path);

private:
void ActivateUVLoop(v8::Isolate* isolate);
Expand Down
54 changes: 54 additions & 0 deletions atom/common/heap_snapshot.cc
@@ -0,0 +1,54 @@
// 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();

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
7 changes: 7 additions & 0 deletions docs/api/process.md
Expand Up @@ -171,6 +171,13 @@ 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.

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

### `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
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('does not throw on success', () => {
const filePath = path.join(remote.app.getPath('temp'), 'test.heapsnapshot')

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

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

it('throws an exception on failure', () => {
expect(() => {
process.takeHeapSnapshot('')
}).to.throw('takeHeapSnapshot failed')
})
})
})

0 comments on commit 83b7f23

Please sign in to comment.