commit d3869e5259f94ab88bf4cc06c39c36589ac0b9e6
parent 913045c3a687b810208fb4e1b010994eaa9d05ef
Author: Tarek Ziadé <tarek@ziade.org>
Date: Wed, 5 Nov 2025 12:13:31 +0000
Bug 1996801 - Add an API to get the current process used memory r=florian
Differential Revision: https://phabricator.services.mozilla.com/D270316
Diffstat:
12 files changed, 200 insertions(+), 0 deletions(-)
diff --git a/dom/base/ChromeUtils.cpp b/dom/base/ChromeUtils.cpp
@@ -2054,6 +2054,28 @@ already_AddRefed<Promise> ChromeUtils::RequestProcInfo(GlobalObject& aGlobal,
}
/* static */
+uint64_t ChromeUtils::GetCurrentProcessMemoryUsage(GlobalObject& aGlobal,
+ ErrorResult& aRv) {
+ uint64_t retVal = 0;
+ nsresult rv = mozilla::GetCurrentProcessMemoryUsage(&retVal);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+ return retVal;
+}
+
+/* static */
+uint64_t ChromeUtils::GetCpuTimeSinceProcessStart(GlobalObject& aGlobal,
+ ErrorResult& aRv) {
+ uint64_t retVal = 0;
+ nsresult rv = mozilla::GetCpuTimeSinceProcessStartInMs(&retVal);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+ return retVal;
+}
+
+/* static */
bool ChromeUtils::VsyncEnabled(GlobalObject& aGlobal) {
return mozilla::gfx::VsyncSource::GetFastestVsyncRate().isSome();
}
diff --git a/dom/base/ChromeUtils.h b/dom/base/ChromeUtils.h
@@ -216,6 +216,11 @@ class ChromeUtils {
static already_AddRefed<Promise> RequestProcInfo(GlobalObject& aGlobal,
ErrorResult& aRv);
+ static uint64_t GetCurrentProcessMemoryUsage(GlobalObject& aGlobal,
+ ErrorResult& aRv);
+ static uint64_t GetCpuTimeSinceProcessStart(GlobalObject& aGlobal,
+ ErrorResult& aRv);
+
static bool VsyncEnabled(GlobalObject& aGlobal);
static void Import(const GlobalObject& aGlobal,
diff --git a/dom/chrome-webidl/ChromeUtils.webidl b/dom/chrome-webidl/ChromeUtils.webidl
@@ -442,6 +442,20 @@ namespace ChromeUtils {
UTF8String encodeURIForSrcset(UTF8String uri);
/**
+ * Returns, in bytes, a platform-normalized estimate of the process's private physical memory usage.
+ * Any error when calling the underlying platform-specific API will be thrown.
+ */
+ [Throws]
+ readonly attribute unsigned long long currentProcessMemoryUsage;
+
+ /**
+ * Return the number of milliseconds of CPU time used since process start.
+ * Any error when calling the underlying platform-specific API will be thrown.
+ */
+ [Throws]
+ readonly attribute unsigned long long cpuTimeSinceProcessStart;
+
+ /**
* IF YOU ADD NEW METHODS HERE, MAKE SURE THEY ARE THREAD-SAFE.
*/
};
diff --git a/toolkit/components/ml/actors/MLEngineParent.sys.mjs b/toolkit/components/ml/actors/MLEngineParent.sys.mjs
@@ -1427,6 +1427,11 @@ export class MLEngine {
* @returns {Promise<null | { cpuTime: null | number, memory: null | number}>}
*/
async getInferenceResources() {
+ // TODO(Greg): ask that question directly to the inference process *or* move your metrics down into the child process
+ // so you don't have to do any IPC at all.
+ // you can get the memory with ChromeUtils.currentProcessMemoryUsage and the CPU since start with ChromeUtils.cpuTimeSinceProcessStart
+ // theses call can be done anywhere in the inference process including the workers, which means you can Glean metrics in any place with
+ // no IPC
try {
const { children } = await ChromeUtils.requestProcInfo();
const [inference] = children.filter(child => child.type == "inference");
diff --git a/toolkit/components/processtools/ProcInfo.h b/toolkit/components/processtools/ProcInfo.h
@@ -21,6 +21,15 @@ class GeckoChildProcessHost;
}
/**
+ * Returns, in bytes, a platform-normalized estimate of the process's
+ * private physical memory usage, that is, how much RAM the process
+ * alone consumes.
+ *
+ * @return NS_OK on success.
+ */
+nsresult GetCurrentProcessMemoryUsage(uint64_t* aResult);
+
+/**
* Return the number of milliseconds of CPU time used since process start.
*
* @return NS_OK on success.
diff --git a/toolkit/components/processtools/ProcInfo.mm b/toolkit/components/processtools/ProcInfo.mm
@@ -28,6 +28,24 @@ static void GetTimeBase(mach_timebase_info_data_t* timebase) {
namespace mozilla {
+nsresult GetCurrentProcessMemoryUsage(uint64_t* aResult) {
+ if (!aResult) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ task_vm_info_data_t info;
+ mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
+
+ kern_return_t kr = task_info(mach_task_self(), TASK_VM_INFO,
+ reinterpret_cast<task_info_t>(&info), &count);
+
+ if (kr != KERN_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+ // phys_footprint matches Activity Monitor’s “Memory” column on macOS 10.11+
+ *aResult = info.phys_footprint;
+ return NS_OK;
+}
+
nsresult GetCpuTimeSinceProcessStartInMs(uint64_t* aResult) {
task_power_info_data_t task_power_info;
mach_msg_type_number_t count = TASK_POWER_INFO_COUNT;
diff --git a/toolkit/components/processtools/ProcInfo_bsd.cpp b/toolkit/components/processtools/ProcInfo_bsd.cpp
@@ -37,6 +37,10 @@ nsresult GetCpuTimeSinceProcessStartInMs(uint64_t* aResult) {
return NS_ERROR_FAILURE;
}
+nsresult GetCurrentProcessMemoryUsage(uint64_t* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
nsresult GetGpuTimeSinceProcessStartInMs(uint64_t* aResult) {
return NS_ERROR_NOT_IMPLEMENTED;
}
diff --git a/toolkit/components/processtools/ProcInfo_linux.cpp b/toolkit/components/processtools/ProcInfo_linux.cpp
@@ -22,6 +22,26 @@
namespace mozilla {
+nsresult GetCurrentProcessMemoryUsage(uint64_t* aResult) {
+ if (!aResult) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ FILE* f = fopen("/proc/self/statm", "r");
+ if (!f) {
+ return NS_ERROR_FAILURE;
+ }
+ size_t vmSize = 0, resident = 0, shared = 0;
+ const int kExpected = 3;
+ int nread = fscanf(f, "%zu %zu %zu", &vmSize, &resident, &shared);
+ fclose(f);
+
+ if (nread != kExpected) {
+ return NS_ERROR_FAILURE;
+ }
+ *aResult = uint64_t(resident - shared) * getpagesize();
+ return NS_OK;
+}
+
int GetCycleTimeFrequencyMHz() { return 0; }
// StatReader can parse and tokenize a POSIX stat file.
diff --git a/toolkit/components/processtools/ProcInfo_solaris.cpp b/toolkit/components/processtools/ProcInfo_solaris.cpp
@@ -28,6 +28,10 @@
namespace mozilla {
+nsresult GetCurrentProcessMemoryUsage(uint64_t* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
int GetCycleTimeFrequencyMHz() { return 0; }
nsresult GetCpuTimeSinceProcessStartInMs(uint64_t* aResult) {
diff --git a/toolkit/components/processtools/ProcInfo_win.cpp b/toolkit/components/processtools/ProcInfo_win.cpp
@@ -32,6 +32,22 @@ static uint64_t ToNanoSeconds(const FILETIME& aFileTime) {
return usec.QuadPart * 100;
}
+nsresult GetCurrentProcessMemoryUsage(uint64_t* aResult) {
+ if (!aResult) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ PROCESS_MEMORY_COUNTERS_EX pmc;
+ if (!GetProcessMemoryInfo(GetCurrentProcess(),
+ reinterpret_cast<PPROCESS_MEMORY_COUNTERS>(&pmc),
+ sizeof(pmc))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResult = static_cast<uint64_t>(pmc.PrivateUsage);
+ return NS_OK;
+}
+
int GetCpuFrequencyMHz() {
static const int frequency = []() {
// Get the nominal CPU frequency.
diff --git a/toolkit/components/processtools/tests/xpcshell/test_proc_info.js b/toolkit/components/processtools/tests/xpcshell/test_proc_info.js
@@ -0,0 +1,81 @@
+"use strict";
+
+// Keep allocations alive across the whole test so GC can't reclaim them.
+const KEEP_ALIVE = [];
+
+// Allocate N MiB using ArrayBuffers and *touch* the memory so it's committed.
+function allocateTypedMiB(totalMiB = 64, chunkMiB = 8) {
+ const chunkSize = chunkMiB * 1024 * 1024;
+ const count = Math.ceil(totalMiB / chunkMiB);
+ for (let i = 0; i < count; i++) {
+ const buf = new ArrayBuffer(chunkSize);
+ // Touch one byte per page to ensure RSS growth.
+ const view = new Uint8Array(buf);
+ for (let j = 0; j < view.length; j += 4096) {
+ view[j] = 1;
+ }
+ KEEP_ALIVE.push(buf);
+ }
+}
+
+add_task(async function test_currentProcessMemoryUsage() {
+ // Simulate memory allocation (strongly reachable + committed)
+ Cu.forceGC();
+ Cu.forceCC();
+ let initialMemory = ChromeUtils.currentProcessMemoryUsage;
+ let arr = [];
+ for (let i = 0; i < 1_000_000; i++) {
+ arr.push({ v: i });
+ }
+
+ KEEP_ALIVE.push(arr);
+ // Plus a guaranteed 64 MiB of typed-array memory to make growth obvious
+ allocateTypedMiB(64, 8);
+
+ let finalMemory = ChromeUtils.currentProcessMemoryUsage;
+
+ info(`Initial memory: ${initialMemory}, final memory: ${finalMemory}`);
+
+ Assert.greater(
+ finalMemory,
+ initialMemory,
+ "Memory usage should increase after allocations"
+ );
+
+ Assert.greater(
+ finalMemory - initialMemory,
+ 64 * 1024 * 1024,
+ "Memory usage should have growned over 64MiB"
+ );
+});
+
+async function getCurrentProcessInfo() {
+ let info = await ChromeUtils.requestProcInfo();
+ // nanoseconds to milliseconds
+ return Math.floor(info.cpuTime / 1000000);
+}
+
+add_task(async function test_cpuTimeSinceProcessStart() {
+ let initialCPUTimeFromRequestProcInfo = await getCurrentProcessInfo();
+ let initialCPUTime = ChromeUtils.cpuTimeSinceProcessStart;
+ let secondCPUTimeFromRequestProcInfo = await getCurrentProcessInfo();
+ Assert.greaterOrEqual(initialCPUTime, initialCPUTimeFromRequestProcInfo);
+ Assert.greaterOrEqual(secondCPUTimeFromRequestProcInfo, initialCPUTime);
+
+ // Simulate some CPU load
+ let sum = [0];
+ for (let i = 0; i < 5_000_000; i++) {
+ sum[0] += Math.sqrt(i);
+ }
+ let finalCPUTimeFromRequestProcInfo = await getCurrentProcessInfo();
+ let finalCPUTime = ChromeUtils.cpuTimeSinceProcessStart;
+ Assert.greaterOrEqual(finalCPUTime, finalCPUTimeFromRequestProcInfo);
+
+ info(`Initial CPU time: ${initialCPUTime}, final CPU time: ${finalCPUTime}`);
+
+ Assert.greater(
+ finalCPUTime,
+ initialCPUTime,
+ "CPU time should increase after computation"
+ );
+});
diff --git a/toolkit/components/processtools/tests/xpcshell/xpcshell.toml b/toolkit/components/processtools/tests/xpcshell/xpcshell.toml
@@ -2,6 +2,8 @@
firefox-appdir = "browser"
subprocess = true
+["test_proc_info.js"]
+
["test_process_kill.js"]
skip-if = ["os == 'android'"]