Bug 1380081 - Part 7: Add all necessary data for BHR to nsIHangDetails, r=froydnj
authorMichael Layzell <michael@thelayzells.com>
Thu, 20 Jul 2017 14:31:56 -0400
changeset 374836 f35ba2fdb5182b8aab79361be5803cad9b570faf
parent 374835 a6159f80441794cde58a1d80b3fdab32d02c2883
child 374837 c9312138747143e11f608b7a9ecf4890f249336a
push id93781
push usermichael@thelayzells.com
push dateTue, 15 Aug 2017 20:37:28 +0000
treeherdermozilla-inbound@ebd9e87fae8d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1380081
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1380081 - Part 7: Add all necessary data for BHR to nsIHangDetails, r=froydnj We're going to use HangDetails as the type containing hang information. We'll have a JS component which reads the data out of nsIHangDetails, builds the payload, and submits it to telemetry for us. We'll do it in JS because telemetry has to be submitted from JS. This patch also adds IPC serization for the relevant types so that we can send HangDetails objects over IPDL. MozReview-Commit-ID: CeikKabY9Vs
toolkit/components/backgroundhangmonitor/BackgroundHangMonitor.cpp
toolkit/components/backgroundhangmonitor/HangDetails.cpp
toolkit/components/backgroundhangmonitor/HangDetails.h
toolkit/components/backgroundhangmonitor/HangStack.cpp
toolkit/components/backgroundhangmonitor/HangStack.h
toolkit/components/backgroundhangmonitor/ThreadHangStats.cpp
toolkit/components/backgroundhangmonitor/ThreadHangStats.h
toolkit/components/backgroundhangmonitor/ThreadStackHelper.cpp
toolkit/components/backgroundhangmonitor/ThreadStackHelper.h
toolkit/components/backgroundhangmonitor/moz.build
toolkit/components/backgroundhangmonitor/nsIHangDetails.idl
toolkit/components/telemetry/Telemetry.cpp
--- a/toolkit/components/backgroundhangmonitor/BackgroundHangMonitor.cpp
+++ b/toolkit/components/backgroundhangmonitor/BackgroundHangMonitor.cpp
@@ -7,31 +7,30 @@
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/BackgroundHangMonitor.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/Move.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/Telemetry.h"
-#include "mozilla/ThreadHangStats.h"
 #include "mozilla/ThreadLocal.h"
 #include "mozilla/SystemGroup.h"
 
 #include "prinrval.h"
 #include "prthread.h"
 #include "ThreadStackHelper.h"
 #include "nsIObserverService.h"
 #include "nsIObserver.h"
 #include "mozilla/Services.h"
 #include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
 #include "GeckoProfiler.h"
 #include "nsNetCID.h"
-#include "nsIHangDetails.h"
+#include "HangDetails.h"
 
 #include <algorithm>
 
 // Activate BHR only for one every BHR_BETA_MOD users.
 // This is now 100% of Beta population for the Beta 45/46 e10s A/B trials
 // It can be scaled back again in the future
 #define BHR_BETA_MOD 1;
 
@@ -181,29 +180,27 @@ public:
   bool mHanging;
   // Is the thread in a waiting state
   bool mWaiting;
   // Is the thread dedicated to a single BackgroundHangMonitor
   BackgroundHangMonitor::ThreadType mThreadType;
   // Platform-specific helper to get hang stacks
   ThreadStackHelper mStackHelper;
   // Stack of current hang
-  Telemetry::HangStack mHangStack;
+  HangStack mHangStack;
   // Native stack of current hang
-  Telemetry::NativeHangStack mNativeHangStack;
+  NativeHangStack mNativeHangStack;
   // Annotations for the current hang
   UniquePtr<HangMonitor::HangAnnotations> mAnnotations;
   // Annotators registered for this thread
   HangMonitor::Observer::Annotators mAnnotators;
   // The name of the runnable which is hanging the current process
   nsCString mRunnableName;
   // The name of the thread which is being monitored
   nsCString mThreadName;
-  // The number of native stacks which have been collected so far.
-  uint32_t mNativeStackCnt;
 
   BackgroundHangThread(const char* aName,
                        uint32_t aTimeoutMs,
                        uint32_t aMaxTimeoutMs,
                        BackgroundHangMonitor::ThreadType aThreadType = BackgroundHangMonitor::THREAD_SHARED);
 
   // Report a hang; aManager->mLock IS locked. The hang will be processed
   // off-main-thread, and will then be submitted back.
@@ -231,38 +228,16 @@ public:
 
   // Returns true if this thread is (or might be) shared between other
   // BackgroundHangMonitors for the monitored thread.
   bool IsShared() {
     return mThreadType == BackgroundHangMonitor::THREAD_SHARED;
   }
 };
 
-/**
- * HangDetails is the concrete implementaion of nsIHangDetails, and contains the
- * infromation which we want to expose to observers of the bhr-thread-hang
- * observer notification.
- */
-class HangDetails : public nsIHangDetails
-{
-public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIHANGDETAILS
-
-  HangDetails(uint32_t aDuration, const nsACString& aName)
-    : mDuration(aDuration)
-    , mName(aName)
-    {}
-private:
-  virtual ~HangDetails() {}
-
-  uint32_t mDuration;
-  nsCString mName;
-};
-
 StaticRefPtr<BackgroundHangManager> BackgroundHangManager::sInstance;
 bool BackgroundHangManager::sDisabled = false;
 
 MOZ_THREAD_LOCAL(BackgroundHangThread*) BackgroundHangThread::sTlsKey;
 bool BackgroundHangThread::sTlsKeyInitialized;
 
 BackgroundHangManager::BackgroundHangManager()
   : mShutdown(false)
@@ -365,28 +340,22 @@ BackgroundHangManager::RunMonitorThread(
         currentThread->ReportPermaHang();
         continue;
       }
 
       if (MOZ_LIKELY(!currentThread->mHanging)) {
         if (MOZ_UNLIKELY(hangTime >= currentThread->mTimeout)) {
           // A hang started
 #ifdef NIGHTLY_BUILD
-          if (currentThread->mNativeStackCnt < Telemetry::kMaximumNativeHangStacks) {
-            // NOTE: In nightly builds of firefox we want to collect native stacks
-            // for all hangs, not just permahangs.
-            currentThread->mNativeStackCnt += 1;
-            currentThread->mStackHelper.GetPseudoAndNativeStack(
-              currentThread->mHangStack,
-              currentThread->mNativeHangStack,
-              currentThread->mRunnableName);
-          } else {
-            currentThread->mStackHelper.GetPseudoStack(currentThread->mHangStack,
-                                                       currentThread->mRunnableName);
-          }
+          // NOTE: In nightly builds of firefox we want to collect native stacks
+          // for all hangs, not just permahangs.
+          currentThread->mStackHelper.GetPseudoAndNativeStack(
+            currentThread->mHangStack,
+            currentThread->mNativeHangStack,
+            currentThread->mRunnableName);
 #else
           currentThread->mStackHelper.GetPseudoStack(currentThread->mHangStack,
                                                      currentThread->mRunnableName);
 #endif
           currentThread->mHangStart = interval;
           currentThread->mHanging = true;
           currentThread->mAnnotations =
             currentThread->mAnnotators.GatherAnnotations();
@@ -439,17 +408,16 @@ BackgroundHangThread::BackgroundHangThre
                 ? PR_INTERVAL_NO_TIMEOUT
                 : PR_MillisecondsToInterval(aMaxTimeoutMs))
   , mInterval(mManager->mIntervalNow)
   , mHangStart(mInterval)
   , mHanging(false)
   , mWaiting(true)
   , mThreadType(aThreadType)
   , mThreadName(aName)
-  , mNativeStackCnt(0)
 {
   if (sTlsKeyInitialized && IsShared()) {
     sTlsKey.set(this);
   }
   // Lock here because LinkedList is not thread-safe
   MonitorAutoLock autoLock(mManager->mLock);
   // Add to thread list
   mManager->mHangThreads.insertBack(this);
@@ -497,30 +465,34 @@ BackgroundHangThread::ReportHang(PRInter
   if (mHangStack.length() > kMaxThreadHangStackDepth) {
     const int elementsToRemove = mHangStack.length() - kMaxThreadHangStackDepth;
     // Replace the oldest frame with a known label so that we can tell this stack
     // was limited.
     mHangStack[0] = "(reduced stack)";
     mHangStack.erase(mHangStack.begin() + 1, mHangStack.begin() + elementsToRemove);
   }
 
-  // XXX: HangDetails will be expanded to contain all of the relevant
-  // information and handle reporting a custom ping to telemetry.
-
-  // Notify any observers of the "bhr-thread-hang" topic that a thread has hung.
-  nsCString name;
-  name.Assign(mThreadName);
-  nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction("NotifyBHRHangObservers", [=] {
-    nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
-    if (os) {
-      // NOTE: Make sure to construct this on the main thread.
-      nsCOMPtr<nsIHangDetails> hangDetails = new HangDetails(aHangTime, name);
-      os->NotifyObservers(hangDetails, "bhr-thread-hang", nullptr);
-    }
-  });
+  HangDetails hangDetails(aHangTime,
+                          XRE_GetProcessType(),
+                          mThreadName,
+                          mRunnableName,
+                          Move(mHangStack),
+                          Move(mAnnotations));
+  // If we have the stream transport service avaliable, we can process the
+  // native stack on it. Otherwise, we are unable to report a native stack, so
+  // we just report without one.
+  if (mManager->mSTS) {
+    nsCOMPtr<nsIRunnable> processHangStackRunnable =
+      new ProcessHangStackRunnable(Move(hangDetails), Move(mNativeHangStack));
+    mManager->mSTS->Dispatch(processHangStackRunnable.forget());
+  } else {
+    NS_WARNING("Unable to report native stack without a StreamTransportService");
+    RefPtr<nsHangDetails> hd = new nsHangDetails(Move(hangDetails));
+    hd->Submit();
+  }
 }
 
 void
 BackgroundHangThread::ReportPermaHang()
 {
   // Permanently hanged; called on the monitor thread
   // mManager->mLock IS locked
 
@@ -750,25 +722,9 @@ BackgroundHangMonitor::UnregisterAnnotat
     return false;
   }
   return thisThread->mAnnotators.Unregister(aAnnotator);
 #else
   return false;
 #endif
 }
 
-NS_IMETHODIMP
-HangDetails::GetDuration(uint32_t* aDuration)
-{
-  *aDuration = mDuration;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-HangDetails::GetThreadName(nsACString& aName)
-{
-  aName.Assign(mName);
-  return NS_OK;
-}
-
-NS_IMPL_ISUPPORTS(HangDetails, nsIHangDetails)
-
 } // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/toolkit/components/backgroundhangmonitor/HangDetails.cpp
@@ -0,0 +1,375 @@
+#include "HangDetails.h"
+#include "nsIHangDetails.h"
+#include "nsPrintfCString.h"
+#include "mozilla/gfx/GPUParent.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/Unused.h"
+#include "mozilla/GfxMessageUtils.h" // For ParamTraits<GeckoProcessType>
+
+namespace mozilla {
+
+HangDetails::HangDetails(const HangDetails& aOther)
+  : mDuration(aOther.mDuration)
+  , mThreadName(aOther.mThreadName)
+  , mRunnableName(aOther.mRunnableName)
+  , mPseudoStack(aOther.mPseudoStack)
+  , mAnnotations(nullptr)
+  , mStack(aOther.mStack)
+{
+  // XXX: The current implementation of mAnnotations is really suboptimal here.
+  if (!aOther.mAnnotations) {
+    return;
+  }
+
+  mAnnotations = mozilla::HangMonitor::CreateEmptyHangAnnotations();
+  auto enumerator = aOther.mAnnotations->GetEnumerator();
+  if (enumerator) {
+    nsAutoString key;
+    nsAutoString value;
+    while (enumerator->Next(key, value)) {
+      mAnnotations->AddAnnotation(key, value);
+    }
+  }
+}
+
+NS_IMETHODIMP
+nsHangDetails::GetDuration(uint32_t* aDuration)
+{
+  *aDuration = mDetails.mDuration;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHangDetails::GetThread(nsACString& aName)
+{
+  aName.Assign(mDetails.mThreadName);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHangDetails::GetRunnableName(nsACString& aRunnableName)
+{
+  aRunnableName.Assign(mDetails.mRunnableName);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHangDetails::GetProcess(nsACString& aName)
+{
+  aName.AssignASCII(XRE_ChildProcessTypeToString(mDetails.mProcess));
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHangDetails::GetStack(JSContext* aCx, JS::MutableHandleValue aVal)
+{
+  size_t length = mDetails.mStack.GetStackSize();
+  JS::RootedObject retObj(aCx, JS_NewArrayObject(aCx, length));
+  if (!retObj) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  for (size_t i = 0; i < length; ++i) {
+    auto& frame = mDetails.mStack.GetFrame(i);
+    JS::RootedObject jsFrame(aCx, JS_NewArrayObject(aCx, 2));
+    if (!jsFrame) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    if (!JS_DefineElement(aCx, jsFrame, 0, frame.mModIndex, JSPROP_ENUMERATE)) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    nsPrintfCString hexString("%" PRIxPTR, frame.mOffset);
+    JS::RootedString hex(aCx, JS_NewStringCopyZ(aCx, hexString.get()));
+    if (!hex || !JS_DefineElement(aCx, jsFrame, 1, hex, JSPROP_ENUMERATE)) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    if (!JS_DefineElement(aCx, retObj, i, jsFrame, JSPROP_ENUMERATE)) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+  }
+
+  aVal.setObject(*retObj);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHangDetails::GetModules(JSContext* aCx, JS::MutableHandleValue aVal)
+{
+  size_t length = mDetails.mStack.GetNumModules();
+  JS::RootedObject retObj(aCx, JS_NewArrayObject(aCx, length));
+  if (!retObj) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  for (size_t i = 0; i < length; ++i) {
+    auto& module = mDetails.mStack.GetModule(i);
+    JS::RootedObject jsModule(aCx, JS_NewArrayObject(aCx, 2));
+    if (!jsModule) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    JS::RootedString name(aCx, JS_NewUCStringCopyZ(aCx, module.mName.get()));
+    if (!name || !JS_DefineElement(aCx, jsModule, 0, name, JSPROP_ENUMERATE)) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    JS::RootedString id(aCx, JS_NewStringCopyZ(aCx, module.mBreakpadId.c_str()));
+    if (!id || !JS_DefineElement(aCx, jsModule, 1, id, JSPROP_ENUMERATE)) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    if (!JS_DefineElement(aCx, retObj, i, jsModule, JSPROP_ENUMERATE)) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+  }
+
+  aVal.setObject(*retObj);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHangDetails::GetAnnotations(JSContext* aCx, JS::MutableHandleValue aVal)
+{
+  // We create an object with { "key" : "value" } string pairs for each item in
+  // our annotations object.
+  JS::RootedObject jsAnnotation(aCx, JS_NewPlainObject(aCx));
+  if (!jsAnnotation) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  if (!mDetails.mAnnotations) {
+    aVal.setObject(*jsAnnotation);
+    return NS_OK;
+  }
+
+  // Loop over all of the annotations in the enumerator and add them.
+  auto enumerator = mDetails.mAnnotations->GetEnumerator();
+  if (enumerator) {
+    nsAutoString key;
+    nsAutoString value;
+    while (enumerator->Next(key, value)) {
+      JS::RootedValue jsValue(aCx);
+      jsValue.setString(JS_NewUCStringCopyN(aCx, value.get(), value.Length()));
+      if (!JS_DefineUCProperty(aCx, jsAnnotation, key.get(), key.Length(),
+                               jsValue, JSPROP_ENUMERATE)) {
+        return NS_ERROR_OUT_OF_MEMORY;
+      }
+    }
+  }
+
+  aVal.setObject(*jsAnnotation);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHangDetails::GetPseudoStack(JSContext* aCx, JS::MutableHandle<JS::Value> aVal)
+{
+  JS::RootedObject ret(aCx, JS_NewArrayObject(aCx, mDetails.mPseudoStack.length()));
+  if (!ret) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  for (size_t i = 0; i < mDetails.mPseudoStack.length(); ++i) {
+    JS::RootedString string(aCx, JS_NewStringCopyZ(aCx, mDetails.mPseudoStack[i]));
+    if (!string) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+    if (!JS_DefineElement(aCx, ret, i, string, JSPROP_ENUMERATE)) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+  }
+
+  aVal.setObject(*ret);
+  return NS_OK;
+}
+
+// Processing and submitting the stack as an observer notification.
+
+void
+nsHangDetails::Submit()
+{
+  if (NS_WARN_IF(!SystemGroup::Initialized())) {
+    return;
+  }
+
+  RefPtr<nsHangDetails> hangDetails = this;
+  nsCOMPtr<nsIRunnable> notifyObservers = NS_NewRunnableFunction("NotifyBHRHangObservers", [hangDetails] {
+    nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+    if (os) {
+      os->NotifyObservers(hangDetails, "bhr-thread-hang", nullptr);
+    }
+  });
+
+  nsresult rv = SystemGroup::Dispatch(TaskCategory::Other,
+                                      notifyObservers.forget());
+  MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+}
+
+NS_IMPL_ISUPPORTS(nsHangDetails, nsIHangDetails)
+
+NS_IMETHODIMP
+ProcessHangStackRunnable::Run()
+{
+  // NOTE: This is an expensive operation on some platforms, so we do it off of
+  // any other critical path, moving it onto the StreamTransportService.
+  mHangDetails.mStack = Telemetry::GetStackAndModules(mNativeStack);
+
+  RefPtr<nsHangDetails> hangDetails = new nsHangDetails(Move(mHangDetails));
+  hangDetails->Submit();
+
+  return NS_OK;
+}
+
+} // namespace mozilla
+
+
+/**
+ * IPC Serialization / Deserialization logic
+ */
+namespace IPC {
+
+void
+ParamTraits<mozilla::HangDetails>::Write(Message* aMsg, const mozilla::HangDetails& aParam)
+{
+  WriteParam(aMsg, aParam.mDuration);
+  WriteParam(aMsg, aParam.mProcess);
+  WriteParam(aMsg, aParam.mThreadName);
+  WriteParam(aMsg, aParam.mRunnableName);
+  WriteParam(aMsg, aParam.mPseudoStack);
+
+  // Write out the annotation information
+  // If we have no annotations, write out a 0-length annotations.
+  // XXX: Everything about HangAnnotations is awful.
+  if (!aParam.mAnnotations) {
+    WriteParam(aMsg, (size_t) 0);
+  } else {
+    size_t length = aParam.mAnnotations->Count();
+    WriteParam(aMsg, length);
+    auto enumerator = aParam.mAnnotations->GetEnumerator();
+    if (enumerator) {
+      nsAutoString key;
+      nsAutoString value;
+      while (enumerator->Next(key, value)) {
+        WriteParam(aMsg, key);
+        WriteParam(aMsg, value);
+      }
+    }
+  }
+
+  // NOTE: ProcessedStack will stop being used for BHR in bug 1367406, so this
+  // inline serialization will survive until then.
+
+  // Write out the native stack module information
+  {
+    size_t length = aParam.mStack.GetNumModules();
+    WriteParam(aMsg, length);
+    for (size_t i = 0; i < length; ++i) {
+      auto& module = aParam.mStack.GetModule(i);
+      WriteParam(aMsg, module.mName);
+      WriteParam(aMsg, module.mBreakpadId);
+    }
+  }
+
+  // Native stack frame information
+  {
+    size_t length = aParam.mStack.GetStackSize();
+    WriteParam(aMsg, length);
+    for (size_t i = 0; i < length; ++i) {
+      auto& frame = aParam.mStack.GetFrame(i);
+      WriteParam(aMsg, frame.mOffset);
+      WriteParam(aMsg, frame.mModIndex);
+    }
+  }
+}
+
+bool
+ParamTraits<mozilla::HangDetails>::Read(const Message* aMsg,
+                                        PickleIterator* aIter,
+                                        mozilla::HangDetails* aResult)
+{
+  if (!ReadParam(aMsg, aIter, &aResult->mDuration)) {
+    return false;
+  }
+  if (!ReadParam(aMsg, aIter, &aResult->mProcess)) {
+    return false;
+  }
+  if (!ReadParam(aMsg, aIter, &aResult->mThreadName)) {
+    return false;
+  }
+  if (!ReadParam(aMsg, aIter, &aResult->mRunnableName)) {
+    return false;
+  }
+  if (!ReadParam(aMsg, aIter, &aResult->mPseudoStack)) {
+    return false;
+  }
+
+  // Annotation information
+  {
+    aResult->mAnnotations =
+      mozilla::HangMonitor::CreateEmptyHangAnnotations();
+
+    size_t length;
+    if (!ReadParam(aMsg, aIter, &length)) {
+      return false;
+    }
+
+    for (size_t i = 0; i < length; ++i) {
+      nsAutoString key;
+      if (!ReadParam(aMsg, aIter, &key)) {
+        return false;
+      }
+      nsAutoString value;
+      if (!ReadParam(aMsg, aIter, &value)) {
+        return false;
+      }
+      aResult->mAnnotations->AddAnnotation(key, value);
+    }
+  }
+
+  // NOTE: ProcessedStack will stop being used for BHR in bug 1367406, so this
+  // inline serialization will survive until then.
+
+  // Native Stack Module Information
+  {
+    size_t length;
+    if (!ReadParam(aMsg, aIter, &length)) {
+      return false;
+    }
+
+    for (size_t i = 0; i < length; ++i) {
+      mozilla::Telemetry::ProcessedStack::Module module;
+      if (!ReadParam(aMsg, aIter, &module.mName)) {
+        return false;
+      }
+      if (!ReadParam(aMsg, aIter, &module.mBreakpadId)) {
+        return false;
+      }
+      aResult->mStack.AddModule(module);
+    }
+  }
+
+  // Native stack frame information
+  {
+    size_t length;
+    if (!ReadParam(aMsg, aIter, &length)) {
+      return false;
+    }
+
+    for (size_t i = 0; i < length; ++i) {
+      mozilla::Telemetry::ProcessedStack::Frame frame;
+      if (!ReadParam(aMsg, aIter, &frame.mOffset)) {
+        return false;
+      }
+      if (!ReadParam(aMsg, aIter, &frame.mModIndex)) {
+        return false;
+      }
+      aResult->mStack.AddFrame(frame);
+    }
+  }
+
+  return true;
+}
+
+} // namespace IPC
new file mode 100644
--- /dev/null
+++ b/toolkit/components/backgroundhangmonitor/HangDetails.h
@@ -0,0 +1,142 @@
+/* -*- 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_HangDetails_h
+#define mozilla_HangDetails_h
+
+#include "ipc/IPCMessageUtils.h"
+#include "mozilla/ProcessedStack.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Move.h"
+#include "mozilla/HangStack.h"
+#include "mozilla/HangAnnotations.h"
+#include "nsTArray.h"
+#include "nsIHangDetails.h"
+
+namespace mozilla {
+
+/**
+ * HangDetails is a POD struct which contains the information collected from the
+ * hang. It can be wrapped in a nsHangDetails to provide an XPCOM interface for
+ * extracting information from it easily.
+ *
+ * This type is separate, as it can be sent over IPC while nsHangDetails is an
+ * XPCOM interface which is harder to serialize over IPC.
+ */
+class HangDetails
+{
+public:
+  HangDetails()
+    : mDuration(0)
+    , mProcess(GeckoProcessType_Invalid)
+  {}
+
+  // XXX: This type has some really gnarly copy/serialize/deserialize methods. I
+  // want to fix this, but I think it needs to happen in a follow up. This patch
+  // is already big enough without me also rewriting/simplifying
+  // HangAnnotations, HangStack, and ProcessedStack.
+  HangDetails(const HangDetails& aOther);
+  HangDetails(HangDetails&& aOther) = default;
+  HangDetails(uint32_t aDuration,
+              GeckoProcessType aProcess,
+              const nsACString& aThreadName,
+              const nsACString& aRunnableName,
+              HangStack&& aPseudoStack,
+              UniquePtr<HangMonitor::HangAnnotations>&& aAnnotations)
+    : mDuration(aDuration)
+    , mProcess(aProcess)
+    , mThreadName(aThreadName)
+    , mRunnableName(aRunnableName)
+    , mPseudoStack(Move(aPseudoStack))
+    , mAnnotations(Move(aAnnotations))
+  {}
+
+  // NOTE: Once we get merged stacks, we will move ProcessedStack into HangStack
+  // and simplify everything. (bug 1367406).
+  uint32_t mDuration;
+  GeckoProcessType mProcess;
+  nsCString mThreadName;
+  nsCString mRunnableName;
+  HangStack mPseudoStack;
+  UniquePtr<HangMonitor::HangAnnotations> mAnnotations;
+
+  // NOTE: Initialized by ProcessHangStackRunnable.
+  Telemetry::ProcessedStack mStack;
+};
+
+/**
+ * HangDetails is the concrete implementaion of nsIHangDetails, and contains the
+ * infromation which we want to expose to observers of the bhr-thread-hang
+ * observer notification.
+ */
+class nsHangDetails : public nsIHangDetails
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIHANGDETAILS
+
+  explicit nsHangDetails(HangDetails&& aDetails)
+    : mDetails(Move(aDetails))
+  {}
+
+  // Submit these HangDetails to the main thread. This will dispatch a runnable
+  // to the main thread which will fire off the bhr-thread-hang observer
+  // notification with this HangDetails as the subject.
+  void Submit();
+
+private:
+  virtual ~nsHangDetails() {}
+
+  HangDetails mDetails;
+};
+
+/**
+ * This runnable is run on the StreamTransportService threadpool in order to
+ * process the stack off main thread before submitting it to the main thread as
+ * an observer notification.
+ *
+ * This object should have the only remaining reference to aHangDetails, as it
+ * will access its fields without synchronization.
+ */
+class ProcessHangStackRunnable final : public Runnable
+{
+public:
+  ProcessHangStackRunnable(HangDetails&& aHangDetails,
+                           std::vector<uintptr_t>&& aNativeStack)
+    : Runnable("ProcessHangStackRunnable")
+    , mHangDetails(Move(aHangDetails))
+    , mNativeStack(Move(aNativeStack))
+  {}
+
+  NS_IMETHOD Run() override;
+
+private:
+  HangDetails mHangDetails;
+  std::vector<uintptr_t> mNativeStack;
+};
+
+} // namespace mozilla
+
+// We implement the ability to send the HangDetails object over IPC. We need to
+// do this rather than rely on StructuredClone of the objects created by the
+// XPCOM getters on nsHangDetails because we want to run BHR in the GPU process
+// which doesn't run any JS.
+namespace IPC {
+
+template<>
+class ParamTraits<mozilla::HangDetails>
+{
+public:
+  typedef mozilla::HangDetails paramType;
+  static void Write(Message* aMsg, const paramType& aParam);
+  static bool Read(const Message* aMsg,
+                   PickleIterator* aIter,
+                   paramType* aResult);
+};
+
+} // namespace IPC
+
+#endif // mozilla_HangDetails_h
new file mode 100644
--- /dev/null
+++ b/toolkit/components/backgroundhangmonitor/HangStack.cpp
@@ -0,0 +1,103 @@
+#include "HangStack.h"
+
+namespace mozilla {
+
+HangStack::HangStack(const HangStack& aOther)
+{
+  if (NS_WARN_IF(!mBuffer.reserve(aOther.mBuffer.length()) ||
+                 !mImpl.reserve(aOther.mImpl.length()))) {
+    return;
+  }
+  // XXX: I should be able to just memcpy the other stack's mImpl and mBuffer,
+  // and then just re-offset pointers.
+  for (size_t i = 0; i < aOther.length(); ++i) {
+    const char* s = aOther[i];
+    if (aOther.IsInBuffer(s)) {
+      InfallibleAppendViaBuffer(s, strlen(s));
+    } else {
+      infallibleAppend(s);
+    }
+  }
+  MOZ_ASSERT(mImpl.length() == aOther.mImpl.length());
+  MOZ_ASSERT(mBuffer.length() == aOther.mBuffer.length());
+}
+
+const char*
+HangStack::InfallibleAppendViaBuffer(const char* aText, size_t aLength)
+{
+  MOZ_ASSERT(this->canAppendWithoutRealloc(1));
+  // Include null-terminator in length count.
+  MOZ_ASSERT(mBuffer.canAppendWithoutRealloc(aLength + 1));
+
+  const char* const entry = mBuffer.end();
+  mBuffer.infallibleAppend(aText, aLength);
+  mBuffer.infallibleAppend('\0'); // Explicitly append null-terminator
+  this->infallibleAppend(entry);
+  return entry;
+}
+
+const char*
+HangStack::AppendViaBuffer(const char* aText, size_t aLength)
+{
+  if (!this->reserve(this->length() + 1)) {
+    return nullptr;
+  }
+
+  // Keep track of the previous buffer in case we need to adjust pointers later.
+  const char* const prevStart = mBuffer.begin();
+  const char* const prevEnd = mBuffer.end();
+
+  // Include null-terminator in length count.
+  if (!mBuffer.reserve(mBuffer.length() + aLength + 1)) {
+    return nullptr;
+  }
+
+  if (prevStart != mBuffer.begin()) {
+    // The buffer has moved; we have to adjust pointers in the stack.
+    for (auto & entry : *this) {
+      if (entry >= prevStart && entry < prevEnd) {
+        // Move from old buffer to new buffer.
+        entry += mBuffer.begin() - prevStart;
+      }
+    }
+  }
+
+  return InfallibleAppendViaBuffer(aText, aLength);
+}
+
+} // namespace mozilla
+
+namespace IPC {
+
+void
+ParamTraits<mozilla::HangStack>::Write(Message* aMsg, const mozilla::HangStack& aParam)
+{
+  size_t length = aParam.length();
+  WriteParam(aMsg, length);
+  for (size_t i = 0; i < length; ++i) {
+    nsDependentCString str(aParam[i]);
+    WriteParam(aMsg, static_cast<nsACString&>(str));
+  }
+}
+
+bool
+ParamTraits<mozilla::HangStack>::Read(const Message* aMsg,
+                                      PickleIterator* aIter,
+                                      mozilla::HangStack* aResult)
+{
+  size_t length;
+  if (!ReadParam(aMsg, aIter, &length)) {
+    return false;
+  }
+
+  for (size_t i = 0; i < length; ++i) {
+    nsAutoCString str;
+    if (!ReadParam(aMsg, aIter, &str)) {
+      return false;
+    }
+    aResult->AppendViaBuffer(str.get(), str.Length());
+  }
+  return true;
+}
+
+} // namespace IPC
new file mode 100644
--- /dev/null
+++ b/toolkit/components/backgroundhangmonitor/HangStack.h
@@ -0,0 +1,144 @@
+/* -*- 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_HangStack_h
+#define mozilla_HangStack_h
+
+#include "ipc/IPCMessageUtils.h"
+#include "mozilla/ProcessedStack.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Move.h"
+#include "nsTArray.h"
+#include "nsIHangDetails.h"
+
+namespace mozilla {
+
+/* A native stack is a simple list of pointers, so rather than building a
+   wrapper type, we typdef the type here. */
+typedef std::vector<uintptr_t> NativeHangStack;
+
+/* HangStack stores an array of const char pointers,
+   with optional internal storage for strings. */
+class HangStack
+{
+public:
+  static const size_t sMaxInlineStorage = 8;
+
+  // The maximum depth for the native stack frames that we might collect.
+  // XXX: Consider moving this to a different object?
+  static const size_t sMaxNativeFrames = 150;
+
+private:
+  typedef mozilla::Vector<const char*, sMaxInlineStorage> Impl;
+  Impl mImpl;
+
+  // Stack entries can either be a static const char*
+  // or a pointer to within this buffer.
+  mozilla::Vector<char, 0> mBuffer;
+
+public:
+  HangStack() {}
+
+  HangStack(const HangStack& aOther);
+  HangStack(HangStack&& aOther)
+    : mImpl(mozilla::Move(aOther.mImpl))
+    , mBuffer(mozilla::Move(aOther.mBuffer))
+  {
+  }
+
+  HangStack& operator=(HangStack&& aOther) {
+    mImpl = mozilla::Move(aOther.mImpl);
+    mBuffer = mozilla::Move(aOther.mBuffer);
+    return *this;
+  }
+
+  bool operator==(const HangStack& aOther) const {
+    for (size_t i = 0; i < length(); i++) {
+      if (!IsSameAsEntry(operator[](i), aOther[i])) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  bool operator!=(const HangStack& aOther) const {
+    return !operator==(aOther);
+  }
+
+  const char*& operator[](size_t aIndex) {
+    return mImpl[aIndex];
+  }
+
+  const char* const& operator[](size_t aIndex) const {
+    return mImpl[aIndex];
+  }
+
+  size_t capacity() const { return mImpl.capacity(); }
+  size_t length() const { return mImpl.length(); }
+  bool empty() const { return mImpl.empty(); }
+  bool canAppendWithoutRealloc(size_t aNeeded) const {
+    return mImpl.canAppendWithoutRealloc(aNeeded);
+  }
+  void infallibleAppend(const char* aEntry) { mImpl.infallibleAppend(aEntry); }
+  bool reserve(size_t aRequest) { return mImpl.reserve(aRequest); }
+  const char** begin() { return mImpl.begin(); }
+  const char* const* begin() const { return mImpl.begin(); }
+  const char** end() { return mImpl.end(); }
+  const char* const* end() const { return mImpl.end(); }
+  const char*& back() { return mImpl.back(); }
+  void erase(const char** aEntry) { mImpl.erase(aEntry); }
+  void erase(const char** aBegin, const char** aEnd) {
+    mImpl.erase(aBegin, aEnd);
+  }
+
+  void clear() {
+    mImpl.clear();
+    mBuffer.clear();
+  }
+
+  bool IsInBuffer(const char* aEntry) const {
+    return aEntry >= mBuffer.begin() && aEntry < mBuffer.end();
+  }
+
+  bool IsSameAsEntry(const char* aEntry, const char* aOther) const {
+    // If the entry came from the buffer, we need to compare its content;
+    // otherwise we only need to compare its pointer.
+    return IsInBuffer(aEntry) ? !strcmp(aEntry, aOther) : (aEntry == aOther);
+  }
+
+  size_t AvailableBufferSize() const {
+    return mBuffer.capacity() - mBuffer.length();
+  }
+
+  bool EnsureBufferCapacity(size_t aCapacity) {
+    // aCapacity is the minimal capacity and Vector may make the actual
+    // capacity larger, in which case we want to use up all the space.
+    return mBuffer.reserve(aCapacity) &&
+           mBuffer.reserve(mBuffer.capacity());
+  }
+
+  const char* InfallibleAppendViaBuffer(const char* aText, size_t aLength);
+  const char* AppendViaBuffer(const char* aText, size_t aLength);
+};
+
+} // namespace mozilla
+
+namespace IPC {
+
+template<>
+class ParamTraits<mozilla::HangStack>
+{
+public:
+  typedef mozilla::HangStack paramType;
+  static void Write(Message* aMsg, const paramType& aParam);
+  static bool Read(const Message* aMsg,
+                   PickleIterator* aIter,
+                   paramType* aResult);
+};
+
+} // namespace IPC
+
+#endif // mozilla_HangStack_h
deleted file mode 100644
--- a/toolkit/components/backgroundhangmonitor/ThreadHangStats.cpp
+++ /dev/null
@@ -1,56 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
-/* 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 "ThreadHangStats.h"
-#include "jsapi.h"
-
-namespace mozilla {
-namespace Telemetry {
-
-const char*
-HangStack::InfallibleAppendViaBuffer(const char* aText, size_t aLength)
-{
-  MOZ_ASSERT(this->canAppendWithoutRealloc(1));
-  // Include null-terminator in length count.
-  MOZ_ASSERT(mBuffer.canAppendWithoutRealloc(aLength + 1));
-
-  const char* const entry = mBuffer.end();
-  mBuffer.infallibleAppend(aText, aLength);
-  mBuffer.infallibleAppend('\0'); // Explicitly append null-terminator
-  this->infallibleAppend(entry);
-  return entry;
-}
-
-const char*
-HangStack::AppendViaBuffer(const char* aText, size_t aLength)
-{
-  if (!this->reserve(this->length() + 1)) {
-    return nullptr;
-  }
-
-  // Keep track of the previous buffer in case we need to adjust pointers later.
-  const char* const prevStart = mBuffer.begin();
-  const char* const prevEnd = mBuffer.end();
-
-  // Include null-terminator in length count.
-  if (!mBuffer.reserve(mBuffer.length() + aLength + 1)) {
-    return nullptr;
-  }
-
-  if (prevStart != mBuffer.begin()) {
-    // The buffer has moved; we have to adjust pointers in the stack.
-    for (auto & entry : *this) {
-      if (entry >= prevStart && entry < prevEnd) {
-        // Move from old buffer to new buffer.
-        entry += mBuffer.begin() - prevStart;
-      }
-    }
-  }
-
-  return InfallibleAppendViaBuffer(aText, aLength);
-}
-
-} // namespace Telemetry
-} // namespace mozilla
deleted file mode 100644
--- a/toolkit/components/backgroundhangmonitor/ThreadHangStats.h
+++ /dev/null
@@ -1,141 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
-/* 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_BackgroundHangTelemetry_h
-#define mozilla_BackgroundHangTelemetry_h
-
-#include "mozilla/Array.h"
-#include "mozilla/Assertions.h"
-#include "mozilla/HangAnnotations.h"
-#include "mozilla/Move.h"
-#include "mozilla/Mutex.h"
-#include "mozilla/PodOperations.h"
-#include "mozilla/Vector.h"
-#include "mozilla/CombinedStacks.h"
-
-#include "nsString.h"
-#include "prinrval.h"
-#include "jsapi.h"
-
-namespace mozilla {
-namespace Telemetry {
-
-// This variable controls the maximum number of native hang stacks which may be
-// attached to a ping. This is due to how large native stacks can be. We want to
-// reduce the chance of a ping being discarded due to it exceeding the maximum
-// ping size.
-static const uint32_t kMaximumNativeHangStacks = 300;
-
-/* A native stack is a simple list of pointers, so rather than building a
-   wrapper type, we typdef the type here. */
-typedef std::vector<uintptr_t> NativeHangStack;
-
-/* HangStack stores an array of const char pointers,
-   with optional internal storage for strings. */
-class HangStack
-{
-public:
-  static const size_t sMaxInlineStorage = 8;
-
-  // The maximum depth for the native stack frames that we might collect.
-  // XXX: Consider moving this to a different object?
-  static const size_t sMaxNativeFrames = 150;
-
-private:
-  typedef mozilla::Vector<const char*, sMaxInlineStorage> Impl;
-  Impl mImpl;
-
-  // Stack entries can either be a static const char*
-  // or a pointer to within this buffer.
-  mozilla::Vector<char, 0> mBuffer;
-
-public:
-  HangStack() {}
-
-  HangStack(HangStack&& aOther)
-    : mImpl(mozilla::Move(aOther.mImpl))
-    , mBuffer(mozilla::Move(aOther.mBuffer))
-  {
-  }
-
-  HangStack& operator=(HangStack&& aOther) {
-    mImpl = mozilla::Move(aOther.mImpl);
-    mBuffer = mozilla::Move(aOther.mBuffer);
-    return *this;
-  }
-
-  bool operator==(const HangStack& aOther) const {
-    for (size_t i = 0; i < length(); i++) {
-      if (!IsSameAsEntry(operator[](i), aOther[i])) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  bool operator!=(const HangStack& aOther) const {
-    return !operator==(aOther);
-  }
-
-  const char*& operator[](size_t aIndex) {
-    return mImpl[aIndex];
-  }
-
-  const char* const& operator[](size_t aIndex) const {
-    return mImpl[aIndex];
-  }
-
-  size_t capacity() const { return mImpl.capacity(); }
-  size_t length() const { return mImpl.length(); }
-  bool empty() const { return mImpl.empty(); }
-  bool canAppendWithoutRealloc(size_t aNeeded) const {
-    return mImpl.canAppendWithoutRealloc(aNeeded);
-  }
-  void infallibleAppend(const char* aEntry) { mImpl.infallibleAppend(aEntry); }
-  bool reserve(size_t aRequest) { return mImpl.reserve(aRequest); }
-  const char** begin() { return mImpl.begin(); }
-  const char* const* begin() const { return mImpl.begin(); }
-  const char** end() { return mImpl.end(); }
-  const char* const* end() const { return mImpl.end(); }
-  const char*& back() { return mImpl.back(); }
-  void erase(const char** aEntry) { mImpl.erase(aEntry); }
-  void erase(const char** aBegin, const char** aEnd) {
-    mImpl.erase(aBegin, aEnd);
-  }
-
-  void clear() {
-    mImpl.clear();
-    mBuffer.clear();
-  }
-
-  bool IsInBuffer(const char* aEntry) const {
-    return aEntry >= mBuffer.begin() && aEntry < mBuffer.end();
-  }
-
-  bool IsSameAsEntry(const char* aEntry, const char* aOther) const {
-    // If the entry came from the buffer, we need to compare its content;
-    // otherwise we only need to compare its pointer.
-    return IsInBuffer(aEntry) ? !strcmp(aEntry, aOther) : (aEntry == aOther);
-  }
-
-  size_t AvailableBufferSize() const {
-    return mBuffer.capacity() - mBuffer.length();
-  }
-
-  bool EnsureBufferCapacity(size_t aCapacity) {
-    // aCapacity is the minimal capacity and Vector may make the actual
-    // capacity larger, in which case we want to use up all the space.
-    return mBuffer.reserve(aCapacity) &&
-           mBuffer.reserve(mBuffer.capacity());
-  }
-
-  const char* InfallibleAppendViaBuffer(const char* aText, size_t aLength);
-  const char* AppendViaBuffer(const char* aText, size_t aLength);
-};
-
-} // namespace Telemetry
-} // namespace mozilla
-
-#endif // mozilla_BackgroundHangTelemetry_h
--- a/toolkit/components/backgroundhangmonitor/ThreadStackHelper.cpp
+++ b/toolkit/components/backgroundhangmonitor/ThreadStackHelper.cpp
@@ -123,17 +123,17 @@ ThreadStackHelper::GetStacksInternal(Sta
   if (aStack && !PrepareStackBuffer(*aStack)) {
     // Skip and return empty aStack
     return;
   }
 
   // Prepare the native stack
   if (aNativeStack) {
     aNativeStack->clear();
-    aNativeStack->reserve(Telemetry::HangStack::sMaxNativeFrames);
+    aNativeStack->reserve(HangStack::sMaxNativeFrames);
   }
 
 #ifdef MOZ_THREADSTACKHELPER_PSEUDO
   ScopedSetPtr<Stack> stackPtr(mStackToFill, aStack);
 #endif
 #ifdef MOZ_THREADSTACKHELPER_NATIVE
   ScopedSetPtr<NativeStack> nativeStackPtr(mNativeStackToFill, aNativeStack);
 #endif
--- a/toolkit/components/backgroundhangmonitor/ThreadStackHelper.h
+++ b/toolkit/components/backgroundhangmonitor/ThreadStackHelper.h
@@ -2,18 +2,18 @@
 /* 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_ThreadStackHelper_h
 #define mozilla_ThreadStackHelper_h
 
-#include "mozilla/ThreadHangStats.h"
 #include "js/ProfilingStack.h"
+#include "HangDetails.h"
 
 #include <stddef.h>
 
 #if defined(XP_LINUX)
 #include <signal.h>
 #include <semaphore.h>
 #include <sys/types.h>
 #elif defined(XP_WIN)
@@ -48,23 +48,23 @@ namespace mozilla {
  * the pseudo-stack of the target thread at that instant.
  *
  * Only non-copying labels are included in the stack, which means labels
  * with custom text and markers are not included.
  */
 class ThreadStackHelper
 {
 public:
-  typedef Telemetry::HangStack Stack;
+  typedef HangStack Stack;
 
   // When a native stack is gathered, this vector holds the raw program counter
   // values that FramePointerStackWalk will return to us after it walks the
   // stack. When gathering the Telemetry payload, Telemetry will take care of
   // mapping these program counters to proper addresses within modules.
-  typedef Telemetry::NativeHangStack NativeStack;
+  typedef NativeHangStack NativeStack;
 
 private:
 #ifdef MOZ_THREADSTACKHELPER_PSEUDO
   Stack* mStackToFill;
   const PseudoStack* const mPseudoStack;
   size_t mMaxStackSize;
   size_t mMaxBufferSize;
 #endif
--- a/toolkit/components/backgroundhangmonitor/moz.build
+++ b/toolkit/components/backgroundhangmonitor/moz.build
@@ -7,22 +7,24 @@
 XPIDL_SOURCES += [
     'nsIHangDetails.idl',
 ]
 
 XPIDL_MODULE = 'bhr'
 
 EXPORTS.mozilla += [
     'BackgroundHangMonitor.h',
-    'ThreadHangStats.h',
+    'HangDetails.h',
+    'HangStack.h',
 ]
 
 UNIFIED_SOURCES += [
     'BackgroundHangMonitor.cpp',
-    'ThreadHangStats.cpp',
+    'HangDetails.cpp',
+    'HangStack.cpp',
     'ThreadStackHelper.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/caps', # For nsScriptSecurityManager.h
 ]
 
 # BHR disabled for Release builds because of bug 965392.
--- a/toolkit/components/backgroundhangmonitor/nsIHangDetails.idl
+++ b/toolkit/components/backgroundhangmonitor/nsIHangDetails.idl
@@ -1,25 +1,71 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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 "nsISupports.idl"
 
+%{ C++
+namespace mozilla {
+class HangDetails;
+}
+%}
+
+[ref] native HangDetailsRef(mozilla::HangDetails);
+
 /**
  * A scriptable interface for getting information about a BHR detected hang.
  * This is the type of the subject of the "bhr-thread-hang" observer topic.
  */
 [scriptable, uuid(23d63fff-38d6-4003-9c57-2c90aca1180a)]
 interface nsIHangDetails : nsISupports
 {
   /**
    * The detected duration of the hang.
    */
   readonly attribute uint32_t duration;
 
   /**
-   * The name of the thread which hung
+   * The name of the thread which hung.
+   */
+  readonly attribute ACString thread;
+
+  /**
+   * The name of the runnable which hung if it hung on the main thread.
+   */
+  readonly attribute ACString runnableName;
+
+  /**
+   * The type of process which produced the hang. This should be either:
+   * "default", "content", or "gpu".
+   */
+  readonly attribute ACString process;
+
+  /**
+   * Returns the stack which was captured by BHR. The offset is encoded as a hex
+   * string, as it can contain numbers larger than JS can hold losslessly.
+   *
+   * This value takes the following form:
+   * [ [moduleIndex, offset], ... ]
    */
-  readonly attribute ACString threadName;
+  [implicit_jscontext] readonly attribute jsval stack;
+
+  /**
+   * Returns the modules which were captured by BHR.
+   *
+   * This value takes the following form:
+   * [ ["fileName", "BreakpadId"], ... ]
+   */
+  [implicit_jscontext] readonly attribute jsval modules;
+
+  /**
+   * The hang annotations which were captured when the hang occured. This
+   * attribute is a JS object of key-value pairs.
+   */
+  [implicit_jscontext] readonly attribute jsval annotations;
+
+  /**
+   * A getter for the pseudoStack. This attribute is a JS array of strings.
+   */
+  [implicit_jscontext] readonly attribute jsval pseudoStack;
 };
-
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -68,17 +68,16 @@
 #endif
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
 #include "nsJSUtils.h"
 #include "nsReadableUtils.h"
 #include "plstr.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "mozilla/BackgroundHangMonitor.h"
-#include "mozilla/ThreadHangStats.h"
 #include "mozilla/ProcessedStack.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/FileUtils.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/IOInterposer.h"
 #include "mozilla/PoisonIOInterposer.h"
 #include "mozilla/StartupTimeline.h"