Merge pull request #9486 from electron/child-observer

Adding CPU & Memory metrics for App
This commit is contained in:
Kevin Sawicki 2017-05-26 09:28:22 -07:00 committed by GitHub
commit 9137a2279a
9 changed files with 186 additions and 36 deletions

View file

@ -29,12 +29,14 @@
#include "base/files/file_util.h" #include "base/files/file_util.h"
#include "base/path_service.h" #include "base/path_service.h"
#include "base/strings/string_util.h" #include "base/strings/string_util.h"
#include "base/sys_info.h"
#include "brightray/browser/brightray_paths.h" #include "brightray/browser/brightray_paths.h"
#include "chrome/browser/browser_process.h" #include "chrome/browser/browser_process.h"
#include "chrome/browser/icon_manager.h" #include "chrome/browser/icon_manager.h"
#include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_paths.h"
#include "content/public/browser/browser_accessibility_state.h" #include "content/public/browser/browser_accessibility_state.h"
#include "content/public/browser/browser_child_process_host.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/client_certificate_delegate.h"
#include "content/public/browser/gpu_data_manager.h" #include "content/public/browser/gpu_data_manager.h"
#include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_frame_host.h"
@ -505,6 +507,14 @@ App::App(v8::Isolate* isolate) {
static_cast<AtomBrowserClient*>(AtomBrowserClient::Get())->set_delegate(this); static_cast<AtomBrowserClient*>(AtomBrowserClient::Get())->set_delegate(this);
Browser::Get()->AddObserver(this); Browser::Get()->AddObserver(this);
content::GpuDataManager::GetInstance()->AddObserver(this); content::GpuDataManager::GetInstance()->AddObserver(this);
content::BrowserChildProcessObserver::Add(this);
base::ProcessId pid = base::GetCurrentProcId();
std::unique_ptr<atom::ProcessMetric> process_metric(
new atom::ProcessMetric(
content::PROCESS_TYPE_BROWSER,
pid,
base::ProcessMetrics::CreateCurrentProcessMetrics()));
app_metrics_[pid] = std::move(process_metric);
Init(isolate); Init(isolate);
} }
@ -513,6 +523,7 @@ App::~App() {
nullptr); nullptr);
Browser::Get()->RemoveObserver(this); Browser::Get()->RemoveObserver(this);
content::GpuDataManager::GetInstance()->RemoveObserver(this); content::GpuDataManager::GetInstance()->RemoveObserver(this);
content::BrowserChildProcessObserver::Remove(this);
} }
void App::OnBeforeQuit(bool* prevent_default) { void App::OnBeforeQuit(bool* prevent_default) {
@ -666,6 +677,54 @@ void App::OnGpuProcessCrashed(base::TerminationStatus status) {
status == base::TERMINATION_STATUS_PROCESS_WAS_KILLED); 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<base::ProcessMetrics> metrics(
base::ProcessMetrics::CreateProcessMetrics(
handle, content::BrowserChildProcessHost::GetPortProvider()));
#else
std::unique_ptr<base::ProcessMetrics> metrics(
base::ProcessMetrics::CreateProcessMetrics(handle));
#endif
std::unique_ptr<atom::ProcessMetric> 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 { base::FilePath App::GetAppPath() const {
return app_path_; return app_path_;
} }
@ -923,42 +982,40 @@ void App::GetFileIcon(const base::FilePath& path,
} }
} }
std::vector<mate::Dictionary> App::GetAppMemoryInfo(v8::Isolate* isolate) { std::vector<mate::Dictionary> App::GetAppMetrics(v8::Isolate* isolate) {
AppIdProcessIterator process_iterator;
auto process_entry = process_iterator.NextProcessEntry();
std::vector<mate::Dictionary> result; std::vector<mate::Dictionary> result;
int processor_count = base::SysInfo::NumberOfProcessors();
while (process_entry != nullptr) { for (const auto& process_metric : app_metrics_) {
int64_t pid = process_entry->pid();
auto process = base::Process::OpenWithExtraPrivileges(pid);
#if defined(OS_MACOSX)
std::unique_ptr<base::ProcessMetrics> metrics(
base::ProcessMetrics::CreateProcessMetrics(
process.Handle(), content::BrowserChildProcessHost::GetPortProvider()));
#else
std::unique_ptr<base::ProcessMetrics> metrics(
base::ProcessMetrics::CreateProcessMetrics(process.Handle()));
#endif
mate::Dictionary pid_dict = mate::Dictionary::CreateEmpty(isolate); mate::Dictionary pid_dict = mate::Dictionary::CreateEmpty(isolate);
mate::Dictionary memory_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", memory_dict.Set("workingSetSize",
static_cast<double>(metrics->GetWorkingSetSize() >> 10)); static_cast<double>(
process_metric.second->metrics->GetWorkingSetSize() >> 10));
memory_dict.Set("peakWorkingSetSize", memory_dict.Set("peakWorkingSetSize",
static_cast<double>(metrics->GetPeakWorkingSetSize() >> 10)); static_cast<double>(
process_metric.second->metrics->GetPeakWorkingSetSize() >> 10));
size_t private_bytes, shared_bytes; 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<double>(private_bytes >> 10)); memory_dict.Set("privateBytes", static_cast<double>(private_bytes >> 10));
memory_dict.Set("sharedBytes", static_cast<double>(shared_bytes >> 10)); memory_dict.Set("sharedBytes", static_cast<double>(shared_bytes >> 10));
} }
pid_dict.Set("memory", memory_dict); 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); result.push_back(pid_dict);
process_entry = process_iterator.NextProcessEntry();
} }
return result; return result;
@ -1036,7 +1093,9 @@ void App::BuildPrototype(
.SetMethod("disableHardwareAcceleration", .SetMethod("disableHardwareAcceleration",
&App::DisableHardwareAcceleration) &App::DisableHardwareAcceleration)
.SetMethod("getFileIcon", &App::GetFileIcon) .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 } // namespace api

View file

@ -5,7 +5,9 @@
#ifndef ATOM_BROWSER_API_ATOM_API_APP_H_ #ifndef ATOM_BROWSER_API_ATOM_API_APP_H_
#define ATOM_BROWSER_API_ATOM_API_APP_H_ #define ATOM_BROWSER_API_ATOM_API_APP_H_
#include <memory>
#include <string> #include <string>
#include <utility>
#include <vector> #include <vector>
#include "atom/browser/api/event_emitter.h" #include "atom/browser/api/event_emitter.h"
@ -17,7 +19,9 @@
#include "base/task/cancelable_task_tracker.h" #include "base/task/cancelable_task_tracker.h"
#include "chrome/browser/icon_manager.h" #include "chrome/browser/icon_manager.h"
#include "chrome/browser/process_singleton.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/gpu_data_manager_observer.h"
#include "content/public/browser/render_process_host.h"
#include "native_mate/dictionary.h" #include "native_mate/dictionary.h"
#include "native_mate/handle.h" #include "native_mate/handle.h"
#include "net/base/completion_callback.h" #include "net/base/completion_callback.h"
@ -40,12 +44,27 @@ namespace atom {
enum class JumpListResult : int; enum class JumpListResult : int;
#endif #endif
struct ProcessMetric {
int type;
base::ProcessId pid;
std::unique_ptr<base::ProcessMetrics> metrics;
ProcessMetric(int type,
base::ProcessId pid,
std::unique_ptr<base::ProcessMetrics> metrics) {
this->type = type;
this->pid = pid;
this->metrics = std::move(metrics);
}
};
namespace api { namespace api {
class App : public AtomBrowserClient::Delegate, class App : public AtomBrowserClient::Delegate,
public mate::EventEmitter<App>, public mate::EventEmitter<App>,
public BrowserObserver, public BrowserObserver,
public content::GpuDataManagerObserver { public content::GpuDataManagerObserver,
public content::BrowserChildProcessObserver {
public: public:
using FileIconCallback = base::Callback<void(v8::Local<v8::Value>, using FileIconCallback = base::Callback<void(v8::Local<v8::Value>,
const gfx::Image&)>; const gfx::Image&)>;
@ -73,6 +92,8 @@ class App : public AtomBrowserClient::Delegate,
#endif #endif
base::FilePath GetAppPath() const; base::FilePath GetAppPath() const;
void RenderProcessReady(content::RenderProcessHost* host);
void RenderProcessDisconnected(base::ProcessId host_pid);
protected: protected:
explicit App(v8::Isolate* isolate); explicit App(v8::Isolate* isolate);
@ -118,8 +139,20 @@ class App : public AtomBrowserClient::Delegate,
// content::GpuDataManagerObserver: // content::GpuDataManagerObserver:
void OnGpuProcessCrashed(base::TerminationStatus status) override; 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: private:
void SetAppPath(const base::FilePath& app_path); 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. // Get/Set the pre-defined path in PathService.
base::FilePath GetPath(mate::Arguments* args, const std::string& name); 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, void GetFileIcon(const base::FilePath& path,
mate::Arguments* args); mate::Arguments* args);
std::vector<mate::Dictionary> GetAppMemoryInfo(v8::Isolate* isolate); std::vector<mate::Dictionary> GetAppMetrics(v8::Isolate* isolate);
#if defined(OS_WIN) #if defined(OS_WIN)
// Get the current Jump List settings. // Get the current Jump List settings.
@ -164,6 +197,11 @@ class App : public AtomBrowserClient::Delegate,
base::FilePath app_path_; base::FilePath app_path_;
using ProcessMetricMap =
std::unordered_map<base::ProcessId,
std::unique_ptr<atom::ProcessMetric>>;
ProcessMetricMap app_metrics_;
DISALLOW_COPY_AND_ASSIGN(App); DISALLOW_COPY_AND_ASSIGN(App);
}; };

View file

@ -410,4 +410,24 @@ void AtomBrowserClient::RenderProcessHostDestroyed(
RemoveProcessPreferences(process_id); RemoveProcessPreferences(process_id);
} }
void AtomBrowserClient::RenderProcessReady(content::RenderProcessHost* host) {
render_process_host_pids_[host->GetID()] = base::GetProcId(host->GetHandle());
if (delegate_) {
static_cast<api::App*>(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<api::App*>(delegate_)->RenderProcessDisconnected(
host_pid->second);
}
render_process_host_pids_.erase(host_pid);
}
}
} // namespace atom } // namespace atom

View file

@ -109,6 +109,10 @@ class AtomBrowserClient : public brightray::BrowserClient,
// content::RenderProcessHostObserver: // content::RenderProcessHostObserver:
void RenderProcessHostDestroyed(content::RenderProcessHost* host) override; 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: private:
bool ShouldCreateNewSiteInstance(content::RenderFrameHost* render_frame_host, bool ShouldCreateNewSiteInstance(content::RenderFrameHost* render_frame_host,
@ -128,6 +132,7 @@ class AtomBrowserClient : public brightray::BrowserClient,
std::map<int, int> pending_processes_; std::map<int, int> pending_processes_;
std::map<int, ProcessPreferences> process_preferences_; std::map<int, ProcessPreferences> process_preferences_;
std::map<int, base::ProcessId> render_process_host_pids_;
base::Lock process_preferences_lock_; base::Lock process_preferences_lock_;
std::unique_ptr<AtomResourceDispatcherHostDelegate> std::unique_ptr<AtomResourceDispatcherHostDelegate>

View file

@ -127,7 +127,7 @@ Returns:
Emitted when the application is activated. Various actions can trigger Emitted when the application is activated. Various actions can trigger
this event, such as launching the application for the first time, attempting 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. application's dock or taskbar icon.
### Event: 'continue-activity' _macOS_ ### Event: 'continue-activity' _macOS_
@ -762,9 +762,14 @@ Disables hardware acceleration for current app.
This method can only be called before app is ready. 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_ ### `app.setBadgeCount(count)` _Linux_ _macOS_

View file

@ -1,4 +0,0 @@
# ProcessMemoryInfo Object
* `pid` Integer - Process id of the process.
* `memory` [MemoryInfo](memory-info.md) - Memory information of the process.

View file

@ -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.

View file

@ -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 There is no timetable for when this release will occur but deprecation
warnings will be added at least 90 days beforehand. warnings will be added at least 90 days beforehand.
## `app`
```js
// Deprecated
app.getAppMemoryInfo()
// Replace with
app.getAppMetrics()
```
## `BrowserWindow` ## `BrowserWindow`
```js ```js

View file

@ -534,16 +534,28 @@ describe('app module', function () {
}) })
}) })
describe('getAppMemoryInfo() API', function () { describe('getAppMetrics() API', function () {
it('returns the process memory of all running electron processes', function () { it('returns memory and cpu stats of all running electron processes', function () {
const appMemoryInfo = app.getAppMemoryInfo() const appMetrics = app.getAppMetrics()
assert.ok(appMemoryInfo.length > 0, 'App memory info object is not > 0') assert.ok(appMetrics.length > 0, 'App memory info object is not > 0')
for (const {memory, pid} of appMemoryInfo) { const types = []
for (const {memory, pid, type, cpu} of appMetrics) {
assert.ok(memory.workingSetSize > 0, 'working set size is not > 0') assert.ok(memory.workingSetSize > 0, 'working set size is not > 0')
assert.ok(memory.privateBytes > 0, 'private bytes 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(memory.sharedBytes > 0, 'shared bytes is not > 0')
assert.ok(pid > 0, 'pid 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'))
}) })
}) })
}) })