From 1dbd559f77f75e63e965116574ff3f0d2c504069 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 14 Jul 2018 02:21:30 -0700 Subject: [PATCH] Bug 1475899: Part 4 - Add memory reporter for committed thread stack sizes on Linux. r=erahm Later patches add support for Windows. OS-X will need a follow-up. MozReview-Commit-ID: DDd6uir4KzM --HG-- extra : source : b4394660fde2b05e972c491246570d8f79d8a7c6 extra : absorb_source : 063bc5db64faa9311ac9db99259cd477fb8fcf7e --- xpcom/base/nsMemoryReporterManager.cpp | 110 +++++++++++++++++++++++++ xpcom/threads/nsThread.cpp | 2 + xpcom/threads/nsThread.h | 3 + 3 files changed, 115 insertions(+) diff --git a/xpcom/base/nsMemoryReporterManager.cpp b/xpcom/base/nsMemoryReporterManager.cpp index 434f8c319675..efe9f74fbc33 100644 --- a/xpcom/base/nsMemoryReporterManager.cpp +++ b/xpcom/base/nsMemoryReporterManager.cpp @@ -29,6 +29,7 @@ #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" @@ -56,6 +57,9 @@ using namespace dom; #if defined(XP_LINUX) +#include "mozilla/MemoryMapping.h" +#include "nsThread.h" + #include #include #include @@ -1398,6 +1402,108 @@ public: }; NS_IMPL_ISUPPORTS(AtomTablesReporter, nsIMemoryReporter) +#ifdef XP_LINUX +class ThreadStacksReporter final : public nsIMemoryReporter +{ + ~ThreadStacksReporter() = default; + +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + nsTArray mappings(1024); + MOZ_TRY(GetMemoryMappings(mappings)); + + // 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; + size_t mPrivateSize; + }; + AutoTArray threads; + + for (auto* thread : nsThread::Enumerate()) { + if (!thread->StackBase()) { + continue; + } + + 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). + size_t privateSize = mappings[idx].Referenced(); + + // On Linux, we have to be very careful matching memory regions to thread + // stacks. + // + // To begin with, the kernel only reports VM stats for regions of all + // adjacent pages with the same flags, protection, and backing file. + // There's no way to get finer-grained usage information for a subset of + // those pages. + // + // Stack segments always have a guard page at the bottom of the stack + // (assuming we only support stacks that grow down), so there's no danger + // of them being merged with other stack regions. At the top, there's no + // protection page, and no way to allocate one without using pthreads + // directly and allocating our own stacks. So we get around the problem by + // adding an extra VM flag (NOHUGEPAGES) to our stack region, which we + // don't expect to be set on any heap regions. But this is not fool-proof. + // + // A second kink is that different C libraries (and different versions + // thereof) report stack base locations and sizes differently with regard + // to the guard page. For the libraries that include the guard page in the + // stack size base pointer, we need to adjust those values to compensate. + // But it's possible that our logic will get out of sync with library + // changes, or someone will compile with an unexpected library. + // + // + // 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"); + + 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 + // possible of majorly over-reporting in that case. + std::min(privateSize, thread->StackSize()), + }); + } + + for (auto& thread : threads) { + nsPrintfCString path("explicit/thread-stacks/%s (tid=%u)", + thread.mName.get(), thread.mThreadId); + + aHandleReport->Callback( + EmptyCString(), path, + KIND_NONHEAP, UNITS_BYTES, + thread.mPrivateSize, + NS_LITERAL_CSTRING("The sizes of thread stacks which have been " + "committed to memory."), + aData); + } + + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(ThreadStacksReporter, nsIMemoryReporter) +#endif + #ifdef DEBUG // Ideally, this would be implemented in BlockingResourceBase.cpp. @@ -1559,6 +1665,10 @@ nsMemoryReporterManager::Init() RegisterStrongReporter(new AtomTablesReporter()); +#ifdef XP_LINUX + RegisterStrongReporter(new ThreadStacksReporter()); +#endif + #ifdef DEBUG RegisterStrongReporter(new DeadlockDetectorReporter()); #endif diff --git a/xpcom/threads/nsThread.cpp b/xpcom/threads/nsThread.cpp index d205107004bb..9657caeffa3d 100644 --- a/xpcom/threads/nsThread.cpp +++ b/xpcom/threads/nsThread.cpp @@ -7,6 +7,7 @@ #include "nsThread.h" #include "base/message_loop.h" +#include "base/platform_thread.h" // Chromium's logging can sometimes leak through... #ifdef LOG @@ -410,6 +411,7 @@ nsThread::ThreadFunc(void* aArg) nsThread* self = initData->thread; // strong reference self->mThread = PR_GetCurrentThread(); + self->mThreadId = uint32_t(PlatformThread::CurrentId()); self->mVirtualThread = GetCurrentVirtualThread(); self->mEventTarget->SetCurrentThread(); SetupCurrentThreadForChaosMode(); diff --git a/xpcom/threads/nsThread.h b/xpcom/threads/nsThread.h index fdd83e98fade..220e0182b2f6 100644 --- a/xpcom/threads/nsThread.h +++ b/xpcom/threads/nsThread.h @@ -74,6 +74,8 @@ public: const void* StackBase() const { return mStackBase; } size_t StackSize() const { return mStackSize; } + uint32_t ThreadId() const { return mThreadId; } + // If this flag is true, then the nsThread was created using // nsIThreadManager::NewThread. bool ShutdownRequired() @@ -177,6 +179,7 @@ protected: nsAutoTObserverArray>, 2> mEventObservers; int32_t mPriority; + uint32_t mThreadId; PRThread* mThread; uint32_t mNestedEventLoopDepth; uint32_t mStackSize;