diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 8c8afe560140f..a097af84beade 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -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" @@ -308,6 +310,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, @@ -1037,6 +1043,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() @@ -2106,6 +2115,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::CreateFrom( v8::Isolate* isolate, diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index d7de8f03c9708..3939f74083cc0 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -433,6 +433,9 @@ class WebContents : public mate::TrackableObject, 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, diff --git a/atom/common/api/api_messages.h b/atom/common/api/api_messages.h index e7317736e0766..afc3aa94fe64d 100644 --- a/atom/common/api/api_messages.h +++ b/atom/common/api/api_messages.h @@ -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" @@ -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 */) diff --git a/atom/common/api/atom_bindings.cc b/atom/common/api/atom_bindings.cc index 3dba3d63673ca..680b888d82476 100644 --- a/atom/common/api/atom_bindings.cc +++ b/atom/common/api/atom_bindings.cc @@ -8,16 +8,22 @@ #include #include +#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/WebKit/public/web/WebLocalFrame.h" +#include "v8/include/v8-profiler.h" namespace atom { @@ -61,6 +67,7 @@ void AtomBindings::BindTo(v8::Isolate* isolate, v8::Local 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 @@ -129,6 +136,48 @@ void AtomBindings::Hang() { base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1)); } +base::FilePath AtomBindings::TakeHeapSnapshot(v8::Isolate* isolate) { + 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* frame = blink::WebLocalFrame::FrameForCurrentContext(); + if (!frame) + return base::FilePath(); + + auto* render_frame = content::RenderFrame::FromWebFrame(frame); + if (!render_frame) + return base::FilePath(); + + IPC::PlatformFileForTransit file_handle; + auto* message = new AtomFrameHostMsg_CreateHeapSnapshotFile( + render_frame->GetRoutingID(), &file_path, &file_handle); + if (!render_frame->Send(message)) + return base::FilePath(); + + file = IPC::PlatformFileForTransitToFile(file_handle); + } + + if (!file.IsValid()) + return base::FilePath(); + + auto* snap = isolate->GetHeapProfiler()->TakeHeapSnapshot(); + + HeapSnapshotOutputStream stream(&file); + snap->Serialize(&stream, v8::HeapSnapshot::kJSON); + + const_cast(snap)->Delete(); + + if (!stream.IsComplete()) + return base::FilePath(); + + return file_path; +} + // static v8::Local AtomBindings::GetHeapStatistics(v8::Isolate* isolate) { v8::HeapStatistics v8_heap_stats; diff --git a/atom/common/api/atom_bindings.h b/atom/common/api/atom_bindings.h index ba8385c979b15..0a2911db9df94 100644 --- a/atom/common/api/atom_bindings.h +++ b/atom/common/api/atom_bindings.h @@ -7,6 +7,7 @@ #include +#include "base/files/file_path.h" #include "base/macros.h" #include "base/process/process_metrics.h" #include "base/strings/string16.h" @@ -35,6 +36,7 @@ class AtomBindings { static void Log(const base::string16& message); static void Crash(); static void Hang(); + static base::FilePath TakeHeapSnapshot(v8::Isolate* isolate); static v8::Local GetHeapStatistics(v8::Isolate* isolate); static v8::Local GetProcessMemoryInfo(v8::Isolate* isolate); static v8::Local GetCreationTime(v8::Isolate* isolate); diff --git a/atom/common/api/heap_snapshot_output_stream.cc b/atom/common/api/heap_snapshot_output_stream.cc new file mode 100644 index 0000000000000..787c955f324c5 --- /dev/null +++ b/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(now / 1000000); + auto usec = static_cast(now % 1000000); + auto filename = base::StringPrintf("heapdump-%u-%u.heapsnapshot", sec, usec); + + return user_data.AppendASCII(filename.c_str()); +} + +} // namespace atom diff --git a/atom/common/api/heap_snapshot_output_stream.h b/atom/common/api/heap_snapshot_output_stream.h new file mode 100644 index 0000000000000..b93f0f278dfe2 --- /dev/null +++ b/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 + virtual int GetChunkSize(); + virtual void EndOfStream(); + virtual v8::OutputStream::WriteResult WriteAsciiChunk(char* data, int size); + + static base::FilePath GetFilePath(); + + private: + base::File* file_ = nullptr; + bool is_complete_ = false; +}; + +} // namespace atom + +#endif // ATOM_COMMON_API_HEAP_SNAPSHOT_OUTPUT_STREAM_H_ diff --git a/atom/renderer/atom_sandboxed_renderer_client.cc b/atom/renderer/atom_sandboxed_renderer_client.cc index f820b88cf28fe..75478db682487 100644 --- a/atom/renderer/atom_sandboxed_renderer_client.cc +++ b/atom/renderer/atom_sandboxed_renderer_client.cc @@ -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" @@ -158,6 +159,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(); diff --git a/docs/api/process.md b/docs/api/process.md index 1f6b20d2f25c3..c8c81240b144e 100644 --- a/docs/api/process.md +++ b/docs/api/process.md @@ -188,6 +188,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. diff --git a/filenames.gypi b/filenames.gypi index 2b51e2040a357..e8f1329a51738 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -449,6 +449,8 @@ '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', diff --git a/lib/sandboxed_renderer/init.js b/lib/sandboxed_renderer/init.js index afb505490e659..80c7f243a468d 100644 --- a/lib/sandboxed_renderer/init.js +++ b/lib/sandboxed_renderer/init.js @@ -53,6 +53,7 @@ preloadProcess.getProcessMemoryInfo = () => binding.getProcessMemoryInfo() 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()