Bug 1373900: Factor ThreadHangStats implementation out of Telemetry.cpp. r?chutten draft
authorIaroslav (yarik) Sheptykin <yarik.sheptykin@gmail.com>
Thu, 22 Jun 2017 09:54:05 +0200
changeset 598996 911d5c5b364d0ce84cbfea80d9d6d136f9515c05
parent 598941 7a9536f89bc75b0672060f16ffbe6eb2c1ff3deb
child 634643 bec8fa6f6047c082f4283200caf1a33538039a3b
push id65387
push userbmo:yarik.sheptykin@googlemail.com
push dateThu, 22 Jun 2017 13:55:44 +0000
reviewerschutten
bugs1373900
milestone56.0a1
Bug 1373900: Factor ThreadHangStats implementation out of Telemetry.cpp. r?chutten MozReview-Commit-ID: C106sVX04s
toolkit/components/telemetry/Telemetry.cpp
toolkit/components/telemetry/ThreadHangStats.cpp
toolkit/components/telemetry/ThreadHangStats.h
toolkit/components/telemetry/moz.build
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -1305,227 +1305,16 @@ ReadStack(const char *aFileName, Telemet
       index
     };
     stack.AddFrame(frame);
   }
 
   aStack = stack;
 }
 
-static JSObject*
-CreateJSTimeHistogram(JSContext* cx, const Telemetry::TimeHistogram& time)
-{
-  /* Create JS representation of TimeHistogram,
-     in the format of Chromium-style histograms. */
-  JS::RootedObject ret(cx, JS_NewPlainObject(cx));
-  if (!ret) {
-    return nullptr;
-  }
-
-  if (!JS_DefineProperty(cx, ret, "min", time.GetBucketMin(0),
-                         JSPROP_ENUMERATE) ||
-      !JS_DefineProperty(cx, ret, "max",
-                         time.GetBucketMax(ArrayLength(time) - 1),
-                         JSPROP_ENUMERATE) ||
-      !JS_DefineProperty(cx, ret, "histogram_type",
-                         nsITelemetry::HISTOGRAM_EXPONENTIAL,
-                         JSPROP_ENUMERATE)) {
-    return nullptr;
-  }
-  // TODO: calculate "sum"
-  if (!JS_DefineProperty(cx, ret, "sum", 0, JSPROP_ENUMERATE)) {
-    return nullptr;
-  }
-
-  JS::RootedObject ranges(
-    cx, JS_NewArrayObject(cx, ArrayLength(time) + 1));
-  JS::RootedObject counts(
-    cx, JS_NewArrayObject(cx, ArrayLength(time) + 1));
-  if (!ranges || !counts) {
-    return nullptr;
-  }
-  /* In a Chromium-style histogram, the first bucket is an "under" bucket
-     that represents all values below the histogram's range. */
-  if (!JS_DefineElement(cx, ranges, 0, time.GetBucketMin(0), JSPROP_ENUMERATE) ||
-      !JS_DefineElement(cx, counts, 0, 0, JSPROP_ENUMERATE)) {
-    return nullptr;
-  }
-  for (size_t i = 0; i < ArrayLength(time); i++) {
-    if (!JS_DefineElement(cx, ranges, i + 1, time.GetBucketMax(i),
-                          JSPROP_ENUMERATE) ||
-        !JS_DefineElement(cx, counts, i + 1, time[i], JSPROP_ENUMERATE)) {
-      return nullptr;
-    }
-  }
-  if (!JS_DefineProperty(cx, ret, "ranges", ranges, JSPROP_ENUMERATE) ||
-      !JS_DefineProperty(cx, ret, "counts", counts, JSPROP_ENUMERATE)) {
-    return nullptr;
-  }
-  return ret;
-}
-
-static JSObject*
-CreateJSHangStack(JSContext* cx, const Telemetry::HangStack& stack)
-{
-  JS::RootedObject ret(cx, JS_NewArrayObject(cx, stack.length()));
-  if (!ret) {
-    return nullptr;
-  }
-  for (size_t i = 0; i < stack.length(); i++) {
-    JS::RootedString string(cx, JS_NewStringCopyZ(cx, stack[i]));
-    if (!JS_DefineElement(cx, ret, i, string, JSPROP_ENUMERATE)) {
-      return nullptr;
-    }
-  }
-  return ret;
-}
-
-static void
-CreateJSHangAnnotations(JSContext* cx, const HangAnnotationsVector& annotations,
-                        JS::MutableHandleObject returnedObject)
-{
-  JS::RootedObject annotationsArray(cx, JS_NewArrayObject(cx, 0));
-  if (!annotationsArray) {
-    returnedObject.set(nullptr);
-    return;
-  }
-  // We keep track of the annotations we reported in this hash set, so we can
-  // discard duplicated ones.
-  nsTHashtable<nsStringHashKey> reportedAnnotations;
-  size_t annotationIndex = 0;
-  for (const auto & curAnnotations : annotations) {
-    JS::RootedObject jsAnnotation(cx, JS_NewPlainObject(cx));
-    if (!jsAnnotation) {
-      continue;
-    }
-    // Build a key to index the current annotations in our hash set.
-    nsAutoString annotationsKey;
-    nsresult rv = ComputeAnnotationsKey(curAnnotations, annotationsKey);
-    if (NS_FAILED(rv)) {
-      continue;
-    }
-    // Check if the annotations are in the set. If that's the case, don't double report.
-    if (reportedAnnotations.GetEntry(annotationsKey)) {
-      continue;
-    }
-    // If not, report them.
-    reportedAnnotations.PutEntry(annotationsKey);
-    UniquePtr<HangAnnotations::Enumerator> annotationsEnum =
-      curAnnotations->GetEnumerator();
-    if (!annotationsEnum) {
-      continue;
-    }
-    nsAutoString key;
-    nsAutoString value;
-    while (annotationsEnum->Next(key, value)) {
-      JS::RootedValue jsValue(cx);
-      jsValue.setString(JS_NewUCStringCopyN(cx, value.get(), value.Length()));
-      if (!JS_DefineUCProperty(cx, jsAnnotation, key.get(), key.Length(),
-                               jsValue, JSPROP_ENUMERATE)) {
-        returnedObject.set(nullptr);
-        return;
-      }
-    }
-    if (!JS_SetElement(cx, annotationsArray, annotationIndex, jsAnnotation)) {
-      continue;
-    }
-    ++annotationIndex;
-  }
-  // Return the array using a |MutableHandleObject| to avoid triggering a false
-  // positive rooting issue in the hazard analysis build.
-  returnedObject.set(annotationsArray);
-}
-
-static JSObject*
-CreateJSHangHistogram(JSContext* cx, const Telemetry::HangHistogram& hang)
-{
-  JS::RootedObject ret(cx, JS_NewPlainObject(cx));
-  if (!ret) {
-    return nullptr;
-  }
-
-  JS::RootedObject stack(cx, CreateJSHangStack(cx, hang.GetStack()));
-  JS::RootedObject time(cx, CreateJSTimeHistogram(cx, hang));
-  auto& hangAnnotations = hang.GetAnnotations();
-  JS::RootedObject annotations(cx);
-  CreateJSHangAnnotations(cx, hangAnnotations, &annotations);
-
-  if (!stack ||
-      !time ||
-      !annotations ||
-      !JS_DefineProperty(cx, ret, "stack", stack, JSPROP_ENUMERATE) ||
-      !JS_DefineProperty(cx, ret, "histogram", time, JSPROP_ENUMERATE) ||
-      (!hangAnnotations.empty() && // <-- Only define annotations when nonempty
-        !JS_DefineProperty(cx, ret, "annotations", annotations, JSPROP_ENUMERATE))) {
-    return nullptr;
-  }
-
-  return ret;
-}
-
-static JSObject*
-CreateJSThreadHangStats(JSContext* cx, const Telemetry::ThreadHangStats& thread)
-{
-  JS::RootedObject ret(cx, JS_NewPlainObject(cx));
-  if (!ret) {
-    return nullptr;
-  }
-  JS::RootedString name(cx, JS_NewStringCopyZ(cx, thread.GetName()));
-  if (!name ||
-      !JS_DefineProperty(cx, ret, "name", name, JSPROP_ENUMERATE)) {
-    return nullptr;
-  }
-
-  JS::RootedObject activity(cx, CreateJSTimeHistogram(cx, thread.mActivity));
-  if (!activity ||
-      !JS_DefineProperty(cx, ret, "activity", activity, JSPROP_ENUMERATE)) {
-    return nullptr;
-  }
-
-  // Process the hangs into a hangs object.
-  JS::RootedObject hangs(cx, JS_NewArrayObject(cx, 0));
-  if (!hangs) {
-    return nullptr;
-  }
-  for (size_t i = 0; i < thread.mHangs.length(); i++) {
-    JS::RootedObject obj(cx, CreateJSHangHistogram(cx, thread.mHangs[i]));
-    if (!ret) {
-      return nullptr;
-    }
-
-    // Check if we have a cached native stack index, and if we do record it.
-    uint32_t index = thread.mHangs[i].GetNativeStackIndex();
-    if (index != Telemetry::HangHistogram::NO_NATIVE_STACK_INDEX) {
-      if (!JS_DefineProperty(cx, obj, "nativeStack", index, JSPROP_ENUMERATE)) {
-        return nullptr;
-      }
-    }
-
-    if (!JS_DefineElement(cx, hangs, i, obj, JSPROP_ENUMERATE)) {
-      return nullptr;
-    }
-  }
-  if (!JS_DefineProperty(cx, ret, "hangs", hangs, JSPROP_ENUMERATE)) {
-    return nullptr;
-  }
-
-  // We should already have a CombinedStacks object on the ThreadHangStats, so
-  // add that one.
-  JS::RootedObject fullReportObj(cx, CreateJSStackObject(cx, thread.mCombinedStacks));
-  if (!fullReportObj) {
-    return nullptr;
-  }
-
-  if (!JS_DefineProperty(cx, ret, "nativeStacks", fullReportObj, JSPROP_ENUMERATE)) {
-    return nullptr;
-  }
-
-  return ret;
-}
-
 NS_IMETHODIMP
 TelemetryImpl::GetThreadHangStats(JSContext* cx, JS::MutableHandle<JS::Value> ret)
 {
   JS::RootedObject retObj(cx, JS_NewArrayObject(cx, 0));
   if (!retObj) {
     return NS_ERROR_FAILURE;
   }
   size_t threadIndex = 0;
@@ -2424,110 +2213,16 @@ RecordShutdownEndTimeStamp() {
 
 } // namespace mozilla
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // EXTERNALLY VISIBLE FUNCTIONS in mozilla::Telemetry::
-// These are NOT listed in Telemetry.h
-
-namespace mozilla {
-namespace Telemetry {
-
-void
-TimeHistogram::Add(PRIntervalTime aTime)
-{
-  uint32_t timeMs = PR_IntervalToMilliseconds(aTime);
-  size_t index = mozilla::FloorLog2(timeMs);
-  operator[](index)++;
-}
-
-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);
-}
-
-uint32_t
-HangHistogram::GetHash(const HangStack& aStack)
-{
-  uint32_t hash = 0;
-  for (const char* const* label = aStack.begin();
-       label != aStack.end(); label++) {
-    /* If the string is within our buffer, we need to hash its content.
-       Otherwise, the string is statically allocated, and we only need
-       to hash the pointer instead of the content. */
-    if (aStack.IsInBuffer(*label)) {
-      hash = AddToHash(hash, HashString(*label));
-    } else {
-      hash = AddToHash(hash, *label);
-    }
-  }
-  return hash;
-}
-
-bool
-HangHistogram::operator==(const HangHistogram& aOther) const
-{
-  if (mHash != aOther.mHash) {
-    return false;
-  }
-  if (mStack.length() != aOther.mStack.length()) {
-    return false;
-  }
-  return mStack == aOther.mStack;
-}
-
-} // namespace Telemetry
-} // namespace mozilla
-
-
-////////////////////////////////////////////////////////////////////////
-////////////////////////////////////////////////////////////////////////
-//
-// EXTERNALLY VISIBLE FUNCTIONS in mozilla::Telemetry::
 // These are listed in Telemetry.h
 
 namespace mozilla {
 namespace Telemetry {
 
 // The external API for controlling recording state
 void
 SetHistogramRecordingEnabled(HistogramID aID, bool aEnabled)
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/ThreadHangStats.cpp
@@ -0,0 +1,316 @@
+/* -*- 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 "mozilla/HangAnnotations.h"
+#include "ThreadHangStats.h"
+#include "nsITelemetry.h"
+#include "HangReports.h"
+#include "jsapi.h"
+
+namespace {
+
+using namespace mozilla;
+using namespace mozilla::HangMonitor;
+using namespace mozilla::Telemetry;
+
+static JSObject*
+CreateJSTimeHistogram(JSContext* cx, const Telemetry::TimeHistogram& time)
+{
+  /* Create JS representation of TimeHistogram,
+     in the format of Chromium-style histograms. */
+  JS::RootedObject ret(cx, JS_NewPlainObject(cx));
+  if (!ret) {
+    return nullptr;
+  }
+
+  if (!JS_DefineProperty(cx, ret, "min", time.GetBucketMin(0),
+                         JSPROP_ENUMERATE) ||
+      !JS_DefineProperty(cx, ret, "max",
+                         time.GetBucketMax(ArrayLength(time) - 1),
+                         JSPROP_ENUMERATE) ||
+      !JS_DefineProperty(cx, ret, "histogram_type",
+                         nsITelemetry::HISTOGRAM_EXPONENTIAL,
+                         JSPROP_ENUMERATE)) {
+    return nullptr;
+  }
+  // TODO: calculate "sum"
+  if (!JS_DefineProperty(cx, ret, "sum", 0, JSPROP_ENUMERATE)) {
+    return nullptr;
+  }
+
+  JS::RootedObject ranges(
+    cx, JS_NewArrayObject(cx, ArrayLength(time) + 1));
+  JS::RootedObject counts(
+    cx, JS_NewArrayObject(cx, ArrayLength(time) + 1));
+  if (!ranges || !counts) {
+    return nullptr;
+  }
+  /* In a Chromium-style histogram, the first bucket is an "under" bucket
+     that represents all values below the histogram's range. */
+  if (!JS_DefineElement(cx, ranges, 0, time.GetBucketMin(0), JSPROP_ENUMERATE) ||
+      !JS_DefineElement(cx, counts, 0, 0, JSPROP_ENUMERATE)) {
+    return nullptr;
+  }
+  for (size_t i = 0; i < ArrayLength(time); i++) {
+    if (!JS_DefineElement(cx, ranges, i + 1, time.GetBucketMax(i),
+                          JSPROP_ENUMERATE) ||
+        !JS_DefineElement(cx, counts, i + 1, time[i], JSPROP_ENUMERATE)) {
+      return nullptr;
+    }
+  }
+  if (!JS_DefineProperty(cx, ret, "ranges", ranges, JSPROP_ENUMERATE) ||
+      !JS_DefineProperty(cx, ret, "counts", counts, JSPROP_ENUMERATE)) {
+    return nullptr;
+  }
+  return ret;
+}
+
+static JSObject*
+CreateJSHangStack(JSContext* cx, const Telemetry::HangStack& stack)
+{
+  JS::RootedObject ret(cx, JS_NewArrayObject(cx, stack.length()));
+  if (!ret) {
+    return nullptr;
+  }
+  for (size_t i = 0; i < stack.length(); i++) {
+    JS::RootedString string(cx, JS_NewStringCopyZ(cx, stack[i]));
+    if (!JS_DefineElement(cx, ret, i, string, JSPROP_ENUMERATE)) {
+      return nullptr;
+    }
+  }
+  return ret;
+}
+
+static void
+CreateJSHangAnnotations(JSContext* cx, const HangAnnotationsVector& annotations,
+                        JS::MutableHandleObject returnedObject)
+{
+  JS::RootedObject annotationsArray(cx, JS_NewArrayObject(cx, 0));
+  if (!annotationsArray) {
+    returnedObject.set(nullptr);
+    return;
+  }
+  // We keep track of the annotations we reported in this hash set, so we can
+  // discard duplicated ones.
+  nsTHashtable<nsStringHashKey> reportedAnnotations;
+  size_t annotationIndex = 0;
+  for (const auto & curAnnotations : annotations) {
+    JS::RootedObject jsAnnotation(cx, JS_NewPlainObject(cx));
+    if (!jsAnnotation) {
+      continue;
+    }
+    // Build a key to index the current annotations in our hash set.
+    nsAutoString annotationsKey;
+    nsresult rv = ComputeAnnotationsKey(curAnnotations, annotationsKey);
+    if (NS_FAILED(rv)) {
+      continue;
+    }
+    // Check if the annotations are in the set. If that's the case, don't double report.
+    if (reportedAnnotations.GetEntry(annotationsKey)) {
+      continue;
+    }
+    // If not, report them.
+    reportedAnnotations.PutEntry(annotationsKey);
+    UniquePtr<HangAnnotations::Enumerator> annotationsEnum =
+      curAnnotations->GetEnumerator();
+    if (!annotationsEnum) {
+      continue;
+    }
+    nsAutoString key;
+    nsAutoString value;
+    while (annotationsEnum->Next(key, value)) {
+      JS::RootedValue jsValue(cx);
+      jsValue.setString(JS_NewUCStringCopyN(cx, value.get(), value.Length()));
+      if (!JS_DefineUCProperty(cx, jsAnnotation, key.get(), key.Length(),
+                               jsValue, JSPROP_ENUMERATE)) {
+        returnedObject.set(nullptr);
+        return;
+      }
+    }
+    if (!JS_SetElement(cx, annotationsArray, annotationIndex, jsAnnotation)) {
+      continue;
+    }
+    ++annotationIndex;
+  }
+  // Return the array using a |MutableHandleObject| to avoid triggering a false
+  // positive rooting issue in the hazard analysis build.
+  returnedObject.set(annotationsArray);
+}
+
+static JSObject*
+CreateJSHangHistogram(JSContext* cx, const Telemetry::HangHistogram& hang)
+{
+  JS::RootedObject ret(cx, JS_NewPlainObject(cx));
+  if (!ret) {
+    return nullptr;
+  }
+
+  JS::RootedObject stack(cx, CreateJSHangStack(cx, hang.GetStack()));
+  JS::RootedObject time(cx, CreateJSTimeHistogram(cx, hang));
+  auto& hangAnnotations = hang.GetAnnotations();
+  JS::RootedObject annotations(cx);
+  CreateJSHangAnnotations(cx, hangAnnotations, &annotations);
+
+  if (!stack ||
+      !time ||
+      !annotations ||
+      !JS_DefineProperty(cx, ret, "stack", stack, JSPROP_ENUMERATE) ||
+      !JS_DefineProperty(cx, ret, "histogram", time, JSPROP_ENUMERATE) ||
+      (!hangAnnotations.empty() && // <-- Only define annotations when nonempty
+        !JS_DefineProperty(cx, ret, "annotations", annotations, JSPROP_ENUMERATE))) {
+    return nullptr;
+  }
+
+  return ret;
+}
+
+} // namespace
+
+namespace mozilla {
+namespace Telemetry {
+
+JSObject*
+CreateJSThreadHangStats(JSContext* cx, const Telemetry::ThreadHangStats& thread)
+{
+  JS::RootedObject ret(cx, JS_NewPlainObject(cx));
+  if (!ret) {
+    return nullptr;
+  }
+  JS::RootedString name(cx, JS_NewStringCopyZ(cx, thread.GetName()));
+  if (!name ||
+      !JS_DefineProperty(cx, ret, "name", name, JSPROP_ENUMERATE)) {
+    return nullptr;
+  }
+
+  JS::RootedObject activity(cx, CreateJSTimeHistogram(cx, thread.mActivity));
+  if (!activity ||
+      !JS_DefineProperty(cx, ret, "activity", activity, JSPROP_ENUMERATE)) {
+    return nullptr;
+  }
+
+  // Process the hangs into a hangs object.
+  JS::RootedObject hangs(cx, JS_NewArrayObject(cx, 0));
+  if (!hangs) {
+    return nullptr;
+  }
+  for (size_t i = 0; i < thread.mHangs.length(); i++) {
+    JS::RootedObject obj(cx, CreateJSHangHistogram(cx, thread.mHangs[i]));
+    if (!ret) {
+      return nullptr;
+    }
+
+    // Check if we have a cached native stack index, and if we do record it.
+    uint32_t index = thread.mHangs[i].GetNativeStackIndex();
+    if (index != Telemetry::HangHistogram::NO_NATIVE_STACK_INDEX) {
+      if (!JS_DefineProperty(cx, obj, "nativeStack", index, JSPROP_ENUMERATE)) {
+        return nullptr;
+      }
+    }
+
+    if (!JS_DefineElement(cx, hangs, i, obj, JSPROP_ENUMERATE)) {
+      return nullptr;
+    }
+  }
+  if (!JS_DefineProperty(cx, ret, "hangs", hangs, JSPROP_ENUMERATE)) {
+    return nullptr;
+  }
+
+  // We should already have a CombinedStacks object on the ThreadHangStats, so
+  // add that one.
+  JS::RootedObject fullReportObj(cx, CreateJSStackObject(cx, thread.mCombinedStacks));
+  if (!fullReportObj) {
+    return nullptr;
+  }
+
+  if (!JS_DefineProperty(cx, ret, "nativeStacks", fullReportObj, JSPROP_ENUMERATE)) {
+    return nullptr;
+  }
+
+  return ret;
+}
+
+void
+TimeHistogram::Add(PRIntervalTime aTime)
+{
+  uint32_t timeMs = PR_IntervalToMilliseconds(aTime);
+  size_t index = mozilla::FloorLog2(timeMs);
+  operator[](index)++;
+}
+
+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);
+}
+
+uint32_t
+HangHistogram::GetHash(const HangStack& aStack)
+{
+  uint32_t hash = 0;
+  for (const char* const* label = aStack.begin();
+       label != aStack.end(); label++) {
+    /* If the string is within our buffer, we need to hash its content.
+       Otherwise, the string is statically allocated, and we only need
+       to hash the pointer instead of the content. */
+    if (aStack.IsInBuffer(*label)) {
+      hash = AddToHash(hash, HashString(*label));
+    } else {
+      hash = AddToHash(hash, *label);
+    }
+  }
+  return hash;
+}
+
+bool
+HangHistogram::operator==(const HangHistogram& aOther) const
+{
+  if (mHash != aOther.mHash) {
+    return false;
+  }
+  if (mStack.length() != aOther.mStack.length()) {
+    return false;
+  }
+  return mStack == aOther.mStack;
+}
+
+} // namespace Telemetry
+} // namespace mozilla
--- a/toolkit/components/telemetry/ThreadHangStats.h
+++ b/toolkit/components/telemetry/ThreadHangStats.h
@@ -12,16 +12,17 @@
 #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.
@@ -259,12 +260,23 @@ public:
   {
     aOther.mNativeStackCnt = 0;
   }
   const char* GetName() const {
     return mName.get();
   }
 };
 
+/**
+ * Reflects thread hang stats object as a JS object.
+ *
+ * @param JSContext* cx javascript context.
+ * @param JSContext* cx thread hang statistics.
+ *
+ * @return JSObject* Javascript reflection of the statistics.
+ */
+JSObject*
+CreateJSThreadHangStats(JSContext* cx, const Telemetry::ThreadHangStats& thread);
+
 } // namespace Telemetry
 } // namespace mozilla
 
 #endif // mozilla_BackgroundHangTelemetry_h
--- a/toolkit/components/telemetry/moz.build
+++ b/toolkit/components/telemetry/moz.build
@@ -59,16 +59,17 @@ SOURCES += [
     'ipc/TelemetryIPC.cpp',
     'ipc/TelemetryIPCAccumulator.cpp',
     'ProcessedStack.cpp',
     'Telemetry.cpp',
     'TelemetryCommon.cpp',
     'TelemetryEvent.cpp',
     'TelemetryHistogram.cpp',
     'TelemetryScalar.cpp',
+    'ThreadHangStats.cpp',
     'WebrtcTelemetry.cpp',
 ]
 
 # KeyedStackCapturer entirely relies on profiler to be enabled.
 if CONFIG['MOZ_GECKO_PROFILER']:
     SOURCES += [
       'KeyedStackCapturer.cpp'
     ]