Bug 1475899: Part 5 - Add thread stack memory reporter for Windows. r=erahm,aklotz draft
authorKris Maglione <maglione.k@gmail.com>
Tue, 17 Jul 2018 13:09:45 -0700
changeset 819391 42d8d4be7b52a66dc165a8dcfa824ea3d000c490
parent 819390 4a8cb980ca2dac89ac6198117af0d0b93b1f81cb
push id116548
push usermaglione.k@gmail.com
push dateTue, 17 Jul 2018 20:10:48 +0000
reviewerserahm, aklotz
bugs1475899
milestone63.0a1
Bug 1475899: Part 5 - Add thread stack memory reporter for Windows. r=erahm,aklotz MozReview-Commit-ID: Bv6OPmUhl5Y
xpcom/base/MemoryInfo.cpp
xpcom/base/MemoryInfo.h
xpcom/base/moz.build
xpcom/base/nsMemoryReporterManager.cpp
xpcom/threads/nsThread.cpp
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,