1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "nsAtomTable.h"
8 #include "nsCOMPtr.h"
9 #include "nsCOMArray.h"
10 #include "nsPrintfCString.h"
11 #include "nsProxyRelease.h"
12 #include "nsServiceManagerUtils.h"
13 #include "nsMemoryReporterManager.h"
14 #include "nsITimer.h"
15 #include "nsThreadUtils.h"
16 #include "nsPIDOMWindow.h"
17 #include "nsIObserverService.h"
18 #include "nsIOService.h"
19 #include "nsIGlobalObject.h"
20 #include "nsIXPConnect.h"
21 #ifdef MOZ_GECKO_PROFILER
22 #  include "GeckoProfilerReporter.h"
23 #endif
24 #if defined(XP_UNIX) || defined(MOZ_DMD)
25 #  include "nsMemoryInfoDumper.h"
26 #endif
27 #include "nsNetCID.h"
28 #include "nsThread.h"
29 #include "VRProcessManager.h"
30 #include "mozilla/Attributes.h"
31 #include "mozilla/MemoryReportingProcess.h"
32 #include "mozilla/PodOperations.h"
33 #include "mozilla/Preferences.h"
34 #include "mozilla/RDDProcessManager.h"
35 #include "mozilla/ResultExtensions.h"
36 #include "mozilla/Services.h"
37 #include "mozilla/Telemetry.h"
38 #include "mozilla/UniquePtrExtensions.h"
39 #include "mozilla/dom/MemoryReportTypes.h"
40 #include "mozilla/dom/ContentParent.h"
41 #include "mozilla/gfx/GPUProcessManager.h"
42 #include "mozilla/ipc/FileDescriptorUtils.h"
43 
44 #ifdef XP_WIN
45 #  include "mozilla/MemoryInfo.h"
46 
47 #  include <process.h>
48 #  ifndef getpid
49 #    define getpid _getpid
50 #  endif
51 #else
52 #  include <unistd.h>
53 #endif
54 
55 using namespace mozilla;
56 using namespace dom;
57 
58 #if defined(MOZ_MEMORY)
59 #  define HAVE_JEMALLOC_STATS 1
60 #  include "mozmemory.h"
61 #endif  // MOZ_MEMORY
62 
63 #if defined(XP_LINUX)
64 
65 #  include "mozilla/MemoryMapping.h"
66 
67 #  include <malloc.h>
68 #  include <string.h>
69 #  include <stdlib.h>
70 
GetProcSelfStatmField(int aField,int64_t * aN)71 [[nodiscard]] static nsresult GetProcSelfStatmField(int aField, int64_t* aN) {
72   // There are more than two fields, but we're only interested in the first
73   // two.
74   static const int MAX_FIELD = 2;
75   size_t fields[MAX_FIELD];
76   MOZ_ASSERT(aField < MAX_FIELD, "bad field number");
77   FILE* f = fopen("/proc/self/statm", "r");
78   if (f) {
79     int nread = fscanf(f, "%zu %zu", &fields[0], &fields[1]);
80     fclose(f);
81     if (nread == MAX_FIELD) {
82       *aN = fields[aField] * getpagesize();
83       return NS_OK;
84     }
85   }
86   return NS_ERROR_FAILURE;
87 }
88 
GetProcSelfSmapsPrivate(int64_t * aN)89 [[nodiscard]] static nsresult GetProcSelfSmapsPrivate(int64_t* aN) {
90   // You might be tempted to calculate USS by subtracting the "shared" value
91   // from the "resident" value in /proc/<pid>/statm. But at least on Linux,
92   // statm's "shared" value actually counts pages backed by files, which has
93   // little to do with whether the pages are actually shared. /proc/self/smaps
94   // on the other hand appears to give us the correct information.
95 
96   nsTArray<MemoryMapping> mappings(1024);
97   MOZ_TRY(GetMemoryMappings(mappings));
98 
99   int64_t amount = 0;
100   for (auto& mapping : mappings) {
101     amount += mapping.Private_Clean();
102     amount += mapping.Private_Dirty();
103   }
104   *aN = amount;
105   return NS_OK;
106 }
107 
108 #  define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1
VsizeDistinguishedAmount(int64_t * aN)109 [[nodiscard]] static nsresult VsizeDistinguishedAmount(int64_t* aN) {
110   return GetProcSelfStatmField(0, aN);
111 }
112 
ResidentDistinguishedAmount(int64_t * aN)113 [[nodiscard]] static nsresult ResidentDistinguishedAmount(int64_t* aN) {
114   return GetProcSelfStatmField(1, aN);
115 }
116 
ResidentFastDistinguishedAmount(int64_t * aN)117 [[nodiscard]] static nsresult ResidentFastDistinguishedAmount(int64_t* aN) {
118   return ResidentDistinguishedAmount(aN);
119 }
120 
121 #  define HAVE_RESIDENT_UNIQUE_REPORTER 1
ResidentUniqueDistinguishedAmount(int64_t * aN)122 [[nodiscard]] static nsresult ResidentUniqueDistinguishedAmount(int64_t* aN) {
123   return GetProcSelfSmapsPrivate(aN);
124 }
125 
126 #  ifdef HAVE_MALLINFO
127 #    define HAVE_SYSTEM_HEAP_REPORTER 1
SystemHeapSize(int64_t * aSizeOut)128 [[nodiscard]] static nsresult SystemHeapSize(int64_t* aSizeOut) {
129   struct mallinfo info = mallinfo();
130 
131   // The documentation in the glibc man page makes it sound like |uordblks|
132   // would suffice, but that only gets the small allocations that are put in
133   // the brk heap. We need |hblkhd| as well to get the larger allocations
134   // that are mmapped.
135   //
136   // The fields in |struct mallinfo| are all |int|, <sigh>, so it is
137   // unreliable if memory usage gets high. However, the system heap size on
138   // Linux should usually be zero (so long as jemalloc is enabled) so that
139   // shouldn't be a problem. Nonetheless, cast the |int|s to |size_t| before
140   // adding them to provide a small amount of extra overflow protection.
141   *aSizeOut = size_t(info.hblkhd) + size_t(info.uordblks);
142   return NS_OK;
143 }
144 #  endif
145 
146 #elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || \
147     defined(__OpenBSD__) || defined(__FreeBSD_kernel__)
148 
149 #  include <sys/param.h>
150 #  include <sys/sysctl.h>
151 #  if defined(__DragonFly__) || defined(__FreeBSD__) || \
152       defined(__FreeBSD_kernel__)
153 #    include <sys/user.h>
154 #  endif
155 
156 #  include <unistd.h>
157 
158 #  if defined(__NetBSD__)
159 #    undef KERN_PROC
160 #    define KERN_PROC KERN_PROC2
161 #    define KINFO_PROC struct kinfo_proc2
162 #  else
163 #    define KINFO_PROC struct kinfo_proc
164 #  endif
165 
166 #  if defined(__DragonFly__)
167 #    define KP_SIZE(kp) (kp.kp_vm_map_size)
168 #    define KP_RSS(kp) (kp.kp_vm_rssize * getpagesize())
169 #  elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
170 #    define KP_SIZE(kp) (kp.ki_size)
171 #    define KP_RSS(kp) (kp.ki_rssize * getpagesize())
172 #  elif defined(__NetBSD__)
173 #    define KP_SIZE(kp) (kp.p_vm_msize * getpagesize())
174 #    define KP_RSS(kp) (kp.p_vm_rssize * getpagesize())
175 #  elif defined(__OpenBSD__)
176 #    define KP_SIZE(kp) \
177       ((kp.p_vm_dsize + kp.p_vm_ssize + kp.p_vm_tsize) * getpagesize())
178 #    define KP_RSS(kp) (kp.p_vm_rssize * getpagesize())
179 #  endif
180 
GetKinfoProcSelf(KINFO_PROC * aProc)181 [[nodiscard]] static nsresult GetKinfoProcSelf(KINFO_PROC* aProc) {
182 #  if defined(__OpenBSD__) && defined(MOZ_SANDBOX)
183   static LazyLogModule sPledgeLog("SandboxPledge");
184   MOZ_LOG(sPledgeLog, LogLevel::Debug,
185           ("%s called when pledged, returning NS_ERROR_FAILURE\n", __func__));
186   return NS_ERROR_FAILURE;
187 #  endif
188   int mib[] = {
189     CTL_KERN,
190     KERN_PROC,
191     KERN_PROC_PID,
192     getpid(),
193 #  if defined(__NetBSD__) || defined(__OpenBSD__)
194     sizeof(KINFO_PROC),
195     1,
196 #  endif
197   };
198   u_int miblen = sizeof(mib) / sizeof(mib[0]);
199   size_t size = sizeof(KINFO_PROC);
200   if (sysctl(mib, miblen, aProc, &size, nullptr, 0)) {
201     return NS_ERROR_FAILURE;
202   }
203   return NS_OK;
204 }
205 
206 #  define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1
VsizeDistinguishedAmount(int64_t * aN)207 [[nodiscard]] static nsresult VsizeDistinguishedAmount(int64_t* aN) {
208   KINFO_PROC proc;
209   nsresult rv = GetKinfoProcSelf(&proc);
210   if (NS_SUCCEEDED(rv)) {
211     *aN = KP_SIZE(proc);
212   }
213   return rv;
214 }
215 
ResidentDistinguishedAmount(int64_t * aN)216 [[nodiscard]] static nsresult ResidentDistinguishedAmount(int64_t* aN) {
217   KINFO_PROC proc;
218   nsresult rv = GetKinfoProcSelf(&proc);
219   if (NS_SUCCEEDED(rv)) {
220     *aN = KP_RSS(proc);
221   }
222   return rv;
223 }
224 
ResidentFastDistinguishedAmount(int64_t * aN)225 [[nodiscard]] static nsresult ResidentFastDistinguishedAmount(int64_t* aN) {
226   return ResidentDistinguishedAmount(aN);
227 }
228 
229 #  ifdef __FreeBSD__
230 #    include <libutil.h>
231 #    include <algorithm>
232 
GetKinfoVmentrySelf(int64_t * aPrss,uint64_t * aMaxreg)233 [[nodiscard]] static nsresult GetKinfoVmentrySelf(int64_t* aPrss,
234                                                   uint64_t* aMaxreg) {
235   int cnt;
236   struct kinfo_vmentry* vmmap;
237   struct kinfo_vmentry* kve;
238   if (!(vmmap = kinfo_getvmmap(getpid(), &cnt))) {
239     return NS_ERROR_FAILURE;
240   }
241   if (aPrss) {
242     *aPrss = 0;
243   }
244   if (aMaxreg) {
245     *aMaxreg = 0;
246   }
247 
248   for (int i = 0; i < cnt; i++) {
249     kve = &vmmap[i];
250     if (aPrss) {
251       *aPrss += kve->kve_private_resident;
252     }
253     if (aMaxreg) {
254       *aMaxreg = std::max(*aMaxreg, kve->kve_end - kve->kve_start);
255     }
256   }
257 
258   free(vmmap);
259   return NS_OK;
260 }
261 
262 #    define HAVE_PRIVATE_REPORTER 1
PrivateDistinguishedAmount(int64_t * aN)263 [[nodiscard]] static nsresult PrivateDistinguishedAmount(int64_t* aN) {
264   int64_t priv;
265   nsresult rv = GetKinfoVmentrySelf(&priv, nullptr);
266   NS_ENSURE_SUCCESS(rv, rv);
267   *aN = priv * getpagesize();
268   return NS_OK;
269 }
270 
271 #    define HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER 1
VsizeMaxContiguousDistinguishedAmount(int64_t * aN)272 [[nodiscard]] static nsresult VsizeMaxContiguousDistinguishedAmount(
273     int64_t* aN) {
274   uint64_t biggestRegion;
275   nsresult rv = GetKinfoVmentrySelf(nullptr, &biggestRegion);
276   if (NS_SUCCEEDED(rv)) {
277     *aN = biggestRegion;
278   }
279   return NS_OK;
280 }
281 #  endif  // FreeBSD
282 
283 #elif defined(SOLARIS)
284 
285 #  include <procfs.h>
286 #  include <fcntl.h>
287 #  include <unistd.h>
288 
XMappingIter(int64_t & aVsize,int64_t & aResident,int64_t & aShared)289 static void XMappingIter(int64_t& aVsize, int64_t& aResident,
290                          int64_t& aShared) {
291   aVsize = -1;
292   aResident = -1;
293   aShared = -1;
294   int mapfd = open("/proc/self/xmap", O_RDONLY);
295   struct stat st;
296   prxmap_t* prmapp = nullptr;
297   if (mapfd >= 0) {
298     if (!fstat(mapfd, &st)) {
299       int nmap = st.st_size / sizeof(prxmap_t);
300       while (1) {
301         // stat(2) on /proc/<pid>/xmap returns an incorrect value,
302         // prior to the release of Solaris 11.
303         // Here is a workaround for it.
304         nmap *= 2;
305         prmapp = (prxmap_t*)malloc((nmap + 1) * sizeof(prxmap_t));
306         if (!prmapp) {
307           // out of memory
308           break;
309         }
310         int n = pread(mapfd, prmapp, (nmap + 1) * sizeof(prxmap_t), 0);
311         if (n < 0) {
312           break;
313         }
314         if (nmap >= n / sizeof(prxmap_t)) {
315           aVsize = 0;
316           aResident = 0;
317           aShared = 0;
318           for (int i = 0; i < n / sizeof(prxmap_t); i++) {
319             aVsize += prmapp[i].pr_size;
320             aResident += prmapp[i].pr_rss * prmapp[i].pr_pagesize;
321             if (prmapp[i].pr_mflags & MA_SHARED) {
322               aShared += prmapp[i].pr_rss * prmapp[i].pr_pagesize;
323             }
324           }
325           break;
326         }
327         free(prmapp);
328       }
329       free(prmapp);
330     }
331     close(mapfd);
332   }
333 }
334 
335 #  define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1
VsizeDistinguishedAmount(int64_t * aN)336 [[nodiscard]] static nsresult VsizeDistinguishedAmount(int64_t* aN) {
337   int64_t vsize, resident, shared;
338   XMappingIter(vsize, resident, shared);
339   if (vsize == -1) {
340     return NS_ERROR_FAILURE;
341   }
342   *aN = vsize;
343   return NS_OK;
344 }
345 
ResidentDistinguishedAmount(int64_t * aN)346 [[nodiscard]] static nsresult ResidentDistinguishedAmount(int64_t* aN) {
347   int64_t vsize, resident, shared;
348   XMappingIter(vsize, resident, shared);
349   if (resident == -1) {
350     return NS_ERROR_FAILURE;
351   }
352   *aN = resident;
353   return NS_OK;
354 }
355 
ResidentFastDistinguishedAmount(int64_t * aN)356 [[nodiscard]] static nsresult ResidentFastDistinguishedAmount(int64_t* aN) {
357   return ResidentDistinguishedAmount(aN);
358 }
359 
360 #  define HAVE_RESIDENT_UNIQUE_REPORTER 1
ResidentUniqueDistinguishedAmount(int64_t * aN)361 [[nodiscard]] static nsresult ResidentUniqueDistinguishedAmount(int64_t* aN) {
362   int64_t vsize, resident, shared;
363   XMappingIter(vsize, resident, shared);
364   if (resident == -1) {
365     return NS_ERROR_FAILURE;
366   }
367   *aN = resident - shared;
368   return NS_OK;
369 }
370 
371 #elif defined(XP_MACOSX)
372 
373 #  include <mach/mach_init.h>
374 #  include <mach/mach_vm.h>
375 #  include <mach/shared_region.h>
376 #  include <mach/task.h>
377 #  include <sys/sysctl.h>
378 
GetTaskBasicInfo(struct task_basic_info * aTi)379 [[nodiscard]] static bool GetTaskBasicInfo(struct task_basic_info* aTi) {
380   mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT;
381   kern_return_t kr =
382       task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)aTi, &count);
383   return kr == KERN_SUCCESS;
384 }
385 
386 // The VSIZE figure on Mac includes huge amounts of shared memory and is always
387 // absurdly high, eg. 2GB+ even at start-up.  But both 'top' and 'ps' report
388 // it, so we might as well too.
389 #  define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1
VsizeDistinguishedAmount(int64_t * aN)390 [[nodiscard]] static nsresult VsizeDistinguishedAmount(int64_t* aN) {
391   task_basic_info ti;
392   if (!GetTaskBasicInfo(&ti)) {
393     return NS_ERROR_FAILURE;
394   }
395   *aN = ti.virtual_size;
396   return NS_OK;
397 }
398 
399 // If we're using jemalloc on Mac, we need to instruct jemalloc to purge the
400 // pages it has madvise(MADV_FREE)'d before we read our RSS in order to get
401 // an accurate result.  The OS will take away MADV_FREE'd pages when there's
402 // memory pressure, so ideally, they shouldn't count against our RSS.
403 //
404 // Purging these pages can take a long time for some users (see bug 789975),
405 // so we provide the option to get the RSS without purging first.
ResidentDistinguishedAmountHelper(int64_t * aN,bool aDoPurge)406 [[nodiscard]] static nsresult ResidentDistinguishedAmountHelper(int64_t* aN,
407                                                                 bool aDoPurge) {
408 #  ifdef HAVE_JEMALLOC_STATS
409   if (aDoPurge) {
410     Telemetry::AutoTimer<Telemetry::MEMORY_FREE_PURGED_PAGES_MS> timer;
411     jemalloc_purge_freed_pages();
412   }
413 #  endif
414 
415   task_basic_info ti;
416   if (!GetTaskBasicInfo(&ti)) {
417     return NS_ERROR_FAILURE;
418   }
419   *aN = ti.resident_size;
420   return NS_OK;
421 }
422 
ResidentFastDistinguishedAmount(int64_t * aN)423 [[nodiscard]] static nsresult ResidentFastDistinguishedAmount(int64_t* aN) {
424   return ResidentDistinguishedAmountHelper(aN, /* doPurge = */ false);
425 }
426 
ResidentDistinguishedAmount(int64_t * aN)427 [[nodiscard]] static nsresult ResidentDistinguishedAmount(int64_t* aN) {
428   return ResidentDistinguishedAmountHelper(aN, /* doPurge = */ true);
429 }
430 
431 #  define HAVE_RESIDENT_UNIQUE_REPORTER 1
432 
InSharedRegion(mach_vm_address_t aAddr,cpu_type_t aType)433 static bool InSharedRegion(mach_vm_address_t aAddr, cpu_type_t aType) {
434   mach_vm_address_t base;
435   mach_vm_address_t size;
436 
437   switch (aType) {
438     case CPU_TYPE_ARM:
439       base = SHARED_REGION_BASE_ARM;
440       size = SHARED_REGION_SIZE_ARM;
441       break;
442     case CPU_TYPE_I386:
443       base = SHARED_REGION_BASE_I386;
444       size = SHARED_REGION_SIZE_I386;
445       break;
446     case CPU_TYPE_X86_64:
447       base = SHARED_REGION_BASE_X86_64;
448       size = SHARED_REGION_SIZE_X86_64;
449       break;
450     default:
451       return false;
452   }
453 
454   return base <= aAddr && aAddr < (base + size);
455 }
456 
ResidentUniqueDistinguishedAmount(int64_t * aN)457 [[nodiscard]] static nsresult ResidentUniqueDistinguishedAmount(int64_t* aN) {
458   if (!aN) {
459     return NS_ERROR_FAILURE;
460   }
461 
462   cpu_type_t cpu_type;
463   size_t len = sizeof(cpu_type);
464   if (sysctlbyname("sysctl.proc_cputype", &cpu_type, &len, NULL, 0) != 0) {
465     return NS_ERROR_FAILURE;
466   }
467 
468   // Roughly based on libtop_update_vm_regions in
469   // http://www.opensource.apple.com/source/top/top-100.1.2/libtop.c
470   size_t privatePages = 0;
471   mach_vm_size_t size = 0;
472   for (mach_vm_address_t addr = MACH_VM_MIN_ADDRESS;; addr += size) {
473     vm_region_top_info_data_t info;
474     mach_msg_type_number_t infoCount = VM_REGION_TOP_INFO_COUNT;
475     mach_port_t objectName;
476 
477     kern_return_t kr = mach_vm_region(
478         mach_task_self(), &addr, &size, VM_REGION_TOP_INFO,
479         reinterpret_cast<vm_region_info_t>(&info), &infoCount, &objectName);
480     if (kr == KERN_INVALID_ADDRESS) {
481       // Done iterating VM regions.
482       break;
483     } else if (kr != KERN_SUCCESS) {
484       return NS_ERROR_FAILURE;
485     }
486 
487     if (InSharedRegion(addr, cpu_type) && info.share_mode != SM_PRIVATE) {
488       continue;
489     }
490 
491     switch (info.share_mode) {
492       case SM_LARGE_PAGE:
493         // NB: Large pages are not shareable and always resident.
494       case SM_PRIVATE:
495         privatePages += info.private_pages_resident;
496         privatePages += info.shared_pages_resident;
497         break;
498       case SM_COW:
499         privatePages += info.private_pages_resident;
500         if (info.ref_count == 1) {
501           // Treat copy-on-write pages as private if they only have one
502           // reference.
503           privatePages += info.shared_pages_resident;
504         }
505         break;
506       case SM_SHARED:
507       default:
508         break;
509     }
510   }
511 
512   vm_size_t pageSize;
513   if (host_page_size(mach_host_self(), &pageSize) != KERN_SUCCESS) {
514     pageSize = PAGE_SIZE;
515   }
516 
517   *aN = privatePages * pageSize;
518   return NS_OK;
519 }
520 
521 #elif defined(XP_WIN)
522 
523 #  include <windows.h>
524 #  include <psapi.h>
525 #  include <algorithm>
526 
527 #  define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1
VsizeDistinguishedAmount(int64_t * aN)528 [[nodiscard]] static nsresult VsizeDistinguishedAmount(int64_t* aN) {
529   MEMORYSTATUSEX s;
530   s.dwLength = sizeof(s);
531 
532   if (!GlobalMemoryStatusEx(&s)) {
533     return NS_ERROR_FAILURE;
534   }
535 
536   *aN = s.ullTotalVirtual - s.ullAvailVirtual;
537   return NS_OK;
538 }
539 
ResidentDistinguishedAmount(int64_t * aN)540 [[nodiscard]] static nsresult ResidentDistinguishedAmount(int64_t* aN) {
541   PROCESS_MEMORY_COUNTERS pmc;
542   pmc.cb = sizeof(PROCESS_MEMORY_COUNTERS);
543 
544   if (!GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) {
545     return NS_ERROR_FAILURE;
546   }
547 
548   *aN = pmc.WorkingSetSize;
549   return NS_OK;
550 }
551 
ResidentFastDistinguishedAmount(int64_t * aN)552 [[nodiscard]] static nsresult ResidentFastDistinguishedAmount(int64_t* aN) {
553   return ResidentDistinguishedAmount(aN);
554 }
555 
556 #  define HAVE_RESIDENT_UNIQUE_REPORTER 1
557 
ResidentUniqueDistinguishedAmount(int64_t * aN)558 [[nodiscard]] static nsresult ResidentUniqueDistinguishedAmount(int64_t* aN) {
559   // Determine how many entries we need.
560   PSAPI_WORKING_SET_INFORMATION tmp;
561   DWORD tmpSize = sizeof(tmp);
562   memset(&tmp, 0, tmpSize);
563 
564   HANDLE proc = GetCurrentProcess();
565   QueryWorkingSet(proc, &tmp, tmpSize);
566 
567   // Fudge the size in case new entries are added between calls.
568   size_t entries = tmp.NumberOfEntries * 2;
569 
570   if (!entries) {
571     return NS_ERROR_FAILURE;
572   }
573 
574   DWORD infoArraySize = tmpSize + (entries * sizeof(PSAPI_WORKING_SET_BLOCK));
575   UniqueFreePtr<PSAPI_WORKING_SET_INFORMATION> infoArray(
576       static_cast<PSAPI_WORKING_SET_INFORMATION*>(malloc(infoArraySize)));
577 
578   if (!infoArray) {
579     return NS_ERROR_FAILURE;
580   }
581 
582   if (!QueryWorkingSet(proc, infoArray.get(), infoArraySize)) {
583     return NS_ERROR_FAILURE;
584   }
585 
586   entries = static_cast<size_t>(infoArray->NumberOfEntries);
587   size_t privatePages = 0;
588   for (size_t i = 0; i < entries; i++) {
589     // Count shared pages that only one process is using as private.
590     if (!infoArray->WorkingSetInfo[i].Shared ||
591         infoArray->WorkingSetInfo[i].ShareCount <= 1) {
592       privatePages++;
593     }
594   }
595 
596   SYSTEM_INFO si;
597   GetSystemInfo(&si);
598 
599   *aN = privatePages * si.dwPageSize;
600   return NS_OK;
601 }
602 
603 #  define HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER 1
VsizeMaxContiguousDistinguishedAmount(int64_t * aN)604 [[nodiscard]] static nsresult VsizeMaxContiguousDistinguishedAmount(
605     int64_t* aN) {
606   SIZE_T biggestRegion = 0;
607   MEMORY_BASIC_INFORMATION vmemInfo = {0};
608   for (size_t currentAddress = 0;;) {
609     if (!VirtualQuery((LPCVOID)currentAddress, &vmemInfo, sizeof(vmemInfo))) {
610       // Something went wrong, just return whatever we've got already.
611       break;
612     }
613 
614     if (vmemInfo.State == MEM_FREE) {
615       biggestRegion = std::max(biggestRegion, vmemInfo.RegionSize);
616     }
617 
618     SIZE_T lastAddress = currentAddress;
619     currentAddress += vmemInfo.RegionSize;
620 
621     // If we overflow, we've examined all of the address space.
622     if (currentAddress < lastAddress) {
623       break;
624     }
625   }
626 
627   *aN = biggestRegion;
628   return NS_OK;
629 }
630 
631 #  define HAVE_PRIVATE_REPORTER 1
PrivateDistinguishedAmount(int64_t * aN)632 [[nodiscard]] static nsresult PrivateDistinguishedAmount(int64_t* aN) {
633   PROCESS_MEMORY_COUNTERS_EX pmcex;
634   pmcex.cb = sizeof(PROCESS_MEMORY_COUNTERS_EX);
635 
636   if (!GetProcessMemoryInfo(GetCurrentProcess(),
637                             (PPROCESS_MEMORY_COUNTERS)&pmcex, sizeof(pmcex))) {
638     return NS_ERROR_FAILURE;
639   }
640 
641   *aN = pmcex.PrivateUsage;
642   return NS_OK;
643 }
644 
645 #  define HAVE_SYSTEM_HEAP_REPORTER 1
646 // Windows can have multiple separate heaps, but we should not touch non-default
647 // heaps because they may be destroyed at anytime while we hold a handle.  So we
648 // count only the default heap.
SystemHeapSize(int64_t * aSizeOut)649 [[nodiscard]] static nsresult SystemHeapSize(int64_t* aSizeOut) {
650   HANDLE heap = GetProcessHeap();
651 
652   NS_ENSURE_TRUE(HeapLock(heap), NS_ERROR_FAILURE);
653 
654   int64_t heapSize = 0;
655   PROCESS_HEAP_ENTRY entry;
656   entry.lpData = nullptr;
657   while (HeapWalk(heap, &entry)) {
658     // We don't count entry.cbOverhead, because we just want to measure the
659     // space available to the program.
660     if (entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) {
661       heapSize += entry.cbData;
662     }
663   }
664 
665   // Check this result only after unlocking the heap, so that we don't leave
666   // the heap locked if there was an error.
667   DWORD lastError = GetLastError();
668 
669   // I have no idea how things would proceed if unlocking this heap failed...
670   NS_ENSURE_TRUE(HeapUnlock(heap), NS_ERROR_FAILURE);
671 
672   NS_ENSURE_TRUE(lastError == ERROR_NO_MORE_ITEMS, NS_ERROR_FAILURE);
673 
674   *aSizeOut = heapSize;
675   return NS_OK;
676 }
677 
678 struct SegmentKind {
679   DWORD mState;
680   DWORD mType;
681   DWORD mProtect;
682   int mIsStack;
683 };
684 
685 struct SegmentEntry : public PLDHashEntryHdr {
HashKeySegmentEntry686   static PLDHashNumber HashKey(const void* aKey) {
687     auto kind = static_cast<const SegmentKind*>(aKey);
688     return mozilla::HashGeneric(kind->mState, kind->mType, kind->mProtect,
689                                 kind->mIsStack);
690   }
691 
MatchEntrySegmentEntry692   static bool MatchEntry(const PLDHashEntryHdr* aEntry, const void* aKey) {
693     auto kind = static_cast<const SegmentKind*>(aKey);
694     auto entry = static_cast<const SegmentEntry*>(aEntry);
695     return kind->mState == entry->mKind.mState &&
696            kind->mType == entry->mKind.mType &&
697            kind->mProtect == entry->mKind.mProtect &&
698            kind->mIsStack == entry->mKind.mIsStack;
699   }
700 
InitEntrySegmentEntry701   static void InitEntry(PLDHashEntryHdr* aEntry, const void* aKey) {
702     auto kind = static_cast<const SegmentKind*>(aKey);
703     auto entry = static_cast<SegmentEntry*>(aEntry);
704     entry->mKind = *kind;
705     entry->mCount = 0;
706     entry->mSize = 0;
707   }
708 
709   static const PLDHashTableOps Ops;
710 
711   SegmentKind mKind;  // The segment kind.
712   uint32_t mCount;    // The number of segments of this kind.
713   size_t mSize;       // The combined size of segments of this kind.
714 };
715 
716 /* static */ const PLDHashTableOps SegmentEntry::Ops = {
717     SegmentEntry::HashKey, SegmentEntry::MatchEntry,
718     PLDHashTable::MoveEntryStub, PLDHashTable::ClearEntryStub,
719     SegmentEntry::InitEntry};
720 
721 class WindowsAddressSpaceReporter final : public nsIMemoryReporter {
~WindowsAddressSpaceReporter()722   ~WindowsAddressSpaceReporter() {}
723 
724  public:
725   NS_DECL_ISUPPORTS
726 
CollectReports(nsIHandleReportCallback * aHandleReport,nsISupports * aData,bool aAnonymize)727   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
728                             nsISupports* aData, bool aAnonymize) override {
729     // First iterate over all the segments and record how many of each kind
730     // there were and their aggregate sizes. We use a hash table for this
731     // because there are a couple of dozen different kinds possible.
732 
733     PLDHashTable table(&SegmentEntry::Ops, sizeof(SegmentEntry));
734     MEMORY_BASIC_INFORMATION info = {0};
735     bool isPrevSegStackGuard = false;
736     for (size_t currentAddress = 0;;) {
737       if (!VirtualQuery((LPCVOID)currentAddress, &info, sizeof(info))) {
738         // Something went wrong, just return whatever we've got already.
739         break;
740       }
741 
742       size_t size = info.RegionSize;
743 
744       // Note that |type| and |protect| are ignored in some cases.
745       DWORD state = info.State;
746       DWORD type =
747           (state == MEM_RESERVE || state == MEM_COMMIT) ? info.Type : 0;
748       DWORD protect = (state == MEM_COMMIT) ? info.Protect : 0;
749       bool isStack = isPrevSegStackGuard && state == MEM_COMMIT &&
750                      type == MEM_PRIVATE && protect == PAGE_READWRITE;
751 
752       SegmentKind kind = {state, type, protect, isStack ? 1 : 0};
753       auto entry =
754           static_cast<SegmentEntry*>(table.Add(&kind, mozilla::fallible));
755       if (entry) {
756         entry->mCount += 1;
757         entry->mSize += size;
758       }
759 
760       isPrevSegStackGuard = info.State == MEM_COMMIT &&
761                             info.Type == MEM_PRIVATE &&
762                             info.Protect == (PAGE_READWRITE | PAGE_GUARD);
763 
764       size_t lastAddress = currentAddress;
765       currentAddress += size;
766 
767       // If we overflow, we've examined all of the address space.
768       if (currentAddress < lastAddress) {
769         break;
770       }
771     }
772 
773     // Then iterate over the hash table and report the details for each segment
774     // kind.
775 
776     for (auto iter = table.Iter(); !iter.Done(); iter.Next()) {
777       // For each range of pages, we consider one or more of its State, Type
778       // and Protect values. These are documented at
779       // https://msdn.microsoft.com/en-us/library/windows/desktop/aa366775%28v=vs.85%29.aspx
780       // (for State and Type) and
781       // https://msdn.microsoft.com/en-us/library/windows/desktop/aa366786%28v=vs.85%29.aspx
782       // (for Protect).
783       //
784       // Not all State values have accompanying Type and Protection values.
785       bool doType = false;
786       bool doProtect = false;
787 
788       auto entry = static_cast<const SegmentEntry*>(iter.Get());
789 
790       nsCString path("address-space");
791 
792       switch (entry->mKind.mState) {
793         case MEM_FREE:
794           path.AppendLiteral("/free");
795           break;
796 
797         case MEM_RESERVE:
798           path.AppendLiteral("/reserved");
799           doType = true;
800           break;
801 
802         case MEM_COMMIT:
803           path.AppendLiteral("/commit");
804           doType = true;
805           doProtect = true;
806           break;
807 
808         default:
809           // Should be impossible, but handle it just in case.
810           path.AppendLiteral("/???");
811           break;
812       }
813 
814       if (doType) {
815         switch (entry->mKind.mType) {
816           case MEM_IMAGE:
817             path.AppendLiteral("/image");
818             break;
819 
820           case MEM_MAPPED:
821             path.AppendLiteral("/mapped");
822             break;
823 
824           case MEM_PRIVATE:
825             path.AppendLiteral("/private");
826             break;
827 
828           default:
829             // Should be impossible, but handle it just in case.
830             path.AppendLiteral("/???");
831             break;
832         }
833       }
834 
835       if (doProtect) {
836         DWORD protect = entry->mKind.mProtect;
837         // Basic attributes. Exactly one of these should be set.
838         if (protect & PAGE_EXECUTE) {
839           path.AppendLiteral("/execute");
840         }
841         if (protect & PAGE_EXECUTE_READ) {
842           path.AppendLiteral("/execute-read");
843         }
844         if (protect & PAGE_EXECUTE_READWRITE) {
845           path.AppendLiteral("/execute-readwrite");
846         }
847         if (protect & PAGE_EXECUTE_WRITECOPY) {
848           path.AppendLiteral("/execute-writecopy");
849         }
850         if (protect & PAGE_NOACCESS) {
851           path.AppendLiteral("/noaccess");
852         }
853         if (protect & PAGE_READONLY) {
854           path.AppendLiteral("/readonly");
855         }
856         if (protect & PAGE_READWRITE) {
857           path.AppendLiteral("/readwrite");
858         }
859         if (protect & PAGE_WRITECOPY) {
860           path.AppendLiteral("/writecopy");
861         }
862 
863         // Modifiers. At most one of these should be set.
864         if (protect & PAGE_GUARD) {
865           path.AppendLiteral("+guard");
866         }
867         if (protect & PAGE_NOCACHE) {
868           path.AppendLiteral("+nocache");
869         }
870         if (protect & PAGE_WRITECOMBINE) {
871           path.AppendLiteral("+writecombine");
872         }
873 
874         // Annotate likely stack segments, too.
875         if (entry->mKind.mIsStack) {
876           path.AppendLiteral("+stack");
877         }
878       }
879 
880       // Append the segment count.
881       path.AppendPrintf("(segments=%u)", entry->mCount);
882 
883       aHandleReport->Callback(
884           EmptyCString(), path, KIND_OTHER, UNITS_BYTES, entry->mSize,
885           NS_LITERAL_CSTRING("From MEMORY_BASIC_INFORMATION."), aData);
886     }
887 
888     return NS_OK;
889   }
890 };
891 NS_IMPL_ISUPPORTS(WindowsAddressSpaceReporter, nsIMemoryReporter)
892 
893 #endif  // XP_<PLATFORM>
894 
895 #ifdef HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER
896 class VsizeMaxContiguousReporter final : public nsIMemoryReporter {
~VsizeMaxContiguousReporter()897   ~VsizeMaxContiguousReporter() {}
898 
899  public:
900   NS_DECL_ISUPPORTS
901 
CollectReports(nsIHandleReportCallback * aHandleReport,nsISupports * aData,bool aAnonymize)902   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
903                             nsISupports* aData, bool aAnonymize) override {
904     int64_t amount;
905     if (NS_SUCCEEDED(VsizeMaxContiguousDistinguishedAmount(&amount))) {
906       MOZ_COLLECT_REPORT(
907           "vsize-max-contiguous", KIND_OTHER, UNITS_BYTES, amount,
908           "Size of the maximum contiguous block of available virtual memory.");
909     }
910     return NS_OK;
911   }
912 };
913 NS_IMPL_ISUPPORTS(VsizeMaxContiguousReporter, nsIMemoryReporter)
914 #endif
915 
916 #ifdef HAVE_PRIVATE_REPORTER
917 class PrivateReporter final : public nsIMemoryReporter {
~PrivateReporter()918   ~PrivateReporter() {}
919 
920  public:
921   NS_DECL_ISUPPORTS
922 
CollectReports(nsIHandleReportCallback * aHandleReport,nsISupports * aData,bool aAnonymize)923   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
924                             nsISupports* aData, bool aAnonymize) override {
925     int64_t amount;
926     if (NS_SUCCEEDED(PrivateDistinguishedAmount(&amount))) {
927       // clang-format off
928       MOZ_COLLECT_REPORT(
929         "private", KIND_OTHER, UNITS_BYTES, amount,
930 "Memory that cannot be shared with other processes, including memory that is "
931 "committed and marked MEM_PRIVATE, data that is not mapped, and executable "
932 "pages that have been written to.");
933       // clang-format on
934     }
935     return NS_OK;
936   }
937 };
938 NS_IMPL_ISUPPORTS(PrivateReporter, nsIMemoryReporter)
939 #endif
940 
941 #ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS
942 class VsizeReporter final : public nsIMemoryReporter {
943   ~VsizeReporter() = default;
944 
945  public:
946   NS_DECL_ISUPPORTS
947 
CollectReports(nsIHandleReportCallback * aHandleReport,nsISupports * aData,bool aAnonymize)948   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
949                             nsISupports* aData, bool aAnonymize) override {
950     int64_t amount;
951     if (NS_SUCCEEDED(VsizeDistinguishedAmount(&amount))) {
952       // clang-format off
953       MOZ_COLLECT_REPORT(
954         "vsize", KIND_OTHER, UNITS_BYTES, amount,
955 "Memory mapped by the process, including code and data segments, the heap, "
956 "thread stacks, memory explicitly mapped by the process via mmap and similar "
957 "operations, and memory shared with other processes. This is the vsize figure "
958 "as reported by 'top' and 'ps'.  This figure is of limited use on Mac, where "
959 "processes share huge amounts of memory with one another.  But even on other "
960 "operating systems, 'resident' is a much better measure of the memory "
961 "resources used by the process.");
962       // clang-format on
963     }
964     return NS_OK;
965   }
966 };
967 NS_IMPL_ISUPPORTS(VsizeReporter, nsIMemoryReporter)
968 
969 class ResidentReporter final : public nsIMemoryReporter {
970   ~ResidentReporter() = default;
971 
972  public:
973   NS_DECL_ISUPPORTS
974 
CollectReports(nsIHandleReportCallback * aHandleReport,nsISupports * aData,bool aAnonymize)975   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
976                             nsISupports* aData, bool aAnonymize) override {
977     int64_t amount;
978     if (NS_SUCCEEDED(ResidentDistinguishedAmount(&amount))) {
979       // clang-format off
980       MOZ_COLLECT_REPORT(
981         "resident", KIND_OTHER, UNITS_BYTES, amount,
982 "Memory mapped by the process that is present in physical memory, also known "
983 "as the resident set size (RSS).  This is the best single figure to use when "
984 "considering the memory resources used by the process, but it depends both on "
985 "other processes being run and details of the OS kernel and so is best used "
986 "for comparing the memory usage of a single process at different points in "
987 "time.");
988       // clang-format on
989     }
990     return NS_OK;
991   }
992 };
993 NS_IMPL_ISUPPORTS(ResidentReporter, nsIMemoryReporter)
994 
995 #endif  // HAVE_VSIZE_AND_RESIDENT_REPORTERS
996 
997 #ifdef HAVE_RESIDENT_UNIQUE_REPORTER
998 class ResidentUniqueReporter final : public nsIMemoryReporter {
999   ~ResidentUniqueReporter() = default;
1000 
1001  public:
1002   NS_DECL_ISUPPORTS
1003 
CollectReports(nsIHandleReportCallback * aHandleReport,nsISupports * aData,bool aAnonymize)1004   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
1005                             nsISupports* aData, bool aAnonymize) override {
1006     int64_t amount = 0;
1007     if (NS_SUCCEEDED(ResidentUniqueDistinguishedAmount(&amount))) {
1008       // clang-format off
1009       MOZ_COLLECT_REPORT(
1010         "resident-unique", KIND_OTHER, UNITS_BYTES, amount,
1011 "Memory mapped by the process that is present in physical memory and not "
1012 "shared with any other processes.  This is also known as the process's unique "
1013 "set size (USS).  This is the amount of RAM we'd expect to be freed if we "
1014 "closed this process.");
1015       // clang-format on
1016     }
1017     return NS_OK;
1018   }
1019 };
1020 NS_IMPL_ISUPPORTS(ResidentUniqueReporter, nsIMemoryReporter)
1021 
1022 #endif  // HAVE_RESIDENT_UNIQUE_REPORTER
1023 
1024 #ifdef HAVE_SYSTEM_HEAP_REPORTER
1025 
1026 class SystemHeapReporter final : public nsIMemoryReporter {
1027   ~SystemHeapReporter() = default;
1028 
1029  public:
1030   NS_DECL_ISUPPORTS
1031 
CollectReports(nsIHandleReportCallback * aHandleReport,nsISupports * aData,bool aAnonymize)1032   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
1033                             nsISupports* aData, bool aAnonymize) override {
1034     int64_t amount;
1035     if (NS_SUCCEEDED(SystemHeapSize(&amount))) {
1036       // clang-format off
1037       MOZ_COLLECT_REPORT(
1038         "system-heap-allocated", KIND_OTHER, UNITS_BYTES, amount,
1039 "Memory used by the system allocator that is currently allocated to the "
1040 "application. This is distinct from the jemalloc heap that Firefox uses for "
1041 "most or all of its heap allocations. Ideally this number is zero, but "
1042 "on some platforms we cannot force every heap allocation through jemalloc.");
1043       // clang-format on
1044     }
1045     return NS_OK;
1046   }
1047 };
NS_IMPL_ISUPPORTS(SystemHeapReporter,nsIMemoryReporter)1048 NS_IMPL_ISUPPORTS(SystemHeapReporter, nsIMemoryReporter)
1049 #endif  // HAVE_SYSTEM_HEAP_REPORTER
1050 
1051 #ifdef XP_UNIX
1052 
1053 #  include <sys/resource.h>
1054 
1055 #  define HAVE_RESIDENT_PEAK_REPORTER 1
1056 
1057 [[nodiscard]] static nsresult ResidentPeakDistinguishedAmount(int64_t* aN) {
1058   struct rusage usage;
1059   if (0 == getrusage(RUSAGE_SELF, &usage)) {
1060     // The units for ru_maxrrs:
1061     // - Mac: bytes
1062     // - Solaris: pages? But some sources it actually always returns 0, so
1063     //   check for that
1064     // - Linux, {Net/Open/Free}BSD, DragonFly: KiB
1065 #  ifdef XP_MACOSX
1066     *aN = usage.ru_maxrss;
1067 #  elif defined(SOLARIS)
1068     *aN = usage.ru_maxrss * getpagesize();
1069 #  else
1070     *aN = usage.ru_maxrss * 1024;
1071 #  endif
1072     if (*aN > 0) {
1073       return NS_OK;
1074     }
1075   }
1076   return NS_ERROR_FAILURE;
1077 }
1078 
1079 class ResidentPeakReporter final : public nsIMemoryReporter {
1080   ~ResidentPeakReporter() = default;
1081 
1082  public:
1083   NS_DECL_ISUPPORTS
1084 
CollectReports(nsIHandleReportCallback * aHandleReport,nsISupports * aData,bool aAnonymize)1085   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
1086                             nsISupports* aData, bool aAnonymize) override {
1087     int64_t amount = 0;
1088     if (NS_SUCCEEDED(ResidentPeakDistinguishedAmount(&amount))) {
1089       MOZ_COLLECT_REPORT(
1090           "resident-peak", KIND_OTHER, UNITS_BYTES, amount,
1091           "The peak 'resident' value for the lifetime of the process.");
1092     }
1093     return NS_OK;
1094   }
1095 };
1096 NS_IMPL_ISUPPORTS(ResidentPeakReporter, nsIMemoryReporter)
1097 
1098 #  define HAVE_PAGE_FAULT_REPORTERS 1
1099 
1100 class PageFaultsSoftReporter final : public nsIMemoryReporter {
1101   ~PageFaultsSoftReporter() = default;
1102 
1103  public:
1104   NS_DECL_ISUPPORTS
1105 
CollectReports(nsIHandleReportCallback * aHandleReport,nsISupports * aData,bool aAnonymize)1106   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
1107                             nsISupports* aData, bool aAnonymize) override {
1108     struct rusage usage;
1109     int err = getrusage(RUSAGE_SELF, &usage);
1110     if (err == 0) {
1111       int64_t amount = usage.ru_minflt;
1112       // clang-format off
1113       MOZ_COLLECT_REPORT(
1114         "page-faults-soft", KIND_OTHER, UNITS_COUNT_CUMULATIVE, amount,
1115 "The number of soft page faults (also known as 'minor page faults') that "
1116 "have occurred since the process started.  A soft page fault occurs when the "
1117 "process tries to access a page which is present in physical memory but is "
1118 "not mapped into the process's address space.  For instance, a process might "
1119 "observe soft page faults when it loads a shared library which is already "
1120 "present in physical memory. A process may experience many thousands of soft "
1121 "page faults even when the machine has plenty of available physical memory, "
1122 "and because the OS services a soft page fault without accessing the disk, "
1123 "they impact performance much less than hard page faults.");
1124       // clang-format on
1125     }
1126     return NS_OK;
1127   }
1128 };
NS_IMPL_ISUPPORTS(PageFaultsSoftReporter,nsIMemoryReporter)1129 NS_IMPL_ISUPPORTS(PageFaultsSoftReporter, nsIMemoryReporter)
1130 
1131 [[nodiscard]] static nsresult
1132     PageFaultsHardDistinguishedAmount(int64_t* aAmount) {
1133   struct rusage usage;
1134   int err = getrusage(RUSAGE_SELF, &usage);
1135   if (err != 0) {
1136     return NS_ERROR_FAILURE;
1137   }
1138   *aAmount = usage.ru_majflt;
1139   return NS_OK;
1140 }
1141 
1142 class PageFaultsHardReporter final : public nsIMemoryReporter {
1143   ~PageFaultsHardReporter() = default;
1144 
1145  public:
1146   NS_DECL_ISUPPORTS
1147 
CollectReports(nsIHandleReportCallback * aHandleReport,nsISupports * aData,bool aAnonymize)1148   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
1149                             nsISupports* aData, bool aAnonymize) override {
1150     int64_t amount = 0;
1151     if (NS_SUCCEEDED(PageFaultsHardDistinguishedAmount(&amount))) {
1152       // clang-format off
1153       MOZ_COLLECT_REPORT(
1154         "page-faults-hard", KIND_OTHER, UNITS_COUNT_CUMULATIVE, amount,
1155 "The number of hard page faults (also known as 'major page faults') that have "
1156 "occurred since the process started.  A hard page fault occurs when a process "
1157 "tries to access a page which is not present in physical memory. The "
1158 "operating system must access the disk in order to fulfill a hard page fault. "
1159 "When memory is plentiful, you should see very few hard page faults. But if "
1160 "the process tries to use more memory than your machine has available, you "
1161 "may see many thousands of hard page faults. Because accessing the disk is up "
1162 "to a million times slower than accessing RAM, the program may run very "
1163 "slowly when it is experiencing more than 100 or so hard page faults a "
1164 "second.");
1165       // clang-format on
1166     }
1167     return NS_OK;
1168   }
1169 };
NS_IMPL_ISUPPORTS(PageFaultsHardReporter,nsIMemoryReporter)1170 NS_IMPL_ISUPPORTS(PageFaultsHardReporter, nsIMemoryReporter)
1171 
1172 #endif  // XP_UNIX
1173 
1174 /**
1175  ** memory reporter implementation for jemalloc and OSX malloc,
1176  ** to obtain info on total memory in use (that we know about,
1177  ** at least -- on OSX, there are sometimes other zones in use).
1178  **/
1179 
1180 #ifdef HAVE_JEMALLOC_STATS
1181 
1182 static size_t HeapOverhead(jemalloc_stats_t* aStats) {
1183   return aStats->waste + aStats->bookkeeping + aStats->page_cache +
1184          aStats->bin_unused;
1185 }
1186 
1187 // This has UNITS_PERCENTAGE, so it is multiplied by 100x *again* on top of the
1188 // 100x for the percentage.
HeapOverheadFraction(jemalloc_stats_t * aStats)1189 static int64_t HeapOverheadFraction(jemalloc_stats_t* aStats) {
1190   size_t heapOverhead = HeapOverhead(aStats);
1191   size_t heapCommitted = aStats->allocated + heapOverhead;
1192   return int64_t(10000 * (heapOverhead / (double)heapCommitted));
1193 }
1194 
1195 class JemallocHeapReporter final : public nsIMemoryReporter {
1196   ~JemallocHeapReporter() = default;
1197 
1198  public:
1199   NS_DECL_ISUPPORTS
1200 
CollectReports(nsIHandleReportCallback * aHandleReport,nsISupports * aData,bool aAnonymize)1201   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
1202                             nsISupports* aData, bool aAnonymize) override {
1203     jemalloc_stats_t stats;
1204     jemalloc_stats(&stats);
1205 
1206     // clang-format off
1207     MOZ_COLLECT_REPORT(
1208       "heap-committed/allocated", KIND_OTHER, UNITS_BYTES, stats.allocated,
1209 "Memory mapped by the heap allocator that is currently allocated to the "
1210 "application.  This may exceed the amount of memory requested by the "
1211 "application because the allocator regularly rounds up request sizes. (The "
1212 "exact amount requested is not recorded.)");
1213 
1214     MOZ_COLLECT_REPORT(
1215       "heap-allocated", KIND_OTHER, UNITS_BYTES, stats.allocated,
1216 "The same as 'heap-committed/allocated'.");
1217 
1218     // We mark this and the other heap-overhead reporters as KIND_NONHEAP
1219     // because KIND_HEAP memory means "counted in heap-allocated", which
1220     // this is not.
1221     MOZ_COLLECT_REPORT(
1222       "explicit/heap-overhead/bin-unused", KIND_NONHEAP, UNITS_BYTES,
1223       stats.bin_unused,
1224 "Unused bytes due to fragmentation in the bins used for 'small' (<= 2 KiB) "
1225 "allocations. These bytes will be used if additional allocations occur.");
1226 
1227     if (stats.waste > 0) {
1228       MOZ_COLLECT_REPORT(
1229         "explicit/heap-overhead/waste", KIND_NONHEAP, UNITS_BYTES,
1230         stats.waste,
1231 "Committed bytes which do not correspond to an active allocation and which the "
1232 "allocator is not intentionally keeping alive (i.e., not "
1233 "'explicit/heap-overhead/{bookkeeping,page-cache,bin-unused}').");
1234     }
1235 
1236     MOZ_COLLECT_REPORT(
1237       "explicit/heap-overhead/bookkeeping", KIND_NONHEAP, UNITS_BYTES,
1238       stats.bookkeeping,
1239 "Committed bytes which the heap allocator uses for internal data structures.");
1240 
1241     MOZ_COLLECT_REPORT(
1242       "explicit/heap-overhead/page-cache", KIND_NONHEAP, UNITS_BYTES,
1243       stats.page_cache,
1244 "Memory which the allocator could return to the operating system, but hasn't. "
1245 "The allocator keeps this memory around as an optimization, so it doesn't "
1246 "have to ask the OS the next time it needs to fulfill a request. This value "
1247 "is typically not larger than a few megabytes.");
1248 
1249     MOZ_COLLECT_REPORT(
1250       "heap-committed/overhead", KIND_OTHER, UNITS_BYTES,
1251       HeapOverhead(&stats),
1252 "The sum of 'explicit/heap-overhead/*'.");
1253 
1254     MOZ_COLLECT_REPORT(
1255       "heap-mapped", KIND_OTHER, UNITS_BYTES, stats.mapped,
1256 "Amount of memory currently mapped. Includes memory that is uncommitted, i.e. "
1257 "neither in physical memory nor paged to disk.");
1258 
1259     MOZ_COLLECT_REPORT(
1260       "heap-chunksize", KIND_OTHER, UNITS_BYTES, stats.chunksize,
1261       "Size of chunks.");
1262 
1263     // clang-format on
1264 
1265     return NS_OK;
1266   }
1267 };
1268 NS_IMPL_ISUPPORTS(JemallocHeapReporter, nsIMemoryReporter)
1269 
1270 #endif  // HAVE_JEMALLOC_STATS
1271 
1272 // Why is this here?  At first glance, you'd think it could be defined and
1273 // registered with nsMemoryReporterManager entirely within nsAtomTable.cpp.
1274 // However, the obvious time to register it is when the table is initialized,
1275 // and that happens before XPCOM components are initialized, which means the
1276 // RegisterStrongMemoryReporter call fails.  So instead we do it here.
1277 class AtomTablesReporter final : public nsIMemoryReporter {
1278   MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
1279 
1280   ~AtomTablesReporter() = default;
1281 
1282  public:
1283   NS_DECL_ISUPPORTS
1284 
CollectReports(nsIHandleReportCallback * aHandleReport,nsISupports * aData,bool aAnonymize)1285   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
1286                             nsISupports* aData, bool aAnonymize) override {
1287     AtomsSizes sizes;
1288     NS_AddSizeOfAtoms(MallocSizeOf, sizes);
1289 
1290     MOZ_COLLECT_REPORT("explicit/atoms/table", KIND_HEAP, UNITS_BYTES,
1291                        sizes.mTable, "Memory used by the atom table.");
1292 
1293     MOZ_COLLECT_REPORT(
1294         "explicit/atoms/dynamic-objects-and-chars", KIND_HEAP, UNITS_BYTES,
1295         sizes.mDynamicAtoms,
1296         "Memory used by dynamic atom objects and chars (which are stored "
1297         "at the end of each atom object).");
1298 
1299     return NS_OK;
1300   }
1301 };
1302 NS_IMPL_ISUPPORTS(AtomTablesReporter, nsIMemoryReporter)
1303 
1304 class ThreadsReporter final : public nsIMemoryReporter {
1305   MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
1306   ~ThreadsReporter() = default;
1307 
1308  public:
1309   NS_DECL_ISUPPORTS
1310 
CollectReports(nsIHandleReportCallback * aHandleReport,nsISupports * aData,bool aAnonymize)1311   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
1312                             nsISupports* aData, bool aAnonymize) override {
1313 #ifdef XP_LINUX
1314     nsTArray<MemoryMapping> mappings(1024);
1315     MOZ_TRY(GetMemoryMappings(mappings));
1316 #endif
1317 
1318     // Enumerating over active threads requires holding a lock, so we collect
1319     // info on all threads, and then call our reporter callbacks after releasing
1320     // the lock.
1321     struct ThreadData {
1322       nsCString mName;
1323       uint32_t mThreadId;
1324       size_t mPrivateSize;
1325     };
1326     AutoTArray<ThreadData, 32> threads;
1327 
1328     size_t eventQueueSizes = 0;
1329     size_t wrapperSizes = 0;
1330     size_t threadCount = 0;
1331 
1332     for (auto* thread : nsThread::Enumerate()) {
1333       threadCount++;
1334       eventQueueSizes += thread->SizeOfEventQueues(MallocSizeOf);
1335       wrapperSizes += thread->ShallowSizeOfIncludingThis(MallocSizeOf);
1336 
1337       if (!thread->StackBase()) {
1338         continue;
1339       }
1340 
1341 #if defined(XP_LINUX)
1342       int idx = mappings.BinaryIndexOf(thread->StackBase());
1343       if (idx < 0) {
1344         continue;
1345       }
1346       // Referenced() is the combined size of all pages in the region which have
1347       // ever been touched, and are therefore consuming memory. For stack
1348       // regions, these pages are guaranteed to be un-shared unless we fork
1349       // after creating threads (which we don't).
1350       size_t privateSize = mappings[idx].Referenced();
1351 
1352       // On Linux, we have to be very careful matching memory regions to thread
1353       // stacks.
1354       //
1355       // To begin with, the kernel only reports VM stats for regions of all
1356       // adjacent pages with the same flags, protection, and backing file.
1357       // There's no way to get finer-grained usage information for a subset of
1358       // those pages.
1359       //
1360       // Stack segments always have a guard page at the bottom of the stack
1361       // (assuming we only support stacks that grow down), so there's no danger
1362       // of them being merged with other stack regions. At the top, there's no
1363       // protection page, and no way to allocate one without using pthreads
1364       // directly and allocating our own stacks. So we get around the problem by
1365       // adding an extra VM flag (NOHUGEPAGES) to our stack region, which we
1366       // don't expect to be set on any heap regions. But this is not fool-proof.
1367       //
1368       // A second kink is that different C libraries (and different versions
1369       // thereof) report stack base locations and sizes differently with regard
1370       // to the guard page. For the libraries that include the guard page in the
1371       // stack size base pointer, we need to adjust those values to compensate.
1372       // But it's possible that our logic will get out of sync with library
1373       // changes, or someone will compile with an unexpected library.
1374       //
1375       //
1376       // The upshot of all of this is that there may be configurations that our
1377       // special cases don't cover. And if there are, we want to know about it.
1378       // So assert that total size of the memory region we're reporting actually
1379       // matches the allocated size of the thread stack.
1380 #  ifndef ANDROID
1381       MOZ_ASSERT(mappings[idx].Size() == thread->StackSize(),
1382                  "Mapping region size doesn't match stack allocation size");
1383 #  endif
1384 #elif defined(XP_WIN)
1385       auto memInfo = MemoryInfo::Get(thread->StackBase(), thread->StackSize());
1386       size_t privateSize = memInfo.Committed();
1387 #else
1388       size_t privateSize = thread->StackSize();
1389       MOZ_ASSERT_UNREACHABLE(
1390           "Shouldn't have stack base pointer on this "
1391           "platform");
1392 #endif
1393 
1394       threads.AppendElement(ThreadData{
1395           nsCString(PR_GetThreadName(thread->GetPRThread())),
1396           thread->ThreadId(),
1397           // On Linux, it's possible (but unlikely) that our stack region will
1398           // have been merged with adjacent heap regions, in which case we'll
1399           // get combined size information for both. So we take the minimum of
1400           // the reported private size and the requested stack size to avoid the
1401           // possible of majorly over-reporting in that case.
1402           std::min(privateSize, thread->StackSize()),
1403       });
1404     }
1405 
1406     for (auto& thread : threads) {
1407       nsPrintfCString path("explicit/threads/stacks/%s (tid=%u)",
1408                            thread.mName.get(), thread.mThreadId);
1409 
1410       aHandleReport->Callback(
1411           EmptyCString(), path, KIND_NONHEAP, UNITS_BYTES, thread.mPrivateSize,
1412           NS_LITERAL_CSTRING("The sizes of thread stacks which have been "
1413                              "committed to memory."),
1414           aData);
1415     }
1416 
1417     MOZ_COLLECT_REPORT("explicit/threads/overhead/event-queues", KIND_HEAP,
1418                        UNITS_BYTES, eventQueueSizes,
1419                        "The sizes of nsThread event queues and observers.");
1420 
1421     MOZ_COLLECT_REPORT("explicit/threads/overhead/wrappers", KIND_HEAP,
1422                        UNITS_BYTES, wrapperSizes,
1423                        "The sizes of nsThread/PRThread wrappers.");
1424 
1425 #if defined(XP_WIN)
1426     // Each thread on Windows has a fixed kernel overhead. For 32 bit Windows,
1427     // that's 12K. For 64 bit, it's 24K.
1428     //
1429     // See
1430     // https://blogs.technet.microsoft.com/markrussinovich/2009/07/05/pushing-the-limits-of-windows-processes-and-threads/
1431     constexpr size_t kKernelSize = (sizeof(void*) == 8 ? 24 : 12) * 1024;
1432 #elif defined(XP_LINUX)
1433     // On Linux, kernel stacks are usually 8K. However, on x86, they are
1434     // allocated virtually, and start out at 4K. They may grow to 8K, but we
1435     // have no way of knowing which ones do, so all we can do is guess.
1436 #  if defined(__x86_64__) || defined(__i386__)
1437     constexpr size_t kKernelSize = 4 * 1024;
1438 #  else
1439     constexpr size_t kKernelSize = 8 * 1024;
1440 #  endif
1441 #elif defined(XP_MACOSX)
1442     // On Darwin, kernel stacks are 16K:
1443     //
1444     // https://books.google.com/books?id=K8vUkpOXhN4C&lpg=PA513&dq=mach%20kernel%20thread%20stack%20size&pg=PA513#v=onepage&q=mach%20kernel%20thread%20stack%20size&f=false
1445     constexpr size_t kKernelSize = 16 * 1024;
1446 #else
1447     // Elsewhere, just assume that kernel stacks require at least 8K.
1448     constexpr size_t kKernelSize = 8 * 1024;
1449 #endif
1450 
1451     MOZ_COLLECT_REPORT("explicit/threads/overhead/kernel", KIND_NONHEAP,
1452                        UNITS_BYTES, threadCount * kKernelSize,
1453                        "The total kernel overhead for all active threads.");
1454 
1455     return NS_OK;
1456   }
1457 };
1458 NS_IMPL_ISUPPORTS(ThreadsReporter, nsIMemoryReporter)
1459 
1460 #ifdef DEBUG
1461 
1462 // Ideally, this would be implemented in BlockingResourceBase.cpp.
1463 // However, this ends up breaking the linking step of various unit tests due
1464 // to adding a new dependency to libdmd for a commonly used feature (mutexes)
1465 // in  DMD  builds. So instead we do it here.
1466 class DeadlockDetectorReporter final : public nsIMemoryReporter {
1467   MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
1468 
1469   ~DeadlockDetectorReporter() = default;
1470 
1471  public:
1472   NS_DECL_ISUPPORTS
1473 
CollectReports(nsIHandleReportCallback * aHandleReport,nsISupports * aData,bool aAnonymize)1474   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
1475                             nsISupports* aData, bool aAnonymize) override {
1476     MOZ_COLLECT_REPORT(
1477         "explicit/deadlock-detector", KIND_HEAP, UNITS_BYTES,
1478         BlockingResourceBase::SizeOfDeadlockDetector(MallocSizeOf),
1479         "Memory used by the deadlock detector.");
1480 
1481     return NS_OK;
1482   }
1483 };
1484 NS_IMPL_ISUPPORTS(DeadlockDetectorReporter, nsIMemoryReporter)
1485 
1486 #endif
1487 
1488 #ifdef MOZ_DMD
1489 
1490 namespace mozilla {
1491 namespace dmd {
1492 
1493 class DMDReporter final : public nsIMemoryReporter {
1494  public:
1495   NS_DECL_ISUPPORTS
1496 
CollectReports(nsIHandleReportCallback * aHandleReport,nsISupports * aData,bool aAnonymize)1497   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
1498                             nsISupports* aData, bool aAnonymize) override {
1499     dmd::Sizes sizes;
1500     dmd::SizeOf(&sizes);
1501 
1502     MOZ_COLLECT_REPORT(
1503         "explicit/dmd/stack-traces/used", KIND_HEAP, UNITS_BYTES,
1504         sizes.mStackTracesUsed,
1505         "Memory used by stack traces which correspond to at least "
1506         "one heap block DMD is tracking.");
1507 
1508     MOZ_COLLECT_REPORT(
1509         "explicit/dmd/stack-traces/unused", KIND_HEAP, UNITS_BYTES,
1510         sizes.mStackTracesUnused,
1511         "Memory used by stack traces which don't correspond to any heap "
1512         "blocks DMD is currently tracking.");
1513 
1514     MOZ_COLLECT_REPORT("explicit/dmd/stack-traces/table", KIND_HEAP,
1515                        UNITS_BYTES, sizes.mStackTraceTable,
1516                        "Memory used by DMD's stack trace table.");
1517 
1518     MOZ_COLLECT_REPORT("explicit/dmd/live-block-table", KIND_HEAP, UNITS_BYTES,
1519                        sizes.mLiveBlockTable,
1520                        "Memory used by DMD's live block table.");
1521 
1522     MOZ_COLLECT_REPORT("explicit/dmd/dead-block-list", KIND_HEAP, UNITS_BYTES,
1523                        sizes.mDeadBlockTable,
1524                        "Memory used by DMD's dead block list.");
1525 
1526     return NS_OK;
1527   }
1528 
1529  private:
1530   ~DMDReporter() = default;
1531 };
1532 NS_IMPL_ISUPPORTS(DMDReporter, nsIMemoryReporter)
1533 
1534 }  // namespace dmd
1535 }  // namespace mozilla
1536 
1537 #endif  // MOZ_DMD
1538 
1539 /**
1540  ** nsMemoryReporterManager implementation
1541  **/
1542 
NS_IMPL_ISUPPORTS(nsMemoryReporterManager,nsIMemoryReporterManager,nsIMemoryReporter)1543 NS_IMPL_ISUPPORTS(nsMemoryReporterManager, nsIMemoryReporterManager,
1544                   nsIMemoryReporter)
1545 
1546 NS_IMETHODIMP
1547 nsMemoryReporterManager::Init() {
1548   if (!NS_IsMainThread()) {
1549     MOZ_CRASH();
1550   }
1551 
1552   // Under normal circumstances this function is only called once. However,
1553   // we've (infrequently) seen memory report dumps in crash reports that
1554   // suggest that this function is sometimes called multiple times. That in
1555   // turn means that multiple reporters of each kind are registered, which
1556   // leads to duplicated reports of individual measurements such as "resident",
1557   // "vsize", etc.
1558   //
1559   // It's unclear how these multiple calls can occur. The only plausible theory
1560   // so far is badly-written extensions, because this function is callable from
1561   // JS code via nsIMemoryReporter.idl.
1562   //
1563   // Whatever the cause, it's a bad thing. So we protect against it with the
1564   // following check.
1565   static bool isInited = false;
1566   if (isInited) {
1567     NS_WARNING("nsMemoryReporterManager::Init() has already been called!");
1568     return NS_OK;
1569   }
1570   isInited = true;
1571 
1572 #if defined(HAVE_JEMALLOC_STATS) && defined(MOZ_GLUE_IN_PROGRAM)
1573   if (!jemalloc_stats) {
1574     return NS_ERROR_FAILURE;
1575   }
1576 #endif
1577 
1578 #ifdef HAVE_JEMALLOC_STATS
1579   RegisterStrongReporter(new JemallocHeapReporter());
1580 #endif
1581 
1582 #ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS
1583   RegisterStrongReporter(new VsizeReporter());
1584   RegisterStrongReporter(new ResidentReporter());
1585 #endif
1586 
1587 #ifdef HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER
1588   RegisterStrongReporter(new VsizeMaxContiguousReporter());
1589 #endif
1590 
1591 #ifdef HAVE_RESIDENT_PEAK_REPORTER
1592   RegisterStrongReporter(new ResidentPeakReporter());
1593 #endif
1594 
1595 #ifdef HAVE_RESIDENT_UNIQUE_REPORTER
1596   RegisterStrongReporter(new ResidentUniqueReporter());
1597 #endif
1598 
1599 #ifdef HAVE_PAGE_FAULT_REPORTERS
1600   RegisterStrongReporter(new PageFaultsSoftReporter());
1601   RegisterStrongReporter(new PageFaultsHardReporter());
1602 #endif
1603 
1604 #ifdef HAVE_PRIVATE_REPORTER
1605   RegisterStrongReporter(new PrivateReporter());
1606 #endif
1607 
1608 #ifdef HAVE_SYSTEM_HEAP_REPORTER
1609   RegisterStrongReporter(new SystemHeapReporter());
1610 #endif
1611 
1612   RegisterStrongReporter(new AtomTablesReporter());
1613 
1614   RegisterStrongReporter(new ThreadsReporter());
1615 
1616 #ifdef DEBUG
1617   RegisterStrongReporter(new DeadlockDetectorReporter());
1618 #endif
1619 
1620 #ifdef MOZ_GECKO_PROFILER
1621   // We have to register this here rather than in profiler_init() because
1622   // profiler_init() runs prior to nsMemoryReporterManager's creation.
1623   RegisterStrongReporter(new GeckoProfilerReporter());
1624 #endif
1625 
1626 #ifdef MOZ_DMD
1627   RegisterStrongReporter(new mozilla::dmd::DMDReporter());
1628 #endif
1629 
1630 #ifdef XP_WIN
1631   RegisterStrongReporter(new WindowsAddressSpaceReporter());
1632 #endif
1633 
1634 #ifdef XP_UNIX
1635   nsMemoryInfoDumper::Initialize();
1636 #endif
1637 
1638   // Report our own memory usage as well.
1639   RegisterWeakReporter(this);
1640 
1641   return NS_OK;
1642 }
1643 
nsMemoryReporterManager()1644 nsMemoryReporterManager::nsMemoryReporterManager()
1645     : mMutex("nsMemoryReporterManager::mMutex"),
1646       mIsRegistrationBlocked(false),
1647       mStrongReporters(new StrongReportersTable()),
1648       mWeakReporters(new WeakReportersTable()),
1649       mSavedStrongReporters(nullptr),
1650       mSavedWeakReporters(nullptr),
1651       mNextGeneration(1),
1652       mPendingProcessesState(nullptr),
1653       mPendingReportersState(nullptr)
1654 #ifdef HAVE_JEMALLOC_STATS
1655       ,
1656       mThreadPool(do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID))
1657 #endif
1658 {
1659 }
1660 
~nsMemoryReporterManager()1661 nsMemoryReporterManager::~nsMemoryReporterManager() {
1662   delete mStrongReporters;
1663   delete mWeakReporters;
1664   NS_ASSERTION(!mSavedStrongReporters, "failed to restore strong reporters");
1665   NS_ASSERTION(!mSavedWeakReporters, "failed to restore weak reporters");
1666 }
1667 
1668 NS_IMETHODIMP
CollectReports(nsIHandleReportCallback * aHandleReport,nsISupports * aData,bool aAnonymize)1669 nsMemoryReporterManager::CollectReports(nsIHandleReportCallback* aHandleReport,
1670                                         nsISupports* aData, bool aAnonymize) {
1671   size_t n = MallocSizeOf(this);
1672   n += mStrongReporters->ShallowSizeOfIncludingThis(MallocSizeOf);
1673   n += mWeakReporters->ShallowSizeOfIncludingThis(MallocSizeOf);
1674 
1675   MOZ_COLLECT_REPORT("explicit/memory-reporter-manager", KIND_HEAP, UNITS_BYTES,
1676                      n, "Memory used by the memory reporter infrastructure.");
1677 
1678   return NS_OK;
1679 }
1680 
1681 #ifdef DEBUG_CHILD_PROCESS_MEMORY_REPORTING
1682 #  define MEMORY_REPORTING_LOG(format, ...) \
1683     printf_stderr("++++ MEMORY REPORTING: " format, ##__VA_ARGS__);
1684 #else
1685 #  define MEMORY_REPORTING_LOG(...)
1686 #endif
1687 
1688 NS_IMETHODIMP
GetReports(nsIHandleReportCallback * aHandleReport,nsISupports * aHandleReportData,nsIFinishReportingCallback * aFinishReporting,nsISupports * aFinishReportingData,bool aAnonymize)1689 nsMemoryReporterManager::GetReports(
1690     nsIHandleReportCallback* aHandleReport, nsISupports* aHandleReportData,
1691     nsIFinishReportingCallback* aFinishReporting,
1692     nsISupports* aFinishReportingData, bool aAnonymize) {
1693   return GetReportsExtended(aHandleReport, aHandleReportData, aFinishReporting,
1694                             aFinishReportingData, aAnonymize,
1695                             /* minimize = */ false,
1696                             /* DMDident = */ EmptyString());
1697 }
1698 
1699 NS_IMETHODIMP
GetReportsExtended(nsIHandleReportCallback * aHandleReport,nsISupports * aHandleReportData,nsIFinishReportingCallback * aFinishReporting,nsISupports * aFinishReportingData,bool aAnonymize,bool aMinimize,const nsAString & aDMDDumpIdent)1700 nsMemoryReporterManager::GetReportsExtended(
1701     nsIHandleReportCallback* aHandleReport, nsISupports* aHandleReportData,
1702     nsIFinishReportingCallback* aFinishReporting,
1703     nsISupports* aFinishReportingData, bool aAnonymize, bool aMinimize,
1704     const nsAString& aDMDDumpIdent) {
1705   nsresult rv;
1706 
1707   // Memory reporters are not necessarily threadsafe, so this function must
1708   // be called from the main thread.
1709   if (!NS_IsMainThread()) {
1710     MOZ_CRASH();
1711   }
1712 
1713   uint32_t generation = mNextGeneration++;
1714 
1715   if (mPendingProcessesState) {
1716     // A request is in flight.  Don't start another one.  And don't report
1717     // an error;  just ignore it, and let the in-flight request finish.
1718     MEMORY_REPORTING_LOG("GetReports (gen=%u, s->gen=%u): abort\n", generation,
1719                          mPendingProcessesState->mGeneration);
1720     return NS_OK;
1721   }
1722 
1723   MEMORY_REPORTING_LOG("GetReports (gen=%u)\n", generation);
1724 
1725   uint32_t concurrency = Preferences::GetUint("memory.report_concurrency", 1);
1726   MOZ_ASSERT(concurrency >= 1);
1727   if (concurrency < 1) {
1728     concurrency = 1;
1729   }
1730   mPendingProcessesState = new PendingProcessesState(
1731       generation, aAnonymize, aMinimize, concurrency, aHandleReport,
1732       aHandleReportData, aFinishReporting, aFinishReportingData, aDMDDumpIdent);
1733 
1734   if (aMinimize) {
1735     nsCOMPtr<nsIRunnable> callback =
1736         NewRunnableMethod("nsMemoryReporterManager::StartGettingReports", this,
1737                           &nsMemoryReporterManager::StartGettingReports);
1738     rv = MinimizeMemoryUsage(callback);
1739   } else {
1740     rv = StartGettingReports();
1741   }
1742   return rv;
1743 }
1744 
StartGettingReports()1745 nsresult nsMemoryReporterManager::StartGettingReports() {
1746   PendingProcessesState* s = mPendingProcessesState;
1747   nsresult rv;
1748 
1749   // Get reports for this process.
1750   FILE* parentDMDFile = nullptr;
1751 #ifdef MOZ_DMD
1752   if (!s->mDMDDumpIdent.IsEmpty()) {
1753     rv = nsMemoryInfoDumper::OpenDMDFile(s->mDMDDumpIdent, getpid(),
1754                                          &parentDMDFile);
1755     if (NS_WARN_IF(NS_FAILED(rv))) {
1756       // Proceed with the memory report as if DMD were disabled.
1757       parentDMDFile = nullptr;
1758     }
1759   }
1760 #endif
1761 
1762   // This is async.
1763   GetReportsForThisProcessExtended(
1764       s->mHandleReport, s->mHandleReportData, s->mAnonymize, parentDMDFile,
1765       s->mFinishReporting, s->mFinishReportingData);
1766 
1767   nsTArray<dom::ContentParent*> childWeakRefs;
1768   dom::ContentParent::GetAll(childWeakRefs);
1769   if (!childWeakRefs.IsEmpty()) {
1770     // Request memory reports from child processes.  This happens
1771     // after the parent report so that the parent's main thread will
1772     // be free to process the child reports, instead of causing them
1773     // to be buffered and consume (possibly scarce) memory.
1774 
1775     for (size_t i = 0; i < childWeakRefs.Length(); ++i) {
1776       s->mChildrenPending.AppendElement(childWeakRefs[i]);
1777     }
1778   }
1779 
1780   if (gfx::GPUProcessManager* gpu = gfx::GPUProcessManager::Get()) {
1781     if (RefPtr<MemoryReportingProcess> proc = gpu->GetProcessMemoryReporter()) {
1782       s->mChildrenPending.AppendElement(proc.forget());
1783     }
1784   }
1785 
1786   if (RDDProcessManager* rdd = RDDProcessManager::Get()) {
1787     if (RefPtr<MemoryReportingProcess> proc = rdd->GetProcessMemoryReporter()) {
1788       s->mChildrenPending.AppendElement(proc.forget());
1789     }
1790   }
1791 
1792   if (gfx::VRProcessManager* vr = gfx::VRProcessManager::Get()) {
1793     if (RefPtr<MemoryReportingProcess> proc = vr->GetProcessMemoryReporter()) {
1794       s->mChildrenPending.AppendElement(proc.forget());
1795     }
1796   }
1797 
1798   if (!mIsRegistrationBlocked && net::gIOService) {
1799     if (RefPtr<MemoryReportingProcess> proc =
1800             net::gIOService->GetSocketProcessMemoryReporter()) {
1801       s->mChildrenPending.AppendElement(proc.forget());
1802     }
1803   }
1804 
1805   if (!s->mChildrenPending.IsEmpty()) {
1806     nsCOMPtr<nsITimer> timer;
1807     rv = NS_NewTimerWithFuncCallback(
1808         getter_AddRefs(timer), TimeoutCallback, this, kTimeoutLengthMS,
1809         nsITimer::TYPE_ONE_SHOT,
1810         "nsMemoryReporterManager::StartGettingReports");
1811     if (NS_WARN_IF(NS_FAILED(rv))) {
1812       FinishReporting();
1813       return rv;
1814     }
1815 
1816     MOZ_ASSERT(!s->mTimer);
1817     s->mTimer.swap(timer);
1818   }
1819 
1820   return NS_OK;
1821 }
1822 
DispatchReporter(nsIMemoryReporter * aReporter,bool aIsAsync,nsIHandleReportCallback * aHandleReport,nsISupports * aHandleReportData,bool aAnonymize)1823 void nsMemoryReporterManager::DispatchReporter(
1824     nsIMemoryReporter* aReporter, bool aIsAsync,
1825     nsIHandleReportCallback* aHandleReport, nsISupports* aHandleReportData,
1826     bool aAnonymize) {
1827   MOZ_ASSERT(mPendingReportersState);
1828 
1829   // Grab refs to everything used in the lambda function.
1830   RefPtr<nsMemoryReporterManager> self = this;
1831   nsCOMPtr<nsIMemoryReporter> reporter = aReporter;
1832   nsCOMPtr<nsIHandleReportCallback> handleReport = aHandleReport;
1833   nsCOMPtr<nsISupports> handleReportData = aHandleReportData;
1834 
1835   nsCOMPtr<nsIRunnable> event = NS_NewRunnableFunction(
1836       "nsMemoryReporterManager::DispatchReporter",
1837       [self, reporter, aIsAsync, handleReport, handleReportData, aAnonymize]() {
1838         reporter->CollectReports(handleReport, handleReportData, aAnonymize);
1839         if (!aIsAsync) {
1840           self->EndReport();
1841         }
1842       });
1843 
1844   NS_DispatchToMainThread(event);
1845   mPendingReportersState->mReportsPending++;
1846 }
1847 
1848 NS_IMETHODIMP
GetReportsForThisProcessExtended(nsIHandleReportCallback * aHandleReport,nsISupports * aHandleReportData,bool aAnonymize,FILE * aDMDFile,nsIFinishReportingCallback * aFinishReporting,nsISupports * aFinishReportingData)1849 nsMemoryReporterManager::GetReportsForThisProcessExtended(
1850     nsIHandleReportCallback* aHandleReport, nsISupports* aHandleReportData,
1851     bool aAnonymize, FILE* aDMDFile,
1852     nsIFinishReportingCallback* aFinishReporting,
1853     nsISupports* aFinishReportingData) {
1854   // Memory reporters are not necessarily threadsafe, so this function must
1855   // be called from the main thread.
1856   if (!NS_IsMainThread()) {
1857     MOZ_CRASH();
1858   }
1859 
1860   if (NS_WARN_IF(mPendingReportersState)) {
1861     // Report is already in progress.
1862     return NS_ERROR_IN_PROGRESS;
1863   }
1864 
1865 #ifdef MOZ_DMD
1866   if (aDMDFile) {
1867     // Clear DMD's reportedness state before running the memory
1868     // reporters, to avoid spurious twice-reported warnings.
1869     dmd::ClearReports();
1870   }
1871 #else
1872   MOZ_ASSERT(!aDMDFile);
1873 #endif
1874 
1875   mPendingReportersState = new PendingReportersState(
1876       aFinishReporting, aFinishReportingData, aDMDFile);
1877 
1878   {
1879     mozilla::MutexAutoLock autoLock(mMutex);
1880 
1881     for (auto iter = mStrongReporters->Iter(); !iter.Done(); iter.Next()) {
1882       DispatchReporter(iter.Key(), iter.Data(), aHandleReport,
1883                        aHandleReportData, aAnonymize);
1884     }
1885 
1886     for (auto iter = mWeakReporters->Iter(); !iter.Done(); iter.Next()) {
1887       nsCOMPtr<nsIMemoryReporter> reporter = iter.Key();
1888       DispatchReporter(reporter, iter.Data(), aHandleReport, aHandleReportData,
1889                        aAnonymize);
1890     }
1891   }
1892 
1893   return NS_OK;
1894 }
1895 
1896 NS_IMETHODIMP
EndReport()1897 nsMemoryReporterManager::EndReport() {
1898   if (--mPendingReportersState->mReportsPending == 0) {
1899 #ifdef MOZ_DMD
1900     if (mPendingReportersState->mDMDFile) {
1901       nsMemoryInfoDumper::DumpDMDToFile(mPendingReportersState->mDMDFile);
1902     }
1903 #endif
1904     if (mPendingProcessesState) {
1905       // This is the parent process.
1906       EndProcessReport(mPendingProcessesState->mGeneration, true);
1907     } else {
1908       mPendingReportersState->mFinishReporting->Callback(
1909           mPendingReportersState->mFinishReportingData);
1910     }
1911 
1912     delete mPendingReportersState;
1913     mPendingReportersState = nullptr;
1914   }
1915 
1916   return NS_OK;
1917 }
1918 
1919 nsMemoryReporterManager::PendingProcessesState*
GetStateForGeneration(uint32_t aGeneration)1920 nsMemoryReporterManager::GetStateForGeneration(uint32_t aGeneration) {
1921   // Memory reporting only happens on the main thread.
1922   MOZ_RELEASE_ASSERT(NS_IsMainThread());
1923 
1924   PendingProcessesState* s = mPendingProcessesState;
1925 
1926   if (!s) {
1927     // If we reach here, then:
1928     //
1929     // - A child process reported back too late, and no subsequent request
1930     //   is in flight.
1931     //
1932     // So there's nothing to be done.  Just ignore it.
1933     MEMORY_REPORTING_LOG("HandleChildReports: no request in flight (aGen=%u)\n",
1934                          aGeneration);
1935     return nullptr;
1936   }
1937 
1938   if (aGeneration != s->mGeneration) {
1939     // If we reach here, a child process must have reported back, too late,
1940     // while a subsequent (higher-numbered) request is in flight.  Again,
1941     // ignore it.
1942     MOZ_ASSERT(aGeneration < s->mGeneration);
1943     MEMORY_REPORTING_LOG(
1944         "HandleChildReports: gen mismatch (aGen=%u, s->gen=%u)\n", aGeneration,
1945         s->mGeneration);
1946     return nullptr;
1947   }
1948 
1949   return s;
1950 }
1951 
1952 // This function has no return value.  If something goes wrong, there's no
1953 // clear place to report the problem to, but that's ok -- we will end up
1954 // hitting the timeout and executing TimeoutCallback().
HandleChildReport(uint32_t aGeneration,const dom::MemoryReport & aChildReport)1955 void nsMemoryReporterManager::HandleChildReport(
1956     uint32_t aGeneration, const dom::MemoryReport& aChildReport) {
1957   PendingProcessesState* s = GetStateForGeneration(aGeneration);
1958   if (!s) {
1959     return;
1960   }
1961 
1962   // Child reports should have a non-empty process.
1963   MOZ_ASSERT(!aChildReport.process().IsEmpty());
1964 
1965   // If the call fails, ignore and continue.
1966   s->mHandleReport->Callback(aChildReport.process(), aChildReport.path(),
1967                              aChildReport.kind(), aChildReport.units(),
1968                              aChildReport.amount(), aChildReport.desc(),
1969                              s->mHandleReportData);
1970 }
1971 
1972 /* static */
StartChildReport(mozilla::MemoryReportingProcess * aChild,const PendingProcessesState * aState)1973 bool nsMemoryReporterManager::StartChildReport(
1974     mozilla::MemoryReportingProcess* aChild,
1975     const PendingProcessesState* aState) {
1976   if (!aChild->IsAlive()) {
1977     MEMORY_REPORTING_LOG(
1978         "StartChildReports (gen=%u): child exited before"
1979         " its report was started\n",
1980         aState->mGeneration);
1981     return false;
1982   }
1983 
1984   Maybe<mozilla::ipc::FileDescriptor> dmdFileDesc;
1985 #ifdef MOZ_DMD
1986   if (!aState->mDMDDumpIdent.IsEmpty()) {
1987     FILE* dmdFile = nullptr;
1988     nsresult rv = nsMemoryInfoDumper::OpenDMDFile(aState->mDMDDumpIdent,
1989                                                   aChild->Pid(), &dmdFile);
1990     if (NS_WARN_IF(NS_FAILED(rv))) {
1991       // Proceed with the memory report as if DMD were disabled.
1992       dmdFile = nullptr;
1993     }
1994     if (dmdFile) {
1995       dmdFileDesc = Some(mozilla::ipc::FILEToFileDescriptor(dmdFile));
1996       fclose(dmdFile);
1997     }
1998   }
1999 #endif
2000   return aChild->SendRequestMemoryReport(
2001       aState->mGeneration, aState->mAnonymize, aState->mMinimize, dmdFileDesc);
2002 }
2003 
EndProcessReport(uint32_t aGeneration,bool aSuccess)2004 void nsMemoryReporterManager::EndProcessReport(uint32_t aGeneration,
2005                                                bool aSuccess) {
2006   PendingProcessesState* s = GetStateForGeneration(aGeneration);
2007   if (!s) {
2008     return;
2009   }
2010 
2011   MOZ_ASSERT(s->mNumProcessesRunning > 0);
2012   s->mNumProcessesRunning--;
2013   s->mNumProcessesCompleted++;
2014   MEMORY_REPORTING_LOG(
2015       "HandleChildReports (aGen=%u): process %u %s"
2016       " (%u running, %u pending)\n",
2017       aGeneration, s->mNumProcessesCompleted,
2018       aSuccess ? "completed" : "exited during report", s->mNumProcessesRunning,
2019       static_cast<unsigned>(s->mChildrenPending.Length()));
2020 
2021   // Start pending children up to the concurrency limit.
2022   while (s->mNumProcessesRunning < s->mConcurrencyLimit &&
2023          !s->mChildrenPending.IsEmpty()) {
2024     // Pop last element from s->mChildrenPending
2025     RefPtr<MemoryReportingProcess> nextChild;
2026     nextChild.swap(s->mChildrenPending.LastElement());
2027     s->mChildrenPending.TruncateLength(s->mChildrenPending.Length() - 1);
2028     // Start report (if the child is still alive).
2029     if (StartChildReport(nextChild, s)) {
2030       ++s->mNumProcessesRunning;
2031       MEMORY_REPORTING_LOG(
2032           "HandleChildReports (aGen=%u): started child report"
2033           " (%u running, %u pending)\n",
2034           aGeneration, s->mNumProcessesRunning,
2035           static_cast<unsigned>(s->mChildrenPending.Length()));
2036     }
2037   }
2038 
2039   // If all the child processes (if any) have reported, we can cancel
2040   // the timer (if started) and finish up.  Otherwise, just return.
2041   if (s->mNumProcessesRunning == 0) {
2042     MOZ_ASSERT(s->mChildrenPending.IsEmpty());
2043     if (s->mTimer) {
2044       s->mTimer->Cancel();
2045     }
2046     FinishReporting();
2047   }
2048 }
2049 
2050 /* static */
TimeoutCallback(nsITimer * aTimer,void * aData)2051 void nsMemoryReporterManager::TimeoutCallback(nsITimer* aTimer, void* aData) {
2052   nsMemoryReporterManager* mgr = static_cast<nsMemoryReporterManager*>(aData);
2053   PendingProcessesState* s = mgr->mPendingProcessesState;
2054 
2055   // Release assert because: if the pointer is null we're about to
2056   // crash regardless of DEBUG, and this way the compiler doesn't
2057   // complain about unused variables.
2058   MOZ_RELEASE_ASSERT(s, "mgr->mPendingProcessesState");
2059   MEMORY_REPORTING_LOG("TimeoutCallback (s->gen=%u; %u running, %u pending)\n",
2060                        s->mGeneration, s->mNumProcessesRunning,
2061                        static_cast<unsigned>(s->mChildrenPending.Length()));
2062 
2063   // We don't bother sending any kind of cancellation message to the child
2064   // processes that haven't reported back.
2065   mgr->FinishReporting();
2066 }
2067 
FinishReporting()2068 nsresult nsMemoryReporterManager::FinishReporting() {
2069   // Memory reporting only happens on the main thread.
2070   if (!NS_IsMainThread()) {
2071     MOZ_CRASH();
2072   }
2073 
2074   MOZ_ASSERT(mPendingProcessesState);
2075   MEMORY_REPORTING_LOG("FinishReporting (s->gen=%u; %u processes reported)\n",
2076                        mPendingProcessesState->mGeneration,
2077                        mPendingProcessesState->mNumProcessesCompleted);
2078 
2079   // Call this before deleting |mPendingProcessesState|.  That way, if
2080   // |mFinishReportData| calls GetReports(), it will silently abort, as
2081   // required.
2082   nsresult rv = mPendingProcessesState->mFinishReporting->Callback(
2083       mPendingProcessesState->mFinishReportingData);
2084 
2085   delete mPendingProcessesState;
2086   mPendingProcessesState = nullptr;
2087   return rv;
2088 }
2089 
PendingProcessesState(uint32_t aGeneration,bool aAnonymize,bool aMinimize,uint32_t aConcurrencyLimit,nsIHandleReportCallback * aHandleReport,nsISupports * aHandleReportData,nsIFinishReportingCallback * aFinishReporting,nsISupports * aFinishReportingData,const nsAString & aDMDDumpIdent)2090 nsMemoryReporterManager::PendingProcessesState::PendingProcessesState(
2091     uint32_t aGeneration, bool aAnonymize, bool aMinimize,
2092     uint32_t aConcurrencyLimit, nsIHandleReportCallback* aHandleReport,
2093     nsISupports* aHandleReportData,
2094     nsIFinishReportingCallback* aFinishReporting,
2095     nsISupports* aFinishReportingData, const nsAString& aDMDDumpIdent)
2096     : mGeneration(aGeneration),
2097       mAnonymize(aAnonymize),
2098       mMinimize(aMinimize),
2099       mChildrenPending(),
2100       mNumProcessesRunning(1),  // reporting starts with the parent
2101       mNumProcessesCompleted(0),
2102       mConcurrencyLimit(aConcurrencyLimit),
2103       mHandleReport(aHandleReport),
2104       mHandleReportData(aHandleReportData),
2105       mFinishReporting(aFinishReporting),
2106       mFinishReportingData(aFinishReportingData),
2107       mDMDDumpIdent(aDMDDumpIdent) {}
2108 
CrashIfRefcountIsZero(nsISupports * aObj)2109 static void CrashIfRefcountIsZero(nsISupports* aObj) {
2110   // This will probably crash if the object's refcount is 0.
2111   uint32_t refcnt = NS_ADDREF(aObj);
2112   if (refcnt <= 1) {
2113     MOZ_CRASH("CrashIfRefcountIsZero: refcount is zero");
2114   }
2115   NS_RELEASE(aObj);
2116 }
2117 
RegisterReporterHelper(nsIMemoryReporter * aReporter,bool aForce,bool aStrong,bool aIsAsync)2118 nsresult nsMemoryReporterManager::RegisterReporterHelper(
2119     nsIMemoryReporter* aReporter, bool aForce, bool aStrong, bool aIsAsync) {
2120   // This method is thread-safe.
2121   mozilla::MutexAutoLock autoLock(mMutex);
2122 
2123   if (mIsRegistrationBlocked && !aForce) {
2124     return NS_ERROR_FAILURE;
2125   }
2126 
2127   if (mStrongReporters->Contains(aReporter) ||
2128       mWeakReporters->Contains(aReporter)) {
2129     return NS_ERROR_FAILURE;
2130   }
2131 
2132   // If |aStrong| is true, |aReporter| may have a refcnt of 0, so we take
2133   // a kung fu death grip before calling PutEntry.  Otherwise, if PutEntry
2134   // addref'ed and released |aReporter| before finally addref'ing it for
2135   // good, it would free aReporter!  The kung fu death grip could itself be
2136   // problematic if PutEntry didn't addref |aReporter| (because then when the
2137   // death grip goes out of scope, we would delete the reporter).  In debug
2138   // mode, we check that this doesn't happen.
2139   //
2140   // If |aStrong| is false, we require that |aReporter| have a non-zero
2141   // refcnt.
2142   //
2143   if (aStrong) {
2144     nsCOMPtr<nsIMemoryReporter> kungFuDeathGrip = aReporter;
2145     mStrongReporters->Put(aReporter, aIsAsync);
2146     CrashIfRefcountIsZero(aReporter);
2147   } else {
2148     CrashIfRefcountIsZero(aReporter);
2149     nsCOMPtr<nsIXPConnectWrappedJS> jsComponent = do_QueryInterface(aReporter);
2150     if (jsComponent) {
2151       // We cannot allow non-native reporters (WrappedJS), since we'll be
2152       // holding onto a raw pointer, which would point to the wrapper,
2153       // and that wrapper is likely to go away as soon as this register
2154       // call finishes.  This would then lead to subsequent crashes in
2155       // CollectReports().
2156       return NS_ERROR_XPC_BAD_CONVERT_JS;
2157     }
2158     mWeakReporters->Put(aReporter, aIsAsync);
2159   }
2160 
2161   return NS_OK;
2162 }
2163 
2164 NS_IMETHODIMP
RegisterStrongReporter(nsIMemoryReporter * aReporter)2165 nsMemoryReporterManager::RegisterStrongReporter(nsIMemoryReporter* aReporter) {
2166   return RegisterReporterHelper(aReporter, /* force = */ false,
2167                                 /* strong = */ true,
2168                                 /* async = */ false);
2169 }
2170 
2171 NS_IMETHODIMP
RegisterStrongAsyncReporter(nsIMemoryReporter * aReporter)2172 nsMemoryReporterManager::RegisterStrongAsyncReporter(
2173     nsIMemoryReporter* aReporter) {
2174   return RegisterReporterHelper(aReporter, /* force = */ false,
2175                                 /* strong = */ true,
2176                                 /* async = */ true);
2177 }
2178 
2179 NS_IMETHODIMP
RegisterWeakReporter(nsIMemoryReporter * aReporter)2180 nsMemoryReporterManager::RegisterWeakReporter(nsIMemoryReporter* aReporter) {
2181   return RegisterReporterHelper(aReporter, /* force = */ false,
2182                                 /* strong = */ false,
2183                                 /* async = */ false);
2184 }
2185 
2186 NS_IMETHODIMP
RegisterWeakAsyncReporter(nsIMemoryReporter * aReporter)2187 nsMemoryReporterManager::RegisterWeakAsyncReporter(
2188     nsIMemoryReporter* aReporter) {
2189   return RegisterReporterHelper(aReporter, /* force = */ false,
2190                                 /* strong = */ false,
2191                                 /* async = */ true);
2192 }
2193 
2194 NS_IMETHODIMP
RegisterStrongReporterEvenIfBlocked(nsIMemoryReporter * aReporter)2195 nsMemoryReporterManager::RegisterStrongReporterEvenIfBlocked(
2196     nsIMemoryReporter* aReporter) {
2197   return RegisterReporterHelper(aReporter, /* force = */ true,
2198                                 /* strong = */ true,
2199                                 /* async = */ false);
2200 }
2201 
2202 NS_IMETHODIMP
UnregisterStrongReporter(nsIMemoryReporter * aReporter)2203 nsMemoryReporterManager::UnregisterStrongReporter(
2204     nsIMemoryReporter* aReporter) {
2205   // This method is thread-safe.
2206   mozilla::MutexAutoLock autoLock(mMutex);
2207 
2208   MOZ_ASSERT(!mWeakReporters->Contains(aReporter));
2209 
2210   if (mStrongReporters->Contains(aReporter)) {
2211     mStrongReporters->Remove(aReporter);
2212     return NS_OK;
2213   }
2214 
2215   // We don't register new reporters when the block is in place, but we do
2216   // unregister existing reporters. This is so we don't keep holding strong
2217   // references that these reporters aren't expecting (which can keep them
2218   // alive longer than intended).
2219   if (mSavedStrongReporters && mSavedStrongReporters->Contains(aReporter)) {
2220     mSavedStrongReporters->Remove(aReporter);
2221     return NS_OK;
2222   }
2223 
2224   return NS_ERROR_FAILURE;
2225 }
2226 
2227 NS_IMETHODIMP
UnregisterWeakReporter(nsIMemoryReporter * aReporter)2228 nsMemoryReporterManager::UnregisterWeakReporter(nsIMemoryReporter* aReporter) {
2229   // This method is thread-safe.
2230   mozilla::MutexAutoLock autoLock(mMutex);
2231 
2232   MOZ_ASSERT(!mStrongReporters->Contains(aReporter));
2233 
2234   if (mWeakReporters->Contains(aReporter)) {
2235     mWeakReporters->Remove(aReporter);
2236     return NS_OK;
2237   }
2238 
2239   // We don't register new reporters when the block is in place, but we do
2240   // unregister existing reporters. This is so we don't keep holding weak
2241   // references that the old reporters aren't expecting (which can end up as
2242   // dangling pointers that lead to use-after-frees).
2243   if (mSavedWeakReporters && mSavedWeakReporters->Contains(aReporter)) {
2244     mSavedWeakReporters->Remove(aReporter);
2245     return NS_OK;
2246   }
2247 
2248   return NS_ERROR_FAILURE;
2249 }
2250 
2251 NS_IMETHODIMP
BlockRegistrationAndHideExistingReporters()2252 nsMemoryReporterManager::BlockRegistrationAndHideExistingReporters() {
2253   // This method is thread-safe.
2254   mozilla::MutexAutoLock autoLock(mMutex);
2255   if (mIsRegistrationBlocked) {
2256     return NS_ERROR_FAILURE;
2257   }
2258   mIsRegistrationBlocked = true;
2259 
2260   // Hide the existing reporters, saving them for later restoration.
2261   MOZ_ASSERT(!mSavedStrongReporters);
2262   MOZ_ASSERT(!mSavedWeakReporters);
2263   mSavedStrongReporters = mStrongReporters;
2264   mSavedWeakReporters = mWeakReporters;
2265   mStrongReporters = new StrongReportersTable();
2266   mWeakReporters = new WeakReportersTable();
2267 
2268   return NS_OK;
2269 }
2270 
2271 NS_IMETHODIMP
UnblockRegistrationAndRestoreOriginalReporters()2272 nsMemoryReporterManager::UnblockRegistrationAndRestoreOriginalReporters() {
2273   // This method is thread-safe.
2274   mozilla::MutexAutoLock autoLock(mMutex);
2275   if (!mIsRegistrationBlocked) {
2276     return NS_ERROR_FAILURE;
2277   }
2278 
2279   // Banish the current reporters, and restore the hidden ones.
2280   delete mStrongReporters;
2281   delete mWeakReporters;
2282   mStrongReporters = mSavedStrongReporters;
2283   mWeakReporters = mSavedWeakReporters;
2284   mSavedStrongReporters = nullptr;
2285   mSavedWeakReporters = nullptr;
2286 
2287   mIsRegistrationBlocked = false;
2288   return NS_OK;
2289 }
2290 
2291 NS_IMETHODIMP
GetVsize(int64_t * aVsize)2292 nsMemoryReporterManager::GetVsize(int64_t* aVsize) {
2293 #ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS
2294   return VsizeDistinguishedAmount(aVsize);
2295 #else
2296   *aVsize = 0;
2297   return NS_ERROR_NOT_AVAILABLE;
2298 #endif
2299 }
2300 
2301 NS_IMETHODIMP
GetVsizeMaxContiguous(int64_t * aAmount)2302 nsMemoryReporterManager::GetVsizeMaxContiguous(int64_t* aAmount) {
2303 #ifdef HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER
2304   return VsizeMaxContiguousDistinguishedAmount(aAmount);
2305 #else
2306   *aAmount = 0;
2307   return NS_ERROR_NOT_AVAILABLE;
2308 #endif
2309 }
2310 
2311 NS_IMETHODIMP
GetResident(int64_t * aAmount)2312 nsMemoryReporterManager::GetResident(int64_t* aAmount) {
2313 #ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS
2314   return ResidentDistinguishedAmount(aAmount);
2315 #else
2316   *aAmount = 0;
2317   return NS_ERROR_NOT_AVAILABLE;
2318 #endif
2319 }
2320 
2321 NS_IMETHODIMP
GetResidentFast(int64_t * aAmount)2322 nsMemoryReporterManager::GetResidentFast(int64_t* aAmount) {
2323 #ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS
2324   return ResidentFastDistinguishedAmount(aAmount);
2325 #else
2326   *aAmount = 0;
2327   return NS_ERROR_NOT_AVAILABLE;
2328 #endif
2329 }
2330 
2331 /*static*/
ResidentFast()2332 int64_t nsMemoryReporterManager::ResidentFast() {
2333 #ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS
2334   int64_t amount;
2335   nsresult rv = ResidentFastDistinguishedAmount(&amount);
2336   NS_ENSURE_SUCCESS(rv, 0);
2337   return amount;
2338 #else
2339   return 0;
2340 #endif
2341 }
2342 
2343 NS_IMETHODIMP
GetResidentPeak(int64_t * aAmount)2344 nsMemoryReporterManager::GetResidentPeak(int64_t* aAmount) {
2345 #ifdef HAVE_RESIDENT_PEAK_REPORTER
2346   return ResidentPeakDistinguishedAmount(aAmount);
2347 #else
2348   *aAmount = 0;
2349   return NS_ERROR_NOT_AVAILABLE;
2350 #endif
2351 }
2352 
2353 /*static*/
ResidentPeak()2354 int64_t nsMemoryReporterManager::ResidentPeak() {
2355 #ifdef HAVE_RESIDENT_PEAK_REPORTER
2356   int64_t amount = 0;
2357   nsresult rv = ResidentPeakDistinguishedAmount(&amount);
2358   NS_ENSURE_SUCCESS(rv, 0);
2359   return amount;
2360 #else
2361   return 0;
2362 #endif
2363 }
2364 
2365 NS_IMETHODIMP
GetResidentUnique(int64_t * aAmount)2366 nsMemoryReporterManager::GetResidentUnique(int64_t* aAmount) {
2367 #ifdef HAVE_RESIDENT_UNIQUE_REPORTER
2368   return ResidentUniqueDistinguishedAmount(aAmount);
2369 #else
2370   *aAmount = 0;
2371   return NS_ERROR_NOT_AVAILABLE;
2372 #endif
2373 }
2374 
2375 /*static*/
ResidentUnique()2376 int64_t nsMemoryReporterManager::ResidentUnique() {
2377 #ifdef HAVE_RESIDENT_UNIQUE_REPORTER
2378   int64_t amount = 0;
2379   nsresult rv = ResidentUniqueDistinguishedAmount(&amount);
2380   NS_ENSURE_SUCCESS(rv, 0);
2381   return amount;
2382 #else
2383   return 0;
2384 #endif
2385 }
2386 
2387 NS_IMETHODIMP
GetHeapAllocated(int64_t * aAmount)2388 nsMemoryReporterManager::GetHeapAllocated(int64_t* aAmount) {
2389 #ifdef HAVE_JEMALLOC_STATS
2390   jemalloc_stats_t stats;
2391   jemalloc_stats(&stats);
2392   *aAmount = stats.allocated;
2393   return NS_OK;
2394 #else
2395   *aAmount = 0;
2396   return NS_ERROR_NOT_AVAILABLE;
2397 #endif
2398 }
2399 
2400 // This has UNITS_PERCENTAGE, so it is multiplied by 100x.
2401 NS_IMETHODIMP
GetHeapOverheadFraction(int64_t * aAmount)2402 nsMemoryReporterManager::GetHeapOverheadFraction(int64_t* aAmount) {
2403 #ifdef HAVE_JEMALLOC_STATS
2404   jemalloc_stats_t stats;
2405   jemalloc_stats(&stats);
2406   *aAmount = HeapOverheadFraction(&stats);
2407   return NS_OK;
2408 #else
2409   *aAmount = 0;
2410   return NS_ERROR_NOT_AVAILABLE;
2411 #endif
2412 }
2413 
GetInfallibleAmount(InfallibleAmountFn aAmountFn,int64_t * aAmount)2414 [[nodiscard]] static nsresult GetInfallibleAmount(InfallibleAmountFn aAmountFn,
2415                                                   int64_t* aAmount) {
2416   if (aAmountFn) {
2417     *aAmount = aAmountFn();
2418     return NS_OK;
2419   }
2420   *aAmount = 0;
2421   return NS_ERROR_NOT_AVAILABLE;
2422 }
2423 
2424 NS_IMETHODIMP
GetJSMainRuntimeGCHeap(int64_t * aAmount)2425 nsMemoryReporterManager::GetJSMainRuntimeGCHeap(int64_t* aAmount) {
2426   return GetInfallibleAmount(mAmountFns.mJSMainRuntimeGCHeap, aAmount);
2427 }
2428 
2429 NS_IMETHODIMP
GetJSMainRuntimeTemporaryPeak(int64_t * aAmount)2430 nsMemoryReporterManager::GetJSMainRuntimeTemporaryPeak(int64_t* aAmount) {
2431   return GetInfallibleAmount(mAmountFns.mJSMainRuntimeTemporaryPeak, aAmount);
2432 }
2433 
2434 NS_IMETHODIMP
GetJSMainRuntimeCompartmentsSystem(int64_t * aAmount)2435 nsMemoryReporterManager::GetJSMainRuntimeCompartmentsSystem(int64_t* aAmount) {
2436   return GetInfallibleAmount(mAmountFns.mJSMainRuntimeCompartmentsSystem,
2437                              aAmount);
2438 }
2439 
2440 NS_IMETHODIMP
GetJSMainRuntimeCompartmentsUser(int64_t * aAmount)2441 nsMemoryReporterManager::GetJSMainRuntimeCompartmentsUser(int64_t* aAmount) {
2442   return GetInfallibleAmount(mAmountFns.mJSMainRuntimeCompartmentsUser,
2443                              aAmount);
2444 }
2445 
2446 NS_IMETHODIMP
GetJSMainRuntimeRealmsSystem(int64_t * aAmount)2447 nsMemoryReporterManager::GetJSMainRuntimeRealmsSystem(int64_t* aAmount) {
2448   return GetInfallibleAmount(mAmountFns.mJSMainRuntimeRealmsSystem, aAmount);
2449 }
2450 
2451 NS_IMETHODIMP
GetJSMainRuntimeRealmsUser(int64_t * aAmount)2452 nsMemoryReporterManager::GetJSMainRuntimeRealmsUser(int64_t* aAmount) {
2453   return GetInfallibleAmount(mAmountFns.mJSMainRuntimeRealmsUser, aAmount);
2454 }
2455 
2456 NS_IMETHODIMP
GetImagesContentUsedUncompressed(int64_t * aAmount)2457 nsMemoryReporterManager::GetImagesContentUsedUncompressed(int64_t* aAmount) {
2458   return GetInfallibleAmount(mAmountFns.mImagesContentUsedUncompressed,
2459                              aAmount);
2460 }
2461 
2462 NS_IMETHODIMP
GetStorageSQLite(int64_t * aAmount)2463 nsMemoryReporterManager::GetStorageSQLite(int64_t* aAmount) {
2464   return GetInfallibleAmount(mAmountFns.mStorageSQLite, aAmount);
2465 }
2466 
2467 NS_IMETHODIMP
GetLowMemoryEventsVirtual(int64_t * aAmount)2468 nsMemoryReporterManager::GetLowMemoryEventsVirtual(int64_t* aAmount) {
2469   return GetInfallibleAmount(mAmountFns.mLowMemoryEventsVirtual, aAmount);
2470 }
2471 
2472 NS_IMETHODIMP
GetLowMemoryEventsCommitSpace(int64_t * aAmount)2473 nsMemoryReporterManager::GetLowMemoryEventsCommitSpace(int64_t* aAmount) {
2474   return GetInfallibleAmount(mAmountFns.mLowMemoryEventsCommitSpace, aAmount);
2475 }
2476 
2477 NS_IMETHODIMP
GetLowMemoryEventsPhysical(int64_t * aAmount)2478 nsMemoryReporterManager::GetLowMemoryEventsPhysical(int64_t* aAmount) {
2479   return GetInfallibleAmount(mAmountFns.mLowMemoryEventsPhysical, aAmount);
2480 }
2481 
2482 NS_IMETHODIMP
GetGhostWindows(int64_t * aAmount)2483 nsMemoryReporterManager::GetGhostWindows(int64_t* aAmount) {
2484   return GetInfallibleAmount(mAmountFns.mGhostWindows, aAmount);
2485 }
2486 
2487 NS_IMETHODIMP
GetPageFaultsHard(int64_t * aAmount)2488 nsMemoryReporterManager::GetPageFaultsHard(int64_t* aAmount) {
2489 #ifdef HAVE_PAGE_FAULT_REPORTERS
2490   return PageFaultsHardDistinguishedAmount(aAmount);
2491 #else
2492   *aAmount = 0;
2493   return NS_ERROR_NOT_AVAILABLE;
2494 #endif
2495 }
2496 
2497 NS_IMETHODIMP
GetHasMozMallocUsableSize(bool * aHas)2498 nsMemoryReporterManager::GetHasMozMallocUsableSize(bool* aHas) {
2499   void* p = malloc(16);
2500   if (!p) {
2501     return NS_ERROR_OUT_OF_MEMORY;
2502   }
2503   size_t usable = moz_malloc_usable_size(p);
2504   free(p);
2505   *aHas = !!(usable > 0);
2506   return NS_OK;
2507 }
2508 
2509 NS_IMETHODIMP
GetIsDMDEnabled(bool * aIsEnabled)2510 nsMemoryReporterManager::GetIsDMDEnabled(bool* aIsEnabled) {
2511 #ifdef MOZ_DMD
2512   *aIsEnabled = true;
2513 #else
2514   *aIsEnabled = false;
2515 #endif
2516   return NS_OK;
2517 }
2518 
2519 NS_IMETHODIMP
GetIsDMDRunning(bool * aIsRunning)2520 nsMemoryReporterManager::GetIsDMDRunning(bool* aIsRunning) {
2521 #ifdef MOZ_DMD
2522   *aIsRunning = dmd::IsRunning();
2523 #else
2524   *aIsRunning = false;
2525 #endif
2526   return NS_OK;
2527 }
2528 
2529 namespace {
2530 
2531 /**
2532  * This runnable lets us implement
2533  * nsIMemoryReporterManager::MinimizeMemoryUsage().  We fire a heap-minimize
2534  * notification, spin the event loop, and repeat this process a few times.
2535  *
2536  * When this sequence finishes, we invoke the callback function passed to the
2537  * runnable's constructor.
2538  */
2539 class MinimizeMemoryUsageRunnable : public Runnable {
2540  public:
MinimizeMemoryUsageRunnable(nsIRunnable * aCallback)2541   explicit MinimizeMemoryUsageRunnable(nsIRunnable* aCallback)
2542       : mozilla::Runnable("MinimizeMemoryUsageRunnable"),
2543         mCallback(aCallback),
2544         mRemainingIters(sNumIters) {}
2545 
Run()2546   NS_IMETHOD Run() override {
2547     nsCOMPtr<nsIObserverService> os = services::GetObserverService();
2548     if (!os) {
2549       return NS_ERROR_FAILURE;
2550     }
2551 
2552     if (mRemainingIters == 0) {
2553       os->NotifyObservers(nullptr, "after-minimize-memory-usage",
2554                           u"MinimizeMemoryUsageRunnable");
2555       if (mCallback) {
2556         mCallback->Run();
2557       }
2558       return NS_OK;
2559     }
2560 
2561     os->NotifyObservers(nullptr, "memory-pressure", u"heap-minimize");
2562     mRemainingIters--;
2563     NS_DispatchToMainThread(this);
2564 
2565     return NS_OK;
2566   }
2567 
2568  private:
2569   // Send sNumIters heap-minimize notifications, spinning the event
2570   // loop after each notification (see bug 610166 comment 12 for an
2571   // explanation), because one notification doesn't cut it.
2572   static const uint32_t sNumIters = 3;
2573 
2574   nsCOMPtr<nsIRunnable> mCallback;
2575   uint32_t mRemainingIters;
2576 };
2577 
2578 }  // namespace
2579 
2580 NS_IMETHODIMP
MinimizeMemoryUsage(nsIRunnable * aCallback)2581 nsMemoryReporterManager::MinimizeMemoryUsage(nsIRunnable* aCallback) {
2582   RefPtr<MinimizeMemoryUsageRunnable> runnable =
2583       new MinimizeMemoryUsageRunnable(aCallback);
2584 
2585   return NS_DispatchToMainThread(runnable);
2586 }
2587 
2588 NS_IMETHODIMP
SizeOfTab(mozIDOMWindowProxy * aTopWindow,int64_t * aJSObjectsSize,int64_t * aJSStringsSize,int64_t * aJSOtherSize,int64_t * aDomSize,int64_t * aStyleSize,int64_t * aOtherSize,int64_t * aTotalSize,double * aJSMilliseconds,double * aNonJSMilliseconds)2589 nsMemoryReporterManager::SizeOfTab(mozIDOMWindowProxy* aTopWindow,
2590                                    int64_t* aJSObjectsSize,
2591                                    int64_t* aJSStringsSize,
2592                                    int64_t* aJSOtherSize, int64_t* aDomSize,
2593                                    int64_t* aStyleSize, int64_t* aOtherSize,
2594                                    int64_t* aTotalSize, double* aJSMilliseconds,
2595                                    double* aNonJSMilliseconds) {
2596   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aTopWindow);
2597   auto* piWindow = nsPIDOMWindowOuter::From(aTopWindow);
2598   if (NS_WARN_IF(!global) || NS_WARN_IF(!piWindow)) {
2599     return NS_ERROR_FAILURE;
2600   }
2601 
2602   TimeStamp t1 = TimeStamp::Now();
2603 
2604   // Measure JS memory consumption (and possibly some non-JS consumption, via
2605   // |jsPrivateSize|).
2606   size_t jsObjectsSize, jsStringsSize, jsPrivateSize, jsOtherSize;
2607   nsresult rv = mSizeOfTabFns.mJS(global->GetGlobalJSObject(), &jsObjectsSize,
2608                                   &jsStringsSize, &jsPrivateSize, &jsOtherSize);
2609   if (NS_WARN_IF(NS_FAILED(rv))) {
2610     return rv;
2611   }
2612 
2613   TimeStamp t2 = TimeStamp::Now();
2614 
2615   // Measure non-JS memory consumption.
2616   size_t domSize, styleSize, otherSize;
2617   rv = mSizeOfTabFns.mNonJS(piWindow, &domSize, &styleSize, &otherSize);
2618   if (NS_WARN_IF(NS_FAILED(rv))) {
2619     return rv;
2620   }
2621 
2622   TimeStamp t3 = TimeStamp::Now();
2623 
2624   *aTotalSize = 0;
2625 #define DO(aN, n)       \
2626   {                     \
2627     *aN = (n);          \
2628     *aTotalSize += (n); \
2629   }
2630   DO(aJSObjectsSize, jsObjectsSize);
2631   DO(aJSStringsSize, jsStringsSize);
2632   DO(aJSOtherSize, jsOtherSize);
2633   DO(aDomSize, jsPrivateSize + domSize);
2634   DO(aStyleSize, styleSize);
2635   DO(aOtherSize, otherSize);
2636 #undef DO
2637 
2638   *aJSMilliseconds = (t2 - t1).ToMilliseconds();
2639   *aNonJSMilliseconds = (t3 - t2).ToMilliseconds();
2640 
2641   return NS_OK;
2642 }
2643 
2644 namespace mozilla {
2645 
2646 #define GET_MEMORY_REPORTER_MANAGER(mgr)      \
2647   RefPtr<nsMemoryReporterManager> mgr =       \
2648       nsMemoryReporterManager::GetOrCreate(); \
2649   if (!mgr) {                                 \
2650     return NS_ERROR_FAILURE;                  \
2651   }
2652 
RegisterStrongMemoryReporter(nsIMemoryReporter * aReporter)2653 nsresult RegisterStrongMemoryReporter(nsIMemoryReporter* aReporter) {
2654   // Hold a strong reference to the argument to make sure it gets released if
2655   // we return early below.
2656   nsCOMPtr<nsIMemoryReporter> reporter = aReporter;
2657   GET_MEMORY_REPORTER_MANAGER(mgr)
2658   return mgr->RegisterStrongReporter(reporter);
2659 }
2660 
RegisterStrongAsyncMemoryReporter(nsIMemoryReporter * aReporter)2661 nsresult RegisterStrongAsyncMemoryReporter(nsIMemoryReporter* aReporter) {
2662   // Hold a strong reference to the argument to make sure it gets released if
2663   // we return early below.
2664   nsCOMPtr<nsIMemoryReporter> reporter = aReporter;
2665   GET_MEMORY_REPORTER_MANAGER(mgr)
2666   return mgr->RegisterStrongAsyncReporter(reporter);
2667 }
2668 
RegisterWeakMemoryReporter(nsIMemoryReporter * aReporter)2669 nsresult RegisterWeakMemoryReporter(nsIMemoryReporter* aReporter) {
2670   GET_MEMORY_REPORTER_MANAGER(mgr)
2671   return mgr->RegisterWeakReporter(aReporter);
2672 }
2673 
RegisterWeakAsyncMemoryReporter(nsIMemoryReporter * aReporter)2674 nsresult RegisterWeakAsyncMemoryReporter(nsIMemoryReporter* aReporter) {
2675   GET_MEMORY_REPORTER_MANAGER(mgr)
2676   return mgr->RegisterWeakAsyncReporter(aReporter);
2677 }
2678 
UnregisterStrongMemoryReporter(nsIMemoryReporter * aReporter)2679 nsresult UnregisterStrongMemoryReporter(nsIMemoryReporter* aReporter) {
2680   GET_MEMORY_REPORTER_MANAGER(mgr)
2681   return mgr->UnregisterStrongReporter(aReporter);
2682 }
2683 
UnregisterWeakMemoryReporter(nsIMemoryReporter * aReporter)2684 nsresult UnregisterWeakMemoryReporter(nsIMemoryReporter* aReporter) {
2685   GET_MEMORY_REPORTER_MANAGER(mgr)
2686   return mgr->UnregisterWeakReporter(aReporter);
2687 }
2688 
2689 // Macro for generating functions that register distinguished amount functions
2690 // with the memory reporter manager.
2691 #define DEFINE_REGISTER_DISTINGUISHED_AMOUNT(kind, name)                   \
2692   nsresult Register##name##DistinguishedAmount(kind##AmountFn aAmountFn) { \
2693     GET_MEMORY_REPORTER_MANAGER(mgr)                                       \
2694     mgr->mAmountFns.m##name = aAmountFn;                                   \
2695     return NS_OK;                                                          \
2696   }
2697 
2698 // Macro for generating functions that unregister distinguished amount
2699 // functions with the memory reporter manager.
2700 #define DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT(name) \
2701   nsresult Unregister##name##DistinguishedAmount() { \
2702     GET_MEMORY_REPORTER_MANAGER(mgr)                 \
2703     mgr->mAmountFns.m##name = nullptr;               \
2704     return NS_OK;                                    \
2705   }
2706 
2707 DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeGCHeap)
2708 DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeTemporaryPeak)
2709 DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible,
2710                                      JSMainRuntimeCompartmentsSystem)
2711 DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeCompartmentsUser)
2712 DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeRealmsSystem)
2713 DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeRealmsUser)
2714 
2715 DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, ImagesContentUsedUncompressed)
2716 DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT(ImagesContentUsedUncompressed)
2717 
2718 DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, StorageSQLite)
2719 DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT(StorageSQLite)
2720 
2721 DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsVirtual)
2722 DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsCommitSpace)
2723 DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsPhysical)
2724 
2725 DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, GhostWindows)
2726 
2727 #undef DEFINE_REGISTER_DISTINGUISHED_AMOUNT
2728 #undef DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT
2729 
2730 #define DEFINE_REGISTER_SIZE_OF_TAB(name)                              \
2731   nsresult Register##name##SizeOfTab(name##SizeOfTabFn aSizeOfTabFn) { \
2732     GET_MEMORY_REPORTER_MANAGER(mgr)                                   \
2733     mgr->mSizeOfTabFns.m##name = aSizeOfTabFn;                         \
2734     return NS_OK;                                                      \
2735   }
2736 
2737 DEFINE_REGISTER_SIZE_OF_TAB(JS);
2738 DEFINE_REGISTER_SIZE_OF_TAB(NonJS);
2739 
2740 #undef DEFINE_REGISTER_SIZE_OF_TAB
2741 
2742 #undef GET_MEMORY_REPORTER_MANAGER
2743 
2744 }  // namespace mozilla
2745