michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsAtomTable.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsCOMArray.h" michael@0: #include "nsPrintfCString.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsMemoryReporterManager.h" michael@0: #include "nsITimer.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsIDOMWindow.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsIGlobalObject.h" michael@0: #include "nsIXPConnect.h" michael@0: #if defined(XP_UNIX) || defined(MOZ_DMD) michael@0: #include "nsMemoryInfoDumper.h" michael@0: #endif michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/PodOperations.h" michael@0: #include "mozilla/Services.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "mozilla/dom/PMemoryReportRequestParent.h" // for dom::MemoryReport michael@0: michael@0: #ifndef XP_WIN michael@0: #include michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: michael@0: #if defined(MOZ_MEMORY) michael@0: # define HAVE_JEMALLOC_STATS 1 michael@0: # include "mozmemory.h" michael@0: #endif // MOZ_MEMORY michael@0: michael@0: #if defined(XP_LINUX) michael@0: michael@0: static nsresult michael@0: GetProcSelfStatmField(int aField, int64_t* aN) michael@0: { michael@0: // There are more than two fields, but we're only interested in the first michael@0: // two. michael@0: static const int MAX_FIELD = 2; michael@0: size_t fields[MAX_FIELD]; michael@0: MOZ_ASSERT(aField < MAX_FIELD, "bad field number"); michael@0: FILE* f = fopen("/proc/self/statm", "r"); michael@0: if (f) { michael@0: int nread = fscanf(f, "%zu %zu", &fields[0], &fields[1]); michael@0: fclose(f); michael@0: if (nread == MAX_FIELD) { michael@0: *aN = fields[aField] * getpagesize(); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: #define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 michael@0: static nsresult michael@0: VsizeDistinguishedAmount(int64_t* aN) michael@0: { michael@0: return GetProcSelfStatmField(0, aN); michael@0: } michael@0: michael@0: static nsresult michael@0: ResidentDistinguishedAmount(int64_t* aN) michael@0: { michael@0: return GetProcSelfStatmField(1, aN); michael@0: } michael@0: michael@0: static nsresult michael@0: ResidentFastDistinguishedAmount(int64_t* aN) michael@0: { michael@0: return ResidentDistinguishedAmount(aN); michael@0: } michael@0: michael@0: #define HAVE_RESIDENT_UNIQUE_REPORTER michael@0: class ResidentUniqueReporter MOZ_FINAL : public nsIMemoryReporter michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_METHOD CollectReports(nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aData) michael@0: { michael@0: // You might be tempted to calculate USS by subtracting the "shared" michael@0: // value from the "resident" value in /proc//statm. But at least michael@0: // on Linux, statm's "shared" value actually counts pages backed by michael@0: // files, which has little to do with whether the pages are actually michael@0: // shared. /proc/self/smaps on the other hand appears to give us the michael@0: // correct information. michael@0: michael@0: FILE* f = fopen("/proc/self/smaps", "r"); michael@0: if (NS_WARN_IF(!f)) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: int64_t amount = 0; michael@0: char line[256]; michael@0: while (fgets(line, sizeof(line), f)) { michael@0: long long val = 0; michael@0: if (sscanf(line, "Private_Dirty: %lld kB", &val) == 1 || michael@0: sscanf(line, "Private_Clean: %lld kB", &val) == 1) { michael@0: amount += val * 1024; // convert from kB to bytes michael@0: } michael@0: } michael@0: michael@0: fclose(f); michael@0: michael@0: return MOZ_COLLECT_REPORT( michael@0: "resident-unique", KIND_OTHER, UNITS_BYTES, amount, michael@0: "Memory mapped by the process that is present in physical memory and not " michael@0: "shared with any other processes. This is also known as the process's unique " michael@0: "set size (USS). This is the amount of RAM we'd expect to be freed if we " michael@0: "closed this process."); michael@0: } michael@0: }; michael@0: NS_IMPL_ISUPPORTS(ResidentUniqueReporter, nsIMemoryReporter) michael@0: michael@0: #elif defined(__DragonFly__) || defined(__FreeBSD__) \ michael@0: || defined(__NetBSD__) || defined(__OpenBSD__) \ michael@0: || defined(__FreeBSD_kernel__) michael@0: michael@0: #include michael@0: #include michael@0: #if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) michael@0: #include michael@0: #endif michael@0: michael@0: #include michael@0: michael@0: #if defined(__NetBSD__) michael@0: #undef KERN_PROC michael@0: #define KERN_PROC KERN_PROC2 michael@0: #define KINFO_PROC struct kinfo_proc2 michael@0: #else michael@0: #define KINFO_PROC struct kinfo_proc michael@0: #endif michael@0: michael@0: #if defined(__DragonFly__) michael@0: #define KP_SIZE(kp) (kp.kp_vm_map_size) michael@0: #define KP_RSS(kp) (kp.kp_vm_rssize * getpagesize()) michael@0: #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) michael@0: #define KP_SIZE(kp) (kp.ki_size) michael@0: #define KP_RSS(kp) (kp.ki_rssize * getpagesize()) michael@0: #elif defined(__NetBSD__) michael@0: #define KP_SIZE(kp) (kp.p_vm_msize * getpagesize()) michael@0: #define KP_RSS(kp) (kp.p_vm_rssize * getpagesize()) michael@0: #elif defined(__OpenBSD__) michael@0: #define KP_SIZE(kp) ((kp.p_vm_dsize + kp.p_vm_ssize \ michael@0: + kp.p_vm_tsize) * getpagesize()) michael@0: #define KP_RSS(kp) (kp.p_vm_rssize * getpagesize()) michael@0: #endif michael@0: michael@0: static nsresult michael@0: GetKinfoProcSelf(KINFO_PROC* aProc) michael@0: { michael@0: int mib[] = { michael@0: CTL_KERN, michael@0: KERN_PROC, michael@0: KERN_PROC_PID, michael@0: getpid(), michael@0: #if defined(__NetBSD__) || defined(__OpenBSD__) michael@0: sizeof(KINFO_PROC), michael@0: 1, michael@0: #endif michael@0: }; michael@0: u_int miblen = sizeof(mib) / sizeof(mib[0]); michael@0: size_t size = sizeof(KINFO_PROC); michael@0: if (sysctl(mib, miblen, aProc, &size, nullptr, 0)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: #define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 michael@0: static nsresult michael@0: VsizeDistinguishedAmount(int64_t* aN) michael@0: { michael@0: KINFO_PROC proc; michael@0: nsresult rv = GetKinfoProcSelf(&proc); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: *aN = KP_SIZE(proc); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: static nsresult michael@0: ResidentDistinguishedAmount(int64_t* aN) michael@0: { michael@0: KINFO_PROC proc; michael@0: nsresult rv = GetKinfoProcSelf(&proc); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: *aN = KP_RSS(proc); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: static nsresult michael@0: ResidentFastDistinguishedAmount(int64_t* aN) michael@0: { michael@0: return ResidentDistinguishedAmount(aN); michael@0: } michael@0: michael@0: #ifdef __FreeBSD__ michael@0: #include michael@0: #include michael@0: michael@0: static nsresult michael@0: GetKinfoVmentrySelf(int64_t* aPrss, uint64_t* aMaxreg) michael@0: { michael@0: int cnt; michael@0: struct kinfo_vmentry *vmmap, *kve; michael@0: if ((vmmap = kinfo_getvmmap(getpid(), &cnt)) == nullptr) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: if (aPrss) { michael@0: *aPrss = 0; michael@0: } michael@0: if (aMaxreg) { michael@0: *aMaxreg = 0; michael@0: } michael@0: michael@0: for (int i = 0; i < cnt; i++) { michael@0: kve = &vmmap[i]; michael@0: if (aPrss) { michael@0: *aPrss += kve->kve_private_resident; michael@0: } michael@0: if (aMaxreg) { michael@0: *aMaxreg = std::max(*aMaxreg, kve->kve_end - kve->kve_start); michael@0: } michael@0: } michael@0: michael@0: free(vmmap); michael@0: return NS_OK; michael@0: } michael@0: michael@0: #define HAVE_PRIVATE_REPORTER michael@0: static nsresult michael@0: PrivateDistinguishedAmount(int64_t* aN) michael@0: { michael@0: int64_t priv; michael@0: nsresult rv = GetKinfoVmentrySelf(&priv, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: *aN = priv * getpagesize(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: #define HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER 1 michael@0: static nsresult michael@0: VsizeMaxContiguousDistinguishedAmount(int64_t* aN) michael@0: { michael@0: uint64_t biggestRegion; michael@0: nsresult rv = GetKinfoVmentrySelf(nullptr, &biggestRegion); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: *aN = biggestRegion; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: #endif // FreeBSD michael@0: michael@0: #elif defined(SOLARIS) michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: static void XMappingIter(int64_t& aVsize, int64_t& aResident) michael@0: { michael@0: aVsize = -1; michael@0: aResident = -1; michael@0: int mapfd = open("/proc/self/xmap", O_RDONLY); michael@0: struct stat st; michael@0: prxmap_t* prmapp = nullptr; michael@0: if (mapfd >= 0) { michael@0: if (!fstat(mapfd, &st)) { michael@0: int nmap = st.st_size / sizeof(prxmap_t); michael@0: while (1) { michael@0: // stat(2) on /proc//xmap returns an incorrect value, michael@0: // prior to the release of Solaris 11. michael@0: // Here is a workaround for it. michael@0: nmap *= 2; michael@0: prmapp = (prxmap_t*)malloc((nmap + 1) * sizeof(prxmap_t)); michael@0: if (!prmapp) { michael@0: // out of memory michael@0: break; michael@0: } michael@0: int n = pread(mapfd, prmapp, (nmap + 1) * sizeof(prxmap_t), 0); michael@0: if (n < 0) { michael@0: break; michael@0: } michael@0: if (nmap >= n / sizeof(prxmap_t)) { michael@0: aVsize = 0; michael@0: aResident = 0; michael@0: for (int i = 0; i < n / sizeof(prxmap_t); i++) { michael@0: aVsize += prmapp[i].pr_size; michael@0: aResident += prmapp[i].pr_rss * prmapp[i].pr_pagesize; michael@0: } michael@0: break; michael@0: } michael@0: free(prmapp); michael@0: } michael@0: free(prmapp); michael@0: } michael@0: close(mapfd); michael@0: } michael@0: } michael@0: michael@0: #define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 michael@0: static nsresult michael@0: VsizeDistinguishedAmount(int64_t* aN) michael@0: { michael@0: int64_t vsize, resident; michael@0: XMappingIter(vsize, resident); michael@0: if (vsize == -1) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: *aN = vsize; michael@0: return NS_OK; michael@0: } michael@0: michael@0: static nsresult michael@0: ResidentDistinguishedAmount(int64_t* aN) michael@0: { michael@0: int64_t vsize, resident; michael@0: XMappingIter(vsize, resident); michael@0: if (resident == -1) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: *aN = resident; michael@0: return NS_OK; michael@0: } michael@0: michael@0: static nsresult michael@0: ResidentFastDistinguishedAmount(int64_t* aN) michael@0: { michael@0: return ResidentDistinguishedAmount(aN); michael@0: } michael@0: michael@0: #elif defined(XP_MACOSX) michael@0: michael@0: #include michael@0: #include michael@0: michael@0: static bool michael@0: GetTaskBasicInfo(struct task_basic_info* aTi) michael@0: { michael@0: mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT; michael@0: kern_return_t kr = task_info(mach_task_self(), TASK_BASIC_INFO, michael@0: (task_info_t)aTi, &count); michael@0: return kr == KERN_SUCCESS; michael@0: } michael@0: michael@0: // The VSIZE figure on Mac includes huge amounts of shared memory and is always michael@0: // absurdly high, eg. 2GB+ even at start-up. But both 'top' and 'ps' report michael@0: // it, so we might as well too. michael@0: #define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 michael@0: static nsresult michael@0: VsizeDistinguishedAmount(int64_t* aN) michael@0: { michael@0: task_basic_info ti; michael@0: if (!GetTaskBasicInfo(&ti)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: *aN = ti.virtual_size; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // If we're using jemalloc on Mac, we need to instruct jemalloc to purge the michael@0: // pages it has madvise(MADV_FREE)'d before we read our RSS in order to get michael@0: // an accurate result. The OS will take away MADV_FREE'd pages when there's michael@0: // memory pressure, so ideally, they shouldn't count against our RSS. michael@0: // michael@0: // Purging these pages can take a long time for some users (see bug 789975), michael@0: // so we provide the option to get the RSS without purging first. michael@0: static nsresult michael@0: ResidentDistinguishedAmountHelper(int64_t* aN, bool aDoPurge) michael@0: { michael@0: #ifdef HAVE_JEMALLOC_STATS michael@0: if (aDoPurge) { michael@0: Telemetry::AutoTimer timer; michael@0: jemalloc_purge_freed_pages(); michael@0: } michael@0: #endif michael@0: michael@0: task_basic_info ti; michael@0: if (!GetTaskBasicInfo(&ti)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: *aN = ti.resident_size; michael@0: return NS_OK; michael@0: } michael@0: michael@0: static nsresult michael@0: ResidentFastDistinguishedAmount(int64_t* aN) michael@0: { michael@0: return ResidentDistinguishedAmountHelper(aN, /* doPurge = */ false); michael@0: } michael@0: michael@0: static nsresult michael@0: ResidentDistinguishedAmount(int64_t* aN) michael@0: { michael@0: return ResidentDistinguishedAmountHelper(aN, /* doPurge = */ true); michael@0: } michael@0: michael@0: #elif defined(XP_WIN) michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 michael@0: static nsresult michael@0: VsizeDistinguishedAmount(int64_t* aN) michael@0: { michael@0: MEMORYSTATUSEX s; michael@0: s.dwLength = sizeof(s); michael@0: michael@0: if (!GlobalMemoryStatusEx(&s)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: *aN = s.ullTotalVirtual - s.ullAvailVirtual; michael@0: return NS_OK; michael@0: } michael@0: michael@0: static nsresult michael@0: ResidentDistinguishedAmount(int64_t* aN) michael@0: { michael@0: PROCESS_MEMORY_COUNTERS pmc; michael@0: pmc.cb = sizeof(PROCESS_MEMORY_COUNTERS); michael@0: michael@0: if (!GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: *aN = pmc.WorkingSetSize; michael@0: return NS_OK; michael@0: } michael@0: michael@0: static nsresult michael@0: ResidentFastDistinguishedAmount(int64_t* aN) michael@0: { michael@0: return ResidentDistinguishedAmount(aN); michael@0: } michael@0: michael@0: #define HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER 1 michael@0: static nsresult michael@0: VsizeMaxContiguousDistinguishedAmount(int64_t* aN) michael@0: { michael@0: SIZE_T biggestRegion = 0; michael@0: MEMORY_BASIC_INFORMATION vmemInfo = { 0 }; michael@0: for (size_t currentAddress = 0; ; ) { michael@0: if (!VirtualQuery((LPCVOID)currentAddress, &vmemInfo, sizeof(vmemInfo))) { michael@0: // Something went wrong, just return whatever we've got already. michael@0: break; michael@0: } michael@0: michael@0: if (vmemInfo.State == MEM_FREE) { michael@0: biggestRegion = std::max(biggestRegion, vmemInfo.RegionSize); michael@0: } michael@0: michael@0: SIZE_T lastAddress = currentAddress; michael@0: currentAddress += vmemInfo.RegionSize; michael@0: michael@0: // If we overflow, we've examined all of the address space. michael@0: if (currentAddress < lastAddress) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: *aN = biggestRegion; michael@0: return NS_OK; michael@0: } michael@0: michael@0: #define HAVE_PRIVATE_REPORTER michael@0: static nsresult michael@0: PrivateDistinguishedAmount(int64_t* aN) michael@0: { michael@0: PROCESS_MEMORY_COUNTERS_EX pmcex; michael@0: pmcex.cb = sizeof(PROCESS_MEMORY_COUNTERS_EX); michael@0: michael@0: if (!GetProcessMemoryInfo(GetCurrentProcess(), michael@0: (PPROCESS_MEMORY_COUNTERS) &pmcex, sizeof(pmcex))) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: *aN = pmcex.PrivateUsage; michael@0: return NS_OK; michael@0: } michael@0: #endif // XP_ michael@0: michael@0: #ifdef HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER michael@0: class VsizeMaxContiguousReporter MOZ_FINAL : public nsIMemoryReporter michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_METHOD CollectReports(nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aData) michael@0: { michael@0: int64_t amount; michael@0: nsresult rv = VsizeMaxContiguousDistinguishedAmount(&amount); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: return MOZ_COLLECT_REPORT( michael@0: "vsize-max-contiguous", KIND_OTHER, UNITS_BYTES, amount, michael@0: "Size of the maximum contiguous block of available virtual " michael@0: "memory."); michael@0: } michael@0: }; michael@0: NS_IMPL_ISUPPORTS(VsizeMaxContiguousReporter, nsIMemoryReporter) michael@0: #endif michael@0: michael@0: #ifdef HAVE_PRIVATE_REPORTER michael@0: class PrivateReporter MOZ_FINAL : public nsIMemoryReporter michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_METHOD CollectReports(nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aData) michael@0: { michael@0: int64_t amount; michael@0: nsresult rv = PrivateDistinguishedAmount(&amount); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: return MOZ_COLLECT_REPORT( michael@0: "private", KIND_OTHER, UNITS_BYTES, amount, michael@0: "Memory that cannot be shared with other processes, including memory that is " michael@0: "committed and marked MEM_PRIVATE, data that is not mapped, and executable " michael@0: "pages that have been written to."); michael@0: } michael@0: }; michael@0: NS_IMPL_ISUPPORTS(PrivateReporter, nsIMemoryReporter) michael@0: #endif michael@0: michael@0: #ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS michael@0: class VsizeReporter MOZ_FINAL : public nsIMemoryReporter michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_METHOD CollectReports(nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aData) michael@0: { michael@0: int64_t amount; michael@0: nsresult rv = VsizeDistinguishedAmount(&amount); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return MOZ_COLLECT_REPORT( michael@0: "vsize", KIND_OTHER, UNITS_BYTES, amount, michael@0: "Memory mapped by the process, including code and data segments, the heap, " michael@0: "thread stacks, memory explicitly mapped by the process via mmap and similar " michael@0: "operations, and memory shared with other processes. This is the vsize figure " michael@0: "as reported by 'top' and 'ps'. This figure is of limited use on Mac, where " michael@0: "processes share huge amounts of memory with one another. But even on other " michael@0: "operating systems, 'resident' is a much better measure of the memory " michael@0: "resources used by the process."); michael@0: } michael@0: }; michael@0: NS_IMPL_ISUPPORTS(VsizeReporter, nsIMemoryReporter) michael@0: michael@0: class ResidentReporter MOZ_FINAL : public nsIMemoryReporter michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_METHOD CollectReports(nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aData) michael@0: { michael@0: int64_t amount; michael@0: nsresult rv = ResidentDistinguishedAmount(&amount); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return MOZ_COLLECT_REPORT( michael@0: "resident", KIND_OTHER, UNITS_BYTES, amount, michael@0: "Memory mapped by the process that is present in physical memory, also known " michael@0: "as the resident set size (RSS). This is the best single figure to use when " michael@0: "considering the memory resources used by the process, but it depends both on " michael@0: "other processes being run and details of the OS kernel and so is best used " michael@0: "for comparing the memory usage of a single process at different points in " michael@0: "time."); michael@0: } michael@0: }; michael@0: NS_IMPL_ISUPPORTS(ResidentReporter, nsIMemoryReporter) michael@0: michael@0: #endif // HAVE_VSIZE_AND_RESIDENT_REPORTERS michael@0: michael@0: #ifdef XP_UNIX michael@0: michael@0: #include michael@0: michael@0: #define HAVE_PAGE_FAULT_REPORTERS 1 michael@0: michael@0: class PageFaultsSoftReporter MOZ_FINAL : public nsIMemoryReporter michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_METHOD CollectReports(nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aData) michael@0: { michael@0: struct rusage usage; michael@0: int err = getrusage(RUSAGE_SELF, &usage); michael@0: if (err != 0) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: int64_t amount = usage.ru_minflt; michael@0: michael@0: return MOZ_COLLECT_REPORT( michael@0: "page-faults-soft", KIND_OTHER, UNITS_COUNT_CUMULATIVE, amount, michael@0: "The number of soft page faults (also known as 'minor page faults') that " michael@0: "have occurred since the process started. A soft page fault occurs when the " michael@0: "process tries to access a page which is present in physical memory but is " michael@0: "not mapped into the process's address space. For instance, a process might " michael@0: "observe soft page faults when it loads a shared library which is already " michael@0: "present in physical memory. A process may experience many thousands of soft " michael@0: "page faults even when the machine has plenty of available physical memory, " michael@0: "and because the OS services a soft page fault without accessing the disk, " michael@0: "they impact performance much less than hard page faults."); michael@0: } michael@0: }; michael@0: NS_IMPL_ISUPPORTS(PageFaultsSoftReporter, nsIMemoryReporter) michael@0: michael@0: static nsresult michael@0: PageFaultsHardDistinguishedAmount(int64_t* aAmount) michael@0: { michael@0: struct rusage usage; michael@0: int err = getrusage(RUSAGE_SELF, &usage); michael@0: if (err != 0) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: *aAmount = usage.ru_majflt; michael@0: return NS_OK; michael@0: } michael@0: michael@0: class PageFaultsHardReporter MOZ_FINAL : public nsIMemoryReporter michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_METHOD CollectReports(nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aData) michael@0: { michael@0: int64_t amount = 0; michael@0: nsresult rv = PageFaultsHardDistinguishedAmount(&amount); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return MOZ_COLLECT_REPORT( michael@0: "page-faults-hard", KIND_OTHER, UNITS_COUNT_CUMULATIVE, amount, michael@0: "The number of hard page faults (also known as 'major page faults') that have " michael@0: "occurred since the process started. A hard page fault occurs when a process " michael@0: "tries to access a page which is not present in physical memory. The " michael@0: "operating system must access the disk in order to fulfill a hard page fault. " michael@0: "When memory is plentiful, you should see very few hard page faults. But if " michael@0: "the process tries to use more memory than your machine has available, you " michael@0: "may see many thousands of hard page faults. Because accessing the disk is up " michael@0: "to a million times slower than accessing RAM, the program may run very " michael@0: "slowly when it is experiencing more than 100 or so hard page faults a " michael@0: "second."); michael@0: } michael@0: }; michael@0: NS_IMPL_ISUPPORTS(PageFaultsHardReporter, nsIMemoryReporter) michael@0: michael@0: #endif // HAVE_PAGE_FAULT_REPORTERS michael@0: michael@0: /** michael@0: ** memory reporter implementation for jemalloc and OSX malloc, michael@0: ** to obtain info on total memory in use (that we know about, michael@0: ** at least -- on OSX, there are sometimes other zones in use). michael@0: **/ michael@0: michael@0: #ifdef HAVE_JEMALLOC_STATS michael@0: michael@0: // This has UNITS_PERCENTAGE, so it is multiplied by 100. michael@0: static int64_t michael@0: HeapOverheadRatio(jemalloc_stats_t* aStats) michael@0: { michael@0: return (int64_t) 10000 * michael@0: (aStats->waste + aStats->bookkeeping + aStats->page_cache) / michael@0: ((double)aStats->allocated); michael@0: } michael@0: michael@0: class JemallocHeapReporter MOZ_FINAL : public nsIMemoryReporter michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_METHOD CollectReports(nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aData) michael@0: { michael@0: jemalloc_stats_t stats; michael@0: jemalloc_stats(&stats); michael@0: michael@0: nsresult rv; michael@0: michael@0: rv = MOZ_COLLECT_REPORT( michael@0: "heap-allocated", KIND_OTHER, UNITS_BYTES, stats.allocated, michael@0: "Memory mapped by the heap allocator that is currently allocated to the " michael@0: "application. This may exceed the amount of memory requested by the " michael@0: "application because the allocator regularly rounds up request sizes. (The " michael@0: "exact amount requested is not recorded.)"); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // We mark this and the other heap-overhead reporters as KIND_NONHEAP michael@0: // because KIND_HEAP memory means "counted in heap-allocated", which michael@0: // this is not. michael@0: rv = MOZ_COLLECT_REPORT( michael@0: "explicit/heap-overhead/waste", KIND_NONHEAP, UNITS_BYTES, michael@0: stats.waste, michael@0: "Committed bytes which do not correspond to an active allocation and which the " michael@0: "allocator is not intentionally keeping alive (i.e., not 'heap-bookkeeping' or " michael@0: "'heap-page-cache'). Although the allocator will waste some space under any " michael@0: "circumstances, a large value here may indicate that the heap is highly " michael@0: "fragmented, or that allocator is performing poorly for some other reason."); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = MOZ_COLLECT_REPORT( michael@0: "explicit/heap-overhead/bookkeeping", KIND_NONHEAP, UNITS_BYTES, michael@0: stats.bookkeeping, michael@0: "Committed bytes which the heap allocator uses for internal data structures."); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = MOZ_COLLECT_REPORT( michael@0: "explicit/heap-overhead/page-cache", KIND_NONHEAP, UNITS_BYTES, michael@0: stats.page_cache, michael@0: "Memory which the allocator could return to the operating system, but hasn't. " michael@0: "The allocator keeps this memory around as an optimization, so it doesn't " michael@0: "have to ask the OS the next time it needs to fulfill a request. This value " michael@0: "is typically not larger than a few megabytes."); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = MOZ_COLLECT_REPORT( michael@0: "heap-committed", KIND_OTHER, UNITS_BYTES, michael@0: stats.allocated + stats.waste + stats.bookkeeping + stats.page_cache, michael@0: "Memory mapped by the heap allocator that is committed, i.e. in physical " michael@0: "memory or paged to disk. This value corresponds to 'heap-allocated' + " michael@0: "'heap-waste' + 'heap-bookkeeping' + 'heap-page-cache', but because " michael@0: "these values are read at different times, the result probably won't match " michael@0: "exactly."); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = MOZ_COLLECT_REPORT( michael@0: "heap-overhead-ratio", KIND_OTHER, UNITS_PERCENTAGE, michael@0: HeapOverheadRatio(&stats), michael@0: "Ratio of committed, unused bytes to allocated bytes; i.e., " michael@0: "'heap-overhead' / 'heap-allocated'. This measures the overhead of " michael@0: "the heap allocator relative to amount of memory allocated."); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: NS_IMPL_ISUPPORTS(JemallocHeapReporter, nsIMemoryReporter) michael@0: michael@0: #endif // HAVE_JEMALLOC_STATS michael@0: michael@0: // Why is this here? At first glance, you'd think it could be defined and michael@0: // registered with nsMemoryReporterManager entirely within nsAtomTable.cpp. michael@0: // However, the obvious time to register it is when the table is initialized, michael@0: // and that happens before XPCOM components are initialized, which means the michael@0: // RegisterStrongMemoryReporter call fails. So instead we do it here. michael@0: class AtomTablesReporter MOZ_FINAL : public nsIMemoryReporter michael@0: { michael@0: MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf) michael@0: michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_METHOD CollectReports(nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aData) michael@0: { michael@0: return MOZ_COLLECT_REPORT( michael@0: "explicit/atom-tables", KIND_HEAP, UNITS_BYTES, michael@0: NS_SizeOfAtomTablesIncludingThis(MallocSizeOf), michael@0: "Memory used by the dynamic and static atoms tables."); michael@0: } michael@0: }; michael@0: NS_IMPL_ISUPPORTS(AtomTablesReporter, nsIMemoryReporter) michael@0: michael@0: #ifdef MOZ_DMD michael@0: michael@0: namespace mozilla { michael@0: namespace dmd { michael@0: michael@0: class DMDReporter MOZ_FINAL : public nsIMemoryReporter michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aData) michael@0: { michael@0: dmd::Sizes sizes; michael@0: dmd::SizeOf(&sizes); michael@0: michael@0: #define REPORT(_path, _amount, _desc) \ michael@0: do { \ michael@0: nsresult rv; \ michael@0: rv = aHandleReport->Callback(EmptyCString(), NS_LITERAL_CSTRING(_path), \ michael@0: KIND_HEAP, UNITS_BYTES, _amount, \ michael@0: NS_LITERAL_CSTRING(_desc), aData); \ michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { \ michael@0: return rv; \ michael@0: } \ michael@0: } while (0) michael@0: michael@0: REPORT("explicit/dmd/stack-traces/used", michael@0: sizes.mStackTracesUsed, michael@0: "Memory used by stack traces which correspond to at least " michael@0: "one heap block DMD is tracking."); michael@0: michael@0: REPORT("explicit/dmd/stack-traces/unused", michael@0: sizes.mStackTracesUnused, michael@0: "Memory used by stack traces which don't correspond to any heap " michael@0: "blocks DMD is currently tracking."); michael@0: michael@0: REPORT("explicit/dmd/stack-traces/table", michael@0: sizes.mStackTraceTable, michael@0: "Memory used by DMD's stack trace table."); michael@0: michael@0: REPORT("explicit/dmd/block-table", michael@0: sizes.mBlockTable, michael@0: "Memory used by DMD's live block table."); michael@0: michael@0: #undef REPORT michael@0: michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: NS_IMPL_ISUPPORTS(DMDReporter, nsIMemoryReporter) michael@0: michael@0: } // namespace dmd michael@0: } // namespace mozilla michael@0: michael@0: #endif // MOZ_DMD michael@0: michael@0: /** michael@0: ** nsMemoryReporterManager implementation michael@0: **/ michael@0: michael@0: NS_IMPL_ISUPPORTS(nsMemoryReporterManager, nsIMemoryReporterManager) michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::Init() michael@0: { michael@0: #if defined(HAVE_JEMALLOC_STATS) && defined(XP_LINUX) michael@0: if (!jemalloc_stats) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: #endif michael@0: michael@0: #ifdef HAVE_JEMALLOC_STATS michael@0: RegisterStrongReporter(new JemallocHeapReporter()); michael@0: #endif michael@0: michael@0: #ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS michael@0: RegisterStrongReporter(new VsizeReporter()); michael@0: RegisterStrongReporter(new ResidentReporter()); michael@0: #endif michael@0: michael@0: #ifdef HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER michael@0: RegisterStrongReporter(new VsizeMaxContiguousReporter()); michael@0: #endif michael@0: michael@0: #ifdef HAVE_RESIDENT_UNIQUE_REPORTER michael@0: RegisterStrongReporter(new ResidentUniqueReporter()); michael@0: #endif michael@0: michael@0: #ifdef HAVE_PAGE_FAULT_REPORTERS michael@0: RegisterStrongReporter(new PageFaultsSoftReporter()); michael@0: RegisterStrongReporter(new PageFaultsHardReporter()); michael@0: #endif michael@0: michael@0: #ifdef HAVE_PRIVATE_REPORTER michael@0: RegisterStrongReporter(new PrivateReporter()); michael@0: #endif michael@0: michael@0: RegisterStrongReporter(new AtomTablesReporter()); michael@0: michael@0: #ifdef MOZ_DMD michael@0: RegisterStrongReporter(new mozilla::dmd::DMDReporter()); michael@0: #endif michael@0: michael@0: #ifdef XP_UNIX michael@0: nsMemoryInfoDumper::Initialize(); michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsMemoryReporterManager::nsMemoryReporterManager() michael@0: : mMutex("nsMemoryReporterManager::mMutex") michael@0: , mIsRegistrationBlocked(false) michael@0: , mStrongReporters(new StrongReportersTable()) michael@0: , mWeakReporters(new WeakReportersTable()) michael@0: , mSavedStrongReporters(nullptr) michael@0: , mSavedWeakReporters(nullptr) michael@0: , mNumChildProcesses(0) michael@0: , mNextGeneration(1) michael@0: , mGetReportsState(nullptr) michael@0: { michael@0: } michael@0: michael@0: nsMemoryReporterManager::~nsMemoryReporterManager() michael@0: { michael@0: delete mStrongReporters; michael@0: delete mWeakReporters; michael@0: NS_ASSERTION(!mSavedStrongReporters, "failed to restore strong reporters"); michael@0: NS_ASSERTION(!mSavedWeakReporters, "failed to restore weak reporters"); michael@0: } michael@0: michael@0: //#define DEBUG_CHILD_PROCESS_MEMORY_REPORTING 1 michael@0: michael@0: #ifdef DEBUG_CHILD_PROCESS_MEMORY_REPORTING michael@0: #define MEMORY_REPORTING_LOG(format, ...) \ michael@0: fprintf(stderr, "++++ MEMORY REPORTING: " format, ##__VA_ARGS__); michael@0: #else michael@0: #define MEMORY_REPORTING_LOG(...) michael@0: #endif michael@0: michael@0: void michael@0: nsMemoryReporterManager::IncrementNumChildProcesses() michael@0: { michael@0: if (!NS_IsMainThread()) { michael@0: MOZ_CRASH(); michael@0: } michael@0: mNumChildProcesses++; michael@0: MEMORY_REPORTING_LOG("IncrementNumChildProcesses --> %d\n", michael@0: mNumChildProcesses); michael@0: } michael@0: michael@0: void michael@0: nsMemoryReporterManager::DecrementNumChildProcesses() michael@0: { michael@0: if (!NS_IsMainThread()) { michael@0: MOZ_CRASH(); michael@0: } michael@0: MOZ_ASSERT(mNumChildProcesses > 0); michael@0: mNumChildProcesses--; michael@0: MEMORY_REPORTING_LOG("DecrementNumChildProcesses --> %d\n", michael@0: mNumChildProcesses); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::GetReports( michael@0: nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aHandleReportData, michael@0: nsIFinishReportingCallback* aFinishReporting, michael@0: nsISupports* aFinishReportingData) michael@0: { michael@0: return GetReportsExtended(aHandleReport, aHandleReportData, michael@0: aFinishReporting, aFinishReportingData, michael@0: /* minimize = */ false, michael@0: /* DMDident = */ nsString()); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::GetReportsExtended( michael@0: nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aHandleReportData, michael@0: nsIFinishReportingCallback* aFinishReporting, michael@0: nsISupports* aFinishReportingData, michael@0: bool aMinimize, michael@0: const nsAString& aDMDDumpIdent) michael@0: { michael@0: nsresult rv; michael@0: michael@0: // Memory reporters are not necessarily threadsafe, so this function must michael@0: // be called from the main thread. michael@0: if (!NS_IsMainThread()) { michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: uint32_t generation = mNextGeneration++; michael@0: michael@0: if (mGetReportsState) { michael@0: // A request is in flight. Don't start another one. And don't report michael@0: // an error; just ignore it, and let the in-flight request finish. michael@0: MEMORY_REPORTING_LOG("GetReports (gen=%u, s->gen=%u): abort\n", michael@0: generation, mGetReportsState->mGeneration); michael@0: return NS_OK; michael@0: } michael@0: michael@0: MEMORY_REPORTING_LOG("GetReports (gen=%u, %d child(ren) present)\n", michael@0: generation, mNumChildProcesses); michael@0: michael@0: if (mNumChildProcesses > 0) { michael@0: // Request memory reports from child processes. We do this *before* michael@0: // collecting reports for this process so each process can collect michael@0: // reports in parallel. michael@0: nsCOMPtr obs = michael@0: do_GetService("@mozilla.org/observer-service;1"); michael@0: NS_ENSURE_STATE(obs); michael@0: michael@0: nsPrintfCString genStr("generation=%x minimize=%d DMDident=", michael@0: generation, aMinimize ? 1 : 0); michael@0: nsAutoString msg = NS_ConvertUTF8toUTF16(genStr); michael@0: msg += aDMDDumpIdent; michael@0: michael@0: obs->NotifyObservers(nullptr, "child-memory-reporter-request", michael@0: msg.get()); michael@0: michael@0: nsCOMPtr timer = do_CreateInstance(NS_TIMER_CONTRACTID); michael@0: NS_ENSURE_TRUE(timer, NS_ERROR_FAILURE); michael@0: rv = timer->InitWithFuncCallback(TimeoutCallback, michael@0: this, kTimeoutLengthMS, michael@0: nsITimer::TYPE_ONE_SHOT); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mGetReportsState = new GetReportsState(generation, michael@0: timer, michael@0: mNumChildProcesses, michael@0: aHandleReport, michael@0: aHandleReportData, michael@0: aFinishReporting, michael@0: aFinishReportingData, michael@0: aDMDDumpIdent); michael@0: } else { michael@0: mGetReportsState = new GetReportsState(generation, michael@0: nullptr, michael@0: /* mNumChildProcesses = */ 0, michael@0: aHandleReport, michael@0: aHandleReportData, michael@0: aFinishReporting, michael@0: aFinishReportingData, michael@0: aDMDDumpIdent); michael@0: } michael@0: michael@0: if (aMinimize) { michael@0: rv = MinimizeMemoryUsage(NS_NewRunnableMethod(this, &nsMemoryReporterManager::StartGettingReports)); michael@0: } else { michael@0: rv = StartGettingReports(); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsMemoryReporterManager::StartGettingReports() michael@0: { michael@0: GetReportsState *s = mGetReportsState; michael@0: michael@0: // Get reports for this process. michael@0: GetReportsForThisProcessExtended(s->mHandleReport, s->mHandleReportData, michael@0: s->mDMDDumpIdent); michael@0: s->mParentDone = true; michael@0: michael@0: // If there are no remaining child processes, we can finish up immediately. michael@0: return (s->mNumChildProcessesCompleted >= s->mNumChildProcesses) michael@0: ? FinishReporting() michael@0: : NS_OK; michael@0: } michael@0: michael@0: typedef nsCOMArray MemoryReporterArray; michael@0: michael@0: static PLDHashOperator michael@0: StrongEnumerator(nsRefPtrHashKey* aElem, void* aData) michael@0: { michael@0: MemoryReporterArray *allReporters = static_cast(aData); michael@0: allReporters->AppendElement(aElem->GetKey()); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: WeakEnumerator(nsPtrHashKey* aElem, void* aData) michael@0: { michael@0: MemoryReporterArray *allReporters = static_cast(aData); michael@0: allReporters->AppendElement(aElem->GetKey()); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::GetReportsForThisProcess( michael@0: nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aHandleReportData) michael@0: { michael@0: return GetReportsForThisProcessExtended(aHandleReport, michael@0: aHandleReportData, michael@0: nsString()); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::GetReportsForThisProcessExtended( michael@0: nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aHandleReportData, michael@0: const nsAString& aDMDDumpIdent) michael@0: { michael@0: // Memory reporters are not necessarily threadsafe, so this function must michael@0: // be called from the main thread. michael@0: if (!NS_IsMainThread()) { michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: #ifdef MOZ_DMD michael@0: if (!aDMDDumpIdent.IsEmpty()) { michael@0: // Clear DMD's reportedness state before running the memory michael@0: // reporters, to avoid spurious twice-reported warnings. michael@0: dmd::ClearReports(); michael@0: } michael@0: #endif michael@0: michael@0: MemoryReporterArray allReporters; michael@0: { michael@0: mozilla::MutexAutoLock autoLock(mMutex); michael@0: mStrongReporters->EnumerateEntries(StrongEnumerator, &allReporters); michael@0: mWeakReporters->EnumerateEntries(WeakEnumerator, &allReporters); michael@0: } michael@0: for (uint32_t i = 0; i < allReporters.Length(); i++) { michael@0: allReporters[i]->CollectReports(aHandleReport, aHandleReportData); michael@0: } michael@0: michael@0: #ifdef MOZ_DMD michael@0: if (!aDMDDumpIdent.IsEmpty()) { michael@0: return nsMemoryInfoDumper::DumpDMD(aDMDDumpIdent); michael@0: } michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // This function has no return value. If something goes wrong, there's no michael@0: // clear place to report the problem to, but that's ok -- we will end up michael@0: // hitting the timeout and executing TimeoutCallback(). michael@0: void michael@0: nsMemoryReporterManager::HandleChildReports( michael@0: const uint32_t& aGeneration, michael@0: const InfallibleTArray& aChildReports) michael@0: { michael@0: // Memory reporting only happens on the main thread. michael@0: if (!NS_IsMainThread()) { michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: GetReportsState* s = mGetReportsState; michael@0: michael@0: if (!s) { michael@0: // If we reach here, either: michael@0: // michael@0: // - A child process reported back too late, and no subsequent request michael@0: // is in flight. michael@0: // michael@0: // - (Unlikely) A "child-memory-reporter-request" notification was michael@0: // triggered from somewhere other than GetReports(), causing child michael@0: // processes to report back when the nsMemoryReporterManager wasn't michael@0: // expecting it. michael@0: // michael@0: // Either way, there's nothing to be done. Just ignore it. michael@0: MEMORY_REPORTING_LOG( michael@0: "HandleChildReports: no request in flight (aGen=%u)\n", michael@0: aGeneration); michael@0: return; michael@0: } michael@0: michael@0: if (aGeneration != s->mGeneration) { michael@0: // If we reach here, a child process must have reported back, too late, michael@0: // while a subsequent (higher-numbered) request is in flight. Again, michael@0: // ignore it. michael@0: MOZ_ASSERT(aGeneration < s->mGeneration); michael@0: MEMORY_REPORTING_LOG( michael@0: "HandleChildReports: gen mismatch (aGen=%u, s->gen=%u)\n", michael@0: aGeneration, s->mGeneration); michael@0: return; michael@0: } michael@0: michael@0: // Process the reports from the child process. michael@0: for (uint32_t i = 0; i < aChildReports.Length(); i++) { michael@0: const dom::MemoryReport& r = aChildReports[i]; michael@0: michael@0: // Child reports should have a non-empty process. michael@0: MOZ_ASSERT(!r.process().IsEmpty()); michael@0: michael@0: // If the call fails, ignore and continue. michael@0: s->mHandleReport->Callback(r.process(), r.path(), r.kind(), michael@0: r.units(), r.amount(), r.desc(), michael@0: s->mHandleReportData); michael@0: } michael@0: michael@0: // If all the child processes have reported, we can cancel the timer and michael@0: // finish up. Otherwise, just return. michael@0: michael@0: s->mNumChildProcessesCompleted++; michael@0: MEMORY_REPORTING_LOG("HandleChildReports (aGen=%u): completed child %d\n", michael@0: aGeneration, s->mNumChildProcessesCompleted); michael@0: michael@0: if (s->mNumChildProcessesCompleted >= s->mNumChildProcesses && michael@0: s->mParentDone) { michael@0: s->mTimer->Cancel(); michael@0: FinishReporting(); michael@0: } michael@0: } michael@0: michael@0: /* static */ void michael@0: nsMemoryReporterManager::TimeoutCallback(nsITimer* aTimer, void* aData) michael@0: { michael@0: nsMemoryReporterManager* mgr = static_cast(aData); michael@0: GetReportsState* s = mgr->mGetReportsState; michael@0: michael@0: MOZ_ASSERT(mgr->mGetReportsState); michael@0: MEMORY_REPORTING_LOG("TimeoutCallback (s->gen=%u)\n", michael@0: s->mGeneration); michael@0: michael@0: // We don't bother sending any kind of cancellation message to the child michael@0: // processes that haven't reported back. michael@0: michael@0: if (s->mParentDone) { michael@0: mgr->FinishReporting(); michael@0: } else { michael@0: // This is unlikely -- the timeout expired during MinimizeMemoryUsage. michael@0: MEMORY_REPORTING_LOG("Timeout expired before parent report started!"); michael@0: // Let the parent continue with its report, but ensure that michael@0: // StartGettingReports gives up immediately after that. michael@0: s->mNumChildProcesses = s->mNumChildProcessesCompleted; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsMemoryReporterManager::FinishReporting() michael@0: { michael@0: // Memory reporting only happens on the main thread. michael@0: if (!NS_IsMainThread()) { michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: MOZ_ASSERT(mGetReportsState); michael@0: MEMORY_REPORTING_LOG("FinishReporting (s->gen=%u)\n", michael@0: mGetReportsState->mGeneration); michael@0: michael@0: // Call this before deleting |mGetReportsState|. That way, if michael@0: // |mFinishReportData| calls GetReports(), it will silently abort, as michael@0: // required. michael@0: nsresult rv = mGetReportsState->mFinishReporting->Callback( michael@0: mGetReportsState->mFinishReportingData); michael@0: michael@0: delete mGetReportsState; michael@0: mGetReportsState = nullptr; michael@0: return rv; michael@0: } michael@0: michael@0: static void michael@0: CrashIfRefcountIsZero(nsISupports* aObj) michael@0: { michael@0: // This will probably crash if the object's refcount is 0. michael@0: uint32_t refcnt = NS_ADDREF(aObj); michael@0: if (refcnt <= 1) { michael@0: MOZ_CRASH("CrashIfRefcountIsZero: refcount is zero"); michael@0: } michael@0: NS_RELEASE(aObj); michael@0: } michael@0: michael@0: nsresult michael@0: nsMemoryReporterManager::RegisterReporterHelper( michael@0: nsIMemoryReporter* aReporter, bool aForce, bool aStrong) michael@0: { michael@0: // This method is thread-safe. michael@0: mozilla::MutexAutoLock autoLock(mMutex); michael@0: michael@0: if (mIsRegistrationBlocked && !aForce) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (mStrongReporters->Contains(aReporter) || michael@0: mWeakReporters->Contains(aReporter)) michael@0: { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // If |aStrong| is true, |aReporter| may have a refcnt of 0, so we take michael@0: // a kung fu death grip before calling PutEntry. Otherwise, if PutEntry michael@0: // addref'ed and released |aReporter| before finally addref'ing it for michael@0: // good, it would free aReporter! The kung fu death grip could itself be michael@0: // problematic if PutEntry didn't addref |aReporter| (because then when the michael@0: // death grip goes out of scope, we would delete the reporter). In debug michael@0: // mode, we check that this doesn't happen. michael@0: // michael@0: // If |aStrong| is false, we require that |aReporter| have a non-zero michael@0: // refcnt. michael@0: // michael@0: if (aStrong) { michael@0: nsCOMPtr kungFuDeathGrip = aReporter; michael@0: mStrongReporters->PutEntry(aReporter); michael@0: CrashIfRefcountIsZero(aReporter); michael@0: } else { michael@0: CrashIfRefcountIsZero(aReporter); michael@0: nsCOMPtr jsComponent = do_QueryInterface(aReporter); michael@0: if (jsComponent) { michael@0: // We cannot allow non-native reporters (WrappedJS), since we'll be michael@0: // holding onto a raw pointer, which would point to the wrapper, michael@0: // and that wrapper is likely to go away as soon as this register michael@0: // call finishes. This would then lead to subsequent crashes in michael@0: // CollectReports(). michael@0: return NS_ERROR_XPC_BAD_CONVERT_JS; michael@0: } michael@0: mWeakReporters->PutEntry(aReporter); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::RegisterStrongReporter(nsIMemoryReporter* aReporter) michael@0: { michael@0: return RegisterReporterHelper(aReporter, /* force = */ false, michael@0: /* strong = */ true); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::RegisterWeakReporter(nsIMemoryReporter* aReporter) michael@0: { michael@0: return RegisterReporterHelper(aReporter, /* force = */ false, michael@0: /* strong = */ false); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::RegisterStrongReporterEvenIfBlocked( michael@0: nsIMemoryReporter* aReporter) michael@0: { michael@0: return RegisterReporterHelper(aReporter, /* force = */ true, michael@0: /* strong = */ true); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::UnregisterStrongReporter(nsIMemoryReporter* aReporter) michael@0: { michael@0: // This method is thread-safe. michael@0: mozilla::MutexAutoLock autoLock(mMutex); michael@0: michael@0: MOZ_ASSERT(!mWeakReporters->Contains(aReporter)); michael@0: michael@0: if (mStrongReporters->Contains(aReporter)) { michael@0: mStrongReporters->RemoveEntry(aReporter); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::UnregisterWeakReporter(nsIMemoryReporter* aReporter) michael@0: { michael@0: // This method is thread-safe. michael@0: mozilla::MutexAutoLock autoLock(mMutex); michael@0: michael@0: MOZ_ASSERT(!mStrongReporters->Contains(aReporter)); michael@0: michael@0: if (mWeakReporters->Contains(aReporter)) { michael@0: mWeakReporters->RemoveEntry(aReporter); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::BlockRegistrationAndHideExistingReporters() michael@0: { michael@0: // This method is thread-safe. michael@0: mozilla::MutexAutoLock autoLock(mMutex); michael@0: if (mIsRegistrationBlocked) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: mIsRegistrationBlocked = true; michael@0: michael@0: // Hide the existing reporters, saving them for later restoration. michael@0: MOZ_ASSERT(!mSavedStrongReporters); michael@0: MOZ_ASSERT(!mSavedWeakReporters); michael@0: mSavedStrongReporters = mStrongReporters; michael@0: mSavedWeakReporters = mWeakReporters; michael@0: mStrongReporters = new StrongReportersTable(); michael@0: mWeakReporters = new WeakReportersTable(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::UnblockRegistrationAndRestoreOriginalReporters() michael@0: { michael@0: // This method is thread-safe. michael@0: mozilla::MutexAutoLock autoLock(mMutex); michael@0: if (!mIsRegistrationBlocked) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Banish the current reporters, and restore the hidden ones. michael@0: delete mStrongReporters; michael@0: delete mWeakReporters; michael@0: mStrongReporters = mSavedStrongReporters; michael@0: mWeakReporters = mSavedWeakReporters; michael@0: mSavedStrongReporters = nullptr; michael@0: mSavedWeakReporters = nullptr; michael@0: michael@0: mIsRegistrationBlocked = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // This is just a wrapper for int64_t that implements nsISupports, so it can be michael@0: // passed to nsIMemoryReporter::CollectReports. michael@0: class Int64Wrapper MOZ_FINAL : public nsISupports michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: Int64Wrapper() : mValue(0) { } michael@0: int64_t mValue; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS0(Int64Wrapper) michael@0: michael@0: class ExplicitCallback MOZ_FINAL : public nsIHandleReportCallback michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_IMETHOD Callback(const nsACString& aProcess, const nsACString& aPath, michael@0: int32_t aKind, int32_t aUnits, int64_t aAmount, michael@0: const nsACString& aDescription, michael@0: nsISupports* aWrappedExplicit) michael@0: { michael@0: // Using the "heap-allocated" reporter here instead of michael@0: // nsMemoryReporterManager.heapAllocated goes against the usual michael@0: // pattern. But it's for a good reason: in tests, we can easily michael@0: // create artificial (i.e. deterministic) reporters -- which allows us michael@0: // to precisely test nsMemoryReporterManager.explicit -- but we can't michael@0: // do that for distinguished amounts. michael@0: if (aPath.Equals("heap-allocated") || michael@0: (aKind == nsIMemoryReporter::KIND_NONHEAP && michael@0: PromiseFlatCString(aPath).Find("explicit") == 0)) michael@0: { michael@0: Int64Wrapper* wrappedInt64 = static_cast(aWrappedExplicit); michael@0: wrappedInt64->mValue += aAmount; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(ExplicitCallback, nsIHandleReportCallback) michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::GetExplicit(int64_t* aAmount) michael@0: { michael@0: if (NS_WARN_IF(!aAmount)) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: *aAmount = 0; michael@0: #ifndef HAVE_JEMALLOC_STATS michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: #else michael@0: michael@0: // For each reporter we call CollectReports and filter out the michael@0: // non-explicit, non-NONHEAP measurements (except for "heap-allocated"). michael@0: // That's lots of wasted work, and we used to have a GetExplicitNonHeap() michael@0: // method which did this more efficiently, but it ended up being more michael@0: // trouble than it was worth. michael@0: michael@0: nsRefPtr handleReport = new ExplicitCallback(); michael@0: nsRefPtr wrappedExplicitSize = new Int64Wrapper(); michael@0: michael@0: GetReportsForThisProcess(handleReport, wrappedExplicitSize); michael@0: michael@0: *aAmount = wrappedExplicitSize->mValue; michael@0: michael@0: return NS_OK; michael@0: #endif // HAVE_JEMALLOC_STATS michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::GetVsize(int64_t* aVsize) michael@0: { michael@0: #ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS michael@0: return VsizeDistinguishedAmount(aVsize); michael@0: #else michael@0: *aVsize = 0; michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: #endif michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::GetVsizeMaxContiguous(int64_t* aAmount) michael@0: { michael@0: #ifdef HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER michael@0: return VsizeMaxContiguousDistinguishedAmount(aAmount); michael@0: #else michael@0: *aAmount = 0; michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: #endif michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::GetResident(int64_t* aAmount) michael@0: { michael@0: #ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS michael@0: return ResidentDistinguishedAmount(aAmount); michael@0: #else michael@0: *aAmount = 0; michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: #endif michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::GetResidentFast(int64_t* aAmount) michael@0: { michael@0: #ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS michael@0: return ResidentFastDistinguishedAmount(aAmount); michael@0: #else michael@0: *aAmount = 0; michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: #endif michael@0: } michael@0: michael@0: /*static*/ michael@0: int64_t nsMemoryReporterManager::ResidentFast() michael@0: { michael@0: #ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS michael@0: int64_t amount; michael@0: ResidentFastDistinguishedAmount(&amount); michael@0: return amount; michael@0: #else michael@0: return 0; michael@0: #endif michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::GetHeapAllocated(int64_t* aAmount) michael@0: { michael@0: #ifdef HAVE_JEMALLOC_STATS michael@0: jemalloc_stats_t stats; michael@0: jemalloc_stats(&stats); michael@0: *aAmount = stats.allocated; michael@0: return NS_OK; michael@0: #else michael@0: *aAmount = 0; michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: #endif michael@0: } michael@0: michael@0: // This has UNITS_PERCENTAGE, so it is multiplied by 100x. michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::GetHeapOverheadRatio(int64_t* aAmount) michael@0: { michael@0: #ifdef HAVE_JEMALLOC_STATS michael@0: jemalloc_stats_t stats; michael@0: jemalloc_stats(&stats); michael@0: *aAmount = HeapOverheadRatio(&stats); michael@0: return NS_OK; michael@0: #else michael@0: *aAmount = 0; michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: #endif michael@0: } michael@0: michael@0: static nsresult michael@0: GetInfallibleAmount(InfallibleAmountFn aAmountFn, int64_t* aAmount) michael@0: { michael@0: if (aAmountFn) { michael@0: *aAmount = aAmountFn(); michael@0: return NS_OK; michael@0: } michael@0: *aAmount = 0; michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::GetJSMainRuntimeGCHeap(int64_t* aAmount) michael@0: { michael@0: return GetInfallibleAmount(mAmountFns.mJSMainRuntimeGCHeap, aAmount); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::GetJSMainRuntimeTemporaryPeak(int64_t* aAmount) michael@0: { michael@0: return GetInfallibleAmount(mAmountFns.mJSMainRuntimeTemporaryPeak, aAmount); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::GetJSMainRuntimeCompartmentsSystem(int64_t* aAmount) michael@0: { michael@0: return GetInfallibleAmount(mAmountFns.mJSMainRuntimeCompartmentsSystem, michael@0: aAmount); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::GetJSMainRuntimeCompartmentsUser(int64_t* aAmount) michael@0: { michael@0: return GetInfallibleAmount(mAmountFns.mJSMainRuntimeCompartmentsUser, michael@0: aAmount); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::GetImagesContentUsedUncompressed(int64_t* aAmount) michael@0: { michael@0: return GetInfallibleAmount(mAmountFns.mImagesContentUsedUncompressed, michael@0: aAmount); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::GetStorageSQLite(int64_t* aAmount) michael@0: { michael@0: return GetInfallibleAmount(mAmountFns.mStorageSQLite, aAmount); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::GetLowMemoryEventsVirtual(int64_t* aAmount) michael@0: { michael@0: return GetInfallibleAmount(mAmountFns.mLowMemoryEventsVirtual, aAmount); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::GetLowMemoryEventsPhysical(int64_t* aAmount) michael@0: { michael@0: return GetInfallibleAmount(mAmountFns.mLowMemoryEventsPhysical, aAmount); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::GetGhostWindows(int64_t* aAmount) michael@0: { michael@0: return GetInfallibleAmount(mAmountFns.mGhostWindows, aAmount); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::GetPageFaultsHard(int64_t* aAmount) michael@0: { michael@0: #ifdef HAVE_PAGE_FAULT_REPORTERS michael@0: return PageFaultsHardDistinguishedAmount(aAmount); michael@0: #else michael@0: *aAmount = 0; michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: #endif michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::GetHasMozMallocUsableSize(bool* aHas) michael@0: { michael@0: void* p = malloc(16); michael@0: if (!p) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: size_t usable = moz_malloc_usable_size(p); michael@0: free(p); michael@0: *aHas = !!(usable > 0); michael@0: return NS_OK; michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: /** michael@0: * This runnable lets us implement michael@0: * nsIMemoryReporterManager::MinimizeMemoryUsage(). We fire a heap-minimize michael@0: * notification, spin the event loop, and repeat this process a few times. michael@0: * michael@0: * When this sequence finishes, we invoke the callback function passed to the michael@0: * runnable's constructor. michael@0: */ michael@0: class MinimizeMemoryUsageRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: MinimizeMemoryUsageRunnable(nsIRunnable* aCallback) michael@0: : mCallback(aCallback) michael@0: , mRemainingIters(sNumIters) michael@0: {} michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: nsCOMPtr os = services::GetObserverService(); michael@0: if (!os) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (mRemainingIters == 0) { michael@0: os->NotifyObservers(nullptr, "after-minimize-memory-usage", michael@0: MOZ_UTF16("MinimizeMemoryUsageRunnable")); michael@0: if (mCallback) { michael@0: mCallback->Run(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: os->NotifyObservers(nullptr, "memory-pressure", michael@0: MOZ_UTF16("heap-minimize")); michael@0: mRemainingIters--; michael@0: NS_DispatchToMainThread(this); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: // Send sNumIters heap-minimize notifications, spinning the event michael@0: // loop after each notification (see bug 610166 comment 12 for an michael@0: // explanation), because one notification doesn't cut it. michael@0: static const uint32_t sNumIters = 3; michael@0: michael@0: nsCOMPtr mCallback; michael@0: uint32_t mRemainingIters; michael@0: }; michael@0: michael@0: } // anonymous namespace michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::MinimizeMemoryUsage(nsIRunnable* aCallback) michael@0: { michael@0: nsRefPtr runnable = michael@0: new MinimizeMemoryUsageRunnable(aCallback); michael@0: michael@0: return NS_DispatchToMainThread(runnable); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMemoryReporterManager::SizeOfTab(nsIDOMWindow* aTopWindow, michael@0: int64_t* aJSObjectsSize, michael@0: int64_t* aJSStringsSize, michael@0: int64_t* aJSOtherSize, michael@0: int64_t* aDomSize, michael@0: int64_t* aStyleSize, michael@0: int64_t* aOtherSize, michael@0: int64_t* aTotalSize, michael@0: double* aJSMilliseconds, michael@0: double* aNonJSMilliseconds) michael@0: { michael@0: nsCOMPtr global = do_QueryInterface(aTopWindow); michael@0: nsCOMPtr piWindow = do_QueryInterface(aTopWindow); michael@0: if (NS_WARN_IF(!global) || NS_WARN_IF(!piWindow)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: TimeStamp t1 = TimeStamp::Now(); michael@0: michael@0: // Measure JS memory consumption (and possibly some non-JS consumption, via michael@0: // |jsPrivateSize|). michael@0: size_t jsObjectsSize, jsStringsSize, jsPrivateSize, jsOtherSize; michael@0: nsresult rv = mSizeOfTabFns.mJS(global->GetGlobalJSObject(), michael@0: &jsObjectsSize, &jsStringsSize, michael@0: &jsPrivateSize, &jsOtherSize); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: return rv; michael@0: } michael@0: michael@0: TimeStamp t2 = TimeStamp::Now(); michael@0: michael@0: // Measure non-JS memory consumption. michael@0: size_t domSize, styleSize, otherSize; michael@0: mSizeOfTabFns.mNonJS(piWindow, &domSize, &styleSize, &otherSize); michael@0: michael@0: TimeStamp t3 = TimeStamp::Now(); michael@0: michael@0: *aTotalSize = 0; michael@0: #define DO(aN, n) { *aN = (n); *aTotalSize += (n); } michael@0: DO(aJSObjectsSize, jsObjectsSize); michael@0: DO(aJSStringsSize, jsStringsSize); michael@0: DO(aJSOtherSize, jsOtherSize); michael@0: DO(aDomSize, jsPrivateSize + domSize); michael@0: DO(aStyleSize, styleSize); michael@0: DO(aOtherSize, otherSize); michael@0: #undef DO michael@0: michael@0: *aJSMilliseconds = (t2 - t1).ToMilliseconds(); michael@0: *aNonJSMilliseconds = (t3 - t2).ToMilliseconds(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: namespace mozilla { michael@0: michael@0: nsresult michael@0: RegisterStrongMemoryReporter(nsIMemoryReporter* aReporter) michael@0: { michael@0: // Hold a strong reference to the argument to make sure it gets released if michael@0: // we return early below. michael@0: nsCOMPtr reporter = aReporter; michael@0: michael@0: nsCOMPtr mgr = michael@0: do_GetService("@mozilla.org/memory-reporter-manager;1"); michael@0: if (!mgr) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: return mgr->RegisterStrongReporter(reporter); michael@0: } michael@0: michael@0: nsresult michael@0: RegisterWeakMemoryReporter(nsIMemoryReporter* aReporter) michael@0: { michael@0: nsCOMPtr mgr = michael@0: do_GetService("@mozilla.org/memory-reporter-manager;1"); michael@0: if (!mgr) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: return mgr->RegisterWeakReporter(aReporter); michael@0: } michael@0: michael@0: nsresult michael@0: UnregisterWeakMemoryReporter(nsIMemoryReporter* aReporter) michael@0: { michael@0: nsCOMPtr mgr = michael@0: do_GetService("@mozilla.org/memory-reporter-manager;1"); michael@0: if (!mgr) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: return mgr->UnregisterWeakReporter(aReporter); michael@0: } michael@0: michael@0: #define GET_MEMORY_REPORTER_MANAGER(mgr) \ michael@0: nsRefPtr mgr = \ michael@0: nsMemoryReporterManager::GetOrCreate(); \ michael@0: if (!mgr) { \ michael@0: return NS_ERROR_FAILURE; \ michael@0: } michael@0: michael@0: // Macro for generating functions that register distinguished amount functions michael@0: // with the memory reporter manager. michael@0: #define DEFINE_REGISTER_DISTINGUISHED_AMOUNT(kind, name) \ michael@0: nsresult \ michael@0: Register##name##DistinguishedAmount(kind##AmountFn aAmountFn) \ michael@0: { \ michael@0: GET_MEMORY_REPORTER_MANAGER(mgr) \ michael@0: mgr->mAmountFns.m##name = aAmountFn; \ michael@0: return NS_OK; \ michael@0: } michael@0: michael@0: #define DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT(name) \ michael@0: nsresult \ michael@0: Unregister##name##DistinguishedAmount() \ michael@0: { \ michael@0: GET_MEMORY_REPORTER_MANAGER(mgr) \ michael@0: mgr->mAmountFns.m##name = nullptr; \ michael@0: return NS_OK; \ michael@0: } michael@0: michael@0: DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeGCHeap) michael@0: DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeTemporaryPeak) michael@0: DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeCompartmentsSystem) michael@0: DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeCompartmentsUser) michael@0: michael@0: DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, ImagesContentUsedUncompressed) michael@0: michael@0: DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, StorageSQLite) michael@0: DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT(StorageSQLite) michael@0: michael@0: DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsVirtual) michael@0: DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsPhysical) michael@0: michael@0: DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, GhostWindows) michael@0: michael@0: #undef DEFINE_REGISTER_DISTINGUISHED_AMOUNT michael@0: #undef DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT michael@0: michael@0: #define DEFINE_REGISTER_SIZE_OF_TAB(name) \ michael@0: nsresult \ michael@0: Register##name##SizeOfTab(name##SizeOfTabFn aSizeOfTabFn) \ michael@0: { \ michael@0: GET_MEMORY_REPORTER_MANAGER(mgr) \ michael@0: mgr->mSizeOfTabFns.m##name = aSizeOfTabFn; \ michael@0: return NS_OK; \ michael@0: } michael@0: michael@0: DEFINE_REGISTER_SIZE_OF_TAB(JS); michael@0: DEFINE_REGISTER_SIZE_OF_TAB(NonJS); michael@0: michael@0: #undef DEFINE_REGISTER_SIZE_OF_TAB michael@0: michael@0: #undef GET_MEMORY_REPORTER_MANAGER michael@0: michael@0: } michael@0: michael@0: #if defined(MOZ_DMD) michael@0: michael@0: namespace mozilla { michael@0: namespace dmd { michael@0: michael@0: class DoNothingCallback MOZ_FINAL : public nsIHandleReportCallback michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_IMETHOD Callback(const nsACString& aProcess, const nsACString& aPath, michael@0: int32_t aKind, int32_t aUnits, int64_t aAmount, michael@0: const nsACString& aDescription, michael@0: nsISupports* aData) michael@0: { michael@0: // Do nothing; the reporter has already reported to DMD. michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(DoNothingCallback, nsIHandleReportCallback) michael@0: michael@0: void michael@0: RunReportersForThisProcess() michael@0: { michael@0: nsCOMPtr mgr = michael@0: do_GetService("@mozilla.org/memory-reporter-manager;1"); michael@0: michael@0: nsRefPtr doNothing = new DoNothingCallback(); michael@0: michael@0: mgr->GetReportsForThisProcess(doNothing, nullptr); michael@0: } michael@0: michael@0: } // namespace dmd michael@0: } // namespace mozilla michael@0: michael@0: #endif // defined(MOZ_DMD) michael@0: