Skip to content

Commit

Permalink
feat: add process.takeHeapSnapshot()
Browse files Browse the repository at this point in the history
  • Loading branch information
miniak committed Sep 12, 2018
1 parent d78a2a1 commit 174dd5a
Show file tree
Hide file tree
Showing 14 changed files with 227 additions and 1 deletion.
24 changes: 24 additions & 0 deletions atom/browser/api/atom_api_web_contents.cc
Expand Up @@ -28,6 +28,8 @@
#include "atom/browser/lib/bluetooth_chooser.h"
#include "atom/browser/native_window.h"
#include "atom/browser/net/atom_network_delegate.h"
#include "atom/common/api/heap_snapshot_output_stream.h"
#include "base/threading/thread_restrictions.h"
#if defined(ENABLE_OSR)
#include "atom/browser/osr/osr_output_device.h"
#include "atom/browser/osr/osr_render_widget_host_view.h"
Expand Down Expand Up @@ -307,6 +309,10 @@ struct WebContents::FrameDispatchHelper {
IPC::Message* message) {
api_web_contents->OnRendererMessageSync(rfh, channel, args, message);
}

void OnCreateHeapSnapshotFile(IPC::Message* message) {
api_web_contents->OnCreateHeapSnapshotFile(rfh, message);
}
};

WebContents::WebContents(v8::Isolate* isolate,
Expand Down Expand Up @@ -1044,6 +1050,9 @@ bool WebContents::OnMessageReceived(const IPC::Message& message,
IPC_MESSAGE_HANDLER(AtomAutofillFrameHostMsg_ShowPopup, ShowAutofillPopup)
IPC_MESSAGE_HANDLER(AtomAutofillFrameHostMsg_HidePopup, HideAutofillPopup)
#endif
IPC_MESSAGE_FORWARD_DELAY_REPLY(
AtomFrameHostMsg_CreateHeapSnapshotFile, &helper,
FrameDispatchHelper::OnCreateHeapSnapshotFile)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()

Expand Down Expand Up @@ -2116,6 +2125,21 @@ void WebContents::OnRendererMessageTo(content::RenderFrameHost* frame_host,
}
}

void WebContents::OnCreateHeapSnapshotFile(content::RenderFrameHost* frame_host,
IPC::Message* message) {
base::ScopedAllowBlockingForTesting allow_blocking;

auto file_path = HeapSnapshotOutputStream::GetFilePath();

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

AtomFrameHostMsg_CreateHeapSnapshotFile::WriteReplyParams(
message, file_path, IPC::TakePlatformFileForTransit(std::move(file)));

frame_host->Send(message);
}

// static
mate::Handle<WebContents> WebContents::CreateFrom(
v8::Isolate* isolate,
Expand Down
3 changes: 3 additions & 0 deletions atom/browser/api/atom_api_web_contents.h
Expand Up @@ -433,6 +433,9 @@ class WebContents : public mate::TrackableObject<WebContents>,
const std::string& channel,
const base::ListValue& args);

void OnCreateHeapSnapshotFile(content::RenderFrameHost* frame_host,
IPC::Message* message);

// Called when received a synchronous message from renderer to
// set temporary zoom level.
void OnSetTemporaryZoomLevel(content::RenderFrameHost* frame_host,
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_SYNC_MESSAGE_ROUTED0_2(AtomFrameHostMsg_CreateHeapSnapshotFile,
base::FilePath /* file_path */,
IPC::PlatformFileForTransit /* file_handle */)
64 changes: 64 additions & 0 deletions atom/common/api/atom_bindings.cc
Expand Up @@ -8,16 +8,22 @@
#include <iostream>
#include <string>

#include "atom/common/api/api_messages.h"
#include "atom/common/api/heap_snapshot_output_stream.h"
#include "atom/common/api/locker.h"
#include "atom/common/atom_version.h"
#include "atom/common/chrome_version.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 "content/public/renderer/render_frame.h"
#include "native_mate/dictionary.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "v8/include/v8-profiler.h"

namespace atom {

Expand All @@ -35,6 +41,14 @@ void FatalErrorCallback(const char* location, const char* message) {
AtomBindings::Crash();
}

content::RenderFrame* GetCurrentRenderFrame() {
auto* frame = blink::WebLocalFrame::FrameForCurrentContext();
if (!frame)
return nullptr;

return content::RenderFrame::FromWebFrame(frame);
}

} // namespace

AtomBindings::AtomBindings(uv_loop_t* loop) {
Expand All @@ -60,6 +74,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 +253,53 @@ v8::Local<v8::Value> AtomBindings::GetIOCounters(v8::Isolate* isolate) {
return dict.GetHandle();
}

// static
base::FilePath AtomBindings::TakeHeapSnapshot(v8::Isolate* isolate,
mate::Arguments* args) {
base::ScopedAllowBlockingForTesting allow_blocking;

auto file_path = HeapSnapshotOutputStream::GetFilePath();

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

if (!file.IsValid()) {
auto* render_frame = GetCurrentRenderFrame();
if (!render_frame) {
args->ThrowError("takeHeapSnapshot failed");
return base::FilePath();
}

IPC::PlatformFileForTransit file_handle;
auto* message = new AtomFrameHostMsg_CreateHeapSnapshotFile(
render_frame->GetRoutingID(), &file_path, &file_handle);

if (!render_frame->Send(message)) {
args->ThrowError("takeHeapSnapshot failed");
return base::FilePath();
}

file = IPC::PlatformFileForTransitToFile(file_handle);
}

if (!file.IsValid()) {
args->ThrowError("takeHeapSnapshot failed - cannot create file");
return base::FilePath();
}

auto* snap = isolate->GetHeapProfiler()->TakeHeapSnapshot();

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

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

if (!stream.IsComplete()) {
args->ThrowError("takeHeapSnapshot failed - snapshot incomplete");
return base::FilePath();
}

return file_path;
}

} // namespace atom
4 changes: 4 additions & 0 deletions atom/common/api/atom_bindings.h
Expand Up @@ -6,7 +6,9 @@
#define ATOM_COMMON_API_ATOM_BINDINGS_H_

#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 @@ -42,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 base::FilePath TakeHeapSnapshot(v8::Isolate* isolate,
mate::Arguments* args);

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

#include "base/path_service.h"
#include "base/strings/stringprintf.h"
#include "brightray/browser/brightray_paths.h"
#include "uv.h" // NOLINT(build/include)

namespace atom {

HeapSnapshotOutputStream::HeapSnapshotOutputStream(base::File* file)
: file_(file) {}

bool HeapSnapshotOutputStream::IsComplete() const {
return is_complete_;
}

int HeapSnapshotOutputStream::GetChunkSize() {
return 65536;
}

void HeapSnapshotOutputStream::EndOfStream() {
is_complete_ = true;
}

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

// static
base::FilePath HeapSnapshotOutputStream::GetFilePath() {
base::FilePath user_data;
if (!PathService::Get(brightray::DIR_USER_DATA, &user_data))
return base::FilePath();

auto now = uv_hrtime();
auto sec = static_cast<unsigned int>(now / 1000000);
auto usec = static_cast<unsigned int>(now % 1000000);
auto filename = base::StringPrintf("heapdump-%u-%u.heapsnapshot", sec, usec);

return user_data.AppendASCII(filename.c_str());
}

} // namespace atom
34 changes: 34 additions & 0 deletions atom/common/api/heap_snapshot_output_stream.h
@@ -0,0 +1,34 @@
// 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_API_HEAP_SNAPSHOT_OUTPUT_STREAM_H_
#define ATOM_COMMON_API_HEAP_SNAPSHOT_OUTPUT_STREAM_H_

#include "base/files/file.h"
#include "base/files/file_path.h"
#include "v8/include/v8-profiler.h"

namespace atom {

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

bool IsComplete() const;

// v8::OutputStream
int GetChunkSize() override;
void EndOfStream() override;
v8::OutputStream::WriteResult WriteAsciiChunk(char* data, int size) override;

static base::FilePath GetFilePath();

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

} // namespace atom

#endif // ATOM_COMMON_API_HEAP_SNAPSHOT_OUTPUT_STREAM_H_
4 changes: 3 additions & 1 deletion atom/renderer/atom_sandboxed_renderer_client.cc
Expand Up @@ -6,6 +6,7 @@

#include "atom/common/api/api_messages.h"
#include "atom/common/api/atom_bindings.h"
#include "atom/common/native_mate_converters/file_path_converter.h"
#include "atom/common/native_mate_converters/string16_converter.h"
#include "atom/common/native_mate_converters/value_converter.h"
#include "atom/common/node_bindings.h"
Expand All @@ -20,8 +21,8 @@
#include "chrome/renderer/printing/print_web_view_helper.h"
#include "content/public/renderer/render_frame.h"
#include "native_mate/dictionary.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/blink.h"
#include "third_party/blink/public/web/web_document.h"

#include "atom/common/node_includes.h"
#include "atom_natives.h" // NOLINT: This file is generated with js2c
Expand Down Expand Up @@ -157,6 +158,7 @@ void AtomSandboxedRendererClient::InitializeBindings(
b.SetMethod("getCPUUsage", base::Bind(&AtomBindings::GetCPUUsage,
base::Unretained(metrics_.get())));
b.SetMethod("getIOCounters", &AtomBindings::GetIOCounters);
b.SetMethod("takeHeapSnapshot", &AtomBindings::TakeHeapSnapshot);

// Pass in CLI flags needed to setup the renderer
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
Expand Down
4 changes: 4 additions & 0 deletions docs/api/process.md
Expand Up @@ -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.

### `process.hang()`

Causes the main thread of the current process hang.
Expand Down
3 changes: 3 additions & 0 deletions filenames.gni
Expand Up @@ -63,6 +63,7 @@ filenames = {
"lib/renderer/callbacks-registry.js",
"lib/renderer/chrome-api.js",
"lib/renderer/content-scripts-injector.js",
"lib/renderer/heap-snapshot.js",
"lib/renderer/init.js",
"lib/renderer/inspector.js",
"lib/renderer/override.js",
Expand Down Expand Up @@ -445,6 +446,8 @@ filenames = {
"atom/common/api/event_emitter_caller.cc",
"atom/common/api/event_emitter_caller.h",
"atom/common/api/features.cc",
"atom/common/api/heap_snapshot_output_stream.cc",
"atom/common/api/heap_snapshot_output_stream.h",
"atom/common/api/locker.cc",
"atom/common/api/locker.h",
"atom/common/api/object_life_monitor.cc",
Expand Down
14 changes: 14 additions & 0 deletions lib/browser/api/web-contents.js
Expand Up @@ -160,6 +160,20 @@ WebContents.prototype.executeJavaScript = function (code, hasUserGesture, callba
}
}

WebContents.prototype.takeHeapSnapshot = function (...args) {
return new Promise((resolve, reject) => {
const requestId = getNextId()
this.send('ELECTRON_RENDERER_TAKE_HEAP_SNAPSHOT', requestId, args)
ipcMain.once(`ELECTRON_BROWSER_TAKE_HEAP_SNAPSHOT_RESULT_${requestId}`, (event, error, result) => {
if (error) {
reject(errorUtils.deserialize(error))
} else {
resolve(result)
}
})
})
}

// Translate the options of printToPDF.
WebContents.prototype.printToPDF = function (options, callback) {
const printingSetting = Object.assign({}, defaultPrintingSetting)
Expand Down
16 changes: 16 additions & 0 deletions lib/renderer/heap-snapshot.js
@@ -0,0 +1,16 @@
const {ipcRenderer} = require('electron')
const errorUtils = require('../common/error-utils')

module.exports = (process) => {
ipcRenderer.on('ELECTRON_RENDERER_TAKE_HEAP_SNAPSHOT', (event, requestId, args) => {
new Promise(resolve =>
process.takeHeapSnapshot(...args)
).then(result => {
return [null, result]
}, error => {
return [errorUtils.serialize(error)]
}).then(responseArgs => {
event.sender.send(`ELECTRON_BROWSER_TAKE_HEAP_SNAPSHOT_RESULT_${requestId}`, ...responseArgs)
})
})
}
1 change: 1 addition & 0 deletions lib/renderer/init.js
Expand Up @@ -40,6 +40,7 @@ const {
} = require('./security-warnings')

require('./web-frame-init')()
require('./heap-snapshot')(process)

// Process command line arguments.
let nodeIntegration = 'false'
Expand Down
2 changes: 2 additions & 0 deletions lib/sandboxed_renderer/init.js
Expand Up @@ -42,6 +42,7 @@ const {
} = electron.ipcRenderer.sendSync('ELECTRON_BROWSER_SANDBOX_LOAD')

require('../renderer/web-frame-init')()
require('../renderer/heap-snapshot')(binding)

// Pass different process object to the preload script(which should not have
// access to things like `process.atomBinding`).
Expand All @@ -52,6 +53,7 @@ preloadProcess.getHeapStatistics = () => binding.getHeapStatistics()
preloadProcess.getSystemMemoryInfo = () => binding.getSystemMemoryInfo()
preloadProcess.getCPUUsage = () => binding.getCPUUsage()
preloadProcess.getIOCounters = () => binding.getIOCounters()
preloadProcess.takeHeapSnapshot = () => binding.takeHeapSnapshot()
preloadProcess.argv = process.argv = binding.getArgv()
preloadProcess.execPath = process.execPath = binding.getExecPath()
preloadProcess.pid = process.pid = binding.getPid()
Expand Down

0 comments on commit 174dd5a

Please sign in to comment.