Bug 1462168 - Report number of IPC channels in about:memory. r?mccr8 draft
authorJed Davis <jld@mozilla.com>
Thu, 19 Jul 2018 17:48:05 -0600
changeset 820692 c9f38d1ad47b1812d7062192f13b088615cb9b53
parent 820622 690cb3015db6645b335ac4835a50073cb6a3e23c
push id116896
push userbmo:jld@mozilla.com
push dateFri, 20 Jul 2018 03:45:54 +0000
reviewersmccr8
bugs1462168
milestone63.0a1
Bug 1462168 - Report number of IPC channels in about:memory. r?mccr8 This adds an about:memory branch, "ipc-channels", which counts cross-process IPC channels (ProcessLink, IPC::Channel) broken down by the top-level IPDL actor name; these use OS resources which may be limited (file descriptors on Linux and Mac). Intra-process use of IPC (ThreadLink) is not counted. The maximum channel count for each actor type is reported in another branch, "ipc-channels-peak". This might be useful if there are conditions that cause transient fd exhaustion, for example. This patch also works around a problem where MessageChannel was trying to register reporters too early in child processes, and failing. MozReview-Commit-ID: CGEwny2ipcu
ipc/glue/MessageChannel.cpp
ipc/glue/MessageChannel.h
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -8,26 +8,29 @@
 #include "mozilla/ipc/MessageChannel.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/ipc/ProtocolUtils.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Move.h"
+#include "mozilla/Mutex.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
 #include "nsAppRunner.h"
-#include "mozilla/UniquePtr.h"
 #include "nsAutoPtr.h"
+#include "nsContentUtils.h"
+#include "nsDataHashtable.h"
 #include "nsDebug.h"
 #include "nsISupportsImpl.h"
-#include "nsContentUtils.h"
+#include "nsPrintfCString.h"
 #include <math.h>
 
 #ifdef MOZ_TASK_TRACER
 #include "GeckoTaskTracer.h"
 using namespace mozilla::tasktracer;
 #endif
 
 // Undo the damage done by mozzconf.h
@@ -497,24 +500,122 @@ public:
             "unresolved-ipc-responses", KIND_OTHER, UNITS_COUNT, MessageChannel::gUnresolvedResponses,
             "Outstanding IPC async message responses that are still not resolved.");
         return NS_OK;
     }
 };
 
 NS_IMPL_ISUPPORTS(PendingResponseReporter, nsIMemoryReporter)
 
+class ChannelCountReporter final : public nsIMemoryReporter
+{
+    ~ChannelCountReporter() = default;
+
+    struct ChannelCounts {
+        size_t mNow;
+        size_t mMax;
+
+        ChannelCounts() : mNow(0), mMax(0) { }
+
+        void Inc() {
+            ++mNow;
+            if (mMax < mNow) {
+                mMax = mNow;
+            }
+        }
+
+        void Dec() {
+            MOZ_ASSERT(mNow > 0);
+            --mNow;
+        }
+    };
+
+    using CountTable = nsDataHashtable<nsDepCharHashKey, ChannelCounts>;
+
+    static StaticMutex sChannelCountMutex;
+    static CountTable* sChannelCounts;
+
+public:
+    NS_DECL_THREADSAFE_ISUPPORTS
+
+    NS_IMETHOD
+    CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
+                   bool aAnonymize) override
+    {
+        StaticMutexAutoLock countLock(sChannelCountMutex);
+        if (!sChannelCounts) {
+            return NS_OK;
+        }
+        for (auto iter = sChannelCounts->Iter(); !iter.Done(); iter.Next()) {
+            nsPrintfCString pathNow("ipc-channels/%s", iter.Key());
+            nsPrintfCString pathMax("ipc-channels-peak/%s", iter.Key());
+            nsPrintfCString descNow("Number of IPC channels for"
+                                    " top-level actor type %s", iter.Key());
+            nsPrintfCString descMax("Peak number of IPC channels for"
+                                    " top-level actor type %s", iter.Key());
+
+            aHandleReport->Callback(EmptyCString(), pathNow, KIND_OTHER,
+                                    UNITS_COUNT, iter.Data().mNow, descNow,
+                                    aData);
+            aHandleReport->Callback(EmptyCString(), pathMax, KIND_OTHER,
+                                    UNITS_COUNT, iter.Data().mMax, descMax,
+                                    aData);
+        }
+        return NS_OK;
+    }
+
+    static void
+    Increment(const char* aName)
+    {
+        StaticMutexAutoLock countLock(sChannelCountMutex);
+        if (!sChannelCounts) {
+            sChannelCounts = new CountTable;
+        }
+        sChannelCounts->GetOrInsert(aName).Inc();
+    }
+
+    static void
+    Decrement(const char* aName)
+    {
+        StaticMutexAutoLock countLock(sChannelCountMutex);
+        MOZ_ASSERT(sChannelCounts);
+        sChannelCounts->GetOrInsert(aName).Dec();
+    }
+};
+
+StaticMutex ChannelCountReporter::sChannelCountMutex;
+ChannelCountReporter::CountTable* ChannelCountReporter::sChannelCounts;
+
+NS_IMPL_ISUPPORTS(ChannelCountReporter, nsIMemoryReporter)
+
+// In child processes, the first MessageChannel is created before
+// XPCOM is initialized enough to construct the memory reporter
+// manager.  This retries every time a MessageChannel is constructed,
+// which is good enough in practice.
+template<class Reporter>
+static void TryRegisterStrongMemoryReporter()
+{
+    static Atomic<bool> registered;
+    if (registered.compareExchange(false, true)) {
+        RefPtr<Reporter> reporter = new Reporter();
+        if (NS_FAILED(RegisterStrongMemoryReporter(reporter))) {
+            registered = false;
+        }
+    }
+}
+
 Atomic<size_t> MessageChannel::gUnresolvedResponses;
 
 MessageChannel::MessageChannel(const char* aName,
                                IToplevelProtocol *aListener)
   : mName(aName),
     mListener(aListener),
     mChannelState(ChannelClosed),
     mSide(UnknownSide),
+    mIsCrossProcess(false),
     mLink(nullptr),
     mWorkerLoop(nullptr),
     mChannelErrorTask(nullptr),
     mWorkerThread(nullptr),
     mTimeoutMs(kNoTimeout),
     mInTimeoutSecondHalf(false),
     mNextSeqno(0),
     mLastSendError(SyncSendError::SendSuccess),
@@ -548,20 +649,18 @@ MessageChannel::MessageChannel(const cha
       this,
       &MessageChannel::DispatchOnChannelConnected);
 
 #ifdef OS_WIN
     mEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
     MOZ_RELEASE_ASSERT(mEvent, "CreateEvent failed! Nothing is going to work!");
 #endif
 
-    static Atomic<bool> registered;
-    if (registered.compareExchange(false, true)) {
-        RegisterStrongMemoryReporter(new PendingResponseReporter());
-    }
+    TryRegisterStrongMemoryReporter<PendingResponseReporter>();
+    TryRegisterStrongMemoryReporter<ChannelCountReporter>();
 }
 
 MessageChannel::~MessageChannel()
 {
     MOZ_COUNT_DTOR(ipc::MessageChannel);
     IPC_ASSERT(mCxxStackFrames.empty(), "mismatched CxxStackFrame ctor/dtors");
 #ifdef OS_WIN
     if (mEvent) {
@@ -744,16 +843,19 @@ MessageChannel::Clear()
 
     gUnresolvedResponses -= mPendingResponses.size();
     for (auto& pair : mPendingResponses) {
         pair.second.get()->Reject(ResponseRejectReason::ChannelClosed);
     }
     mPendingResponses.clear();
 
     mWorkerLoop = nullptr;
+    if (mLink != nullptr && mIsCrossProcess) {
+        ChannelCountReporter::Decrement(mName);
+    }
     delete mLink;
     mLink = nullptr;
 
     mOnChannelConnectedTask->Cancel();
 
     if (mChannelErrorTask) {
         mChannelErrorTask->Cancel();
         mChannelErrorTask = nullptr;
@@ -782,16 +884,18 @@ MessageChannel::Open(Transport* aTranspo
     mWorkerLoop = MessageLoop::current();
     mWorkerThread = GetCurrentVirtualThread();
     mWorkerLoop->AddDestructionObserver(this);
     mListener->SetIsMainThreadProtocol();
 
     ProcessLink *link = new ProcessLink(this);
     link->Open(aTransport, aIOLoop, aSide); // :TODO: n.b.: sets mChild
     mLink = link;
+    mIsCrossProcess = true;
+    ChannelCountReporter::Increment(mName);
     return true;
 }
 
 bool
 MessageChannel::Open(MessageChannel *aTargetChan, nsIEventTarget *aEventTarget, Side aSide)
 {
     // Opens a connection to another thread in the same process.
 
--- a/ipc/glue/MessageChannel.h
+++ b/ipc/glue/MessageChannel.h
@@ -628,16 +628,17 @@ private:
     const char* mName;
 
     // Based on presumption the listener owns and overlives the channel,
     // this is never nullified.
     IToplevelProtocol* mListener;
     ChannelState mChannelState;
     RefPtr<RefCountedMonitor> mMonitor;
     Side mSide;
+    bool mIsCrossProcess;
     MessageLink* mLink;
     MessageLoop* mWorkerLoop;           // thread where work is done
     RefPtr<CancelableRunnable> mChannelErrorTask;  // NotifyMaybeChannelError runnable
 
     // Thread we are allowed to send and receive on. This persists even after
     // mWorkerLoop is cleared during channel shutdown.
     PRThread* mWorkerThread;