diff --git a/atom/common/api/atom_bindings.cc b/atom/common/api/atom_bindings.cc index 991cbd96943..0dd1ceab607 100644 --- a/atom/common/api/atom_bindings.cc +++ b/atom/common/api/atom_bindings.cc @@ -7,21 +7,31 @@ #include #include #include +#include +#include +#include "atom/browser/browser.h" #include "atom/common/api/locker.h" #include "atom/common/application_info.h" #include "atom/common/atom_version.h" #include "atom/common/heap_snapshot.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 "atom/common/promise_util.h" #include "base/logging.h" +#include "base/process/process_handle.h" #include "base/process/process_info.h" #include "base/process/process_metrics_iocounters.h" #include "base/sys_info.h" #include "base/threading/thread_restrictions.h" #include "chrome/common/chrome_version.h" #include "native_mate/dictionary.h" +#include "services/resource_coordinator/public/cpp/memory_instrumentation/global_memory_dump.h" +#include "services/resource_coordinator/public/cpp/memory_instrumentation/memory_instrumentation.h" + +// Must be the last in the includes list, otherwise the definition of chromium +// macros conflicts with node macros. +#include "atom/common/node_includes.h" namespace atom { @@ -61,6 +71,7 @@ void AtomBindings::BindTo(v8::Isolate* isolate, v8::Local process) { dict.SetMethod("getHeapStatistics", &GetHeapStatistics); dict.SetMethod("getCreationTime", &GetCreationTime); dict.SetMethod("getSystemMemoryInfo", &GetSystemMemoryInfo); + dict.SetMethod("getProcessMemoryInfo", &GetProcessMemoryInfo); dict.SetMethod("getCPUUsage", base::Bind(&AtomBindings::GetCPUUsage, base::Unretained(metrics_.get()))); dict.SetMethod("getIOCounters", &GetIOCounters); @@ -208,6 +219,67 @@ v8::Local AtomBindings::GetSystemMemoryInfo(v8::Isolate* isolate, return dict.GetHandle(); } +// static +v8::Local AtomBindings::GetProcessMemoryInfo( + v8::Isolate* isolate) { + scoped_refptr promise = new util::Promise(isolate); + + if (mate::Locker::IsBrowserProcess() && !Browser::Get()->is_ready()) { + promise->RejectWithErrorMessage( + "Memory Info is available only after app ready"); + return promise->GetHandle(); + } + + v8::Global context(isolate, isolate->GetCurrentContext()); + memory_instrumentation::MemoryInstrumentation::GetInstance() + ->RequestGlobalDumpForPid(base::GetCurrentProcId(), + std::vector(), + base::Bind(&AtomBindings::DidReceiveMemoryDump, + std::move(context), promise)); + return promise->GetHandle(); +} + +// static +void AtomBindings::DidReceiveMemoryDump( + const v8::Global& context, + scoped_refptr promise, + bool success, + std::unique_ptr global_dump) { + v8::Isolate* isolate = promise->isolate(); + mate::Locker locker(isolate); + v8::HandleScope handle_scope(isolate); + v8::MicrotasksScope script_scope(isolate, + v8::MicrotasksScope::kRunMicrotasks); + v8::Context::Scope context_scope( + v8::Local::New(isolate, context)); + + if (!success) { + promise->RejectWithErrorMessage("Failed to create memory dump"); + return; + } + + bool resolved = false; + for (const memory_instrumentation::GlobalMemoryDump::ProcessDump& dump : + global_dump->process_dumps()) { + if (base::GetCurrentProcId() == dump.pid()) { + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + const auto& osdump = dump.os_dump(); +#if defined(OS_LINUX) || defined(OS_WIN) + dict.Set("residentSet", osdump.resident_set_kb); +#endif + dict.Set("private", osdump.private_footprint_kb); + dict.Set("shared", osdump.shared_footprint_kb); + promise->Resolve(dict.GetHandle()); + resolved = true; + break; + } + } + if (!resolved) { + promise->RejectWithErrorMessage( + R"(Failed to find current process memory details in memory dump)"); + } +} + // static v8::Local AtomBindings::GetCPUUsage(base::ProcessMetrics* metrics, v8::Isolate* isolate) { diff --git a/atom/common/api/atom_bindings.h b/atom/common/api/atom_bindings.h index fd08c2dc1b6..92cd9d2eb17 100644 --- a/atom/common/api/atom_bindings.h +++ b/atom/common/api/atom_bindings.h @@ -10,18 +10,27 @@ #include "base/files/file_path.h" #include "base/macros.h" +#include "base/memory/scoped_refptr.h" #include "base/process/process_metrics.h" #include "base/strings/string16.h" #include "native_mate/arguments.h" #include "uv.h" // NOLINT(build/include) #include "v8/include/v8.h" +namespace memory_instrumentation { +class GlobalMemoryDump; +} + namespace node { class Environment; } namespace atom { +namespace util { +class Promise; +} + class AtomBindings { public: explicit AtomBindings(uv_loop_t* loop); @@ -41,6 +50,7 @@ class AtomBindings { static v8::Local GetCreationTime(v8::Isolate* isolate); static v8::Local GetSystemMemoryInfo(v8::Isolate* isolate, mate::Arguments* args); + static v8::Local GetProcessMemoryInfo(v8::Isolate* isolate); static v8::Local GetCPUUsage(base::ProcessMetrics* metrics, v8::Isolate* isolate); static v8::Local GetIOCounters(v8::Isolate* isolate); @@ -52,6 +62,12 @@ class AtomBindings { static void OnCallNextTick(uv_async_t* handle); + static void DidReceiveMemoryDump( + const v8::Global& context, + scoped_refptr promise, + bool success, + std::unique_ptr dump); + uv_async_t call_next_tick_async_; std::list pending_next_ticks_; std::unique_ptr metrics_; diff --git a/docs/api/process.md b/docs/api/process.md index a14f9170942..6f9a04b47f2 100644 --- a/docs/api/process.md +++ b/docs/api/process.md @@ -14,6 +14,7 @@ In sandboxed renderers the `process` object contains only a subset of the APIs: - `crash()` - `hang()` - `getHeapStatistics()` +- `getProcessMemoryInfo()` - `getSystemMemoryInfo()` - `getCPUUsage()` - `getIOCounters()` @@ -162,6 +163,27 @@ Returns `Object`: Returns an object with V8 heap statistics. Note that all statistics are reported in Kilobytes. +### `process.getProcessMemoryInfo()` + +Returns `Object`: + +* `residentSet` Integer _Linux_ and _Windows_ - The amount of memory +currently pinned to actual physical RAM in Kilobytes. +* `private` Integer - The amount of memory not shared by other processes, such as + JS heap or HTML content in Kilobytes. +* `shared` Integer - The amount of memory shared between processes, typically + memory consumed by the Electron code itself in Kilobytes. + +Returns an object giving memory usage statistics about the current process. Note +that all statistics are reported in Kilobytes. +This api should be called after app ready. + +Chromium does not provide `residentSet` value for macOS. This is because macOS +performs in-memory compression of pages that haven't been recently used. As a +result the resident set size value is not what one would expect. `private` memory +is more representative of the actual pre-compression memory usage of the process +on macOS. + ### `process.getSystemMemoryInfo()` Returns `Object`: diff --git a/spec/api-process-spec.js b/spec/api-process-spec.js index 42d84b115b4..6c6452892f4 100644 --- a/spec/api-process-spec.js +++ b/spec/api-process-spec.js @@ -38,16 +38,18 @@ describe('process module', () => { }) }) - // FIXME: Chromium 67 - getProcessMemoryInfo has been removed - // describe('process.getProcessMemoryInfo()', () => { - // it('returns process memory info object', () => { - // const processMemoryInfo = process.getProcessMemoryInfo() - // expect(processMemoryInfo.peakWorkingSetSize).to.be.a('number') - // expect(processMemoryInfo.privateBytes).to.be.a('number') - // expect(processMemoryInfo.sharedBytes).to.be.a('number') - // expect(processMemoryInfo.workingSetSize).to.be.a('number') - // }) - // }) + describe('process.getProcessMemoryInfo()', async () => { + it('resolves promise successfully with valid data', async () => { + const memoryInfo = await process.getProcessMemoryInfo() + expect(memoryInfo).to.be.an('object') + if (process.platform === 'linux' || process.platform === 'windows') { + expect(memoryInfo.residentSet).to.be.a('number').greaterThan(0) + } + expect(memoryInfo.private).to.be.a('number').greaterThan(0) + // Shared bytes can be zero + expect(memoryInfo.shared).to.be.a('number').greaterThan(-1) + }) + }) describe('process.getSystemMemoryInfo()', () => { it('returns system memory info object', () => {