Bug 1431755 - Part 2: Teach GeckoProfiler to profile responsiveness on nsIThreads. draft
authorByron Campen [:bwc] <docfaraday@gmail.com>
Fri, 19 Jan 2018 09:42:47 -0600
changeset 750237 806376e676e46e8b34d44940968d82ef5af94933
parent 749003 728ce5811598a3b9bce5d9eb576d060184108007
child 750238 f33b74f557d67f45530439ecbd05ff28ffdf03af
push id97593
push userbcampen@mozilla.com
push dateThu, 01 Feb 2018 19:52:27 +0000
bugs1431755
milestone60.0a1
Bug 1431755 - Part 2: Teach GeckoProfiler to profile responsiveness on nsIThreads. MozReview-Commit-ID: AqpNf9pDzrg
tools/profiler/core/ThreadInfo.cpp
tools/profiler/core/ThreadInfo.h
tools/profiler/core/platform.cpp
tools/profiler/gecko/ThreadResponsiveness.cpp
tools/profiler/gecko/ThreadResponsiveness.h
tools/profiler/tests/gtest/ThreadProfileTest.cpp
--- a/tools/profiler/core/ThreadInfo.cpp
+++ b/tools/profiler/core/ThreadInfo.cpp
@@ -17,20 +17,22 @@
 #define getpid _getpid
 #else
 #include <unistd.h> // for getpid()
 #endif
 
 ThreadInfo::ThreadInfo(const char* aName,
                        int aThreadId,
                        bool aIsMainThread,
+                       nsIEventTarget* aThread,
                        void* aStackTop)
   : mName(strdup(aName))
   , mRegisterTime(TimeStamp::Now())
   , mIsMainThread(aIsMainThread)
+  , mThread(aThread)
   , mRacyInfo(mozilla::MakeNotNull<RacyThreadInfo*>(aThreadId))
   , mPlatformData(AllocPlatformData(aThreadId))
   , mStackTop(aStackTop)
   , mIsBeingProfiled(false)
   , mFirstSavedStreamedSampleTime{0.0}
   , mContext(nullptr)
   , mJSSampling(INACTIVE)
   , mLastSample()
@@ -55,19 +57,17 @@ ThreadInfo::~ThreadInfo()
   delete mRacyInfo;
 }
 
 void
 ThreadInfo::StartProfiling()
 {
   mIsBeingProfiled = true;
   mRacyInfo->ReinitializeOnResume();
-  if (mIsMainThread) {
-    mResponsiveness.emplace();
-  }
+  mResponsiveness.emplace(mThread, mIsMainThread);
 }
 
 void
 ThreadInfo::StopProfiling()
 {
   mResponsiveness.reset();
   mIsBeingProfiled = false;
 }
--- a/tools/profiler/core/ThreadInfo.h
+++ b/tools/profiler/core/ThreadInfo.h
@@ -170,17 +170,17 @@ private:
 // some of the fields in this class are only relevant while the thread is
 // alive. It's possible that this class could be refactored so there is a
 // clearer split between those fields and the fields that are still relevant
 // after the thread exists.
 class ThreadInfo final
 {
 public:
   ThreadInfo(const char* aName, int aThreadId, bool aIsMainThread,
-             void* aStackTop);
+             nsIEventTarget* aThread, void* aStackTop);
 
   ~ThreadInfo();
 
   const char* Name() const { return mName.get(); }
 
   // This is a safe read even when the target thread is not blocked, as this
   // thread id is never mutated.
   int ThreadId() const { return RacyInfo()->ThreadId(); }
@@ -202,16 +202,17 @@ public:
 
   ProfileBuffer::LastSample& LastSample() { return mLastSample; }
 
 private:
   mozilla::UniqueFreePtr<char> mName;
   mozilla::TimeStamp mRegisterTime;
   mozilla::TimeStamp mUnregisterTime;
   const bool mIsMainThread;
+  nsCOMPtr<nsIEventTarget> mThread;
 
   // The thread's RacyThreadInfo. This is an owning pointer. It could be an
   // inline member, but we don't do that because RacyThreadInfo is quite large
   // (due to the PseudoStack within it), and we have ThreadInfo vectors and so
   // we'd end up wasting a lot of space in those vectors for excess elements.
   mozilla::NotNull<RacyThreadInfo*> mRacyInfo;
 
   UniquePlatformData mPlatformData;
@@ -233,17 +234,17 @@ public:
   void FlushSamplesAndMarkers(const mozilla::TimeStamp& aProcessStartTime,
                               ProfileBuffer& aBuffer);
 
   // Returns nullptr if this is not the main thread or if this thread is not
   // being profiled.
   ThreadResponsiveness* GetThreadResponsiveness()
   {
     ThreadResponsiveness* responsiveness = mResponsiveness.ptrOr(nullptr);
-    MOZ_ASSERT(!!responsiveness == (mIsMainThread && mIsBeingProfiled));
+    MOZ_ASSERT(!responsiveness || mIsBeingProfiled);
     return responsiveness;
   }
 
   // Set the JSContext of the thread to be sampled. Sampling cannot begin until
   // this has been set.
   void SetJSContext(JSContext* aContext)
   {
     // This function runs on-thread.
@@ -318,17 +319,17 @@ private:
   // stringifying JIT frames). In the case of JSRuntime destruction,
   // FlushSamplesAndMarkers should be called to save them. These are spliced
   // into the final stream.
   mozilla::UniquePtr<char[]> mSavedStreamedSamples;
   double mFirstSavedStreamedSampleTime;
   mozilla::UniquePtr<char[]> mSavedStreamedMarkers;
   mozilla::Maybe<UniqueStacks> mUniqueStacks;
 
-  // This is only used for the main thread.
+  // This is used only for nsIThreads.
   mozilla::Maybe<ThreadResponsiveness> mResponsiveness;
 
 public:
   // If this is a JS thread, this is its JSContext, which is required for any
   // JS sampling.
   JSContext* mContext;
 
 private:
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -1998,20 +1998,17 @@ SamplerThread::Run()
               ActivePS::Buffer(lock).DuplicateLastSample(
                 info->ThreadId(), CorePS::ProcessStartTime(),
                 info->LastSample());
             if (dup_ok) {
               continue;
             }
           }
 
-          // We only track responsiveness for the main thread.
-          if (info->IsMainThread()) {
-            info->GetThreadResponsiveness()->Update();
-          }
+          info->GetThreadResponsiveness()->Update();
 
           // We only get the memory measurements once for all live threads.
           int64_t rssMemory = 0;
           int64_t ussMemory = 0;
           if (i == 0 && ActivePS::FeatureMemory(lock)) {
             rssMemory = nsMemoryReporterManager::ResidentFast();
 #if defined(GP_OS_linux) || defined(GP_OS_android)
             ussMemory = nsMemoryReporterManager::ResidentUnique();
@@ -2184,17 +2181,19 @@ locked_register_thread(PSLockRef aLock, 
 
   VTUNE_REGISTER_THREAD(aName);
 
   if (!TLSInfo::Init(aLock)) {
     return;
   }
 
   ThreadInfo* info = new ThreadInfo(aName, Thread::GetCurrentId(),
-                                    NS_IsMainThread(), aStackTop);
+                                    NS_IsMainThread(),
+                                    NS_GetCurrentThreadNoCreate(),
+                                    aStackTop);
   TLSInfo::SetInfo(aLock, info);
 
   if (ActivePS::Exists(aLock) && ActivePS::ShouldProfileThread(aLock, info)) {
     info->StartProfiling();
     if (ActivePS::FeatureJS(aLock)) {
       // This StartJSSampling() call is on-thread, so we can poll manually to
       // start JS sampling immediately.
       info->StartJSSampling();
--- a/tools/profiler/gecko/ThreadResponsiveness.cpp
+++ b/tools/profiler/gecko/ThreadResponsiveness.cpp
@@ -8,129 +8,163 @@
 #include "mozilla/Atomics.h"
 #include "mozilla/SystemGroup.h"
 
 #include "nsITimer.h"
 #include "platform.h"
 
 using namespace mozilla;
 
-class CheckResponsivenessTask : public Runnable,
+class CheckResponsivenessTask : public CancelableRunnable,
                                 public nsITimerCallback {
 public:
-  CheckResponsivenessTask()
-    : Runnable("CheckResponsivenessTask")
+  explicit CheckResponsivenessTask(nsIEventTarget* aThread, bool aIsMainThread)
+    : CancelableRunnable("CheckResponsivenessTask")
     , mStartToPrevTracer_us(uint64_t(profiler_time() * 1000.0))
     , mStop(false)
     , mHasEverBeenSuccessfullyDispatched(false)
+    , mThread(aThread)
+    , mIsMainThread(aIsMainThread)
   {
   }
 
 protected:
   ~CheckResponsivenessTask()
   {
   }
 
 public:
 
   // Must be called from the same thread every time. Call that the update
   // thread, because it's the thread that ThreadResponsiveness::Update() is
   // called on. In reality it's the profiler's sampler thread.
-  void DoFirstDispatchIfNeeded()
+  bool DoFirstDispatchIfNeeded()
   {
     if (mHasEverBeenSuccessfullyDispatched) {
-      return;
+      return true;
     }
 
-    // We can hit this code very early during startup, at a time where the
-    // thread manager hasn't been initialized with the main thread yet.
-    // In that case, calling SystemGroup::Dispatch would assert, so we make
-    // sure that NS_GetMainThread succeeds before attempting to dispatch this
-    // runnable.
-    nsCOMPtr<nsIThread> mainThread;
-    nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread));
-    if (NS_SUCCEEDED(rv) && mainThread) {
-      rv = SystemGroup::Dispatch(TaskCategory::Other, do_AddRef(this));
+    // The profiler for the main thread is set up before the thread manager is,
+    // meaning we can't get the nsIThread when the CheckResponsivenessTask is
+    // constructed. We _do_ know whether it is the main thread at that time,
+    // however, so here's the workaround. We can still hit this code before the
+    // thread manager is initted, in which case we won't try to record
+    // responsiveness, which is fine because there's no event queue to check
+    // responsiveness on anyway.
+    if (mIsMainThread) {
+      if (!mThread) {
+        nsCOMPtr<nsIThread> temp;
+        NS_GetMainThread(getter_AddRefs(temp));
+        mThread = temp.forget();
+      }
+
+      if (mThread) {
+        nsresult rv = SystemGroup::Dispatch(TaskCategory::Other, do_AddRef(this));
+        if (NS_SUCCEEDED(rv)) {
+          mHasEverBeenSuccessfullyDispatched = true;
+        }
+      }
+    } else if (mThread) {
+      nsresult rv = mThread->Dispatch(this, nsIThread::NS_DISPATCH_NORMAL);
       if (NS_SUCCEEDED(rv)) {
         mHasEverBeenSuccessfullyDispatched = true;
       }
     }
+
+    return mHasEverBeenSuccessfullyDispatched;
   }
 
-  // Can only run on the main thread.
+  nsresult Cancel() override
+  {
+    // No special work needed.
+    return NS_OK;
+  }
+
+  // Only runs on the thread being profiled
   NS_IMETHOD Run() override
   {
     mStartToPrevTracer_us = uint64_t(profiler_time() * 1000.0);
 
     if (!mStop) {
       if (!mTimer) {
-        mTimer = NS_NewTimer(
-          SystemGroup::EventTargetFor(TaskCategory::Other));
+        if (mIsMainThread) {
+          mTimer = NS_NewTimer(
+            SystemGroup::EventTargetFor(TaskCategory::Other));
+        } else {
+          mTimer = NS_NewTimer();
+        }
       }
       mTimer->InitWithCallback(this, 16, nsITimer::TYPE_ONE_SHOT);
     }
 
     return NS_OK;
   }
 
-  // Main thread only
+  // Should always fire on the thread being profiled
   NS_IMETHOD Notify(nsITimer* aTimer) final override
   {
-    SystemGroup::Dispatch(TaskCategory::Other,
-                          do_AddRef(this));
+    mThread->Dispatch(this, nsIThread::NS_DISPATCH_NORMAL);
     return NS_OK;
   }
 
   // Can be called on any thread.
   void Terminate() {
     mStop = true;
   }
 
   // Can be called on any thread.
   double GetStartToPrevTracer_ms() const {
     return mStartToPrevTracer_us / 1000.0;
   }
 
   NS_DECL_ISUPPORTS_INHERITED
 
 private:
-  // The timer that's responsible for redispatching this event to the main
-  // thread. This field is only accessed on the main thread.
+  // The timer that's responsible for redispatching this event to the thread we
+  // are profiling (ie; mThread). Only touched on mThread.
   nsCOMPtr<nsITimer> mTimer;
 
   // The time (in integer microseconds since process startup) at which this
   // event was last processed (Run() was last called).
-  // This field is written on the main thread and read on the update thread.
+  // This field is written on mThread and read on the update thread.
   // This is stored as integer microseconds instead of double milliseconds
   // because Atomic<double> is not available.
   Atomic<uint64_t> mStartToPrevTracer_us;
 
   // Whether we should stop redispatching this event once the timer fires the
   // next time. Set to true by any thread when the profiler is stopped; read on
-  // the main thread.
+  // mThread.
   Atomic<bool> mStop;
 
   // Only accessed on the update thread.
   bool mHasEverBeenSuccessfullyDispatched;
+
+  // The thread that we're profiling. Use nsIEventTarget to allow for checking
+  // responsiveness on non-nsIThreads someday.
+  nsCOMPtr<nsIEventTarget> mThread;
+  bool mIsMainThread;
 };
 
-NS_IMPL_ISUPPORTS_INHERITED(CheckResponsivenessTask, mozilla::Runnable,
+NS_IMPL_ISUPPORTS_INHERITED(CheckResponsivenessTask, CancelableRunnable,
                             nsITimerCallback)
 
-ThreadResponsiveness::ThreadResponsiveness()
-  : mActiveTracerEvent(new CheckResponsivenessTask())
+ThreadResponsiveness::ThreadResponsiveness(nsIEventTarget* aThread,
+                                           bool aIsMainThread)
+  : mActiveTracerEvent(new CheckResponsivenessTask(aThread, aIsMainThread))
 {
   MOZ_COUNT_CTOR(ThreadResponsiveness);
 }
 
 ThreadResponsiveness::~ThreadResponsiveness()
 {
   MOZ_COUNT_DTOR(ThreadResponsiveness);
   mActiveTracerEvent->Terminate();
 }
 
 void
 ThreadResponsiveness::Update()
 {
-  mActiveTracerEvent->DoFirstDispatchIfNeeded();
+  if (!mActiveTracerEvent->DoFirstDispatchIfNeeded()) {
+    return;
+  }
   mStartToPrevTracer_ms = Some(mActiveTracerEvent->GetStartToPrevTracer_ms());
 }
 
--- a/tools/profiler/gecko/ThreadResponsiveness.h
+++ b/tools/profiler/gecko/ThreadResponsiveness.h
@@ -7,21 +7,22 @@
 #define ThreadResponsiveness_h
 
 #include "nsISupports.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/TimeStamp.h"
 
 class CheckResponsivenessTask;
+class nsIEventTarget;
 
 // This class should only be used for the main thread.
 class ThreadResponsiveness {
 public:
-  explicit ThreadResponsiveness();
+  explicit ThreadResponsiveness(nsIEventTarget* aThread, bool aIsMainThread);
 
   ~ThreadResponsiveness();
 
   void Update();
 
   // The number of milliseconds that elapsed since the last
   // CheckResponsivenessTask was processed.
   double GetUnresponsiveDuration(double aStartToNow_ms) const {
--- a/tools/profiler/tests/gtest/ThreadProfileTest.cpp
+++ b/tools/profiler/tests/gtest/ThreadProfileTest.cpp
@@ -7,35 +7,41 @@
 #include "gtest/gtest.h"
 
 #include "ProfileBufferEntry.h"
 #include "ThreadInfo.h"
 
 // Make sure we can initialize our thread profile
 TEST(ThreadProfile, Initialization) {
   int tid = 1000;
-  ThreadInfo info("testThread", tid, true, nullptr);
+  nsCOMPtr<nsIThread> mainThread;
+  NS_GetMainThread(getter_AddRefs(mainThread));
+  ThreadInfo info("testThread", tid, true, mainThread, nullptr);
   info.StartProfiling();
 }
 
 // Make sure we can record one entry and read it
 TEST(ThreadProfile, InsertOneEntry) {
   int tid = 1000;
-  ThreadInfo info("testThread", tid, true, nullptr);
+  nsCOMPtr<nsIThread> mainThread;
+  NS_GetMainThread(getter_AddRefs(mainThread));
+  ThreadInfo info("testThread", tid, true, mainThread, nullptr);
   auto pb = MakeUnique<ProfileBuffer>(10);
   pb->AddEntry(ProfileBufferEntry::Time(123.1));
   ASSERT_TRUE(pb->mEntries != nullptr);
   ASSERT_TRUE(pb->mEntries[pb->mReadPos].IsTime());
   ASSERT_TRUE(pb->mEntries[pb->mReadPos].u.mDouble == 123.1);
 }
 
 // See if we can insert some entries
 TEST(ThreadProfile, InsertEntriesNoWrap) {
   int tid = 1000;
-  ThreadInfo info("testThread", tid, true, nullptr);
+  nsCOMPtr<nsIThread> mainThread;
+  NS_GetMainThread(getter_AddRefs(mainThread));
+  ThreadInfo info("testThread", tid, true, mainThread, nullptr);
   auto pb = MakeUnique<ProfileBuffer>(100);
   int test_size = 50;
   for (int i = 0; i < test_size; i++) {
     pb->AddEntry(ProfileBufferEntry::Time(i));
   }
   ASSERT_TRUE(pb->mEntries != nullptr);
   int readPos = pb->mReadPos;
   while (readPos != pb->mWritePos) {
@@ -46,17 +52,19 @@ TEST(ThreadProfile, InsertEntriesNoWrap)
 }
 
 // See if wrapping works as it should in the basic case
 TEST(ThreadProfile, InsertEntriesWrap) {
   int tid = 1000;
   // we can fit only 24 entries in this buffer because of the empty slot
   int entries = 24;
   int buffer_size = entries + 1;
-  ThreadInfo info("testThread", tid, true, nullptr);
+  nsCOMPtr<nsIThread> mainThread;
+  NS_GetMainThread(getter_AddRefs(mainThread));
+  ThreadInfo info("testThread", tid, true, mainThread, nullptr);
   auto pb = MakeUnique<ProfileBuffer>(buffer_size);
   int test_size = 43;
   for (int i = 0; i < test_size; i++) {
     pb->AddEntry(ProfileBufferEntry::Time(i));
   }
   ASSERT_TRUE(pb->mEntries != nullptr);
   int readPos = pb->mReadPos;
   int ctr = 0;