/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsAtomTable.h" #include "nsAutoPtr.h" #include "nsCOMPtr.h" #include "nsCOMArray.h" #include "nsPrintfCString.h" #include "nsServiceManagerUtils.h" #include "nsMemoryReporterManager.h" #include "nsITimer.h" #include "nsThreadUtils.h" #include "nsPIDOMWindow.h" #include "nsIObserverService.h" #include "nsIGlobalObject.h" #include "nsIXPConnect.h" #if defined(XP_UNIX) || defined(MOZ_DMD) #include "nsMemoryInfoDumper.h" #endif #include "mozilla/Attributes.h" #include "mozilla/PodOperations.h" #include "mozilla/Preferences.h" #include "mozilla/Services.h" #include "mozilla/Telemetry.h" #include "mozilla/UniquePtrExtensions.h" #include "mozilla/dom/PMemoryReportRequestParent.h" // for dom::MemoryReport #include "mozilla/dom/ContentParent.h" #include "mozilla/ipc/FileDescriptorUtils.h" #ifdef XP_WIN #include <process.h> #ifndef getpid #define getpid _getpid #endif #else #include <unistd.h> #endif using namespace mozilla; #if defined(MOZ_MEMORY) # define HAVE_JEMALLOC_STATS 1 # include "mozmemory.h" #endif // MOZ_MEMORY #if defined(XP_LINUX) #include <malloc.h> #include <string.h> #include <stdlib.h> static MOZ_MUST_USE nsresult GetProcSelfStatmField(int aField, int64_t* aN) { // There are more than two fields, but we're only interested in the first // two. static const int MAX_FIELD = 2; size_t fields[MAX_FIELD]; MOZ_ASSERT(aField < MAX_FIELD, "bad field number"); FILE* f = fopen("/proc/self/statm", "r"); if (f) { int nread = fscanf(f, "%zu %zu", &fields[0], &fields[1]); fclose(f); if (nread == MAX_FIELD) { *aN = fields[aField] * getpagesize(); return NS_OK; } } return NS_ERROR_FAILURE; } static MOZ_MUST_USE nsresult GetProcSelfSmapsPrivate(int64_t* aN) { // You might be tempted to calculate USS by subtracting the "shared" value // from the "resident" value in /proc/<pid>/statm. But at least on Linux, // statm's "shared" value actually counts pages backed by files, which has // little to do with whether the pages are actually shared. /proc/self/smaps // on the other hand appears to give us the correct information. FILE* f = fopen("/proc/self/smaps", "r"); if (NS_WARN_IF(!f)) { return NS_ERROR_UNEXPECTED; } // We carry over the end of the buffer to the beginning to make sure we only // interpret complete lines. static const uint32_t carryOver = 32; static const uint32_t readSize = 4096; int64_t amount = 0; char buffer[carryOver + readSize + 1]; // Fill the beginning of the buffer with spaces, as a sentinel for the first // iteration. memset(buffer, ' ', carryOver); for (;;) { size_t bytes = fread(buffer + carryOver, sizeof(*buffer), readSize, f); char* end = buffer + bytes; char* ptr = buffer; end[carryOver] = '\0'; // We are looking for lines like "Private_{Clean,Dirty}: 4 kB". while ((ptr = strstr(ptr, "Private"))) { if (ptr >= end) { break; } ptr += sizeof("Private_Xxxxx:"); amount += strtol(ptr, nullptr, 10); } if (bytes < readSize) { // We do not expect any match within the end of the buffer. MOZ_ASSERT(!strstr(end, "Private")); break; } // Carry the end of the buffer over to the beginning. memcpy(buffer, end, carryOver); } fclose(f); // Convert from kB to bytes. *aN = amount * 1024; return NS_OK; } #define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 static MOZ_MUST_USE nsresult VsizeDistinguishedAmount(int64_t* aN) { return GetProcSelfStatmField(0, aN); } static MOZ_MUST_USE nsresult ResidentDistinguishedAmount(int64_t* aN) { return GetProcSelfStatmField(1, aN); } static MOZ_MUST_USE nsresult ResidentFastDistinguishedAmount(int64_t* aN) { return ResidentDistinguishedAmount(aN); } #define HAVE_RESIDENT_UNIQUE_REPORTER 1 static MOZ_MUST_USE nsresult ResidentUniqueDistinguishedAmount(int64_t* aN) { return GetProcSelfSmapsPrivate(aN); } #ifdef HAVE_MALLINFO #define HAVE_SYSTEM_HEAP_REPORTER 1 static MOZ_MUST_USE nsresult SystemHeapSize(int64_t* aSizeOut) { struct mallinfo info = mallinfo(); // The documentation in the glibc man page makes it sound like |uordblks| // would suffice, but that only gets the small allocations that are put in // the brk heap. We need |hblkhd| as well to get the larger allocations // that are mmapped. // // The fields in |struct mallinfo| are all |int|, <sigh>, so it is // unreliable if memory usage gets high. However, the system heap size on // Linux should usually be zero (so long as jemalloc is enabled) so that // shouldn't be a problem. Nonetheless, cast the |int|s to |size_t| before // adding them to provide a small amount of extra overflow protection. *aSizeOut = size_t(info.hblkhd) + size_t(info.uordblks); return NS_OK; } #endif #elif defined(__DragonFly__) || defined(__FreeBSD__) \ || defined(__NetBSD__) || defined(__OpenBSD__) \ || defined(__FreeBSD_kernel__) #include <sys/param.h> #include <sys/sysctl.h> #if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) #include <sys/user.h> #endif #include <unistd.h> #if defined(__NetBSD__) #undef KERN_PROC #define KERN_PROC KERN_PROC2 #define KINFO_PROC struct kinfo_proc2 #else #define KINFO_PROC struct kinfo_proc #endif #if defined(__DragonFly__) #define KP_SIZE(kp) (kp.kp_vm_map_size) #define KP_RSS(kp) (kp.kp_vm_rssize * getpagesize()) #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) #define KP_SIZE(kp) (kp.ki_size) #define KP_RSS(kp) (kp.ki_rssize * getpagesize()) #elif defined(__NetBSD__) #define KP_SIZE(kp) (kp.p_vm_msize * getpagesize()) #define KP_RSS(kp) (kp.p_vm_rssize * getpagesize()) #elif defined(__OpenBSD__) #define KP_SIZE(kp) ((kp.p_vm_dsize + kp.p_vm_ssize \ + kp.p_vm_tsize) * getpagesize()) #define KP_RSS(kp) (kp.p_vm_rssize * getpagesize()) #endif static MOZ_MUST_USE nsresult GetKinfoProcSelf(KINFO_PROC* aProc) { int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid(), #if defined(__NetBSD__) || defined(__OpenBSD__) sizeof(KINFO_PROC), 1, #endif }; u_int miblen = sizeof(mib) / sizeof(mib[0]); size_t size = sizeof(KINFO_PROC); if (sysctl(mib, miblen, aProc, &size, nullptr, 0)) { return NS_ERROR_FAILURE; } return NS_OK; } #define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 static MOZ_MUST_USE nsresult VsizeDistinguishedAmount(int64_t* aN) { KINFO_PROC proc; nsresult rv = GetKinfoProcSelf(&proc); if (NS_SUCCEEDED(rv)) { *aN = KP_SIZE(proc); } return rv; } static MOZ_MUST_USE nsresult ResidentDistinguishedAmount(int64_t* aN) { KINFO_PROC proc; nsresult rv = GetKinfoProcSelf(&proc); if (NS_SUCCEEDED(rv)) { *aN = KP_RSS(proc); } return rv; } static MOZ_MUST_USE nsresult ResidentFastDistinguishedAmount(int64_t* aN) { return ResidentDistinguishedAmount(aN); } #ifdef __FreeBSD__ #include <libutil.h> #include <algorithm> static MOZ_MUST_USE nsresult GetKinfoVmentrySelf(int64_t* aPrss, uint64_t* aMaxreg) { int cnt; struct kinfo_vmentry* vmmap; struct kinfo_vmentry* kve; if (!(vmmap = kinfo_getvmmap(getpid(), &cnt))) { return NS_ERROR_FAILURE; } if (aPrss) { *aPrss = 0; } if (aMaxreg) { *aMaxreg = 0; } for (int i = 0; i < cnt; i++) { kve = &vmmap[i]; if (aPrss) { *aPrss += kve->kve_private_resident; } if (aMaxreg) { *aMaxreg = std::max(*aMaxreg, kve->kve_end - kve->kve_start); } } free(vmmap); return NS_OK; } #define HAVE_PRIVATE_REPORTER 1 static MOZ_MUST_USE nsresult PrivateDistinguishedAmount(int64_t* aN) { int64_t priv; nsresult rv = GetKinfoVmentrySelf(&priv, nullptr); NS_ENSURE_SUCCESS(rv, rv); *aN = priv * getpagesize(); return NS_OK; } #define HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER 1 static MOZ_MUST_USE nsresult VsizeMaxContiguousDistinguishedAmount(int64_t* aN) { uint64_t biggestRegion; nsresult rv = GetKinfoVmentrySelf(nullptr, &biggestRegion); if (NS_SUCCEEDED(rv)) { *aN = biggestRegion; } return NS_OK; } #endif // FreeBSD #elif defined(SOLARIS) #include <procfs.h> #include <fcntl.h> #include <unistd.h> static void XMappingIter(int64_t& aVsize, int64_t& aResident) { aVsize = -1; aResident = -1; int mapfd = open("/proc/self/xmap", O_RDONLY); struct stat st; prxmap_t* prmapp = nullptr; if (mapfd >= 0) { if (!fstat(mapfd, &st)) { int nmap = st.st_size / sizeof(prxmap_t); while (1) { // stat(2) on /proc/<pid>/xmap returns an incorrect value, // prior to the release of Solaris 11. // Here is a workaround for it. nmap *= 2; prmapp = (prxmap_t*)malloc((nmap + 1) * sizeof(prxmap_t)); if (!prmapp) { // out of memory break; } int n = pread(mapfd, prmapp, (nmap + 1) * sizeof(prxmap_t), 0); if (n < 0) { break; } if (nmap >= n / sizeof(prxmap_t)) { aVsize = 0; aResident = 0; for (int i = 0; i < n / sizeof(prxmap_t); i++) { aVsize += prmapp[i].pr_size; aResident += prmapp[i].pr_rss * prmapp[i].pr_pagesize; } break; } free(prmapp); } free(prmapp); } close(mapfd); } } #define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 static MOZ_MUST_USE nsresult VsizeDistinguishedAmount(int64_t* aN) { int64_t vsize, resident; XMappingIter(vsize, resident); if (vsize == -1) { return NS_ERROR_FAILURE; } *aN = vsize; return NS_OK; } static MOZ_MUST_USE nsresult ResidentDistinguishedAmount(int64_t* aN) { int64_t vsize, resident; XMappingIter(vsize, resident); if (resident == -1) { return NS_ERROR_FAILURE; } *aN = resident; return NS_OK; } static MOZ_MUST_USE nsresult ResidentFastDistinguishedAmount(int64_t* aN) { return ResidentDistinguishedAmount(aN); } #elif defined(XP_MACOSX) #include <mach/mach_init.h> #include <mach/mach_vm.h> #include <mach/shared_region.h> #include <mach/task.h> #include <sys/sysctl.h> static MOZ_MUST_USE bool GetTaskBasicInfo(struct task_basic_info* aTi) { mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT; kern_return_t kr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)aTi, &count); return kr == KERN_SUCCESS; } // The VSIZE figure on Mac includes huge amounts of shared memory and is always // absurdly high, eg. 2GB+ even at start-up. But both 'top' and 'ps' report // it, so we might as well too. #define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 static MOZ_MUST_USE nsresult VsizeDistinguishedAmount(int64_t* aN) { task_basic_info ti; if (!GetTaskBasicInfo(&ti)) { return NS_ERROR_FAILURE; } *aN = ti.virtual_size; return NS_OK; } // If we're using jemalloc on Mac, we need to instruct jemalloc to purge the // pages it has madvise(MADV_FREE)'d before we read our RSS in order to get // an accurate result. The OS will take away MADV_FREE'd pages when there's // memory pressure, so ideally, they shouldn't count against our RSS. // // Purging these pages can take a long time for some users (see bug 789975), // so we provide the option to get the RSS without purging first. static MOZ_MUST_USE nsresult ResidentDistinguishedAmountHelper(int64_t* aN, bool aDoPurge) { #ifdef HAVE_JEMALLOC_STATS #ifndef MOZ_JEMALLOC4 if (aDoPurge) { Telemetry::AutoTimer<Telemetry::MEMORY_FREE_PURGED_PAGES_MS> timer; jemalloc_purge_freed_pages(); } #endif #endif task_basic_info ti; if (!GetTaskBasicInfo(&ti)) { return NS_ERROR_FAILURE; } *aN = ti.resident_size; return NS_OK; } static MOZ_MUST_USE nsresult ResidentFastDistinguishedAmount(int64_t* aN) { return ResidentDistinguishedAmountHelper(aN, /* doPurge = */ false); } static MOZ_MUST_USE nsresult ResidentDistinguishedAmount(int64_t* aN) { return ResidentDistinguishedAmountHelper(aN, /* doPurge = */ true); } #define HAVE_RESIDENT_UNIQUE_REPORTER 1 static bool InSharedRegion(mach_vm_address_t aAddr, cpu_type_t aType) { mach_vm_address_t base; mach_vm_address_t size; switch (aType) { case CPU_TYPE_ARM: base = SHARED_REGION_BASE_ARM; size = SHARED_REGION_SIZE_ARM; break; case CPU_TYPE_I386: base = SHARED_REGION_BASE_I386; size = SHARED_REGION_SIZE_I386; break; case CPU_TYPE_X86_64: base = SHARED_REGION_BASE_X86_64; size = SHARED_REGION_SIZE_X86_64; break; default: return false; } return base <= aAddr && aAddr < (base + size); } static MOZ_MUST_USE nsresult ResidentUniqueDistinguishedAmount(int64_t* aN) { if (!aN) { return NS_ERROR_FAILURE; } cpu_type_t cpu_type; size_t len = sizeof(cpu_type); if (sysctlbyname("sysctl.proc_cputype", &cpu_type, &len, NULL, 0) != 0) { return NS_ERROR_FAILURE; } // Roughly based on libtop_update_vm_regions in // http://www.opensource.apple.com/source/top/top-100.1.2/libtop.c size_t privatePages = 0; mach_vm_size_t size = 0; for (mach_vm_address_t addr = MACH_VM_MIN_ADDRESS; ; addr += size) { vm_region_top_info_data_t info; mach_msg_type_number_t infoCount = VM_REGION_TOP_INFO_COUNT; mach_port_t objectName; kern_return_t kr = mach_vm_region(mach_task_self(), &addr, &size, VM_REGION_TOP_INFO, reinterpret_cast<vm_region_info_t>(&info), &infoCount, &objectName); if (kr == KERN_INVALID_ADDRESS) { // Done iterating VM regions. break; } else if (kr != KERN_SUCCESS) { return NS_ERROR_FAILURE; } if (InSharedRegion(addr, cpu_type) && info.share_mode != SM_PRIVATE) { continue; } switch (info.share_mode) { case SM_LARGE_PAGE: // NB: Large pages are not shareable and always resident. case SM_PRIVATE: privatePages += info.private_pages_resident; privatePages += info.shared_pages_resident; break; case SM_COW: privatePages += info.private_pages_resident; if (info.ref_count == 1) { // Treat copy-on-write pages as private if they only have one reference. privatePages += info.shared_pages_resident; } break; case SM_SHARED: default: break; } } vm_size_t pageSize; if (host_page_size(mach_host_self(), &pageSize) != KERN_SUCCESS) { pageSize = PAGE_SIZE; } *aN = privatePages * pageSize; return NS_OK; } #elif defined(XP_WIN) #include <windows.h> #include <psapi.h> #include <algorithm> #define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 static MOZ_MUST_USE nsresult VsizeDistinguishedAmount(int64_t* aN) { MEMORYSTATUSEX s; s.dwLength = sizeof(s); if (!GlobalMemoryStatusEx(&s)) { return NS_ERROR_FAILURE; } *aN = s.ullTotalVirtual - s.ullAvailVirtual; return NS_OK; } static MOZ_MUST_USE nsresult ResidentDistinguishedAmount(int64_t* aN) { PROCESS_MEMORY_COUNTERS pmc; pmc.cb = sizeof(PROCESS_MEMORY_COUNTERS); if (!GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) { return NS_ERROR_FAILURE; } *aN = pmc.WorkingSetSize; return NS_OK; } static MOZ_MUST_USE nsresult ResidentFastDistinguishedAmount(int64_t* aN) { return ResidentDistinguishedAmount(aN); } #define HAVE_RESIDENT_UNIQUE_REPORTER 1 static MOZ_MUST_USE nsresult ResidentUniqueDistinguishedAmount(int64_t* aN) { // Determine how many entries we need. PSAPI_WORKING_SET_INFORMATION tmp; DWORD tmpSize = sizeof(tmp); memset(&tmp, 0, tmpSize); HANDLE proc = GetCurrentProcess(); QueryWorkingSet(proc, &tmp, tmpSize); // Fudge the size in case new entries are added between calls. size_t entries = tmp.NumberOfEntries * 2; if (!entries) { return NS_ERROR_FAILURE; } DWORD infoArraySize = tmpSize + (entries * sizeof(PSAPI_WORKING_SET_BLOCK)); UniqueFreePtr<PSAPI_WORKING_SET_INFORMATION> infoArray( static_cast<PSAPI_WORKING_SET_INFORMATION*>(malloc(infoArraySize))); if (!infoArray) { return NS_ERROR_FAILURE; } if (!QueryWorkingSet(proc, infoArray.get(), infoArraySize)) { return NS_ERROR_FAILURE; } entries = static_cast<size_t>(infoArray->NumberOfEntries); size_t privatePages = 0; for (size_t i = 0; i < entries; i++) { // Count shared pages that only one process is using as private. if (!infoArray->WorkingSetInfo[i].Shared || infoArray->WorkingSetInfo[i].ShareCount <= 1) { privatePages++; } } SYSTEM_INFO si; GetSystemInfo(&si); *aN = privatePages * si.dwPageSize; return NS_OK; } #define HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER 1 static MOZ_MUST_USE nsresult VsizeMaxContiguousDistinguishedAmount(int64_t* aN) { SIZE_T biggestRegion = 0; MEMORY_BASIC_INFORMATION vmemInfo = { 0 }; for (size_t currentAddress = 0; ; ) { if (!VirtualQuery((LPCVOID)currentAddress, &vmemInfo, sizeof(vmemInfo))) { // Something went wrong, just return whatever we've got already. break; } if (vmemInfo.State == MEM_FREE) { biggestRegion = std::max(biggestRegion, vmemInfo.RegionSize); } SIZE_T lastAddress = currentAddress; currentAddress += vmemInfo.RegionSize; // If we overflow, we've examined all of the address space. if (currentAddress < lastAddress) { break; } } *aN = biggestRegion; return NS_OK; } #define HAVE_PRIVATE_REPORTER 1 static MOZ_MUST_USE nsresult PrivateDistinguishedAmount(int64_t* aN) { PROCESS_MEMORY_COUNTERS_EX pmcex; pmcex.cb = sizeof(PROCESS_MEMORY_COUNTERS_EX); if (!GetProcessMemoryInfo(GetCurrentProcess(), (PPROCESS_MEMORY_COUNTERS) &pmcex, sizeof(pmcex))) { return NS_ERROR_FAILURE; } *aN = pmcex.PrivateUsage; return NS_OK; } #define HAVE_SYSTEM_HEAP_REPORTER 1 // Windows can have multiple separate heaps. During testing there were multiple // heaps present but the non-default ones had sizes no more than a few 10s of // KiBs. So we combine their sizes into a single measurement. static MOZ_MUST_USE nsresult SystemHeapSize(int64_t* aSizeOut) { // Get the number of heaps. DWORD nHeaps = GetProcessHeaps(0, nullptr); NS_ENSURE_TRUE(nHeaps != 0, NS_ERROR_FAILURE); // Get handles to all heaps, checking that the number of heaps hasn't // changed in the meantime. UniquePtr<HANDLE[]> heaps(new HANDLE[nHeaps]); DWORD nHeaps2 = GetProcessHeaps(nHeaps, heaps.get()); NS_ENSURE_TRUE(nHeaps2 != 0 && nHeaps2 == nHeaps, NS_ERROR_FAILURE); // Lock and iterate over each heap to get its size. int64_t heapsSize = 0; for (DWORD i = 0; i < nHeaps; i++) { HANDLE heap = heaps[i]; NS_ENSURE_TRUE(HeapLock(heap), NS_ERROR_FAILURE); int64_t heapSize = 0; PROCESS_HEAP_ENTRY entry; entry.lpData = nullptr; while (HeapWalk(heap, &entry)) { // We don't count entry.cbOverhead, because we just want to measure the // space available to the program. if (entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) { heapSize += entry.cbData; } } // Check this result only after unlocking the heap, so that we don't leave // the heap locked if there was an error. DWORD lastError = GetLastError(); // I have no idea how things would proceed if unlocking this heap failed... NS_ENSURE_TRUE(HeapUnlock(heap), NS_ERROR_FAILURE); NS_ENSURE_TRUE(lastError == ERROR_NO_MORE_ITEMS, NS_ERROR_FAILURE); heapsSize += heapSize; } *aSizeOut = heapsSize; return NS_OK; } struct SegmentKind { DWORD mState; DWORD mType; DWORD mProtect; int mIsStack; }; struct SegmentEntry : public PLDHashEntryHdr { static PLDHashNumber HashKey(const void* aKey) { auto kind = static_cast<const SegmentKind*>(aKey); return mozilla::HashGeneric(kind->mState, kind->mType, kind->mProtect, kind->mIsStack); } static bool MatchEntry(const PLDHashEntryHdr* aEntry, const void* aKey) { auto kind = static_cast<const SegmentKind*>(aKey); auto entry = static_cast<const SegmentEntry*>(aEntry); return kind->mState == entry->mKind.mState && kind->mType == entry->mKind.mType && kind->mProtect == entry->mKind.mProtect && kind->mIsStack == entry->mKind.mIsStack; } static void InitEntry(PLDHashEntryHdr* aEntry, const void* aKey) { auto kind = static_cast<const SegmentKind*>(aKey); auto entry = static_cast<SegmentEntry*>(aEntry); entry->mKind = *kind; entry->mCount = 0; entry->mSize = 0; } static const PLDHashTableOps Ops; SegmentKind mKind; // The segment kind. uint32_t mCount; // The number of segments of this kind. size_t mSize; // The combined size of segments of this kind. }; /* static */ const PLDHashTableOps SegmentEntry::Ops = { SegmentEntry::HashKey, SegmentEntry::MatchEntry, PLDHashTable::MoveEntryStub, PLDHashTable::ClearEntryStub, SegmentEntry::InitEntry }; class WindowsAddressSpaceReporter final : public nsIMemoryReporter { ~WindowsAddressSpaceReporter() {} public: NS_DECL_ISUPPORTS NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize) override { // First iterate over all the segments and record how many of each kind // there were and their aggregate sizes. We use a hash table for this // because there are a couple of dozen different kinds possible. PLDHashTable table(&SegmentEntry::Ops, sizeof(SegmentEntry)); MEMORY_BASIC_INFORMATION info = { 0 }; bool isPrevSegStackGuard = false; for (size_t currentAddress = 0; ; ) { if (!VirtualQuery((LPCVOID)currentAddress, &info, sizeof(info))) { // Something went wrong, just return whatever we've got already. break; } size_t size = info.RegionSize; // Note that |type| and |protect| are ignored in some cases. DWORD state = info.State; DWORD type = (state == MEM_RESERVE || state == MEM_COMMIT) ? info.Type : 0; DWORD protect = (state == MEM_COMMIT) ? info.Protect : 0; bool isStack = isPrevSegStackGuard && state == MEM_COMMIT && type == MEM_PRIVATE && protect == PAGE_READWRITE; SegmentKind kind = { state, type, protect, isStack ? 1 : 0 }; auto entry = static_cast<SegmentEntry*>(table.Add(&kind, mozilla::fallible)); if (entry) { entry->mCount += 1; entry->mSize += size; } isPrevSegStackGuard = info.State == MEM_COMMIT && info.Type == MEM_PRIVATE && info.Protect == (PAGE_READWRITE|PAGE_GUARD); size_t lastAddress = currentAddress; currentAddress += size; // If we overflow, we've examined all of the address space. if (currentAddress < lastAddress) { break; } } // Then iterate over the hash table and report the details for each segment // kind. for (auto iter = table.Iter(); !iter.Done(); iter.Next()) { // For each range of pages, we consider one or more of its State, Type // and Protect values. These are documented at // https://msdn.microsoft.com/en-us/library/windows/desktop/aa366775%28v=vs.85%29.aspx // (for State and Type) and // https://msdn.microsoft.com/en-us/library/windows/desktop/aa366786%28v=vs.85%29.aspx // (for Protect). // // Not all State values have accompanying Type and Protection values. bool doType = false; bool doProtect = false; auto entry = static_cast<const SegmentEntry*>(iter.Get()); nsCString path("address-space"); switch (entry->mKind.mState) { case MEM_FREE: path.AppendLiteral("/free"); break; case MEM_RESERVE: path.AppendLiteral("/reserved"); doType = true; break; case MEM_COMMIT: path.AppendLiteral("/commit"); doType = true; doProtect = true; break; default: // Should be impossible, but handle it just in case. path.AppendLiteral("/???"); break; } if (doType) { switch (entry->mKind.mType) { case MEM_IMAGE: path.AppendLiteral("/image"); break; case MEM_MAPPED: path.AppendLiteral("/mapped"); break; case MEM_PRIVATE: path.AppendLiteral("/private"); break; default: // Should be impossible, but handle it just in case. path.AppendLiteral("/???"); break; } } if (doProtect) { DWORD protect = entry->mKind.mProtect; // Basic attributes. Exactly one of these should be set. if (protect & PAGE_EXECUTE) { path.AppendLiteral("/execute"); } if (protect & PAGE_EXECUTE_READ) { path.AppendLiteral("/execute-read"); } if (protect & PAGE_EXECUTE_READWRITE) { path.AppendLiteral("/execute-readwrite"); } if (protect & PAGE_EXECUTE_WRITECOPY) { path.AppendLiteral("/execute-writecopy"); } if (protect & PAGE_NOACCESS) { path.AppendLiteral("/noaccess"); } if (protect & PAGE_READONLY) { path.AppendLiteral("/readonly"); } if (protect & PAGE_READWRITE) { path.AppendLiteral("/readwrite"); } if (protect & PAGE_WRITECOPY) { path.AppendLiteral("/writecopy"); } // Modifiers. At most one of these should be set. if (protect & PAGE_GUARD) { path.AppendLiteral("+guard"); } if (protect & PAGE_NOCACHE) { path.AppendLiteral("+nocache"); } if (protect & PAGE_WRITECOMBINE) { path.AppendLiteral("+writecombine"); } // Annotate likely stack segments, too. if (entry->mKind.mIsStack) { path.AppendLiteral("+stack"); } } // Append the segment count. path.AppendPrintf("(segments=%u)", entry->mCount); aHandleReport->Callback( EmptyCString(), path, KIND_OTHER, UNITS_BYTES, entry->mSize, NS_LITERAL_CSTRING("From MEMORY_BASIC_INFORMATION."), aData); } return NS_OK; } }; NS_IMPL_ISUPPORTS(WindowsAddressSpaceReporter, nsIMemoryReporter) #endif // XP_<PLATFORM> #ifdef HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER class VsizeMaxContiguousReporter final : public nsIMemoryReporter { ~VsizeMaxContiguousReporter() {} public: NS_DECL_ISUPPORTS NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize) override { int64_t amount; if (NS_SUCCEEDED(VsizeMaxContiguousDistinguishedAmount(&amount))) { MOZ_COLLECT_REPORT( "vsize-max-contiguous", KIND_OTHER, UNITS_BYTES, amount, "Size of the maximum contiguous block of available virtual memory."); } return NS_OK; } }; NS_IMPL_ISUPPORTS(VsizeMaxContiguousReporter, nsIMemoryReporter) #endif #ifdef HAVE_PRIVATE_REPORTER class PrivateReporter final : public nsIMemoryReporter { ~PrivateReporter() {} public: NS_DECL_ISUPPORTS NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize) override { int64_t amount; if (NS_SUCCEEDED(PrivateDistinguishedAmount(&amount))) { MOZ_COLLECT_REPORT( "private", KIND_OTHER, UNITS_BYTES, amount, "Memory that cannot be shared with other processes, including memory that is " "committed and marked MEM_PRIVATE, data that is not mapped, and executable " "pages that have been written to."); } return NS_OK; } }; NS_IMPL_ISUPPORTS(PrivateReporter, nsIMemoryReporter) #endif #ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS class VsizeReporter final : public nsIMemoryReporter { ~VsizeReporter() {} public: NS_DECL_ISUPPORTS NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize) override { int64_t amount; if (NS_SUCCEEDED(VsizeDistinguishedAmount(&amount))) { MOZ_COLLECT_REPORT( "vsize", KIND_OTHER, UNITS_BYTES, amount, "Memory mapped by the process, including code and data segments, the heap, " "thread stacks, memory explicitly mapped by the process via mmap and similar " "operations, and memory shared with other processes. This is the vsize figure " "as reported by 'top' and 'ps'. This figure is of limited use on Mac, where " "processes share huge amounts of memory with one another. But even on other " "operating systems, 'resident' is a much better measure of the memory " "resources used by the process."); } return NS_OK; } }; NS_IMPL_ISUPPORTS(VsizeReporter, nsIMemoryReporter) class ResidentReporter final : public nsIMemoryReporter { ~ResidentReporter() {} public: NS_DECL_ISUPPORTS NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize) override { int64_t amount; if (NS_SUCCEEDED(ResidentDistinguishedAmount(&amount))) { MOZ_COLLECT_REPORT( "resident", KIND_OTHER, UNITS_BYTES, amount, "Memory mapped by the process that is present in physical memory, also known " "as the resident set size (RSS). This is the best single figure to use when " "considering the memory resources used by the process, but it depends both on " "other processes being run and details of the OS kernel and so is best used " "for comparing the memory usage of a single process at different points in " "time."); } return NS_OK; } }; NS_IMPL_ISUPPORTS(ResidentReporter, nsIMemoryReporter) #endif // HAVE_VSIZE_AND_RESIDENT_REPORTERS #ifdef HAVE_RESIDENT_UNIQUE_REPORTER class ResidentUniqueReporter final : public nsIMemoryReporter { ~ResidentUniqueReporter() {} public: NS_DECL_ISUPPORTS NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize) override { int64_t amount = 0; if (NS_SUCCEEDED(ResidentUniqueDistinguishedAmount(&amount))) { MOZ_COLLECT_REPORT( "resident-unique", KIND_OTHER, UNITS_BYTES, amount, "Memory mapped by the process that is present in physical memory and not " "shared with any other processes. This is also known as the process's unique " "set size (USS). This is the amount of RAM we'd expect to be freed if we " "closed this process."); } return NS_OK; } }; NS_IMPL_ISUPPORTS(ResidentUniqueReporter, nsIMemoryReporter) #endif // HAVE_RESIDENT_UNIQUE_REPORTER #ifdef HAVE_SYSTEM_HEAP_REPORTER class SystemHeapReporter final : public nsIMemoryReporter { ~SystemHeapReporter() {} public: NS_DECL_ISUPPORTS NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize) override { int64_t amount; if (NS_SUCCEEDED(SystemHeapSize(&amount))) { MOZ_COLLECT_REPORT( "system-heap-allocated", KIND_OTHER, UNITS_BYTES, amount, "Memory used by the system allocator that is currently allocated to the " "application. This is distinct from the jemalloc heap that Firefox uses for " "most or all of its heap allocations. Ideally this number is zero, but " "on some platforms we cannot force every heap allocation through jemalloc."); } return NS_OK; } }; NS_IMPL_ISUPPORTS(SystemHeapReporter, nsIMemoryReporter) #endif // HAVE_SYSTEM_HEAP_REPORTER #ifdef XP_UNIX #include <sys/resource.h> #define HAVE_RESIDENT_PEAK_REPORTER 1 static MOZ_MUST_USE nsresult ResidentPeakDistinguishedAmount(int64_t* aN) { struct rusage usage; if (0 == getrusage(RUSAGE_SELF, &usage)) { // The units for ru_maxrrs: // - Mac: bytes // - Solaris: pages? But some sources it actually always returns 0, so // check for that // - Linux, {Net/Open/Free}BSD, DragonFly: KiB #ifdef XP_MACOSX *aN = usage.ru_maxrss; #elif defined(SOLARIS) *aN = usage.ru_maxrss * getpagesize(); #else *aN = usage.ru_maxrss * 1024; #endif if (*aN > 0) { return NS_OK; } } return NS_ERROR_FAILURE; } class ResidentPeakReporter final : public nsIMemoryReporter { ~ResidentPeakReporter() {} public: NS_DECL_ISUPPORTS NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize) override { int64_t amount = 0; if (NS_SUCCEEDED(ResidentPeakDistinguishedAmount(&amount))) { MOZ_COLLECT_REPORT( "resident-peak", KIND_OTHER, UNITS_BYTES, amount, "The peak 'resident' value for the lifetime of the process."); } return NS_OK; } }; NS_IMPL_ISUPPORTS(ResidentPeakReporter, nsIMemoryReporter) #define HAVE_PAGE_FAULT_REPORTERS 1 class PageFaultsSoftReporter final : public nsIMemoryReporter { ~PageFaultsSoftReporter() {} public: NS_DECL_ISUPPORTS NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize) override { struct rusage usage; int err = getrusage(RUSAGE_SELF, &usage); if (err == 0) { int64_t amount = usage.ru_minflt; MOZ_COLLECT_REPORT( "page-faults-soft", KIND_OTHER, UNITS_COUNT_CUMULATIVE, amount, "The number of soft page faults (also known as 'minor page faults') that " "have occurred since the process started. A soft page fault occurs when the " "process tries to access a page which is present in physical memory but is " "not mapped into the process's address space. For instance, a process might " "observe soft page faults when it loads a shared library which is already " "present in physical memory. A process may experience many thousands of soft " "page faults even when the machine has plenty of available physical memory, " "and because the OS services a soft page fault without accessing the disk, " "they impact performance much less than hard page faults."); } return NS_OK; } }; NS_IMPL_ISUPPORTS(PageFaultsSoftReporter, nsIMemoryReporter) static MOZ_MUST_USE nsresult PageFaultsHardDistinguishedAmount(int64_t* aAmount) { struct rusage usage; int err = getrusage(RUSAGE_SELF, &usage); if (err != 0) { return NS_ERROR_FAILURE; } *aAmount = usage.ru_majflt; return NS_OK; } class PageFaultsHardReporter final : public nsIMemoryReporter { ~PageFaultsHardReporter() {} public: NS_DECL_ISUPPORTS NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize) override { int64_t amount = 0; if (NS_SUCCEEDED(PageFaultsHardDistinguishedAmount(&amount))) { MOZ_COLLECT_REPORT( "page-faults-hard", KIND_OTHER, UNITS_COUNT_CUMULATIVE, amount, "The number of hard page faults (also known as 'major page faults') that have " "occurred since the process started. A hard page fault occurs when a process " "tries to access a page which is not present in physical memory. The " "operating system must access the disk in order to fulfill a hard page fault. " "When memory is plentiful, you should see very few hard page faults. But if " "the process tries to use more memory than your machine has available, you " "may see many thousands of hard page faults. Because accessing the disk is up " "to a million times slower than accessing RAM, the program may run very " "slowly when it is experiencing more than 100 or so hard page faults a " "second."); } return NS_OK; } }; NS_IMPL_ISUPPORTS(PageFaultsHardReporter, nsIMemoryReporter) #endif // XP_UNIX /** ** memory reporter implementation for jemalloc and OSX malloc, ** to obtain info on total memory in use (that we know about, ** at least -- on OSX, there are sometimes other zones in use). **/ #ifdef HAVE_JEMALLOC_STATS static size_t HeapOverhead(jemalloc_stats_t* aStats) { return aStats->waste + aStats->bookkeeping + aStats->page_cache + aStats->bin_unused; } // This has UNITS_PERCENTAGE, so it is multiplied by 100x *again* on top of the // 100x for the percentage. static int64_t HeapOverheadFraction(jemalloc_stats_t* aStats) { size_t heapOverhead = HeapOverhead(aStats); size_t heapCommitted = aStats->allocated + heapOverhead; return int64_t(10000 * (heapOverhead / (double)heapCommitted)); } class JemallocHeapReporter final : public nsIMemoryReporter { ~JemallocHeapReporter() {} public: NS_DECL_ISUPPORTS NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize) override { jemalloc_stats_t stats; jemalloc_stats(&stats); MOZ_COLLECT_REPORT( "heap-committed/allocated", KIND_OTHER, UNITS_BYTES, stats.allocated, "Memory mapped by the heap allocator that is currently allocated to the " "application. This may exceed the amount of memory requested by the " "application because the allocator regularly rounds up request sizes. (The " "exact amount requested is not recorded.)"); MOZ_COLLECT_REPORT( "heap-allocated", KIND_OTHER, UNITS_BYTES, stats.allocated, "The same as 'heap-committed/allocated'."); // We mark this and the other heap-overhead reporters as KIND_NONHEAP // because KIND_HEAP memory means "counted in heap-allocated", which // this is not. MOZ_COLLECT_REPORT( "explicit/heap-overhead/bin-unused", KIND_NONHEAP, UNITS_BYTES, stats.bin_unused, "Unused bytes due to fragmentation in the bins used for 'small' (<= 2 KiB) " "allocations. These bytes will be used if additional allocations occur."); if (stats.waste > 0) { MOZ_COLLECT_REPORT( "explicit/heap-overhead/waste", KIND_NONHEAP, UNITS_BYTES, stats.waste, "Committed bytes which do not correspond to an active allocation and which the " "allocator is not intentionally keeping alive (i.e., not " "'explicit/heap-overhead/{bookkeeping,page-cache,bin-unused}')."); } MOZ_COLLECT_REPORT( "explicit/heap-overhead/bookkeeping", KIND_NONHEAP, UNITS_BYTES, stats.bookkeeping, "Committed bytes which the heap allocator uses for internal data structures."); MOZ_COLLECT_REPORT( "explicit/heap-overhead/page-cache", KIND_NONHEAP, UNITS_BYTES, stats.page_cache, "Memory which the allocator could return to the operating system, but hasn't. " "The allocator keeps this memory around as an optimization, so it doesn't " "have to ask the OS the next time it needs to fulfill a request. This value " "is typically not larger than a few megabytes."); MOZ_COLLECT_REPORT( "heap-committed/overhead", KIND_OTHER, UNITS_BYTES, HeapOverhead(&stats), "The sum of 'explicit/heap-overhead/*'."); MOZ_COLLECT_REPORT( "heap-mapped", KIND_OTHER, UNITS_BYTES, stats.mapped, "Amount of memory currently mapped. Includes memory that is uncommitted, i.e. " "neither in physical memory nor paged to disk."); MOZ_COLLECT_REPORT( "heap-chunksize", KIND_OTHER, UNITS_BYTES, stats.chunksize, "Size of chunks."); return NS_OK; } }; NS_IMPL_ISUPPORTS(JemallocHeapReporter, nsIMemoryReporter) #endif // HAVE_JEMALLOC_STATS // Why is this here? At first glance, you'd think it could be defined and // registered with nsMemoryReporterManager entirely within nsAtomTable.cpp. // However, the obvious time to register it is when the table is initialized, // and that happens before XPCOM components are initialized, which means the // RegisterStrongMemoryReporter call fails. So instead we do it here. class AtomTablesReporter final : public nsIMemoryReporter { MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf) ~AtomTablesReporter() {} public: NS_DECL_ISUPPORTS NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize) override { size_t Main, Static; NS_SizeOfAtomTablesIncludingThis(MallocSizeOf, &Main, &Static); MOZ_COLLECT_REPORT( "explicit/atom-tables/main", KIND_HEAP, UNITS_BYTES, Main, "Memory used by the main atoms table."); MOZ_COLLECT_REPORT( "explicit/atom-tables/static", KIND_HEAP, UNITS_BYTES, Static, "Memory used by the static atoms table."); return NS_OK; } }; NS_IMPL_ISUPPORTS(AtomTablesReporter, nsIMemoryReporter) #ifdef DEBUG // Ideally, this would be implemented in BlockingResourceBase.cpp. // However, this ends up breaking the linking step of various unit tests due // to adding a new dependency to libdmd for a commonly used feature (mutexes) // in DMD builds. So instead we do it here. class DeadlockDetectorReporter final : public nsIMemoryReporter { MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf) ~DeadlockDetectorReporter() {} public: NS_DECL_ISUPPORTS NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize) override { MOZ_COLLECT_REPORT( "explicit/deadlock-detector", KIND_HEAP, UNITS_BYTES, BlockingResourceBase::SizeOfDeadlockDetector(MallocSizeOf), "Memory used by the deadlock detector."); return NS_OK; } }; NS_IMPL_ISUPPORTS(DeadlockDetectorReporter, nsIMemoryReporter) #endif #ifdef MOZ_DMD namespace mozilla { namespace dmd { class DMDReporter final : public nsIMemoryReporter { public: NS_DECL_ISUPPORTS NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize) override { dmd::Sizes sizes; dmd::SizeOf(&sizes); MOZ_COLLECT_REPORT( "explicit/dmd/stack-traces/used", KIND_HEAP, UNITS_BYTES, sizes.mStackTracesUsed, "Memory used by stack traces which correspond to at least " "one heap block DMD is tracking."); MOZ_COLLECT_REPORT( "explicit/dmd/stack-traces/unused", KIND_HEAP, UNITS_BYTES, sizes.mStackTracesUnused, "Memory used by stack traces which don't correspond to any heap " "blocks DMD is currently tracking."); MOZ_COLLECT_REPORT( "explicit/dmd/stack-traces/table", KIND_HEAP, UNITS_BYTES, sizes.mStackTraceTable, "Memory used by DMD's stack trace table."); MOZ_COLLECT_REPORT( "explicit/dmd/live-block-table", KIND_HEAP, UNITS_BYTES, sizes.mLiveBlockTable, "Memory used by DMD's live block table."); MOZ_COLLECT_REPORT( "explicit/dmd/dead-block-list", KIND_HEAP, UNITS_BYTES, sizes.mDeadBlockTable, "Memory used by DMD's dead block list."); return NS_OK; } private: ~DMDReporter() {} }; NS_IMPL_ISUPPORTS(DMDReporter, nsIMemoryReporter) } // namespace dmd } // namespace mozilla #endif // MOZ_DMD /** ** nsMemoryReporterManager implementation **/ NS_IMPL_ISUPPORTS(nsMemoryReporterManager, nsIMemoryReporterManager) NS_IMETHODIMP nsMemoryReporterManager::Init() { if (!NS_IsMainThread()) { MOZ_CRASH(); } // Under normal circumstances this function is only called once. However, // we've (infrequently) seen memory report dumps in crash reports that // suggest that this function is sometimes called multiple times. That in // turn means that multiple reporters of each kind are registered, which // leads to duplicated reports of individual measurements such as "resident", // "vsize", etc. // // It's unclear how these multiple calls can occur. The only plausible theory // so far is badly-written extensions, because this function is callable from // JS code via nsIMemoryReporter.idl. // // Whatever the cause, it's a bad thing. So we protect against it with the // following check. static bool isInited = false; if (isInited) { NS_WARNING("nsMemoryReporterManager::Init() has already been called!"); return NS_OK; } isInited = true; #if defined(HAVE_JEMALLOC_STATS) && defined(MOZ_GLUE_IN_PROGRAM) if (!jemalloc_stats) { return NS_ERROR_FAILURE; } #endif #ifdef HAVE_JEMALLOC_STATS RegisterStrongReporter(new JemallocHeapReporter()); #endif #ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS RegisterStrongReporter(new VsizeReporter()); RegisterStrongReporter(new ResidentReporter()); #endif #ifdef HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER RegisterStrongReporter(new VsizeMaxContiguousReporter()); #endif #ifdef HAVE_RESIDENT_PEAK_REPORTER RegisterStrongReporter(new ResidentPeakReporter()); #endif #ifdef HAVE_RESIDENT_UNIQUE_REPORTER RegisterStrongReporter(new ResidentUniqueReporter()); #endif #ifdef HAVE_PAGE_FAULT_REPORTERS RegisterStrongReporter(new PageFaultsSoftReporter()); RegisterStrongReporter(new PageFaultsHardReporter()); #endif #ifdef HAVE_PRIVATE_REPORTER RegisterStrongReporter(new PrivateReporter()); #endif #ifdef HAVE_SYSTEM_HEAP_REPORTER RegisterStrongReporter(new SystemHeapReporter()); #endif RegisterStrongReporter(new AtomTablesReporter()); #ifdef DEBUG RegisterStrongReporter(new DeadlockDetectorReporter()); #endif #ifdef MOZ_DMD RegisterStrongReporter(new mozilla::dmd::DMDReporter()); #endif #ifdef XP_WIN RegisterStrongReporter(new WindowsAddressSpaceReporter()); #endif #ifdef XP_UNIX nsMemoryInfoDumper::Initialize(); #endif return NS_OK; } nsMemoryReporterManager::nsMemoryReporterManager() : mMutex("nsMemoryReporterManager::mMutex") , mIsRegistrationBlocked(false) , mStrongReporters(new StrongReportersTable()) , mWeakReporters(new WeakReportersTable()) , mSavedStrongReporters(nullptr) , mSavedWeakReporters(nullptr) , mNextGeneration(1) , mPendingProcessesState(nullptr) , mPendingReportersState(nullptr) { } nsMemoryReporterManager::~nsMemoryReporterManager() { delete mStrongReporters; delete mWeakReporters; NS_ASSERTION(!mSavedStrongReporters, "failed to restore strong reporters"); NS_ASSERTION(!mSavedWeakReporters, "failed to restore weak reporters"); } #ifdef MOZ_WIDGET_GONK #define DEBUG_CHILD_PROCESS_MEMORY_REPORTING 1 #endif #ifdef DEBUG_CHILD_PROCESS_MEMORY_REPORTING #define MEMORY_REPORTING_LOG(format, ...) \ printf_stderr("++++ MEMORY REPORTING: " format, ##__VA_ARGS__); #else #define MEMORY_REPORTING_LOG(...) #endif NS_IMETHODIMP nsMemoryReporterManager::GetReports( nsIHandleReportCallback* aHandleReport, nsISupports* aHandleReportData, nsIFinishReportingCallback* aFinishReporting, nsISupports* aFinishReportingData, bool aAnonymize) { return GetReportsExtended(aHandleReport, aHandleReportData, aFinishReporting, aFinishReportingData, aAnonymize, /* minimize = */ false, /* DMDident = */ EmptyString()); } NS_IMETHODIMP nsMemoryReporterManager::GetReportsExtended( nsIHandleReportCallback* aHandleReport, nsISupports* aHandleReportData, nsIFinishReportingCallback* aFinishReporting, nsISupports* aFinishReportingData, bool aAnonymize, bool aMinimize, const nsAString& aDMDDumpIdent) { nsresult rv; // Memory reporters are not necessarily threadsafe, so this function must // be called from the main thread. if (!NS_IsMainThread()) { MOZ_CRASH(); } uint32_t generation = mNextGeneration++; if (mPendingProcessesState) { // A request is in flight. Don't start another one. And don't report // an error; just ignore it, and let the in-flight request finish. MEMORY_REPORTING_LOG("GetReports (gen=%u, s->gen=%u): abort\n", generation, mPendingProcessesState->mGeneration); return NS_OK; } MEMORY_REPORTING_LOG("GetReports (gen=%u)\n", generation); uint32_t concurrency = Preferences::GetUint("memory.report_concurrency", 1); MOZ_ASSERT(concurrency >= 1); if (concurrency < 1) { concurrency = 1; } mPendingProcessesState = new PendingProcessesState(generation, aAnonymize, aMinimize, concurrency, aHandleReport, aHandleReportData, aFinishReporting, aFinishReportingData, aDMDDumpIdent); if (aMinimize) { nsCOMPtr<nsIRunnable> callback = NewRunnableMethod(this, &nsMemoryReporterManager::StartGettingReports); rv = MinimizeMemoryUsage(callback); } else { rv = StartGettingReports(); } return rv; } nsresult nsMemoryReporterManager::StartGettingReports() { PendingProcessesState* s = mPendingProcessesState; nsresult rv; // Get reports for this process. FILE* parentDMDFile = nullptr; #ifdef MOZ_DMD if (!s->mDMDDumpIdent.IsEmpty()) { rv = nsMemoryInfoDumper::OpenDMDFile(s->mDMDDumpIdent, getpid(), &parentDMDFile); if (NS_WARN_IF(NS_FAILED(rv))) { // Proceed with the memory report as if DMD were disabled. parentDMDFile = nullptr; } } #endif // This is async. GetReportsForThisProcessExtended(s->mHandleReport, s->mHandleReportData, s->mAnonymize, parentDMDFile, s->mFinishReporting, s->mFinishReportingData); nsTArray<ContentParent*> childWeakRefs; ContentParent::GetAll(childWeakRefs); if (!childWeakRefs.IsEmpty()) { // Request memory reports from child processes. This happens // after the parent report so that the parent's main thread will // be free to process the child reports, instead of causing them // to be buffered and consume (possibly scarce) memory. for (size_t i = 0; i < childWeakRefs.Length(); ++i) { s->mChildrenPending.AppendElement(childWeakRefs[i]); } nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID); // Don't use NS_ENSURE_* here; can't return until the report is finished. if (NS_WARN_IF(!timer)) { FinishReporting(); return NS_ERROR_FAILURE; } rv = timer->InitWithFuncCallback(TimeoutCallback, this, kTimeoutLengthMS, nsITimer::TYPE_ONE_SHOT); if (NS_WARN_IF(NS_FAILED(rv))) { FinishReporting(); return rv; } MOZ_ASSERT(!s->mTimer); s->mTimer.swap(timer); } return NS_OK; } void nsMemoryReporterManager::DispatchReporter( nsIMemoryReporter* aReporter, bool aIsAsync, nsIHandleReportCallback* aHandleReport, nsISupports* aHandleReportData, bool aAnonymize) { MOZ_ASSERT(mPendingReportersState); // Grab refs to everything used in the lambda function. RefPtr<nsMemoryReporterManager> self = this; nsCOMPtr<nsIMemoryReporter> reporter = aReporter; nsCOMPtr<nsIHandleReportCallback> handleReport = aHandleReport; nsCOMPtr<nsISupports> handleReportData = aHandleReportData; nsCOMPtr<nsIRunnable> event = NS_NewRunnableFunction( [self, reporter, aIsAsync, handleReport, handleReportData, aAnonymize] () { reporter->CollectReports(handleReport, handleReportData, aAnonymize); if (!aIsAsync) { self->EndReport(); } }); NS_DispatchToMainThread(event); mPendingReportersState->mReportsPending++; } NS_IMETHODIMP nsMemoryReporterManager::GetReportsForThisProcessExtended( nsIHandleReportCallback* aHandleReport, nsISupports* aHandleReportData, bool aAnonymize, FILE* aDMDFile, nsIFinishReportingCallback* aFinishReporting, nsISupports* aFinishReportingData) { // Memory reporters are not necessarily threadsafe, so this function must // be called from the main thread. if (!NS_IsMainThread()) { MOZ_CRASH(); } if (NS_WARN_IF(mPendingReportersState)) { // Report is already in progress. return NS_ERROR_IN_PROGRESS; } #ifdef MOZ_DMD if (aDMDFile) { // Clear DMD's reportedness state before running the memory // reporters, to avoid spurious twice-reported warnings. dmd::ClearReports(); } #else MOZ_ASSERT(!aDMDFile); #endif mPendingReportersState = new PendingReportersState( aFinishReporting, aFinishReportingData, aDMDFile); { mozilla::MutexAutoLock autoLock(mMutex); for (auto iter = mStrongReporters->Iter(); !iter.Done(); iter.Next()) { DispatchReporter(iter.Key(), iter.Data(), aHandleReport, aHandleReportData, aAnonymize); } for (auto iter = mWeakReporters->Iter(); !iter.Done(); iter.Next()) { nsCOMPtr<nsIMemoryReporter> reporter = iter.Key(); DispatchReporter(reporter, iter.Data(), aHandleReport, aHandleReportData, aAnonymize); } } return NS_OK; } NS_IMETHODIMP nsMemoryReporterManager::EndReport() { if (--mPendingReportersState->mReportsPending == 0) { #ifdef MOZ_DMD if (mPendingReportersState->mDMDFile) { nsMemoryInfoDumper::DumpDMDToFile(mPendingReportersState->mDMDFile); } #endif if (mPendingProcessesState) { // This is the parent process. EndProcessReport(mPendingProcessesState->mGeneration, true); } else { mPendingReportersState->mFinishReporting->Callback( mPendingReportersState->mFinishReportingData); } delete mPendingReportersState; mPendingReportersState = nullptr; } return NS_OK; } nsMemoryReporterManager::PendingProcessesState* nsMemoryReporterManager::GetStateForGeneration(uint32_t aGeneration) { // Memory reporting only happens on the main thread. MOZ_RELEASE_ASSERT(NS_IsMainThread()); PendingProcessesState* s = mPendingProcessesState; if (!s) { // If we reach here, then: // // - A child process reported back too late, and no subsequent request // is in flight. // // So there's nothing to be done. Just ignore it. MEMORY_REPORTING_LOG( "HandleChildReports: no request in flight (aGen=%u)\n", aGeneration); return nullptr; } if (aGeneration != s->mGeneration) { // If we reach here, a child process must have reported back, too late, // while a subsequent (higher-numbered) request is in flight. Again, // ignore it. MOZ_ASSERT(aGeneration < s->mGeneration); MEMORY_REPORTING_LOG( "HandleChildReports: gen mismatch (aGen=%u, s->gen=%u)\n", aGeneration, s->mGeneration); return nullptr; } return s; } // This function has no return value. If something goes wrong, there's no // clear place to report the problem to, but that's ok -- we will end up // hitting the timeout and executing TimeoutCallback(). void nsMemoryReporterManager::HandleChildReport( uint32_t aGeneration, const dom::MemoryReport& aChildReport) { PendingProcessesState* s = GetStateForGeneration(aGeneration); if (!s) { return; } // Child reports should have a non-empty process. MOZ_ASSERT(!aChildReport.process().IsEmpty()); // If the call fails, ignore and continue. s->mHandleReport->Callback(aChildReport.process(), aChildReport.path(), aChildReport.kind(), aChildReport.units(), aChildReport.amount(), aChildReport.desc(), s->mHandleReportData); } /* static */ bool nsMemoryReporterManager::StartChildReport(mozilla::dom::ContentParent* aChild, const PendingProcessesState* aState) { if (!aChild->IsAlive()) { MEMORY_REPORTING_LOG("StartChildReports (gen=%u): child exited before" " its report was started\n", aState->mGeneration); return false; } mozilla::dom::MaybeFileDesc dmdFileDesc = void_t(); #ifdef MOZ_DMD if (!aState->mDMDDumpIdent.IsEmpty()) { FILE *dmdFile = nullptr; nsresult rv = nsMemoryInfoDumper::OpenDMDFile(aState->mDMDDumpIdent, aChild->Pid(), &dmdFile); if (NS_WARN_IF(NS_FAILED(rv))) { // Proceed with the memory report as if DMD were disabled. dmdFile = nullptr; } if (dmdFile) { dmdFileDesc = mozilla::ipc::FILEToFileDescriptor(dmdFile); fclose(dmdFile); } } #endif return aChild->SendPMemoryReportRequestConstructor( aState->mGeneration, aState->mAnonymize, aState->mMinimize, dmdFileDesc); } void nsMemoryReporterManager::EndProcessReport(uint32_t aGeneration, bool aSuccess) { PendingProcessesState* s = GetStateForGeneration(aGeneration); if (!s) { return; } MOZ_ASSERT(s->mNumProcessesRunning > 0); s->mNumProcessesRunning--; s->mNumProcessesCompleted++; MEMORY_REPORTING_LOG("HandleChildReports (aGen=%u): process %u %s" " (%u running, %u pending)\n", aGeneration, s->mNumProcessesCompleted, aSuccess ? "completed" : "exited during report", s->mNumProcessesRunning, static_cast<unsigned>(s->mChildrenPending.Length())); // Start pending children up to the concurrency limit. while (s->mNumProcessesRunning < s->mConcurrencyLimit && !s->mChildrenPending.IsEmpty()) { // Pop last element from s->mChildrenPending RefPtr<ContentParent> nextChild; nextChild.swap(s->mChildrenPending.LastElement()); s->mChildrenPending.TruncateLength(s->mChildrenPending.Length() - 1); // Start report (if the child is still alive). if (StartChildReport(nextChild, s)) { ++s->mNumProcessesRunning; MEMORY_REPORTING_LOG("HandleChildReports (aGen=%u): started child report" " (%u running, %u pending)\n", aGeneration, s->mNumProcessesRunning, static_cast<unsigned>(s->mChildrenPending.Length())); } } // If all the child processes (if any) have reported, we can cancel // the timer (if started) and finish up. Otherwise, just return. if (s->mNumProcessesRunning == 0) { MOZ_ASSERT(s->mChildrenPending.IsEmpty()); if (s->mTimer) { s->mTimer->Cancel(); } FinishReporting(); } } /* static */ void nsMemoryReporterManager::TimeoutCallback(nsITimer* aTimer, void* aData) { nsMemoryReporterManager* mgr = static_cast<nsMemoryReporterManager*>(aData); PendingProcessesState* s = mgr->mPendingProcessesState; // Release assert because: if the pointer is null we're about to // crash regardless of DEBUG, and this way the compiler doesn't // complain about unused variables. MOZ_RELEASE_ASSERT(s, "mgr->mPendingProcessesState"); MEMORY_REPORTING_LOG("TimeoutCallback (s->gen=%u; %u running, %u pending)\n", s->mGeneration, s->mNumProcessesRunning, static_cast<unsigned>(s->mChildrenPending.Length())); // We don't bother sending any kind of cancellation message to the child // processes that haven't reported back. mgr->FinishReporting(); } nsresult nsMemoryReporterManager::FinishReporting() { // Memory reporting only happens on the main thread. if (!NS_IsMainThread()) { MOZ_CRASH(); } MOZ_ASSERT(mPendingProcessesState); MEMORY_REPORTING_LOG("FinishReporting (s->gen=%u; %u processes reported)\n", mPendingProcessesState->mGeneration, mPendingProcessesState->mNumProcessesCompleted); // Call this before deleting |mPendingProcessesState|. That way, if // |mFinishReportData| calls GetReports(), it will silently abort, as // required. nsresult rv = mPendingProcessesState->mFinishReporting->Callback( mPendingProcessesState->mFinishReportingData); delete mPendingProcessesState; mPendingProcessesState = nullptr; return rv; } nsMemoryReporterManager::PendingProcessesState::PendingProcessesState( uint32_t aGeneration, bool aAnonymize, bool aMinimize, uint32_t aConcurrencyLimit, nsIHandleReportCallback* aHandleReport, nsISupports* aHandleReportData, nsIFinishReportingCallback* aFinishReporting, nsISupports* aFinishReportingData, const nsAString& aDMDDumpIdent) : mGeneration(aGeneration) , mAnonymize(aAnonymize) , mMinimize(aMinimize) , mChildrenPending() , mNumProcessesRunning(1) // reporting starts with the parent , mNumProcessesCompleted(0) , mConcurrencyLimit(aConcurrencyLimit) , mHandleReport(aHandleReport) , mHandleReportData(aHandleReportData) , mFinishReporting(aFinishReporting) , mFinishReportingData(aFinishReportingData) , mDMDDumpIdent(aDMDDumpIdent) { } static void CrashIfRefcountIsZero(nsISupports* aObj) { // This will probably crash if the object's refcount is 0. uint32_t refcnt = NS_ADDREF(aObj); if (refcnt <= 1) { MOZ_CRASH("CrashIfRefcountIsZero: refcount is zero"); } NS_RELEASE(aObj); } nsresult nsMemoryReporterManager::RegisterReporterHelper( nsIMemoryReporter* aReporter, bool aForce, bool aStrong, bool aIsAsync) { // This method is thread-safe. mozilla::MutexAutoLock autoLock(mMutex); if (mIsRegistrationBlocked && !aForce) { return NS_ERROR_FAILURE; } if (mStrongReporters->Contains(aReporter) || mWeakReporters->Contains(aReporter)) { return NS_ERROR_FAILURE; } // If |aStrong| is true, |aReporter| may have a refcnt of 0, so we take // a kung fu death grip before calling PutEntry. Otherwise, if PutEntry // addref'ed and released |aReporter| before finally addref'ing it for // good, it would free aReporter! The kung fu death grip could itself be // problematic if PutEntry didn't addref |aReporter| (because then when the // death grip goes out of scope, we would delete the reporter). In debug // mode, we check that this doesn't happen. // // If |aStrong| is false, we require that |aReporter| have a non-zero // refcnt. // if (aStrong) { nsCOMPtr<nsIMemoryReporter> kungFuDeathGrip = aReporter; mStrongReporters->Put(aReporter, aIsAsync); CrashIfRefcountIsZero(aReporter); } else { CrashIfRefcountIsZero(aReporter); nsCOMPtr<nsIXPConnectWrappedJS> jsComponent = do_QueryInterface(aReporter); if (jsComponent) { // We cannot allow non-native reporters (WrappedJS), since we'll be // holding onto a raw pointer, which would point to the wrapper, // and that wrapper is likely to go away as soon as this register // call finishes. This would then lead to subsequent crashes in // CollectReports(). return NS_ERROR_XPC_BAD_CONVERT_JS; } mWeakReporters->Put(aReporter, aIsAsync); } return NS_OK; } NS_IMETHODIMP nsMemoryReporterManager::RegisterStrongReporter(nsIMemoryReporter* aReporter) { return RegisterReporterHelper(aReporter, /* force = */ false, /* strong = */ true, /* async = */ false); } NS_IMETHODIMP nsMemoryReporterManager::RegisterStrongAsyncReporter(nsIMemoryReporter* aReporter) { return RegisterReporterHelper(aReporter, /* force = */ false, /* strong = */ true, /* async = */ true); } NS_IMETHODIMP nsMemoryReporterManager::RegisterWeakReporter(nsIMemoryReporter* aReporter) { return RegisterReporterHelper(aReporter, /* force = */ false, /* strong = */ false, /* async = */ false); } NS_IMETHODIMP nsMemoryReporterManager::RegisterWeakAsyncReporter(nsIMemoryReporter* aReporter) { return RegisterReporterHelper(aReporter, /* force = */ false, /* strong = */ false, /* async = */ true); } NS_IMETHODIMP nsMemoryReporterManager::RegisterStrongReporterEvenIfBlocked( nsIMemoryReporter* aReporter) { return RegisterReporterHelper(aReporter, /* force = */ true, /* strong = */ true, /* async = */ false); } NS_IMETHODIMP nsMemoryReporterManager::UnregisterStrongReporter(nsIMemoryReporter* aReporter) { // This method is thread-safe. mozilla::MutexAutoLock autoLock(mMutex); MOZ_ASSERT(!mWeakReporters->Contains(aReporter)); if (mStrongReporters->Contains(aReporter)) { mStrongReporters->Remove(aReporter); return NS_OK; } // We don't register new reporters when the block is in place, but we do // unregister existing reporters. This is so we don't keep holding strong // references that these reporters aren't expecting (which can keep them // alive longer than intended). if (mSavedStrongReporters && mSavedStrongReporters->Contains(aReporter)) { mSavedStrongReporters->Remove(aReporter); return NS_OK; } return NS_ERROR_FAILURE; } NS_IMETHODIMP nsMemoryReporterManager::UnregisterWeakReporter(nsIMemoryReporter* aReporter) { // This method is thread-safe. mozilla::MutexAutoLock autoLock(mMutex); MOZ_ASSERT(!mStrongReporters->Contains(aReporter)); if (mWeakReporters->Contains(aReporter)) { mWeakReporters->Remove(aReporter); return NS_OK; } // We don't register new reporters when the block is in place, but we do // unregister existing reporters. This is so we don't keep holding weak // references that the old reporters aren't expecting (which can end up as // dangling pointers that lead to use-after-frees). if (mSavedWeakReporters && mSavedWeakReporters->Contains(aReporter)) { mSavedWeakReporters->Remove(aReporter); return NS_OK; } return NS_ERROR_FAILURE; } NS_IMETHODIMP nsMemoryReporterManager::BlockRegistrationAndHideExistingReporters() { // This method is thread-safe. mozilla::MutexAutoLock autoLock(mMutex); if (mIsRegistrationBlocked) { return NS_ERROR_FAILURE; } mIsRegistrationBlocked = true; // Hide the existing reporters, saving them for later restoration. MOZ_ASSERT(!mSavedStrongReporters); MOZ_ASSERT(!mSavedWeakReporters); mSavedStrongReporters = mStrongReporters; mSavedWeakReporters = mWeakReporters; mStrongReporters = new StrongReportersTable(); mWeakReporters = new WeakReportersTable(); return NS_OK; } NS_IMETHODIMP nsMemoryReporterManager::UnblockRegistrationAndRestoreOriginalReporters() { // This method is thread-safe. mozilla::MutexAutoLock autoLock(mMutex); if (!mIsRegistrationBlocked) { return NS_ERROR_FAILURE; } // Banish the current reporters, and restore the hidden ones. delete mStrongReporters; delete mWeakReporters; mStrongReporters = mSavedStrongReporters; mWeakReporters = mSavedWeakReporters; mSavedStrongReporters = nullptr; mSavedWeakReporters = nullptr; mIsRegistrationBlocked = false; return NS_OK; } NS_IMETHODIMP nsMemoryReporterManager::GetVsize(int64_t* aVsize) { #ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS return VsizeDistinguishedAmount(aVsize); #else *aVsize = 0; return NS_ERROR_NOT_AVAILABLE; #endif } NS_IMETHODIMP nsMemoryReporterManager::GetVsizeMaxContiguous(int64_t* aAmount) { #ifdef HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER return VsizeMaxContiguousDistinguishedAmount(aAmount); #else *aAmount = 0; return NS_ERROR_NOT_AVAILABLE; #endif } NS_IMETHODIMP nsMemoryReporterManager::GetResident(int64_t* aAmount) { #ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS return ResidentDistinguishedAmount(aAmount); #else *aAmount = 0; return NS_ERROR_NOT_AVAILABLE; #endif } NS_IMETHODIMP nsMemoryReporterManager::GetResidentFast(int64_t* aAmount) { #ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS return ResidentFastDistinguishedAmount(aAmount); #else *aAmount = 0; return NS_ERROR_NOT_AVAILABLE; #endif } /*static*/ int64_t nsMemoryReporterManager::ResidentFast() { #ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS int64_t amount; nsresult rv = ResidentFastDistinguishedAmount(&amount); NS_ENSURE_SUCCESS(rv, 0); return amount; #else return 0; #endif } NS_IMETHODIMP nsMemoryReporterManager::GetResidentPeak(int64_t* aAmount) { #ifdef HAVE_RESIDENT_PEAK_REPORTER return ResidentPeakDistinguishedAmount(aAmount); #else *aAmount = 0; return NS_ERROR_NOT_AVAILABLE; #endif } /*static*/ int64_t nsMemoryReporterManager::ResidentPeak() { #ifdef HAVE_RESIDENT_PEAK_REPORTER int64_t amount = 0; nsresult rv = ResidentPeakDistinguishedAmount(&amount); NS_ENSURE_SUCCESS(rv, 0); return amount; #else return 0; #endif } NS_IMETHODIMP nsMemoryReporterManager::GetResidentUnique(int64_t* aAmount) { #ifdef HAVE_RESIDENT_UNIQUE_REPORTER return ResidentUniqueDistinguishedAmount(aAmount); #else *aAmount = 0; return NS_ERROR_NOT_AVAILABLE; #endif } /*static*/ int64_t nsMemoryReporterManager::ResidentUnique() { #ifdef HAVE_RESIDENT_UNIQUE_REPORTER int64_t amount = 0; nsresult rv = ResidentUniqueDistinguishedAmount(&amount); NS_ENSURE_SUCCESS(rv, 0); return amount; #else return 0; #endif } NS_IMETHODIMP nsMemoryReporterManager::GetHeapAllocated(int64_t* aAmount) { #ifdef HAVE_JEMALLOC_STATS jemalloc_stats_t stats; jemalloc_stats(&stats); *aAmount = stats.allocated; return NS_OK; #else *aAmount = 0; return NS_ERROR_NOT_AVAILABLE; #endif } // This has UNITS_PERCENTAGE, so it is multiplied by 100x. NS_IMETHODIMP nsMemoryReporterManager::GetHeapOverheadFraction(int64_t* aAmount) { #ifdef HAVE_JEMALLOC_STATS jemalloc_stats_t stats; jemalloc_stats(&stats); *aAmount = HeapOverheadFraction(&stats); return NS_OK; #else *aAmount = 0; return NS_ERROR_NOT_AVAILABLE; #endif } static MOZ_MUST_USE nsresult GetInfallibleAmount(InfallibleAmountFn aAmountFn, int64_t* aAmount) { if (aAmountFn) { *aAmount = aAmountFn(); return NS_OK; } *aAmount = 0; return NS_ERROR_NOT_AVAILABLE; } NS_IMETHODIMP nsMemoryReporterManager::GetJSMainRuntimeGCHeap(int64_t* aAmount) { return GetInfallibleAmount(mAmountFns.mJSMainRuntimeGCHeap, aAmount); } NS_IMETHODIMP nsMemoryReporterManager::GetJSMainRuntimeTemporaryPeak(int64_t* aAmount) { return GetInfallibleAmount(mAmountFns.mJSMainRuntimeTemporaryPeak, aAmount); } NS_IMETHODIMP nsMemoryReporterManager::GetJSMainRuntimeCompartmentsSystem(int64_t* aAmount) { return GetInfallibleAmount(mAmountFns.mJSMainRuntimeCompartmentsSystem, aAmount); } NS_IMETHODIMP nsMemoryReporterManager::GetJSMainRuntimeCompartmentsUser(int64_t* aAmount) { return GetInfallibleAmount(mAmountFns.mJSMainRuntimeCompartmentsUser, aAmount); } NS_IMETHODIMP nsMemoryReporterManager::GetImagesContentUsedUncompressed(int64_t* aAmount) { return GetInfallibleAmount(mAmountFns.mImagesContentUsedUncompressed, aAmount); } NS_IMETHODIMP nsMemoryReporterManager::GetStorageSQLite(int64_t* aAmount) { return GetInfallibleAmount(mAmountFns.mStorageSQLite, aAmount); } NS_IMETHODIMP nsMemoryReporterManager::GetLowMemoryEventsVirtual(int64_t* aAmount) { return GetInfallibleAmount(mAmountFns.mLowMemoryEventsVirtual, aAmount); } NS_IMETHODIMP nsMemoryReporterManager::GetLowMemoryEventsPhysical(int64_t* aAmount) { return GetInfallibleAmount(mAmountFns.mLowMemoryEventsPhysical, aAmount); } NS_IMETHODIMP nsMemoryReporterManager::GetGhostWindows(int64_t* aAmount) { return GetInfallibleAmount(mAmountFns.mGhostWindows, aAmount); } NS_IMETHODIMP nsMemoryReporterManager::GetPageFaultsHard(int64_t* aAmount) { #ifdef HAVE_PAGE_FAULT_REPORTERS return PageFaultsHardDistinguishedAmount(aAmount); #else *aAmount = 0; return NS_ERROR_NOT_AVAILABLE; #endif } NS_IMETHODIMP nsMemoryReporterManager::GetHasMozMallocUsableSize(bool* aHas) { void* p = malloc(16); if (!p) { return NS_ERROR_OUT_OF_MEMORY; } size_t usable = moz_malloc_usable_size(p); free(p); *aHas = !!(usable > 0); return NS_OK; } NS_IMETHODIMP nsMemoryReporterManager::GetIsDMDEnabled(bool* aIsEnabled) { #ifdef MOZ_DMD *aIsEnabled = true; #else *aIsEnabled = false; #endif return NS_OK; } NS_IMETHODIMP nsMemoryReporterManager::GetIsDMDRunning(bool* aIsRunning) { #ifdef MOZ_DMD *aIsRunning = dmd::IsRunning(); #else *aIsRunning = false; #endif return NS_OK; } namespace { /** * This runnable lets us implement * nsIMemoryReporterManager::MinimizeMemoryUsage(). We fire a heap-minimize * notification, spin the event loop, and repeat this process a few times. * * When this sequence finishes, we invoke the callback function passed to the * runnable's constructor. */ class MinimizeMemoryUsageRunnable : public Runnable { public: explicit MinimizeMemoryUsageRunnable(nsIRunnable* aCallback) : mCallback(aCallback) , mRemainingIters(sNumIters) { } NS_IMETHOD Run() override { nsCOMPtr<nsIObserverService> os = services::GetObserverService(); if (!os) { return NS_ERROR_FAILURE; } if (mRemainingIters == 0) { os->NotifyObservers(nullptr, "after-minimize-memory-usage", u"MinimizeMemoryUsageRunnable"); if (mCallback) { mCallback->Run(); } return NS_OK; } os->NotifyObservers(nullptr, "memory-pressure", u"heap-minimize"); mRemainingIters--; NS_DispatchToMainThread(this); return NS_OK; } private: // Send sNumIters heap-minimize notifications, spinning the event // loop after each notification (see bug 610166 comment 12 for an // explanation), because one notification doesn't cut it. static const uint32_t sNumIters = 3; nsCOMPtr<nsIRunnable> mCallback; uint32_t mRemainingIters; }; } // namespace NS_IMETHODIMP nsMemoryReporterManager::MinimizeMemoryUsage(nsIRunnable* aCallback) { RefPtr<MinimizeMemoryUsageRunnable> runnable = new MinimizeMemoryUsageRunnable(aCallback); return NS_DispatchToMainThread(runnable); } NS_IMETHODIMP nsMemoryReporterManager::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) { nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aTopWindow); auto* piWindow = nsPIDOMWindowOuter::From(aTopWindow); if (NS_WARN_IF(!global) || NS_WARN_IF(!piWindow)) { return NS_ERROR_FAILURE; } TimeStamp t1 = TimeStamp::Now(); // Measure JS memory consumption (and possibly some non-JS consumption, via // |jsPrivateSize|). size_t jsObjectsSize, jsStringsSize, jsPrivateSize, jsOtherSize; nsresult rv = mSizeOfTabFns.mJS(global->GetGlobalJSObject(), &jsObjectsSize, &jsStringsSize, &jsPrivateSize, &jsOtherSize); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } TimeStamp t2 = TimeStamp::Now(); // Measure non-JS memory consumption. size_t domSize, styleSize, otherSize; rv = mSizeOfTabFns.mNonJS(piWindow, &domSize, &styleSize, &otherSize); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } TimeStamp t3 = TimeStamp::Now(); *aTotalSize = 0; #define DO(aN, n) { *aN = (n); *aTotalSize += (n); } DO(aJSObjectsSize, jsObjectsSize); DO(aJSStringsSize, jsStringsSize); DO(aJSOtherSize, jsOtherSize); DO(aDomSize, jsPrivateSize + domSize); DO(aStyleSize, styleSize); DO(aOtherSize, otherSize); #undef DO *aJSMilliseconds = (t2 - t1).ToMilliseconds(); *aNonJSMilliseconds = (t3 - t2).ToMilliseconds(); return NS_OK; } namespace mozilla { #define GET_MEMORY_REPORTER_MANAGER(mgr) \ RefPtr<nsMemoryReporterManager> mgr = \ nsMemoryReporterManager::GetOrCreate(); \ if (!mgr) { \ return NS_ERROR_FAILURE; \ } nsresult RegisterStrongMemoryReporter(nsIMemoryReporter* aReporter) { // Hold a strong reference to the argument to make sure it gets released if // we return early below. nsCOMPtr<nsIMemoryReporter> reporter = aReporter; GET_MEMORY_REPORTER_MANAGER(mgr) return mgr->RegisterStrongReporter(reporter); } nsresult RegisterStrongAsyncMemoryReporter(nsIMemoryReporter* aReporter) { // Hold a strong reference to the argument to make sure it gets released if // we return early below. nsCOMPtr<nsIMemoryReporter> reporter = aReporter; GET_MEMORY_REPORTER_MANAGER(mgr) return mgr->RegisterStrongAsyncReporter(reporter); } nsresult RegisterWeakMemoryReporter(nsIMemoryReporter* aReporter) { GET_MEMORY_REPORTER_MANAGER(mgr) return mgr->RegisterWeakReporter(aReporter); } nsresult RegisterWeakAsyncMemoryReporter(nsIMemoryReporter* aReporter) { GET_MEMORY_REPORTER_MANAGER(mgr) return mgr->RegisterWeakAsyncReporter(aReporter); } nsresult UnregisterStrongMemoryReporter(nsIMemoryReporter* aReporter) { GET_MEMORY_REPORTER_MANAGER(mgr) return mgr->UnregisterStrongReporter(aReporter); } nsresult UnregisterWeakMemoryReporter(nsIMemoryReporter* aReporter) { GET_MEMORY_REPORTER_MANAGER(mgr) return mgr->UnregisterWeakReporter(aReporter); } // Macro for generating functions that register distinguished amount functions // with the memory reporter manager. #define DEFINE_REGISTER_DISTINGUISHED_AMOUNT(kind, name) \ nsresult \ Register##name##DistinguishedAmount(kind##AmountFn aAmountFn) \ { \ GET_MEMORY_REPORTER_MANAGER(mgr) \ mgr->mAmountFns.m##name = aAmountFn; \ return NS_OK; \ } // Macro for generating functions that unregister distinguished amount // functions with the memory reporter manager. #define DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT(name) \ nsresult \ Unregister##name##DistinguishedAmount() \ { \ GET_MEMORY_REPORTER_MANAGER(mgr) \ mgr->mAmountFns.m##name = nullptr; \ return NS_OK; \ } DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeGCHeap) DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeTemporaryPeak) DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeCompartmentsSystem) DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeCompartmentsUser) DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, ImagesContentUsedUncompressed) DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT(ImagesContentUsedUncompressed) DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, StorageSQLite) DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT(StorageSQLite) DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsVirtual) DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsPhysical) DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, GhostWindows) #undef DEFINE_REGISTER_DISTINGUISHED_AMOUNT #undef DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT #define DEFINE_REGISTER_SIZE_OF_TAB(name) \ nsresult \ Register##name##SizeOfTab(name##SizeOfTabFn aSizeOfTabFn) \ { \ GET_MEMORY_REPORTER_MANAGER(mgr) \ mgr->mSizeOfTabFns.m##name = aSizeOfTabFn; \ return NS_OK; \ } DEFINE_REGISTER_SIZE_OF_TAB(JS); DEFINE_REGISTER_SIZE_OF_TAB(NonJS); #undef DEFINE_REGISTER_SIZE_OF_TAB #undef GET_MEMORY_REPORTER_MANAGER } // namespace mozilla