Bug 932865 - Expose thread hang stats through nsITelemetry and telemetry ping; r=vladan
authorJim Chen <nchen@mozilla.com>
Fri, 22 Nov 2013 14:17:31 -0500
changeset 157082 4c31b0875e214c33a4abce64b9e8e27d2ef135f6
parent 157081 c32c0526e7b56e40eb2d4cab666a9634850039e6
child 157083 5365478bdea9ca9e1eb8299a5f3705a907e34955
push id36633
push usernchen@mozilla.com
push dateFri, 22 Nov 2013 19:20:41 +0000
treeherdermozilla-inbound@5365478bdea9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvladan
bugs932865
milestone28.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 932865 - Expose thread hang stats through nsITelemetry and telemetry ping; r=vladan
toolkit/components/telemetry/Telemetry.cpp
toolkit/components/telemetry/TelemetryPing.js
toolkit/components/telemetry/ThreadHangStats.h
toolkit/components/telemetry/nsITelemetry.idl
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -41,16 +41,17 @@
 #include "nsHashKeys.h"
 #include "nsBaseHashtable.h"
 #include "nsXULAppAPI.h"
 #include "nsThreadUtils.h"
 #include "nsNetCID.h"
 #include "nsNetUtil.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/PoisonIOInterposer.h"
 #if defined(MOZ_ENABLE_PROFILER_SPS)
 #include "shared-libraries.h"
@@ -1708,16 +1709,190 @@ 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_NewObject(cx, nullptr, nullptr, nullptr));
+  if (!ret) {
+    return nullptr;
+  }
+
+  if (!JS_DefineProperty(cx, ret, "min",
+                         UINT_TO_JSVAL(time.GetBucketMin(0)),
+                         nullptr, nullptr, JSPROP_ENUMERATE) ||
+      !JS_DefineProperty(cx, ret, "max",
+                         UINT_TO_JSVAL(time.GetBucketMax(
+                           ArrayLength(time) - 1)),
+                         nullptr, nullptr, JSPROP_ENUMERATE) ||
+      !JS_DefineProperty(cx, ret, "histogram_type",
+                         INT_TO_JSVAL(nsITelemetry::HISTOGRAM_EXPONENTIAL),
+                         nullptr, nullptr, JSPROP_ENUMERATE)) {
+    return nullptr;
+  }
+  // TODO: calculate "sum", "log_sum", and "log_sum_squares"
+  if (!JS_DefineProperty(cx, ret, "sum", INT_TO_JSVAL(0),
+                         nullptr, nullptr, JSPROP_ENUMERATE) ||
+      !JS_DefineProperty(cx, ret, "log_sum", DOUBLE_TO_JSVAL(0.0),
+                         nullptr, nullptr, JSPROP_ENUMERATE) ||
+      !JS_DefineProperty(cx, ret, "log_sum_squares", DOUBLE_TO_JSVAL(0.0),
+                         nullptr, nullptr, JSPROP_ENUMERATE)) {
+    return nullptr;
+  }
+
+  JS::RootedObject ranges(
+    cx, JS_NewArrayObject(cx, ArrayLength(time) + 1, nullptr));
+  JS::RootedObject counts(
+    cx, JS_NewArrayObject(cx, ArrayLength(time) + 1, nullptr));
+  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. */
+  JS::RootedValue underRange(cx, INT_TO_JSVAL(time.GetBucketMin(0)));
+  JS::RootedValue underCount(cx, INT_TO_JSVAL(0));
+  if (!JS_SetElement(cx, ranges, 0, &underRange) ||
+      !JS_SetElement(cx, counts, 0, &underCount)) {
+    return nullptr;
+  }
+  for (size_t i = 0; i < ArrayLength(time); i++) {
+    JS::RootedValue range(cx, UINT_TO_JSVAL(time.GetBucketMax(i)));
+    JS::RootedValue count(cx, UINT_TO_JSVAL(time[i]));
+    if (!JS_SetElement(cx, ranges, i + 1, &range) ||
+        !JS_SetElement(cx, counts, i + 1, &count)) {
+      return nullptr;
+    }
+  }
+  if (!JS_DefineProperty(cx, ret, "ranges", OBJECT_TO_JSVAL(ranges),
+                         nullptr, nullptr, JSPROP_ENUMERATE) ||
+      !JS_DefineProperty(cx, ret, "counts", OBJECT_TO_JSVAL(counts),
+                         nullptr, nullptr, JSPROP_ENUMERATE)) {
+    return nullptr;
+  }
+  return ret;
+}
+
+static JSObject*
+CreateJSHangHistogram(JSContext* cx, const Telemetry::HangHistogram& hang)
+{
+  JS::RootedObject ret(cx, JS_NewObject(cx, nullptr, nullptr, nullptr));
+  if (!ret) {
+    return nullptr;
+  }
+
+  const Telemetry::HangHistogram::Stack& hangStack = hang.GetStack();
+  JS::RootedObject stack(cx,
+    JS_NewArrayObject(cx, hangStack.length(), nullptr));
+  if (!ret) {
+    return nullptr;
+  }
+  for (size_t i = 0; i < hangStack.length(); i++) {
+    JS::RootedString string(cx, JS_NewStringCopyZ(cx, hangStack[i]));
+    JS::RootedValue frame(cx, STRING_TO_JSVAL(string));
+    if (!JS_SetElement(cx, stack, i, &frame)) {
+      return nullptr;
+    }
+  }
+
+  JS::RootedObject time(cx, CreateJSTimeHistogram(cx, hang));
+  if (!time ||
+      !JS_DefineProperty(cx, ret, "stack", OBJECT_TO_JSVAL(stack),
+                         nullptr, nullptr, JSPROP_ENUMERATE) ||
+      !JS_DefineProperty(cx, ret, "histogram", OBJECT_TO_JSVAL(time),
+                         nullptr, nullptr, JSPROP_ENUMERATE)) {
+    return nullptr;
+  }
+  return ret;
+}
+
+static JSObject*
+CreateJSThreadHangStats(JSContext* cx, const Telemetry::ThreadHangStats& thread)
+{
+  JS::RootedObject ret(cx, JS_NewObject(cx, nullptr, nullptr, nullptr));
+  if (!ret) {
+    return nullptr;
+  }
+  JS::RootedString name(cx, JS_NewStringCopyZ(cx, thread.GetName()));
+  if (!name ||
+      !JS_DefineProperty(cx, ret, "name", STRING_TO_JSVAL(name),
+                         nullptr, nullptr, JSPROP_ENUMERATE)) {
+    return nullptr;
+  }
+
+  JS::RootedObject activity(cx, CreateJSTimeHistogram(cx, thread.mActivity));
+  if (!activity ||
+      !JS_DefineProperty(cx, ret, "activity", OBJECT_TO_JSVAL(activity),
+                         nullptr, nullptr, JSPROP_ENUMERATE)) {
+    return nullptr;
+  }
+
+  JS::RootedObject hangs(cx, JS_NewArrayObject(cx, 0, nullptr));
+  if (!hangs) {
+    return nullptr;
+  }
+  for (size_t i = 0; i < thread.mHangs.length(); i++) {
+    JS::RootedObject obj(cx, CreateJSHangHistogram(cx, thread.mHangs[i]));
+    JS::RootedValue hang(cx, OBJECT_TO_JSVAL(obj));
+    if (!JS_SetElement(cx, hangs, i, &hang)) {
+      return nullptr;
+    }
+  }
+  if (!JS_DefineProperty(cx, ret, "hangs", OBJECT_TO_JSVAL(hangs),
+                         nullptr, nullptr, JSPROP_ENUMERATE)) {
+    return nullptr;
+  }
+  return ret;
+}
+
+NS_IMETHODIMP
+TelemetryImpl::GetThreadHangStats(JSContext* cx, JS::Value* ret)
+{
+  JS::RootedObject retObj(cx, JS_NewArrayObject(cx, 0, nullptr));
+  if (!retObj) {
+    return NS_ERROR_FAILURE;
+  }
+  size_t threadIndex = 0;
+
+  /* First add active threads; we need to hold |iter| (and its lock)
+     throughout this method to avoid a race condition where a thread can
+     be recorded twice if the thread is destroyed while this method is
+     running */
+  BackgroundHangMonitor::ThreadHangStatsIterator iter;
+  for (Telemetry::ThreadHangStats* histogram = iter.GetNext();
+       histogram; histogram = iter.GetNext()) {
+    JS::RootedObject obj(cx,
+      CreateJSThreadHangStats(cx, *histogram));
+    JS::RootedValue thread(cx, OBJECT_TO_JSVAL(obj));
+    if (!JS_SetElement(cx, retObj, threadIndex++, &thread)) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+
+  // Add saved threads next
+  MutexAutoLock autoLock(mThreadHangStatsMutex);
+  for (size_t i = 0; i < mThreadHangStats.length(); i++) {
+    JS::RootedObject obj(cx,
+      CreateJSThreadHangStats(cx, mThreadHangStats[i]));
+    JS::RootedValue thread(cx, OBJECT_TO_JSVAL(obj));
+    if (!JS_SetElement(cx, retObj, threadIndex++, &thread)) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+  *ret = OBJECT_TO_JSVAL(retObj);
+  return NS_OK;
+}
+
 void
 TelemetryImpl::ReadLateWritesStacks(nsIFile* aProfileDir)
 {
   nsAutoCString nativePath;
   nsresult rv = aProfileDir->GetNativePath(nativePath);
   if (NS_FAILED(rv)) {
     return;
   }
--- a/toolkit/components/telemetry/TelemetryPing.js
+++ b/toolkit/components/telemetry/TelemetryPing.js
@@ -306,16 +306,26 @@ TelemetryPing.prototype = {
       }
       if (Object.keys(packedHistograms).length != 0)
         ret[addonName] = packedHistograms;
     }
 
     return ret;
   },
 
+  getThreadHangStats: function getThreadHangStats(stats) {
+    stats.forEach((thread) => {
+      thread.activity = this.packHistogram(thread.activity);
+      thread.hangs.forEach((hang) => {
+        hang.histogram = this.packHistogram(hang.histogram);
+      });
+    });
+    return stats;
+  },
+
   /**
    * Descriptive metadata
    *
    * @param  reason
    *         The reason for the telemetry ping, this will be included in the
    *         returned metadata,
    * @return The metadata as a JS object
    */
@@ -543,16 +553,17 @@ TelemetryPing.prototype = {
    */
   assemblePayloadWithMeasurements: function assemblePayloadWithMeasurements(simpleMeasurements, info) {
     let payloadObj = {
       ver: PAYLOAD_VERSION,
       simpleMeasurements: simpleMeasurements,
       histograms: this.getHistograms(Telemetry.histogramSnapshots),
       slowSQL: Telemetry.slowSQL,
       chromeHangs: Telemetry.chromeHangs,
+      threadHangStats: this.getThreadHangStats(Telemetry.threadHangStats),
       lateWrites: Telemetry.lateWrites,
       addonHistograms: this.getAddonHistograms(),
       addonDetails: AddonManagerPrivate.getTelemetryDetails(),
       info: info
     };
 
     if (Object.keys(this._slowSQLStartup).length != 0 &&
         (Object.keys(this._slowSQLStartup.mainThread).length ||
--- a/toolkit/components/telemetry/ThreadHangStats.h
+++ b/toolkit/components/telemetry/ThreadHangStats.h
@@ -2,16 +2,17 @@
 /* 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/Move.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/Vector.h"
 
 #include "nsString.h"
 #include "prinrval.h"
 
@@ -25,16 +26,26 @@ static const size_t kTimeHistogramBucket
    stored in milliseconds. */
 class TimeHistogram : public mozilla::Array<uint32_t, kTimeHistogramBuckets>
 {
 public:
   TimeHistogram()
   {
     mozilla::PodArrayZero(*this);
   }
+  // Get minimum (inclusive) range of bucket in milliseconds
+  uint32_t GetBucketMin(size_t aBucket) const {
+    MOZ_ASSERT(aBucket < ArrayLength(*this));
+    return (1u << aBucket) & ~1u; // Bucket 0 starts at 0, not 1
+  }
+  // Get maximum (inclusive) range of bucket in milliseconds
+  uint32_t GetBucketMax(size_t aBucket) const {
+    MOZ_ASSERT(aBucket < ArrayLength(*this));
+    return (1u << (aBucket + 1u)) - 1u;
+  }
   void Add(PRIntervalTime aTime);
 };
 
 /* A hang histogram consists of a stack associated with the
    hang, along with a time histogram of the hang times. */
 class HangHistogram : public TimeHistogram
 {
 public:
--- a/toolkit/components/telemetry/nsITelemetry.idl
+++ b/toolkit/components/telemetry/nsITelemetry.idl
@@ -95,16 +95,33 @@ interface nsITelemetry : nsISupports
    * An array of chrome hang reports. Each element is a hang report represented
    * as an object containing the hang duration, call stack PCs and information
    * about modules in memory.
    */
   [implicit_jscontext]
   readonly attribute jsval chromeHangs;
 
   /*
+   * An array of thread hang stats,
+   *   [<thread>, <thread>, ...]
+   * <thread> represents a single thread,
+   *   {"name": "<name>",
+   *    "activity": <time>,
+   *    "hangs": [<hang>, <hang>, ...]}
+   * <time> represents a histogram of time intervals in milliseconds,
+   *   with the same format as histogramSnapshots
+   * <hang> represents a particular hang,
+   *   {"stack": <stack>, "histogram": <time>}
+   * <stack> represents the hang's stack,
+   *   ["<frame_0>", "<frame_1>", ...]
+   */
+  [implicit_jscontext]
+  readonly attribute jsval threadHangStats;
+
+  /*
    * An object with two fields: memoryMap and stacks.
    * * memoryMap is a list of loaded libraries.
    * * stacks is a list of stacks. Each stack is a list of pairs of the form
    *   [moduleIndex, offset]. The moduleIndex is an index into the memoryMap and
    *   offset is an offset in the library at memoryMap[moduleIndex].
    * This format is used to make it easier to send the stacks to the
    * symbolication server.
    */