diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index cf5cb4386cb5..d6fe607de496 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -29,12 +29,14 @@ #include "base/files/file_util.h" #include "base/path_service.h" #include "base/strings/string_util.h" +#include "base/sys_info.h" #include "brightray/browser/brightray_paths.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/icon_manager.h" #include "chrome/common/chrome_paths.h" #include "content/public/browser/browser_accessibility_state.h" #include "content/public/browser/browser_child_process_host.h" +#include "content/public/browser/child_process_data.h" #include "content/public/browser/client_certificate_delegate.h" #include "content/public/browser/gpu_data_manager.h" #include "content/public/browser/render_frame_host.h" @@ -505,6 +507,14 @@ App::App(v8::Isolate* isolate) { static_cast(AtomBrowserClient::Get())->set_delegate(this); Browser::Get()->AddObserver(this); content::GpuDataManager::GetInstance()->AddObserver(this); + content::BrowserChildProcessObserver::Add(this); + base::ProcessId pid = base::GetCurrentProcId(); + std::unique_ptr process_metric( + new atom::ProcessMetric( + content::PROCESS_TYPE_BROWSER, + pid, + base::ProcessMetrics::CreateCurrentProcessMetrics())); + app_metrics_[pid] = std::move(process_metric); Init(isolate); } @@ -513,6 +523,7 @@ App::~App() { nullptr); Browser::Get()->RemoveObserver(this); content::GpuDataManager::GetInstance()->RemoveObserver(this); + content::BrowserChildProcessObserver::Remove(this); } void App::OnBeforeQuit(bool* prevent_default) { @@ -666,6 +677,54 @@ void App::OnGpuProcessCrashed(base::TerminationStatus status) { status == base::TERMINATION_STATUS_PROCESS_WAS_KILLED); } +void App::BrowserChildProcessLaunchedAndConnected( + const content::ChildProcessData& data) { + ChildProcessLaunched(data.process_type, data.handle); +} + +void App::BrowserChildProcessHostDisconnected( + const content::ChildProcessData& data) { + ChildProcessDisconnected(base::GetProcId(data.handle)); +} + +void App::BrowserChildProcessCrashed(const content::ChildProcessData& data, + int exit_code) { + ChildProcessDisconnected(base::GetProcId(data.handle)); +} + +void App::BrowserChildProcessKilled(const content::ChildProcessData& data, + int exit_code) { + ChildProcessDisconnected(base::GetProcId(data.handle)); +} + +void App::RenderProcessReady(content::RenderProcessHost* host) { + ChildProcessLaunched(content::PROCESS_TYPE_RENDERER, host->GetHandle()); +} + +void App::RenderProcessDisconnected(base::ProcessId host_pid) { + ChildProcessDisconnected(host_pid); +} + +void App::ChildProcessLaunched(int process_type, base::ProcessHandle handle) { + auto pid = base::GetProcId(handle); + +#if defined(OS_MACOSX) + std::unique_ptr metrics( + base::ProcessMetrics::CreateProcessMetrics( + handle, content::BrowserChildProcessHost::GetPortProvider())); +#else + std::unique_ptr metrics( + base::ProcessMetrics::CreateProcessMetrics(handle)); +#endif + std::unique_ptr process_metric( + new atom::ProcessMetric(process_type, pid, std::move(metrics))); + app_metrics_[pid] = std::move(process_metric); +} + +void App::ChildProcessDisconnected(base::ProcessId pid) { + app_metrics_.erase(pid); +} + base::FilePath App::GetAppPath() const { return app_path_; } @@ -923,42 +982,40 @@ void App::GetFileIcon(const base::FilePath& path, } } -std::vector App::GetAppMemoryInfo(v8::Isolate* isolate) { - AppIdProcessIterator process_iterator; - auto process_entry = process_iterator.NextProcessEntry(); +std::vector App::GetAppMetrics(v8::Isolate* isolate) { std::vector result; + int processor_count = base::SysInfo::NumberOfProcessors(); - while (process_entry != nullptr) { - int64_t pid = process_entry->pid(); - auto process = base::Process::OpenWithExtraPrivileges(pid); - -#if defined(OS_MACOSX) - std::unique_ptr metrics( - base::ProcessMetrics::CreateProcessMetrics( - process.Handle(), content::BrowserChildProcessHost::GetPortProvider())); -#else - std::unique_ptr metrics( - base::ProcessMetrics::CreateProcessMetrics(process.Handle())); -#endif - + for (const auto& process_metric : app_metrics_) { mate::Dictionary pid_dict = mate::Dictionary::CreateEmpty(isolate); mate::Dictionary memory_dict = mate::Dictionary::CreateEmpty(isolate); + mate::Dictionary cpu_dict = mate::Dictionary::CreateEmpty(isolate); memory_dict.Set("workingSetSize", - static_cast(metrics->GetWorkingSetSize() >> 10)); + static_cast( + process_metric.second->metrics->GetWorkingSetSize() >> 10)); memory_dict.Set("peakWorkingSetSize", - static_cast(metrics->GetPeakWorkingSetSize() >> 10)); + static_cast( + process_metric.second->metrics->GetPeakWorkingSetSize() >> 10)); size_t private_bytes, shared_bytes; - if (metrics->GetMemoryBytes(&private_bytes, &shared_bytes)) { + if (process_metric.second->metrics->GetMemoryBytes(&private_bytes, + &shared_bytes)) { memory_dict.Set("privateBytes", static_cast(private_bytes >> 10)); memory_dict.Set("sharedBytes", static_cast(shared_bytes >> 10)); } pid_dict.Set("memory", memory_dict); - pid_dict.Set("pid", pid); + cpu_dict.Set("percentCPUUsage", + process_metric.second->metrics->GetPlatformIndependentCPUUsage() + / processor_count); + cpu_dict.Set("idleWakeupsPerSecond", + process_metric.second->metrics->GetIdleWakeupsPerSecond()); + pid_dict.Set("cpu", cpu_dict); + pid_dict.Set("pid", process_metric.second->pid); + pid_dict.Set("type", + content::GetProcessTypeNameInEnglish(process_metric.second->type)); result.push_back(pid_dict); - process_entry = process_iterator.NextProcessEntry(); } return result; @@ -1036,7 +1093,9 @@ void App::BuildPrototype( .SetMethod("disableHardwareAcceleration", &App::DisableHardwareAcceleration) .SetMethod("getFileIcon", &App::GetFileIcon) - .SetMethod("getAppMemoryInfo", &App::GetAppMemoryInfo); + .SetMethod("getAppMetrics", &App::GetAppMetrics) + // TODO(juturu): Remove in 2.0, deprecate before then with warnings + .SetMethod("getAppMemoryInfo", &App::GetAppMetrics); } } // namespace api diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index 9543e4b74b22..d68b172a7fad 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -5,7 +5,9 @@ #ifndef ATOM_BROWSER_API_ATOM_API_APP_H_ #define ATOM_BROWSER_API_ATOM_API_APP_H_ +#include #include +#include #include #include "atom/browser/api/event_emitter.h" @@ -17,7 +19,9 @@ #include "base/task/cancelable_task_tracker.h" #include "chrome/browser/icon_manager.h" #include "chrome/browser/process_singleton.h" +#include "content/public/browser/browser_child_process_observer.h" #include "content/public/browser/gpu_data_manager_observer.h" +#include "content/public/browser/render_process_host.h" #include "native_mate/dictionary.h" #include "native_mate/handle.h" #include "net/base/completion_callback.h" @@ -40,12 +44,27 @@ namespace atom { enum class JumpListResult : int; #endif +struct ProcessMetric { + int type; + base::ProcessId pid; + std::unique_ptr metrics; + + ProcessMetric(int type, + base::ProcessId pid, + std::unique_ptr metrics) { + this->type = type; + this->pid = pid; + this->metrics = std::move(metrics); + } +}; + namespace api { class App : public AtomBrowserClient::Delegate, public mate::EventEmitter, public BrowserObserver, - public content::GpuDataManagerObserver { + public content::GpuDataManagerObserver, + public content::BrowserChildProcessObserver { public: using FileIconCallback = base::Callback, const gfx::Image&)>; @@ -73,6 +92,8 @@ class App : public AtomBrowserClient::Delegate, #endif base::FilePath GetAppPath() const; + void RenderProcessReady(content::RenderProcessHost* host); + void RenderProcessDisconnected(base::ProcessId host_pid); protected: explicit App(v8::Isolate* isolate); @@ -118,8 +139,20 @@ class App : public AtomBrowserClient::Delegate, // content::GpuDataManagerObserver: void OnGpuProcessCrashed(base::TerminationStatus status) override; + // content::BrowserChildProcessObserver: + void BrowserChildProcessLaunchedAndConnected( + const content::ChildProcessData& data) override; + void BrowserChildProcessHostDisconnected( + const content::ChildProcessData& data) override; + void BrowserChildProcessCrashed( + const content::ChildProcessData& data, int exit_code) override; + void BrowserChildProcessKilled( + const content::ChildProcessData& data, int exit_code) override; + private: void SetAppPath(const base::FilePath& app_path); + void ChildProcessLaunched(int process_type, base::ProcessHandle handle); + void ChildProcessDisconnected(base::ProcessId pid); // Get/Set the pre-defined path in PathService. base::FilePath GetPath(mate::Arguments* args, const std::string& name); @@ -143,7 +176,7 @@ class App : public AtomBrowserClient::Delegate, void GetFileIcon(const base::FilePath& path, mate::Arguments* args); - std::vector GetAppMemoryInfo(v8::Isolate* isolate); + std::vector GetAppMetrics(v8::Isolate* isolate); #if defined(OS_WIN) // Get the current Jump List settings. @@ -164,6 +197,11 @@ class App : public AtomBrowserClient::Delegate, base::FilePath app_path_; + using ProcessMetricMap = + std::unordered_map>; + ProcessMetricMap app_metrics_; + DISALLOW_COPY_AND_ASSIGN(App); }; diff --git a/atom/browser/atom_browser_client.cc b/atom/browser/atom_browser_client.cc index 6cede9c0112c..be4ee262adf1 100644 --- a/atom/browser/atom_browser_client.cc +++ b/atom/browser/atom_browser_client.cc @@ -410,4 +410,24 @@ void AtomBrowserClient::RenderProcessHostDestroyed( RemoveProcessPreferences(process_id); } +void AtomBrowserClient::RenderProcessReady(content::RenderProcessHost* host) { + render_process_host_pids_[host->GetID()] = base::GetProcId(host->GetHandle()); + if (delegate_) { + static_cast(delegate_)->RenderProcessReady(host); + } +} + +void AtomBrowserClient::RenderProcessExited(content::RenderProcessHost* host, + base::TerminationStatus status, + int exit_code) { + auto host_pid = render_process_host_pids_.find(host->GetID()); + if (host_pid != render_process_host_pids_.end()) { + if (delegate_) { + static_cast(delegate_)->RenderProcessDisconnected( + host_pid->second); + } + render_process_host_pids_.erase(host_pid); + } +} + } // namespace atom diff --git a/atom/browser/atom_browser_client.h b/atom/browser/atom_browser_client.h index c9a58981a2ce..69ac51c66310 100644 --- a/atom/browser/atom_browser_client.h +++ b/atom/browser/atom_browser_client.h @@ -109,6 +109,10 @@ class AtomBrowserClient : public brightray::BrowserClient, // content::RenderProcessHostObserver: void RenderProcessHostDestroyed(content::RenderProcessHost* host) override; + void RenderProcessReady(content::RenderProcessHost* host) override; + void RenderProcessExited(content::RenderProcessHost* host, + base::TerminationStatus status, + int exit_code) override; private: bool ShouldCreateNewSiteInstance(content::RenderFrameHost* render_frame_host, @@ -128,6 +132,7 @@ class AtomBrowserClient : public brightray::BrowserClient, std::map pending_processes_; std::map process_preferences_; + std::map render_process_host_pids_; base::Lock process_preferences_lock_; std::unique_ptr diff --git a/docs/api/app.md b/docs/api/app.md index 614989dc99c6..044e6449a24b 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -127,7 +127,7 @@ Returns: Emitted when the application is activated. Various actions can trigger this event, such as launching the application for the first time, attempting -to re-launch the application when it's already running, or clicking on the +to re-launch the application when it's already running, or clicking on the application's dock or taskbar icon. ### Event: 'continue-activity' _macOS_ @@ -762,9 +762,14 @@ Disables hardware acceleration for current app. This method can only be called before app is ready. -### `app.getAppMemoryInfo()` +### `app.getAppMemoryInfo()` _Deprecated_ -Returns [ProcessMemoryInfo[]](structures/process-memory-info.md): Array of `ProcessMemoryInfo` objects that correspond to memory usage statistics of all the processes associated with the app. +Returns [ProcessMetric[]](structures/process-metric.md): Array of `ProcessMetric` objects that correspond to memory and cpu usage statistics of all the processes associated with the app. +**Note:** This method is deprecated, use `app.getAppMetrics()` instead. + +### `app.getAppMetrics()` + +Returns [ProcessMetric[]](structures/process-metric.md): Array of `ProcessMetric` objects that correspond to memory and cpu usage statistics of all the processes associated with the app. ### `app.setBadgeCount(count)` _Linux_ _macOS_ diff --git a/docs/api/structures/process-memory-info.md b/docs/api/structures/process-memory-info.md deleted file mode 100644 index 68198f2d4540..000000000000 --- a/docs/api/structures/process-memory-info.md +++ /dev/null @@ -1,4 +0,0 @@ -# ProcessMemoryInfo Object - -* `pid` Integer - Process id of the process. -* `memory` [MemoryInfo](memory-info.md) - Memory information of the process. diff --git a/docs/api/structures/process-metric.md b/docs/api/structures/process-metric.md new file mode 100644 index 000000000000..15a75a796647 --- /dev/null +++ b/docs/api/structures/process-metric.md @@ -0,0 +1,6 @@ +# ProcessMetric Object + +* `pid` Integer - Process id of the process. +* `type` String - Process type (Browser or Tab or GPU etc). +* `memory` [MemoryInfo](memory-info.md) - Memory information for the process. +* `cpu` [CPUUsage](cpu-usage.md) - CPU usage of the process. diff --git a/docs/tutorial/planned-breaking-changes.md b/docs/tutorial/planned-breaking-changes.md index b5771feea5c2..7544c0c4d821 100644 --- a/docs/tutorial/planned-breaking-changes.md +++ b/docs/tutorial/planned-breaking-changes.md @@ -5,6 +5,15 @@ The following list includes the APIs that will be removed in Electron 2.0. There is no timetable for when this release will occur but deprecation warnings will be added at least 90 days beforehand. +## `app` + +```js +// Deprecated +app.getAppMemoryInfo() +// Replace with +app.getAppMetrics() +``` + ## `BrowserWindow` ```js diff --git a/spec/api-app-spec.js b/spec/api-app-spec.js index 1bcc1a5c6223..60d87e9008ad 100644 --- a/spec/api-app-spec.js +++ b/spec/api-app-spec.js @@ -534,16 +534,28 @@ describe('app module', function () { }) }) - describe('getAppMemoryInfo() API', function () { - it('returns the process memory of all running electron processes', function () { - const appMemoryInfo = app.getAppMemoryInfo() - assert.ok(appMemoryInfo.length > 0, 'App memory info object is not > 0') - for (const {memory, pid} of appMemoryInfo) { + describe('getAppMetrics() API', function () { + it('returns memory and cpu stats of all running electron processes', function () { + const appMetrics = app.getAppMetrics() + assert.ok(appMetrics.length > 0, 'App memory info object is not > 0') + const types = [] + for (const {memory, pid, type, cpu} of appMetrics) { assert.ok(memory.workingSetSize > 0, 'working set size is not > 0') assert.ok(memory.privateBytes > 0, 'private bytes is not > 0') assert.ok(memory.sharedBytes > 0, 'shared bytes is not > 0') assert.ok(pid > 0, 'pid is not > 0') + assert.ok(type.length > 0, 'process type is null') + types.push(type) + assert.equal(typeof cpu.percentCPUUsage, 'number') + assert.equal(typeof cpu.idleWakeupsPerSecond, 'number') } + + if (process.platform !== 'linux') { + assert.ok(types.includes('GPU')) + } + + assert.ok(types.includes('Browser')) + assert.ok(types.includes('Tab')) }) }) })