diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index ebf57235333a0..76d09cf54a8f2 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -28,6 +28,7 @@ #include "atom/browser/lib/bluetooth_chooser.h" #include "atom/browser/native_window.h" #include "atom/browser/net/atom_network_delegate.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" @@ -60,6 +61,7 @@ #include "base/strings/utf_string_conversions.h" #include "base/threading/thread_task_runner_handle.h" #include "base/values.h" +#include "brightray/browser/brightray_paths.h" #include "brightray/browser/inspectable_web_contents.h" #include "brightray/browser/inspectable_web_contents_view.h" #include "chrome/browser/browser_process.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,29 @@ void WebContents::OnRendererMessageTo(content::RenderFrameHost* frame_host, } } +void WebContents::OnCreateHeapSnapshotFile(content::RenderFrameHost* frame_host, + IPC::Message* message) { + base::ScopedAllowBlockingForTesting allow_blocking; + + 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); + + auto file_path = user_data.AppendASCII(filename.c_str()); + + 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..6c23fd77bb354 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/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" #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 { @@ -54,6 +60,7 @@ void AtomBindings::BindTo(v8::Isolate* isolate, v8::Local process) { dict.SetMethod("crash", &AtomBindings::Crash); dict.SetMethod("hang", &Hang); dict.SetMethod("log", &Log); + dict.SetMethod("getHeapSnapshot", &GetHeapSnapshot); dict.SetMethod("getHeapStatistics", &GetHeapStatistics); dict.SetMethod("getProcessMemoryInfo", &GetProcessMemoryInfo); dict.SetMethod("getCreationTime", &GetCreationTime); @@ -129,6 +136,40 @@ void AtomBindings::Hang() { base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1)); } +// static +base::FilePath AtomBindings::GetHeapSnapshot(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; +} + // 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..0af90fc270a61 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 GetHeapSnapshot(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..e32de2af0c261 --- /dev/null +++ b/atom/common/api/file_output_stream.cc @@ -0,0 +1,29 @@ +// 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" + +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; +} + +} // 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..259c8bcac8d1b --- /dev/null +++ b/atom/common/api/file_output_stream.h @@ -0,0 +1,32 @@ +// 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); + + 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..2320472e5b0a3 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" @@ -152,6 +153,7 @@ void AtomSandboxedRendererClient::InitializeBindings( b.SetMethod("getExecPath", GetExecPath); b.SetMethod("getPid", &base::GetCurrentProcId); b.SetMethod("getResourcesPath", &NodeBindings::GetHelperResourcesPath); + b.SetMethod("getHeapSnapshot", &AtomBindings::GetHeapSnapshot); b.SetMethod("getHeapStatistics", &AtomBindings::GetHeapStatistics); b.SetMethod("getProcessMemoryInfo", &AtomBindings::GetProcessMemoryInfo); b.SetMethod("getSystemMemoryInfo", &AtomBindings::GetSystemMemoryInfo); 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..a8ea3e03da933 100644 --- a/lib/sandboxed_renderer/init.js +++ b/lib/sandboxed_renderer/init.js @@ -48,6 +48,7 @@ require('../renderer/web-frame-init')() const preloadProcess = new events.EventEmitter() preloadProcess.crash = () => binding.crash() preloadProcess.hang = () => binding.hang() +preloadProcess.getHeapSnapshot = () => binding.getHeapSnapshot() preloadProcess.getHeapStatistics = () => binding.getHeapStatistics() preloadProcess.getProcessMemoryInfo = () => binding.getProcessMemoryInfo() preloadProcess.getSystemMemoryInfo = () => binding.getSystemMemoryInfo()