Bug 808699 - Change the wire format for hang reports. r=vladan.
authorRafael Ávila de Espíndola <respindola@mozilla.org>
Tue, 20 Nov 2012 09:45:14 -0500
changeset 113775 2b3982c803ec8436db700f8e26e5165f05d4b0e3
parent 113774 b7e492bf7c1386e9847913d91dab00415937a296
child 113776 f0a6c0c3cd8db7ecfd527c418456473291d59493
push id23890
push userryanvm@gmail.com
push dateWed, 21 Nov 2012 02:43:32 +0000
treeherdermozilla-central@4f19e7fd8bea [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvladan
bugs808699
milestone20.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 808699 - Change the wire format for hang reports. r=vladan.
toolkit/components/telemetry/ProcessedStack.h
toolkit/components/telemetry/Telemetry.cpp
toolkit/content/aboutTelemetry.js
xpcom/build/mozPoisonWriteMac.cpp
xpcom/threads/HangMonitor.cpp
--- a/toolkit/components/telemetry/ProcessedStack.h
+++ b/toolkit/components/telemetry/ProcessedStack.h
@@ -30,23 +30,16 @@ public:
     // was in.
     uint16_t mModIndex;
   };
   struct Module
   {
     // The file name, /foo/bar/libxul.so for example.
     std::string mName;
 
-    // The address it was loaded to.
-    // FIXME: remove this once chrome hang has switched to using offsets.
-    uintptr_t mStart;
-
-    // The size of this mapping. May or may not be the entire file.
-    // FIXME: remove this. It was only used as a sanity check.
-    size_t mMappingSize;
     // Windows specific fields. On other platforms they are 0/empty.
     int mPdbAge;
     std::string mPdbSignature;
     std::string mPdbName;
 
     bool operator==(const Module& other) const;
   };
 
@@ -60,16 +53,14 @@ public:
 private:
   std::vector<Module> mModules;
   std::vector<Frame> mStack;
 };
 
 // Get the current list of loaded modules, filter and pair it to the provided
 // stack. We let the caller collect the stack since different callers have
 // different needs (current thread X main thread, stopping the thread, etc).
-// FIXME: remove the aRelative option once chrome hang has switched to using
-// offsets.
 ProcessedStack
-GetStackAndModules(const std::vector<uintptr_t> &aPCs, bool aRelative);
+GetStackAndModules(const std::vector<uintptr_t> &aPCs);
 
 } // namespace Telemetry
 } // namespace mozilla
 #endif // ProcessedStack_h__
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -1216,147 +1216,145 @@ TelemetryImpl::GetDebugSlowSQL(JSContext
     return NS_OK;
   return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 TelemetryImpl::GetChromeHangs(JSContext *cx, jsval *ret)
 {
   MutexAutoLock hangReportMutex(mHangReportsMutex);
+
+  JSObject *fullReportObj = JS_NewObject(cx, nullptr, nullptr, nullptr);
+  if (!fullReportObj) {
+    return NS_ERROR_FAILURE;
+  }
+
+  *ret = OBJECT_TO_JSVAL(fullReportObj);
+
+  JSObject *moduleArray = JS_NewArrayObject(cx, 0, nullptr);
+  if (!moduleArray) {
+    return NS_ERROR_FAILURE;
+  }
+  JSBool ok = JS_DefineProperty(cx, fullReportObj, "memoryMap",
+                                OBJECT_TO_JSVAL(moduleArray),
+                                NULL, NULL, JSPROP_ENUMERATE);
+  if (!ok) {
+    return NS_ERROR_FAILURE;
+  }
+
+  const uint32_t moduleCount = mHangReports.GetModuleCount();
+  for (size_t moduleIndex = 0; moduleIndex < moduleCount; ++moduleIndex) {
+    // Current module
+    const Telemetry::ProcessedStack::Module& module =
+      mHangReports.GetModule(moduleIndex);
+
+    JSObject *moduleInfoArray = JS_NewArrayObject(cx, 0, nullptr);
+    if (!moduleInfoArray) {
+      return NS_ERROR_FAILURE;
+    }
+    jsval val = OBJECT_TO_JSVAL(moduleInfoArray);
+    if (!JS_SetElement(cx, moduleArray, moduleIndex, &val)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    unsigned index = 0;
+
+    // Module name
+    JSString *str = JS_NewStringCopyZ(cx, module.mName.c_str());
+    if (!str) {
+      return NS_ERROR_FAILURE;
+    }
+    val = STRING_TO_JSVAL(str);
+    if (!JS_SetElement(cx, moduleInfoArray, index++, &val)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    // "PDB Age" identifier
+    val = INT_TO_JSVAL(module.mPdbAge);
+    if (!JS_SetElement(cx, moduleInfoArray, index++, &val)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    // "PDB Signature" GUID
+    str = JS_NewStringCopyZ(cx, module.mPdbSignature.c_str());
+    if (!str) {
+      return NS_ERROR_FAILURE;
+    }
+    val = STRING_TO_JSVAL(str);
+    if (!JS_SetElement(cx, moduleInfoArray, index++, &val)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    // Name of associated PDB file
+    str = JS_NewStringCopyZ(cx, module.mPdbName.c_str());
+    if (!str) {
+      return NS_ERROR_FAILURE;
+    }
+    val = STRING_TO_JSVAL(str);
+    if (!JS_SetElement(cx, moduleInfoArray, index++, &val)) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+
   JSObject *reportArray = JS_NewArrayObject(cx, 0, nullptr);
   if (!reportArray) {
     return NS_ERROR_FAILURE;
   }
-  *ret = OBJECT_TO_JSVAL(reportArray);
+  ok = JS_DefineProperty(cx, fullReportObj, "stacks",
+                         OBJECT_TO_JSVAL(reportArray),
+                         NULL, NULL, JSPROP_ENUMERATE);
+  if (!ok) {
+    return NS_ERROR_FAILURE;
+  }
 
-  // Each hang report is an object in the 'chromeHangs' array
-  for (size_t i = 0; i < mHangReports.GetStackCount(); ++i) {
-    const CombinedStacks::Stack &stack = mHangReports.GetStack(i);
-    JSObject *reportObj = JS_NewObject(cx, NULL, NULL, NULL);
-    if (!reportObj) {
-      return NS_ERROR_FAILURE;
-    }
-    jsval reportObjVal = OBJECT_TO_JSVAL(reportObj);
-    if (!JS_SetElement(cx, reportArray, i, &reportObjVal)) {
+  JSObject *durationArray = JS_NewArrayObject(cx, 0, nullptr);
+  ok = JS_DefineProperty(cx, fullReportObj, "durations",
+                         OBJECT_TO_JSVAL(durationArray),
+                         NULL, NULL, JSPROP_ENUMERATE);
+  if (!ok) {
+    return NS_ERROR_FAILURE;
+  }
+
+  for (size_t i = 0, n = mHangReports.GetStackCount(); i < n; ++i) {
+    jsval duration = INT_TO_JSVAL(mHangReports.GetDuration(i));
+    if (!JS_SetElement(cx, durationArray, i, &duration)) {
       return NS_ERROR_FAILURE;
     }
 
-    // Record the hang duration (expressed in seconds)
-    JSBool ok = JS_DefineProperty(cx, reportObj, "duration",
-                                  INT_TO_JSVAL(mHangReports.GetDuration(i)),
-                                  NULL, NULL, JSPROP_ENUMERATE);
-    if (!ok) {
-      return NS_ERROR_FAILURE;
-    }
-
-    // Represent call stack PCs as strings
-    // (JS can't represent all 64-bit integer values)
+    // Represent call stack PCs as (module index, offset) pairs.
     JSObject *pcArray = JS_NewArrayObject(cx, 0, nullptr);
     if (!pcArray) {
       return NS_ERROR_FAILURE;
     }
-    ok = JS_DefineProperty(cx, reportObj, "stack", OBJECT_TO_JSVAL(pcArray),
-                           NULL, NULL, JSPROP_ENUMERATE);
-    if (!ok) {
-      return NS_ERROR_FAILURE;
-    }
 
-    const uint32_t pcCount = stack.size();
-    for (size_t pcIndex = 0; pcIndex < pcCount; ++pcIndex) {
-      nsAutoCString pcString;
-      const Telemetry::ProcessedStack::Frame &Frame = stack[pcIndex];
-      pcString.AppendPrintf("0x%p", Frame.mOffset);
-      JSString *str = JS_NewStringCopyZ(cx, pcString.get());
-      if (!str) {
-        return NS_ERROR_FAILURE;
-      }
-      jsval v = STRING_TO_JSVAL(str);
-      if (!JS_SetElement(cx, pcArray, pcIndex, &v)) {
-        return NS_ERROR_FAILURE;
-      }
-    }
-
-    // Record memory map info
-    JSObject *moduleArray = JS_NewArrayObject(cx, 0, nullptr);
-    if (!moduleArray) {
-      return NS_ERROR_FAILURE;
-    }
-    ok = JS_DefineProperty(cx, reportObj, "memoryMap",
-                           OBJECT_TO_JSVAL(moduleArray),
-                           NULL, NULL, JSPROP_ENUMERATE);
-    if (!ok) {
+    jsval pcArrayVal = OBJECT_TO_JSVAL(pcArray);
+    if (!JS_SetElement(cx, reportArray, i, &pcArrayVal)) {
       return NS_ERROR_FAILURE;
     }
 
-    const uint32_t moduleCount = (i == 0) ? mHangReports.GetModuleCount() : 0;
-    for (size_t moduleIndex = 0; moduleIndex < moduleCount; ++moduleIndex) {
-      // Current module
-      const Telemetry::ProcessedStack::Module &module =
-        mHangReports.GetModule(moduleIndex);
-
-      JSObject *moduleInfoArray = JS_NewArrayObject(cx, 0, nullptr);
-      if (!moduleInfoArray) {
-        return NS_ERROR_FAILURE;
-      }
-      jsval val = OBJECT_TO_JSVAL(moduleInfoArray);
-      if (!JS_SetElement(cx, moduleArray, moduleIndex, &val)) {
-        return NS_ERROR_FAILURE;
-      }
-
-      // Start address
-      nsAutoCString addressString;
-      addressString.AppendPrintf("0x%p", module.mStart);
-      JSString *str = JS_NewStringCopyZ(cx, addressString.get());
-      if (!str) {
-        return NS_ERROR_FAILURE;
-      }
-      val = STRING_TO_JSVAL(str);
-      if (!JS_SetElement(cx, moduleInfoArray, 0, &val)) {
-        return NS_ERROR_FAILURE;
-      }
-
-      // Module name
-      str = JS_NewStringCopyZ(cx, module.mName.c_str());
-      if (!str) {
+    const CombinedStacks::Stack& stack = mHangReports.GetStack(i);
+    const uint32_t pcCount = stack.size();
+    for (size_t pcIndex = 0; pcIndex < pcCount; ++pcIndex) {
+      const Telemetry::ProcessedStack::Frame& frame = stack[pcIndex];
+      JSObject *framePair = JS_NewArrayObject(cx, 0, nullptr);
+      if (!framePair) {
         return NS_ERROR_FAILURE;
       }
-      val = STRING_TO_JSVAL(str);
-      if (!JS_SetElement(cx, moduleInfoArray, 1, &val)) {
-        return NS_ERROR_FAILURE;
-      }
-
-      // Module size in memory
-      val = INT_TO_JSVAL(int32_t(module.mMappingSize));
-      if (!JS_SetElement(cx, moduleInfoArray, 2, &val)) {
-        return NS_ERROR_FAILURE;
-      }
-
-      // "PDB Age" identifier
-      val = INT_TO_JSVAL(module.mPdbAge);
-      if (!JS_SetElement(cx, moduleInfoArray, 3, &val)) {
+      int modIndex = (std::numeric_limits<uint16_t>::max() == frame.mModIndex) ?
+        -1 : frame.mModIndex;
+      jsval modIndexVal = INT_TO_JSVAL(modIndex);
+      if (!JS_SetElement(cx, framePair, 0, &modIndexVal)) {
         return NS_ERROR_FAILURE;
       }
-
-      // "PDB Signature" GUID
-      str = JS_NewStringCopyZ(cx, module.mPdbSignature.c_str());
-      if (!str) {
+      jsval mOffsetVal = INT_TO_JSVAL(frame.mOffset);
+      if (!JS_SetElement(cx, framePair, 1, &mOffsetVal)) {
         return NS_ERROR_FAILURE;
       }
-      val = STRING_TO_JSVAL(str);
-      if (!JS_SetElement(cx, moduleInfoArray, 4, &val)) {
-        return NS_ERROR_FAILURE;
-      }
-
-      // Name of associated PDB file
-      str = JS_NewStringCopyZ(cx, module.mPdbName.c_str());
-      if (!str) {
-        return NS_ERROR_FAILURE;
-      }
-      val = STRING_TO_JSVAL(str);
-      if (!JS_SetElement(cx, moduleInfoArray, 5, &val)) {
+      jsval framePairVal = OBJECT_TO_JSVAL(framePair);
+      if (!JS_SetElement(cx, pcArray, pcIndex, &framePairVal)) {
         return NS_ERROR_FAILURE;
       }
     }
   }
 
   return NS_OK;
 }
 
@@ -1758,18 +1756,16 @@ void ProcessedStack::AddModule(const Mod
 
 void ProcessedStack::Clear() {
   mModules.clear();
   mStack.clear();
 }
 
 bool ProcessedStack::Module::operator==(const Module& aOther) const {
   return  mName == aOther.mName &&
-    mStart == aOther.mStart &&
-    mMappingSize == aOther.mMappingSize &&
     mPdbAge == aOther.mPdbAge &&
     mPdbSignature == aOther.mPdbSignature &&
     mPdbName == aOther.mPdbName;
 }
 
 struct StackFrame
 {
   uintptr_t mPC;      // The program counter at this position in the call stack.
@@ -1785,17 +1781,18 @@ static bool CompareByPC(const StackFrame
 }
 
 static bool CompareByIndex(const StackFrame &a, const StackFrame &b)
 {
   return a.mIndex < b.mIndex;
 }
 #endif
 
-ProcessedStack GetStackAndModules(const std::vector<uintptr_t> &aPCs, bool aRelative)
+ProcessedStack
+GetStackAndModules(const std::vector<uintptr_t>& aPCs)
 {
   std::vector<StackFrame> rawStack;
   for (std::vector<uintptr_t>::const_iterator i = aPCs.begin(),
          e = aPCs.end(); i != e; ++i) {
     uintptr_t aPC = *i;
     StackFrame Frame = {aPC, static_cast<uint16_t>(rawStack.size()),
                         std::numeric_limits<uint16_t>::max()};
     rawStack.push_back(Frame);
@@ -1823,18 +1820,17 @@ ProcessedStack GetStackAndModules(const 
       uintptr_t pc = rawStack[stackIndex].mPC;
       if (pc >= moduleEnd)
         break;
 
       if (pc >= moduleStart) {
         // If the current PC is within the current module, mark
         // module as used
         moduleReferenced = true;
-        if (aRelative)
-          rawStack[stackIndex].mPC -= moduleStart;
+        rawStack[stackIndex].mPC -= moduleStart;
         rawStack[stackIndex].mModIndex = moduleIndex;
       } else {
         // PC does not belong to any module. It is probably from
         // the JIT. Use a fixed mPC so that we don't get different
         // stacks on different runs.
         rawStack[stackIndex].mPC =
           std::numeric_limits<uintptr_t>::max();
       }
@@ -1865,18 +1861,16 @@ ProcessedStack GetStackAndModules(const 
     Ret.AddFrame(frame);
   }
 
 #ifdef MOZ_ENABLE_PROFILER_SPS
   for (unsigned i = 0, n = rawModules.GetSize(); i != n; ++i) {
     const SharedLibrary &info = rawModules.GetEntry(i);
     ProcessedStack::Module module = {
       info.GetName(),
-      info.GetStart(),
-      info.GetEnd() - info.GetStart(),
 #ifdef XP_WIN
       info.GetPdbAge(),
       "", // mPdbSignature
       info.GetPdbName(),
 #else
       0, // mPdbAge
       "", // mPdbSignature
       "" // mPdbName
--- a/toolkit/content/aboutTelemetry.js
+++ b/toolkit/content/aboutTelemetry.js
@@ -217,27 +217,29 @@ let ChromeHangs = {
    */
   render: function ChromeHangs_render() {
     let hangsDiv = document.getElementById("chrome-hangs-data");
     this.clearHangData(hangsDiv);
     document.getElementById("fetch-symbols").classList.remove("hidden");
     document.getElementById("hide-symbols").classList.add("hidden");
 
     let hangs = Telemetry.chromeHangs;
-    if (hangs.length == 0) {
+    let stacks = hangs.stacks;
+    if (stacks.length == 0) {
       showEmptySectionMessage("chrome-hangs-section");
       return;
     }
 
     this.renderMemoryMap(hangsDiv);
 
-    for (let i = 0; i < hangs.length; ++i) {
-      let currentHang = hangs[i];
-      this.renderHangHeader(hangsDiv, i + 1, currentHang.duration);
-      this.renderStack(hangsDiv, currentHang.stack)
+    let durations = hangs.durations;
+    for (let i = 0; i < stacks.length; ++i) {
+      let stack = stacks[i];
+      this.renderHangHeader(hangsDiv, i + 1, durations[i]);
+      this.renderStack(hangsDiv, stack)
     }
   },
 
   /**
    * Renders the title of the hang: e.g. "Hang Report #1 (6 seconds)"
    *
    * @param aDiv Output div
    * @param aIndex The number of the hang
@@ -274,42 +276,48 @@ let ChromeHangs = {
    * Outputs the memory map associated with this hang report
    *
    * @param aDiv Output div
    */
   renderMemoryMap: function ChromeHangs_renderMemoryMap(aDiv) {
     aDiv.appendChild(document.createTextNode(this.memoryMapTitle));
     aDiv.appendChild(document.createElement("br"));
 
-    let singleMemoryMap = Telemetry.chromeHangs[0].memoryMap;
-    for (let currentModule of singleMemoryMap) {
+    let hangs = Telemetry.chromeHangs;
+    let memoryMap = hangs.memoryMap;
+    for (let currentModule of memoryMap) {
       aDiv.appendChild(document.createTextNode(currentModule.join(" ")));
       aDiv.appendChild(document.createElement("br"));
     }
 
     aDiv.appendChild(document.createElement("br"));
   },
 
   /**
    * Sends a symbolication request for the recorded hangs
    */
   fetchSymbols: function ChromeHangs_fetchSymbols() {
     let symbolServerURI =
       getPref(PREF_SYMBOL_SERVER_URI, DEFAULT_SYMBOL_SERVER_URI);
 
-    let chromeHangsJSON = JSON.stringify(Telemetry.chromeHangs);
+    let hangs = Telemetry.chromeHangs;
+    let memoryMap = hangs.memoryMap;
+    let stacks = hangs.stacks;
+    let request = {"memoryMap" : memoryMap, "stacks" : stacks,
+                   "version" : 2};
+    let requestJSON = JSON.stringify(request);
 
     this.symbolRequest = XMLHttpRequest();
     this.symbolRequest.open("POST", symbolServerURI, true);
     this.symbolRequest.setRequestHeader("Content-type", "application/json");
-    this.symbolRequest.setRequestHeader("Content-length", chromeHangsJSON.length);
+    this.symbolRequest.setRequestHeader("Content-length", requestJSON.length);
     this.symbolRequest.setRequestHeader("Connection", "close");
 
     this.symbolRequest.onreadystatechange = this.handleSymbolResponse.bind(this);
-    this.symbolRequest.send(chromeHangsJSON);
+    this.symbolRequest.send(requestJSON);
   },
 
   /**
    * Called when the 'readyState' of the XMLHttpRequest changes. We only care
    * about state 4 ("completed") - handling the response data.
    */
   handleSymbolResponse: function ChromeHangs_handleSymbolResponse() {
     if (this.symbolRequest.readyState != 4)
@@ -330,19 +338,21 @@ let ChromeHangs = {
     try {
       jsonResponse = JSON.parse(this.symbolRequest.responseText);
     } catch (e) {
       hangsDiv.appendChild(document.createTextNode(this.errorMessage));
       return;
     }
 
     let hangs = Telemetry.chromeHangs;
+    let stacks = hangs.stacks;
+    let durations = hangs.durations;
     for (let i = 0; i < jsonResponse.length; ++i) {
       let stack = jsonResponse[i];
-      let hangDuration = hangs[i].duration;
+      let hangDuration = durations[i];
       this.renderHangHeader(hangsDiv, i + 1, hangDuration);
 
       for (let symbol of stack) {
         hangsDiv.appendChild(document.createTextNode(symbol));
         hangsDiv.appendChild(document.createElement("br"));
       }
       hangsDiv.appendChild(document.createElement("br"));
     }
--- a/xpcom/build/mozPoisonWriteMac.cpp
+++ b/xpcom/build/mozPoisonWriteMac.cpp
@@ -91,17 +91,17 @@ bool ValidWriteAssert(bool ok)
     if (ok || !sProfileDirectory || !Telemetry::CanRecord())
         return ok;
 
     // Write the stack and loaded libraries to a file. We can get here
     // concurrently from many writes, so we use multiple temporary files.
     std::vector<uintptr_t> rawStack;
 
     NS_StackWalk(RecordStackWalker, 0, reinterpret_cast<void*>(&rawStack), 0);
-    Telemetry::ProcessedStack stack = Telemetry::GetStackAndModules(rawStack, true);
+    Telemetry::ProcessedStack stack = Telemetry::GetStackAndModules(rawStack);
 
     nsPrintfCString nameAux("%s%s", sProfileDirectory,
                             "/Telemetry.LateWriteTmpXXXXXX");
     char *name;
     nameAux.GetMutableData(&name);
 
     // We want the sha1 of the entire file, so please don't write to fd
     // directly; use sha1Stream.
--- a/xpcom/threads/HangMonitor.cpp
+++ b/xpcom/threads/HangMonitor.cpp
@@ -133,17 +133,17 @@ GetChromeHangReport(Telemetry::Processed
   DWORD ret = ::SuspendThread(winMainThreadHandle);
   if (ret == -1)
     return;
   NS_StackWalk(ChromeStackWalker, 0, reinterpret_cast<void*>(&rawStack),
                reinterpret_cast<uintptr_t>(winMainThreadHandle));
   ret = ::ResumeThread(winMainThreadHandle);
   if (ret == -1)
     return;
-  aStack = Telemetry::GetStackAndModules(rawStack, false);
+  aStack = Telemetry::GetStackAndModules(rawStack);
 }
 #endif
 
 void
 ThreadMain(void*)
 {
   PR_SetCurrentThreadName("Hang Monitor");