From 103b38650f8444ae0d8ac4774484f158da74eb5d Mon Sep 17 00:00:00 2001 From: Milan Burda Date: Tue, 23 Jul 2019 22:41:59 +0200 Subject: [PATCH] feat: add memory to app.getAppMetrics() (#18831) --- docs/api/structures/memory-info.md | 9 ++++ docs/api/structures/process-metric.md | 1 + filenames.auto.gni | 1 + lib/browser/api/app.ts | 38 +++++++++++++++++ shell/browser/api/atom_api_app.cc | 19 +++++++++ shell/browser/api/process_metric.cc | 61 ++++++++++++++++++++++++++- shell/browser/api/process_metric.h | 14 ++++++ spec-main/api-app-spec.ts | 7 +++ 8 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 docs/api/structures/memory-info.md diff --git a/docs/api/structures/memory-info.md b/docs/api/structures/memory-info.md new file mode 100644 index 000000000000..57b96e5696da --- /dev/null +++ b/docs/api/structures/memory-info.md @@ -0,0 +1,9 @@ +# MemoryInfo Object + +* `workingSetSize` Integer - The amount of memory currently pinned to actual physical RAM. +* `peakWorkingSetSize` Integer - The maximum amount of memory that has ever been pinned + to actual physical RAM. +* `privateBytes` Integer (optional) _Windows_ - The amount of memory not shared by other processes, such as + JS heap or HTML content. + +Note that all statistics are reported in Kilobytes. diff --git a/docs/api/structures/process-metric.md b/docs/api/structures/process-metric.md index 65ddeb3f4410..bd5a349def62 100644 --- a/docs/api/structures/process-metric.md +++ b/docs/api/structures/process-metric.md @@ -16,6 +16,7 @@ The time is represented as number of milliseconds since epoch. Since the `pid` can be reused after a process dies, it is useful to use both the `pid` and the `creationTime` to uniquely identify a process. +* `memory` [MemoryInfo](memory-info.md) - Memory information for the process. * `sandboxed` Boolean (optional) _macOS_ _Windows_ - Whether the process is sandboxed on OS level. * `integrityLevel` String (optional) _Windows_ - One of the following values: * `untrusted` diff --git a/filenames.auto.gni b/filenames.auto.gni index b6822d11515b..88c1ea5b4f5f 100644 --- a/filenames.auto.gni +++ b/filenames.auto.gni @@ -86,6 +86,7 @@ auto_filenames = { "docs/api/structures/jump-list-category.md", "docs/api/structures/jump-list-item.md", "docs/api/structures/keyboard-event.md", + "docs/api/structures/memory-info.md", "docs/api/structures/memory-usage-details.md", "docs/api/structures/mime-typed-buffer.md", "docs/api/structures/notification-action.md", diff --git a/lib/browser/api/app.ts b/lib/browser/api/app.ts index ddb7dda0b6ca..36320d94e36d 100644 --- a/lib/browser/api/app.ts +++ b/lib/browser/api/app.ts @@ -1,3 +1,4 @@ +import * as fs from 'fs' import * as path from 'path' import { deprecate, Menu } from 'electron' @@ -66,6 +67,43 @@ if (process.platform === 'darwin') { app.dock!.getMenu = () => dockMenu } +if (process.platform === 'linux') { + const patternVmRSS = /^VmRSS:\s*(\d+) kB$/m + const patternVmHWM = /^VmHWM:\s*(\d+) kB$/m + + const getStatus = (pid: number) => { + try { + return fs.readFileSync(`/proc/${pid}/status`, 'utf8') + } catch { + return '' + } + } + + const getEntry = (file: string, pattern: RegExp) => { + const match = file.match(pattern) + return match ? parseInt(match[1], 10) : 0 + } + + const getProcessMemoryInfo = (pid: number) => { + const file = getStatus(pid) + + return { + workingSetSize: getEntry(file, patternVmRSS), + peakWorkingSetSize: getEntry(file, patternVmHWM) + } + } + + const nativeFn = app.getAppMetrics + app.getAppMetrics = () => { + const metrics = nativeFn.call(app) + for (const metric of metrics) { + metric.memory = getProcessMemoryInfo(metric.pid) + } + + return metrics + } +} + // Routes the events to webContents. const events = ['login', 'certificate-error', 'select-client-certificate'] for (const name of events) { diff --git a/shell/browser/api/atom_api_app.cc b/shell/browser/api/atom_api_app.cc index cf12f417ea3d..8532119ec3da 100644 --- a/shell/browser/api/atom_api_app.cc +++ b/shell/browser/api/atom_api_app.cc @@ -1218,6 +1218,25 @@ std::vector App::GetAppMetrics(v8::Isolate* isolate) { pid_dict.Set("creationTime", process_metric.second->process.CreationTime().ToJsTime()); +#if !defined(OS_LINUX) + auto memory_info = process_metric.second->GetMemoryInfo(); + + mate::Dictionary memory_dict = mate::Dictionary::CreateEmpty(isolate); + memory_dict.SetHidden("simple", true); + memory_dict.Set("workingSetSize", + static_cast(memory_info.working_set_size >> 10)); + memory_dict.Set( + "peakWorkingSetSize", + static_cast(memory_info.peak_working_set_size >> 10)); + +#if defined(OS_WIN) + memory_dict.Set("privateBytes", + static_cast(memory_info.private_bytes >> 10)); +#endif + + pid_dict.Set("memory", memory_dict); +#endif + #if defined(OS_MACOSX) pid_dict.Set("sandboxed", process_metric.second->IsSandboxed()); #elif defined(OS_WIN) diff --git a/shell/browser/api/process_metric.cc b/shell/browser/api/process_metric.cc index ea5f8aaa4a72..6e8de05ab66d 100644 --- a/shell/browser/api/process_metric.cc +++ b/shell/browser/api/process_metric.cc @@ -7,13 +7,46 @@ #include #include +#include "base/optional.h" + #if defined(OS_WIN) #include + +#include +#include "base/win/win_util.h" #endif #if defined(OS_MACOSX) +#include +#include "base/process/port_provider_mac.h" +#include "content/public/browser/browser_child_process_host.h" + extern "C" int sandbox_check(pid_t pid, const char* operation, int type, ...); -#endif + +namespace { + +mach_port_t TaskForPid(pid_t pid) { + mach_port_t task = MACH_PORT_NULL; + if (auto* port_provider = content::BrowserChildProcessHost::GetPortProvider()) + task = port_provider->TaskForPid(pid); + if (task == MACH_PORT_NULL && pid == getpid()) + task = mach_task_self(); + return task; +} + +base::Optional GetTaskInfo(mach_port_t task) { + if (task == MACH_PORT_NULL) + return base::nullopt; + mach_task_basic_info_data_t info = {}; + mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT; + kern_return_t kr = task_info(task, MACH_TASK_BASIC_INFO, + reinterpret_cast(&info), &count); + return (kr == KERN_SUCCESS) ? base::make_optional(info) : base::nullopt; +} + +} // namespace + +#endif // defined(OS_MACOSX) namespace electron { @@ -37,6 +70,21 @@ ProcessMetric::~ProcessMetric() = default; #if defined(OS_WIN) +ProcessMemoryInfo ProcessMetric::GetMemoryInfo() const { + ProcessMemoryInfo result; + + PROCESS_MEMORY_COUNTERS_EX info = {}; + if (::GetProcessMemoryInfo(process.Handle(), + reinterpret_cast(&info), + sizeof(info))) { + result.working_set_size = info.WorkingSetSize; + result.peak_working_set_size = info.PeakWorkingSetSize; + result.private_bytes = info.PrivateUsage; + } + + return result; +} + ProcessIntegrityLevel ProcessMetric::GetIntegrityLevel() const { HANDLE token = nullptr; if (!::OpenProcessToken(process.Handle(), TOKEN_QUERY, &token)) { @@ -96,6 +144,17 @@ bool ProcessMetric::IsSandboxed(ProcessIntegrityLevel integrity_level) { #elif defined(OS_MACOSX) +ProcessMemoryInfo ProcessMetric::GetMemoryInfo() const { + ProcessMemoryInfo result; + + if (auto info = GetTaskInfo(TaskForPid(process.Pid()))) { + result.working_set_size = info->resident_size; + result.peak_working_set_size = info->resident_size_max; + } + + return result; +} + bool ProcessMetric::IsSandboxed() const { #if defined(MAS_BUILD) return true; diff --git a/shell/browser/api/process_metric.h b/shell/browser/api/process_metric.h index 97cc03be0633..2f60ac0a82b6 100644 --- a/shell/browser/api/process_metric.h +++ b/shell/browser/api/process_metric.h @@ -13,6 +13,16 @@ namespace electron { +#if !defined(OS_LINUX) +struct ProcessMemoryInfo { + size_t working_set_size = 0; + size_t peak_working_set_size = 0; +#if defined(OS_WIN) + size_t private_bytes = 0; +#endif +}; +#endif + #if defined(OS_WIN) enum class ProcessIntegrityLevel { Unknown, @@ -33,6 +43,10 @@ struct ProcessMetric { std::unique_ptr metrics); ~ProcessMetric(); +#if !defined(OS_LINUX) + ProcessMemoryInfo GetMemoryInfo() const; +#endif + #if defined(OS_WIN) ProcessIntegrityLevel GetIntegrityLevel() const; static bool IsSandboxed(ProcessIntegrityLevel integrity_level); diff --git a/spec-main/api-app-spec.ts b/spec-main/api-app-spec.ts index e042ee1a11ba..3c1af73b0db6 100644 --- a/spec-main/api-app-spec.ts +++ b/spec-main/api-app-spec.ts @@ -966,6 +966,13 @@ describe('app module', () => { expect(entry.cpu).to.have.ownProperty('percentCPUUsage').that.is.a('number') expect(entry.cpu).to.have.ownProperty('idleWakeupsPerSecond').that.is.a('number') + expect(entry.memory).to.have.property('workingSetSize').that.is.greaterThan(0) + expect(entry.memory).to.have.property('peakWorkingSetSize').that.is.greaterThan(0) + + if (process.platform === 'win32') { + expect(entry.memory).to.have.property('privateBytes').that.is.greaterThan(0) + } + if (process.platform !== 'linux') { expect(entry.sandboxed).to.be.a('boolean') }