diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index ebf57235333a0..864f9a4838917 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/file_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, @@ -1032,6 +1038,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() @@ -2101,6 +2110,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 = FileOutputStream::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 ec5a4a756ebdb..5e3e1fbb956e6 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -431,6 +431,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..b8ba06d610b6f 100644 --- a/atom/common/api/atom_bindings.cc +++ b/atom/common/api/atom_bindings.cc @@ -8,9 +8,11 @@ #include #include +#include "atom/common/api/file_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" @@ -18,6 +20,7 @@ #include "base/process/process_metrics_iocounters.h" #include "base/sys_info.h" #include "native_mate/dictionary.h" +#include "v8/include/v8-profiler.h" namespace atom { @@ -61,6 +64,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 +133,26 @@ void AtomBindings::Hang() { base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1)); } +// static +base::FilePath AtomBindings::TakeHeapSnapshot(v8::Isolate* isolate) { + auto file_path = FileOutputStream::GetFilePath(); + + base::File file(file_path, + base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); + + auto* snap = isolate->GetHeapProfiler()->TakeHeapSnapshot(); + + FileOutputStream 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/file_output_stream.cc b/atom/common/api/file_output_stream.cc new file mode 100644 index 0000000000000..b778671650412 --- /dev/null +++ b/atom/common/api/file_output_stream.cc @@ -0,0 +1,47 @@ +// 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/file_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 { + +FileOutputStream::FileOutputStream(base::File* file) : file_(file) {} + +bool FileOutputStream::IsComplete() const { + return is_complete_; +} + +int FileOutputStream::GetChunkSize() { + return 65536; +} + +void FileOutputStream::EndOfStream() { + is_complete_ = true; +} + +v8::OutputStream::WriteResult FileOutputStream::WriteAsciiChunk(char* data, + int size) { + auto bytes_written = file_->WriteAtCurrentPos(data, size); + return bytes_written == size ? kContinue : kAbort; +} + +// static +base::FilePath FileOutputStream::GetFilePath() { + base::FilePath user_data; + PathService::Get(brightray::DIR_USER_DATA, &user_data); + + 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/file_output_stream.h b/atom/common/api/file_output_stream.h new file mode 100644 index 0000000000000..71f1439385d11 --- /dev/null +++ b/atom/common/api/file_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_FILE_OUTPUT_STREAM_H_ +#define ATOM_COMMON_API_FILE_OUTPUT_STREAM_H_ + +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "v8/include/v8-profiler.h" + +namespace atom { + +class FileOutputStream : public v8::OutputStream { + public: + explicit FileOutputStream(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_FILE_OUTPUT_STREAM_H_ diff --git a/atom/renderer/atom_sandboxed_renderer_client.cc b/atom/renderer/atom_sandboxed_renderer_client.cc index f820b88cf28fe..923af6f8843ce 100644 --- a/atom/renderer/atom_sandboxed_renderer_client.cc +++ b/atom/renderer/atom_sandboxed_renderer_client.cc @@ -6,6 +6,8 @@ #include "atom/common/api/api_messages.h" #include "atom/common/api/atom_bindings.h" +#include "atom/common/api/file_output_stream.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" @@ -22,6 +24,8 @@ #include "native_mate/dictionary.h" #include "third_party/WebKit/public/web/WebDocument.h" #include "third_party/WebKit/public/web/WebKit.h" +#include "third_party/WebKit/public/web/WebLocalFrame.h" +#include "v8/include/v8-profiler.h" #include "atom/common/node_includes.h" #include "atom_natives.h" // NOLINT: This file is generated with js2c @@ -97,6 +101,40 @@ v8::Local CreatePreloadScript(v8::Isolate* isolate, return func; } +// static +base::FilePath TakeHeapSnapshot(v8::Isolate* isolate) { + auto* frame = blink::WebLocalFrame::FrameForCurrentContext(); + if (!frame) + return base::FilePath(); + + auto* render_frame = content::RenderFrame::FromWebFrame(frame); + if (!render_frame) + return base::FilePath(); + + base::FilePath file_path; + IPC::PlatformFileForTransit file_handle; + auto* message = new AtomFrameHostMsg_CreateHeapSnapshotFile( + render_frame->GetRoutingID(), &file_path, &file_handle); + if (!render_frame->Send(message)) + return base::FilePath(); + + auto file = IPC::PlatformFileForTransitToFile(file_handle); + if (!file.IsValid()) + return base::FilePath(); + + auto* snap = isolate->GetHeapProfiler()->TakeHeapSnapshot(); + + FileOutputStream stream(&file); + snap->Serialize(&stream, v8::HeapSnapshot::kJSON); + + const_cast(snap)->Delete(); + + if (!stream.IsComplete()) + return base::FilePath(); + + return file_path; +} + class AtomSandboxedRenderFrameObserver : public AtomRenderFrameObserver { public: AtomSandboxedRenderFrameObserver(content::RenderFrame* render_frame, @@ -158,6 +196,7 @@ void AtomSandboxedRendererClient::InitializeBindings( b.SetMethod("getCPUUsage", base::Bind(&AtomBindings::GetCPUUsage, base::Unretained(metrics_.get()))); b.SetMethod("getIOCounters", &AtomBindings::GetIOCounters); + b.SetMethod("takeHeapSnapshot", &TakeHeapSnapshot); // Pass in CLI flags needed to setup the renderer base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); diff --git a/filenames.gypi b/filenames.gypi index a7563a699c459..184b3ebd541c9 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/file_output_stream.cc', + 'atom/common/api/file_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()