Bug 1475899: Part 5 - Add thread stack memory reporter for Windows. r=erahm,aklotz
MozReview-Commit-ID: Bv6OPmUhl5Y
new file mode 100644
--- /dev/null
+++ b/xpcom/base/MemoryInfo.cpp
@@ -0,0 +1,105 @@
+/* -*- 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 "mozilla/MemoryInfo.h"
+
+#include "mozilla/DebugOnly.h"
+
+#include <WinBase.h>
+
+namespace mozilla {
+
+/* static */ MemoryInfo
+MemoryInfo::Get(const void* aPtr, size_t aSize)
+{
+ MemoryInfo result;
+
+ result.mStart = uintptr_t(aPtr);
+ const char* ptr = reinterpret_cast<const char*>(aPtr);
+ const char* end = ptr + aSize;
+ DebugOnly<void*> base = nullptr;
+ while (ptr < end) {
+ MEMORY_BASIC_INFORMATION basicInfo;
+ if (!VirtualQuery(ptr, &basicInfo, sizeof(basicInfo))) {
+ break;
+ }
+
+ MOZ_ASSERT_IF(base, base == basicInfo.AllocationBase);
+ base = basicInfo.AllocationBase;
+
+ size_t regionSize = std::min(size_t(basicInfo.RegionSize),
+ size_t(end - ptr));
+
+ if (basicInfo.State == MEM_COMMIT) {
+ result.mCommitted += regionSize;
+ } else if (basicInfo.State == MEM_RESERVE) {
+ result.mReserved += regionSize;
+ } else if (basicInfo.State == MEM_FREE) {
+ result.mFree += regionSize;
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unexpected region state");
+ }
+ result.mSize += regionSize;
+ ptr += regionSize;
+
+ if (result.mType.isEmpty()) {
+ if (basicInfo.Type & MEM_IMAGE) {
+ result.mType += PageType::Image;
+ }
+ if (basicInfo.Type & MEM_MAPPED) {
+ result.mType += PageType::Mapped;
+ }
+ if (basicInfo.Type & MEM_PRIVATE) {
+ result.mType += PageType::Private;
+ }
+
+ // The first 8 bits of AllocationProtect are an enum. The remaining bits
+ // are flags.
+ switch (basicInfo.AllocationProtect & 0xff) {
+ case PAGE_EXECUTE_WRITECOPY:
+ result.mPerms += Perm::CopyOnWrite;
+ MOZ_FALLTHROUGH;
+ case PAGE_EXECUTE_READWRITE:
+ result.mPerms += Perm::Write;
+ MOZ_FALLTHROUGH;
+ case PAGE_EXECUTE_READ:
+ result.mPerms += Perm::Read;
+ MOZ_FALLTHROUGH;
+ case PAGE_EXECUTE:
+ result.mPerms += Perm::Execute;
+ break;
+
+ case PAGE_WRITECOPY:
+ result.mPerms += Perm::CopyOnWrite;
+ MOZ_FALLTHROUGH;
+ case PAGE_READWRITE:
+ result.mPerms += Perm::Write;
+ MOZ_FALLTHROUGH;
+ case PAGE_READONLY:
+ result.mPerms += Perm::Read;
+ break;
+
+ default:
+ break;
+ }
+
+ if (basicInfo.AllocationProtect & PAGE_GUARD) {
+ result.mPerms += Perm::Guard;
+ }
+ if (basicInfo.AllocationProtect & PAGE_NOCACHE) {
+ result.mPerms += Perm::NoCache;
+ }
+ if (basicInfo.AllocationProtect & PAGE_WRITECOMBINE) {
+ result.mPerms += Perm::WriteCombine;
+ }
+ }
+ }
+
+ result.mEnd = uintptr_t(ptr);
+ return result;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/xpcom/base/MemoryInfo.h
@@ -0,0 +1,83 @@
+/* -*- 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/. */
+
+#ifndef mozilla_MemoryInfo_h
+#define mozilla_MemoryInfo_h
+
+#include "mozilla/EnumSet.h"
+#include "nsTArray.h"
+
+/**
+ * MemoryInfo is a helper class which describes the attributes and sizes of a
+ * particular region of VM memory on Windows. It roughtly corresponds to the
+ * values in a MEMORY_BASIC_INFORMATION struct, summed over an entire region or
+ * memory.
+ */
+
+namespace mozilla {
+
+class MemoryInfo final
+{
+public:
+ enum class Perm : uint8_t
+ {
+ Read,
+ Write,
+ Execute,
+ CopyOnWrite,
+ Guard,
+ NoCache,
+ WriteCombine,
+ };
+ enum class PageType : uint8_t
+ {
+ Image,
+ Mapped,
+ Private,
+ };
+
+ using PermSet = EnumSet<Perm>;
+ using PageTypeSet = EnumSet<PageType>;
+
+ MemoryInfo() = default;
+ MOZ_IMPLICIT MemoryInfo(const MemoryInfo&) = default;
+
+ uintptr_t Start() const { return mStart; }
+ uintptr_t End() const { return mEnd; }
+
+ PageTypeSet Type() const { return mType; }
+ PermSet Perms() const { return mPerms; }
+
+ size_t Reserved() const { return mReserved; }
+ size_t Committed() const { return mCommitted; }
+ size_t Free() const { return mFree; }
+ size_t Size() const { return mSize; }
+
+ // Returns a MemoryInfo object containing the sums of all region sizes,
+ // divided into Reserved, Committed, and Free, depending on their State
+ // properties.
+ //
+ // The entire range of aSize bytes starting at aPtr must correspond to a
+ // single allocation. This restriction is enforced in debug builds.
+ static MemoryInfo Get(const void* aPtr, size_t aSize);
+
+private:
+ uintptr_t mStart = 0;
+ uintptr_t mEnd = 0;
+
+ size_t mReserved = 0;
+ size_t mCommitted = 0;
+ size_t mFree = 0;
+ size_t mSize = 0;
+
+ PageTypeSet mType{};
+
+ PermSet mPerms{};
+};
+
+} // namespace mozilla
+
+#endif // mozilla_MemoryInfo_h
--- a/xpcom/base/moz.build
+++ b/xpcom/base/moz.build
@@ -107,16 +107,17 @@ EXPORTS.mozilla += [
'DebuggerOnGCRunnable.h',
'DeferredFinalize.h',
'EnumeratedArrayCycleCollection.h',
'ErrorNames.h',
'HoldDropJSObjects.h',
'IntentionalCrash.h',
'JSObjectHolder.h',
'Logging.h',
+ 'MemoryInfo.h',
'MemoryMapping.h',
'MemoryReportingProcess.h',
'nsMemoryInfoDumper.h',
'NSPRLogModulesParser.h',
'OwningNonNull.h',
'SizeOfState.h',
'StaticMutex.h',
'StaticPtr.h',
@@ -174,16 +175,21 @@ UNIFIED_SOURCES += [
'nsWeakReference.cpp',
]
if CONFIG['OS_TARGET'] == 'Linux':
UNIFIED_SOURCES += [
'MemoryMapping.cpp',
]
+if CONFIG['OS_TARGET'] == 'WINNT':
+ UNIFIED_SOURCES += [
+ 'MemoryInfo.cpp',
+ ]
+
GENERATED_FILES += [
"error_list.rs",
"ErrorList.h",
"ErrorNamesInternal.h",
]
GENERATED_FILES["ErrorList.h"].script = "ErrorList.py:error_list_h"
GENERATED_FILES["ErrorNamesInternal.h"].script = "ErrorList.py:error_names_internal_h"
--- a/xpcom/base/nsMemoryReporterManager.cpp
+++ b/xpcom/base/nsMemoryReporterManager.cpp
@@ -20,30 +20,33 @@
#include "nsIXPConnect.h"
#ifdef MOZ_GECKO_PROFILER
#include "GeckoProfilerReporter.h"
#endif
#if defined(XP_UNIX) || defined(MOZ_DMD)
#include "nsMemoryInfoDumper.h"
#endif
#include "nsNetCID.h"
+#include "nsThread.h"
#include "mozilla/Attributes.h"
#include "mozilla/MemoryReportingProcess.h"
#include "mozilla/PodOperations.h"
#include "mozilla/Preferences.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/Services.h"
#include "mozilla/Telemetry.h"
#include "mozilla/UniquePtrExtensions.h"
#include "mozilla/dom/MemoryReportTypes.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/gfx/GPUProcessManager.h"
#include "mozilla/ipc/FileDescriptorUtils.h"
#ifdef XP_WIN
+#include "mozilla/MemoryInfo.h"
+
#include <process.h>
#ifndef getpid
#define getpid _getpid
#endif
#else
#include <unistd.h>
#endif
@@ -53,17 +56,16 @@ using namespace dom;
#if defined(MOZ_MEMORY)
# define HAVE_JEMALLOC_STATS 1
# include "mozmemory.h"
#endif // MOZ_MEMORY
#if defined(XP_LINUX)
#include "mozilla/MemoryMapping.h"
-#include "nsThread.h"
#include <malloc.h>
#include <string.h>
#include <stdlib.h>
static MOZ_MUST_USE nsresult
GetProcSelfStatmField(int aField, int64_t* aN)
{
@@ -1397,29 +1399,31 @@ public:
"Memory used by dynamic atom objects and chars (which are stored "
"at the end of each atom object).");
return NS_OK;
}
};
NS_IMPL_ISUPPORTS(AtomTablesReporter, nsIMemoryReporter)
-#ifdef XP_LINUX
+#if defined(XP_LINUX) || defined(XP_WIN)
class ThreadStacksReporter final : public nsIMemoryReporter
{
~ThreadStacksReporter() = default;
public:
NS_DECL_ISUPPORTS
NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
nsISupports* aData, bool aAnonymize) override
{
+#ifdef XP_LINUX
nsTArray<MemoryMapping> mappings(1024);
MOZ_TRY(GetMemoryMappings(mappings));
+#endif
// Enumerating over active threads requires holding a lock, so we collect
// info on all threads, and then call our reporter callbacks after releasing
// the lock.
struct ThreadData
{
nsCString mName;
uint32_t mThreadId;
@@ -1427,16 +1431,17 @@ public:
};
AutoTArray<ThreadData, 32> threads;
for (auto* thread : nsThread::Enumerate()) {
if (!thread->StackBase()) {
continue;
}
+#ifdef XP_LINUX
int idx = mappings.BinaryIndexOf(thread->StackBase());
if (idx < 0) {
continue;
}
// Referenced() is the combined size of all pages in the region which have
// ever been touched, and are therefore consuming memory. For stack
// regions, these pages are guaranteed to be un-shared unless we fork
// after creating threads (which we don't).
@@ -1467,16 +1472,20 @@ public:
//
//
// The upshot of all of this is that there may be configurations that our
// special cases don't cover. And if there are, we want to know about it.
// So assert that total size of the memory region we're reporting actually
// matches the allocated size of the thread stack.
MOZ_ASSERT(mappings[idx].Size() == thread->StackSize(),
"Mapping region size doesn't match stack allocation size");
+#else
+ auto memInfo = MemoryInfo::Get(thread->StackBase(), thread->StackSize());
+ size_t privateSize = memInfo.Committed();
+#endif
threads.AppendElement(ThreadData{
nsCString(PR_GetThreadName(thread->GetPRThread())),
thread->ThreadId(),
// On Linux, it's possible (but unlikely) that our stack region will
// have been merged with adjacent heap regions, in which case we'll get
// combined size information for both. So we take the minimum of the
// reported private size and the requested stack size to avoid the
@@ -1660,17 +1669,17 @@ nsMemoryReporterManager::Init()
#endif
#ifdef HAVE_SYSTEM_HEAP_REPORTER
RegisterStrongReporter(new SystemHeapReporter());
#endif
RegisterStrongReporter(new AtomTablesReporter());
-#ifdef XP_LINUX
+#if defined(XP_LINUX) || defined(XP_WIN)
RegisterStrongReporter(new ThreadStacksReporter());
#endif
#ifdef DEBUG
RegisterStrongReporter(new DeadlockDetectorReporter());
#endif
#ifdef MOZ_GECKO_PROFILER
--- a/xpcom/threads/nsThread.cpp
+++ b/xpcom/threads/nsThread.cpp
@@ -56,16 +56,25 @@
#endif
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sched.h>
#include <stdio.h>
#endif
+#ifdef XP_WIN
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+
+#include <Winbase.h>
+
+using GetCurrentThreadStackLimitsFn = void (WINAPI*)(
+ PULONG_PTR LowLimit, PULONG_PTR HighLimit);
+#endif
+
#define HAVE_UALARM _BSD_SOURCE || (_XOPEN_SOURCE >= 500 || \
_XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED) && \
!(_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700)
#if defined(XP_LINUX) && !defined(ANDROID) && defined(_GNU_SOURCE)
#define HAVE_SCHED_SETAFFINITY
#endif
@@ -415,18 +424,18 @@ nsThread::ThreadFunc(void* aArg)
self->mVirtualThread = GetCurrentVirtualThread();
self->mEventTarget->SetCurrentThread();
SetupCurrentThreadForChaosMode();
if (!initData->name.IsEmpty()) {
NS_SetCurrentThreadName(initData->name.BeginReading());
}
-#ifdef XP_LINUX
{
+#if defined(XP_LINUX)
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_getattr_np(pthread_self(), &attr);
size_t stackSize;
pthread_attr_getstack(&attr, &self->mStackBase, &stackSize);
// Glibc prior to 2.27 reports the stack size and base including the guard
@@ -468,18 +477,28 @@ nsThread::ThreadFunc(void* aArg)
// What this does get us, however, is a different set of VM flags on our
// thread stacks compared to normal heap memory. Which makes the Linux
// kernel report them as separate regions, even when they are adjacent to
// heap memory. This allows us to accurately track the actual memory
// consumption of our allocated stacks.
madvise(self->mStackBase, stackSize, MADV_NOHUGEPAGE);
pthread_attr_destroy(&attr);
+#elif defined(XP_WIN)
+ static const DynamicallyLinkedFunctionPtr<GetCurrentThreadStackLimitsFn>
+ sGetStackLimits(L"kernel32.dll", "GetCurrentThreadStackLimits");
+
+ if (sGetStackLimits) {
+ ULONG_PTR stackBottom, stackTop;
+ sGetStackLimits(&stackBottom, &stackTop);
+ self->mStackBase = reinterpret_cast<void*>(stackBottom);
+ self->mStackSize = stackTop - stackBottom;
+ }
+#endif
}
-#endif
// Inform the ThreadManager
nsThreadManager::get().RegisterCurrentThread(*self);
mozilla::IOInterposer::RegisterCurrentThread();
// This must come after the call to nsThreadManager::RegisterCurrentThread(),
// because that call is needed to properly set up this thread as an nsThread,