Bug 1417976 - Part 1: Store the information of DocShells in CorePS r?mstange draft
authorNazım Can Altınova <canaltinova@gmail.com>
Fri, 20 Jul 2018 16:06:44 +0200
changeset 825816 05bf20daf6dff853fc8e77e9d449849b5eff4c65
parent 816856 aff060ad3204234adae2d59b3776207c6687ebfc
child 825817 b6670234c07a8fa0e9c55171a3fd71510c9270b4
push id118183
push userbmo:canaltinova@gmail.com
push dateThu, 02 Aug 2018 13:16:45 +0000
reviewersmstange
bugs1417976
milestone63.0a1
Bug 1417976 - Part 1: Store the information of DocShells in CorePS r?mstange Added a mechanism to register and unregister the DocShells from the CorePS depending on the state of the profiler. Registering mechanism is straightforward. During unregistration, if profiler is not active, we remove the DocShell information immediately. If profiler is active, we don't remove and we keep the profiler buffer position at that moment. During another DocShell registration we Discard the unregistered DocShells. If the profiler buffer position is greater than the position when we captured during unregistration, we delete the DocShell since that means there can't be any markers associated to this DocShell anymore. MozReview-Commit-ID: IVuKQ6drvkR
docshell/base/nsDocShell.cpp
tools/profiler/core/RegisteredDocShell.cpp
tools/profiler/core/RegisteredDocShell.h
tools/profiler/core/platform.cpp
tools/profiler/moz.build
tools/profiler/public/GeckoProfiler.h
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -415,16 +415,18 @@ nsDocShell::nsDocShell()
 
 nsDocShell::~nsDocShell()
 {
   MOZ_ASSERT(!mObserved);
 
   // Avoid notifying observers while we're in the dtor.
   mIsBeingDestroyed = true;
 
+  profiler_unregister_docshell(mHistoryID);
+
   Destroy();
 
   if (mSessionHistory) {
     mSessionHistory->LegacySHistoryInternal()->SetRootDocShell(nullptr);
   }
 
   if (--gDocShellCount == 0) {
     NS_IF_RELEASE(sURIFixup);
@@ -9181,16 +9183,22 @@ nsDocShell::InternalLoad(nsIURI* aURI,
 
   NS_ENSURE_TRUE(!mIsBeingDestroyed, NS_ERROR_NOT_AVAILABLE);
 
   rv = EnsureScriptEnvironment();
   if (NS_FAILED(rv)) {
     return rv;
   }
 
+#ifdef MOZ_GECKO_PROFILER
+  nsAutoCString spec;
+  aURI->GetSpec(spec);
+  profiler_register_docshell(mHistoryID, mName, spec);
+#endif
+
   // wyciwyg urls can only be loaded through history. Any normal load of
   // wyciwyg through docshell is  illegal. Disallow such loads.
   if (aLoadType & LOAD_CMD_NORMAL) {
     bool isWyciwyg = false;
     rv = aURI->SchemeIs("wyciwyg", &isWyciwyg);
     if ((isWyciwyg && NS_SUCCEEDED(rv)) || NS_FAILED(rv)) {
       return NS_ERROR_FAILURE;
     }
new file mode 100644
--- /dev/null
+++ b/tools/profiler/core/RegisteredDocShell.cpp
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 2; 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 "RegisteredDocShell.h"
+
+RegisteredDocShell::RegisteredDocShell(const nsID& aDocShellId,
+  const nsString& aName, const nsCString& aUrl)
+  : mDocShellId(aDocShellId)
+  , mName(aName)
+  , mUrl(aUrl)
+{
+  MOZ_COUNT_CTOR(RegisteredDocShell);
+}
+
+RegisteredDocShell::~RegisteredDocShell()
+{
+  MOZ_COUNT_DTOR(RegisteredDocShell);
+}
+
+void
+RegisteredDocShell::AppendElement(SpliceableJSONWriter& aWriter)
+{
+  aWriter.StringProperty("id", DocShellId().ToString());
+  aWriter.StringProperty("name", NS_ConvertUTF16toUTF8(Name()).get());
+  aWriter.StringProperty("url", Url().get());
+}
+
+size_t
+RegisteredDocShell::SizeOfIncludingThis(
+  mozilla::MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this);
+}
new file mode 100644
--- /dev/null
+++ b/tools/profiler/core/RegisteredDocShell.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; 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 RegisteredDocShell_h
+#define RegisteredDocShell_h
+
+#include "nsID.h"
+#include "platform.h"
+
+// This class contains information that's relevant to a single DocShell only
+// while that DocShell is running and registered with the profiler, but
+// regardless of whether the profiler is running. All accesses to it are
+// protected by the profiler state lock.
+// When the DocShell gets unregistered, we keep the profiler buffer position
+// to determine if we are still using this DocShell. If not, we unregister
+// it in the next DocShell registration.
+class RegisteredDocShell final
+{
+public:
+  RegisteredDocShell(const nsID& aDocShellId, const nsString& aName, const nsCString& aUrl);
+
+  ~RegisteredDocShell();
+
+  size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+  void AppendElement(SpliceableJSONWriter& aWriter);
+
+  const nsID& DocShellId() { return mDocShellId; }
+  const nsString& Name() { return mName; }
+  const nsCString& Url() { return mUrl; }
+
+  mozilla::Maybe<uint64_t> BufferPositionWhenUnregistered()
+  {
+    return mBufferPositionWhenUnregistered;
+  }
+
+  void NotifyUnregistered(uint64_t aBufferPosition)
+  {
+    mBufferPositionWhenUnregistered = mozilla::Some(aBufferPosition);
+  }
+
+private:
+  const nsID mDocShellId;
+  nsString mName;
+  nsCString mUrl;
+
+  // Holds the buffer position when DocShell is unregistered.
+  // It's used to determine if we still use this DocShell in the profiler or not.
+  mozilla::Maybe<uint64_t> mBufferPositionWhenUnregistered;
+};
+
+#endif // RegisteredDocShell_h
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -252,20 +252,26 @@ public:
                         size_t& aProfSize, size_t& aLulSize)
   {
     aProfSize += aMallocSizeOf(sInstance);
 
     for (auto& registeredThread : sInstance->mRegisteredThreads) {
       aProfSize += registeredThread->SizeOfIncludingThis(aMallocSizeOf);
     }
 
+    for (auto& registeredDocShell : sInstance->mRegisteredDocShells) {
+      aProfSize += registeredDocShell->SizeOfIncludingThis(aMallocSizeOf);
+    }
+
     // Measurement of the following things may be added later if DMD finds it
     // is worthwhile:
     // - CorePS::mRegisteredThreads itself (its elements' children are measured
     //   above)
+    // - CorePS::mRegisteredDocShells itself (its elements' children are measured
+    //   above)
     // - CorePS::mInterposeObserver
 
 #if defined(USE_LUL_STACKWALK)
     if (sInstance->mLul) {
       aLulSize += sInstance->mLul->SizeOfIncludingThis(aMallocSizeOf);
     }
 #endif
   }
@@ -284,16 +290,56 @@ public:
   {
     // Remove aRegisteredThread from mRegisteredThreads.
     // Can't use RemoveElement() because we can't equality-compare a UniquePtr
     // to a raw pointer.
     sInstance->mRegisteredThreads.RemoveElementsBy(
       [&](UniquePtr<RegisteredThread>& rt) { return rt.get() == aRegisteredThread; });
   }
 
+  static void AppendRegisteredDocShell(PSLockRef, UniquePtr<RegisteredDocShell>&& aRegisteredDocShell)
+  {
+    sInstance->mRegisteredDocShells.AppendElement(std::move(aRegisteredDocShell));
+  }
+
+  static void RemoveRegisteredDocShell(PSLockRef, const nsID& aRegisteredDocShellId)
+  {
+    // Remove RegisteredDocShell from mRegisteredDocShells by given Id.
+    sInstance->mRegisteredDocShells.RemoveElementsBy(
+      [&](UniquePtr<RegisteredDocShell>& rd) { return rd->DocShellId() == aRegisteredDocShellId; });
+  }
+
+  static const nsTArray<UniquePtr<RegisteredDocShell>>&
+  GetRegisteredDocShells(PSLockRef)
+  {
+    return sInstance->mRegisteredDocShells;
+  }
+
+  static void UnregisterDocshell(PSLockRef, const nsID& aRegisteredDocShellId, uint64_t aBufferPosition) {
+    for (size_t i = 0; i < sInstance->mRegisteredDocShells.Length(); i++) {
+      const mozilla::UniquePtr<RegisteredDocShell>& docshell = sInstance->mRegisteredDocShells[i];
+      if (docshell->DocShellId() == aRegisteredDocShellId) {
+        docshell->NotifyUnregistered(aBufferPosition);
+      }
+    }
+  }
+
+  static void DiscardExpiredDocshells(PSLockRef, uint64_t aBufferPosition)
+  {
+    sInstance->mRegisteredDocShells.RemoveElementsBy(
+      [aBufferPosition](UniquePtr<RegisteredDocShell>& aProfiledDocShell) {
+        Maybe<uint64_t> bufferPosition =
+          aProfiledDocShell->BufferPositionWhenUnregistered();
+          if (bufferPosition) {
+            return *bufferPosition < aBufferPosition;
+          }
+          return false;
+      });
+  }
+
 #ifdef USE_LUL_STACKWALK
   static lul::LUL* Lul(PSLockRef) { return sInstance->mLul.get(); }
   static void SetLul(PSLockRef, UniquePtr<lul::LUL> aLul)
   {
     sInstance->mLul = std::move(aLul);
   }
 #endif
 
@@ -303,16 +349,20 @@ private:
 
   // The time that the process started.
   const TimeStamp mProcessStartTime;
 
   // Info on all the registered threads.
   // ThreadIds in mRegisteredThreads are unique.
   nsTArray<UniquePtr<RegisteredThread>> mRegisteredThreads;
 
+  // Info on all the registered Docshells.
+  // DocshellIds in mRegisteredDocShells are unique.
+  nsTArray<UniquePtr<RegisteredDocShell>> mRegisteredDocShells;
+
 #ifdef USE_LUL_STACKWALK
   // LUL's state. Null prior to the first activation, non-null thereafter.
   UniquePtr<lul::LUL> mLul;
 #endif
 };
 
 CorePS* CorePS::sInstance = nullptr;
 
@@ -3324,16 +3374,59 @@ profiler_unregister_thread()
     //   (Whether or not it should, this does happen in practice.)
     //
     // Either way, TLSRegisteredThread should be empty.
     MOZ_RELEASE_ASSERT(!TLSRegisteredThread::RegisteredThread(lock));
   }
 }
 
 void
+profiler_register_docshell(const nsID& aId, const nsString& aName, const nsCString& aUrl)
+{
+  DEBUG_LOG("profiler_register_docshell(%s, %s, %s)", aId.ToString(),
+            NS_ConvertUTF16toUTF8(aName).get(), aUrl.get());
+
+  MOZ_RELEASE_ASSERT(CorePS::Exists());
+
+  PSAutoLock lock(gPSMutex);
+
+  UniquePtr<RegisteredDocShell> registeredDocShell =
+    MakeUnique<RegisteredDocShell>(aId, aName, aUrl);
+  CorePS::AppendRegisteredDocShell(lock, std::move(registeredDocShell));
+
+  // After appending the given DocShell to CorePS, looking for the expired
+  // DocShells and removing them if there are any.
+  if (ActivePS::Exists(lock)) {
+    CorePS::DiscardExpiredDocshells(lock, ActivePS::Buffer(lock).mRangeStart);
+  }
+}
+
+void
+profiler_unregister_docshell(const nsID& aRegisteredDocShellId)
+{
+  if (!CorePS::Exists()) {
+    // This function can be called after the main thread has already shut down.
+    return;
+  }
+
+  PSAutoLock lock(gPSMutex);
+
+  // During unregistration, if the profiler is active, we have to keep the DocShell
+  // information since there may be some markers associated to given DocShell.
+  // But if profiler is not active. we have no reason to keep the DocShell information
+  // here because there can't be any marker associated to given DocShell.
+  if (ActivePS::Exists(lock)) {
+    CorePS::UnregisterDocshell(lock, aRegisteredDocShellId, ActivePS::Buffer(lock).mRangeEnd);
+    return;
+  }
+
+  CorePS::RemoveRegisteredDocShell(lock, aRegisteredDocShellId);
+}
+
+void
 profiler_thread_sleep()
 {
   // This function runs both on and off the main thread.
 
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
   RacyRegisteredThread* racyRegisteredThread =
     TLSRegisteredThread::RacyRegisteredThread();
--- a/tools/profiler/moz.build
+++ b/tools/profiler/moz.build
@@ -20,16 +20,17 @@ if CONFIG['MOZ_GECKO_PROFILER']:
     UNIFIED_SOURCES += [
         'core/platform.cpp',
         'core/ProfileBuffer.cpp',
         'core/ProfileBufferEntry.cpp',
         'core/ProfiledThreadData.cpp',
         'core/ProfileJSONWriter.cpp',
         'core/ProfilerBacktrace.cpp',
         'core/ProfilerMarkerPayload.cpp',
+        'core/RegisteredDocShell.cpp',
         'core/RegisteredThread.cpp',
         'gecko/ChildProfilerController.cpp',
         'gecko/nsProfilerFactory.cpp',
         'gecko/nsProfilerStartParams.cpp',
         'gecko/ProfilerChild.cpp',
         'gecko/ProfilerIOInterposeObserver.cpp',
         'gecko/ProfilerParent.cpp',
         'gecko/ThreadResponsiveness.cpp',
--- a/tools/profiler/public/GeckoProfiler.h
+++ b/tools/profiler/public/GeckoProfiler.h
@@ -277,16 +277,20 @@ void profiler_ensure_started(uint32_t aE
 // same whether the profiler is active or inactive.
 #define PROFILER_REGISTER_THREAD(name) \
   do { char stackTop; profiler_register_thread(name, &stackTop); } while (0)
 #define PROFILER_UNREGISTER_THREAD() \
   profiler_unregister_thread()
 void profiler_register_thread(const char* name, void* guessStackTop);
 void profiler_unregister_thread();
 
+// Register/unregister DocShells with the profiler.
+void profiler_register_docshell(const nsID& id, const nsString& name, const nsCString& url);
+void profiler_unregister_docshell(const nsID& aRegisteredDocShellId);
+
 // Register and unregister a thread within a scope.
 #define AUTO_PROFILER_REGISTER_THREAD(name) \
   mozilla::AutoProfilerRegisterThread PROFILER_RAII(name)
 
 // Pause and resume the profiler. No-ops if the profiler is inactive. While
 // paused the profile will not take any samples and will not record any data
 // into its buffers. The profiler remains fully initialized in this state.
 // Timeline markers will still be stored. This feature will keep JavaScript