Bug 805046 - Add a class to represent a set of stacks that share a object list. r=vdjeric.
authorRafael Ávila de Espíndola <respindola@mozilla.org>
Mon, 05 Nov 2012 13:45:19 -0500
changeset 112339 f8cd6fa12a8bee4f601bea35b7f8be6e94969bce
parent 112338 0a6cb7947e07189eb4d4673c0b15bd584e79e2db
child 112340 c69822a9bd0fe015d1696be81c9e92a9203a477a
push id23812
push useremorley@mozilla.com
push dateTue, 06 Nov 2012 14:01:34 +0000
treeherdermozilla-central@f4aeed115e54 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvdjeric
bugs805046
milestone19.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 805046 - Add a class to represent a set of stacks that share a object list. r=vdjeric.
toolkit/components/telemetry/ProcessedStack.h
toolkit/components/telemetry/Telemetry.cpp
--- a/toolkit/components/telemetry/ProcessedStack.h
+++ b/toolkit/components/telemetry/ProcessedStack.h
@@ -52,20 +52,16 @@ public:
 
   const Frame &GetFrame(unsigned aIndex) const;
   void AddFrame(const Frame& aFrame);
   const Module &GetModule(unsigned aIndex) const;
   void AddModule(const Module& aFrame);
 
   void Clear();
 
-  // FIXME: remove these once chrome hang has switched to using offsets.
-  bool HasModule(const Module &aModule) const;
-  void RemoveModule(unsigned aIndex);
-
 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).
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -85,16 +85,155 @@ bool
 AutoHashtable<EntryType>::ReflectIntoJS(ReflectEntryFunc entryFunc,
                                         JSContext *cx, JSObject *obj)
 {
   EnumeratorArgs args = { cx, obj, entryFunc };
   uint32_t num = this->EnumerateEntries(ReflectEntryStub, static_cast<void*>(&args));
   return num == this->Count();
 }
 
+// This class is conceptually a list of ProcessedStack objects, but it represents them
+// more efficiently by keeping a single global list of modules.
+class CombinedStacks {
+public:
+  typedef std::vector<Telemetry::ProcessedStack::Frame> Stack;
+  const Telemetry::ProcessedStack::Module& GetModule(unsigned aIndex) const;
+  size_t GetModuleCount() const;
+  const Stack& GetStack(unsigned aIndex) const;
+  void AddStack(const Telemetry::ProcessedStack& aStack);
+  size_t GetStackCount() const;
+  size_t SizeOfExcludingThis() const;
+private:
+  std::vector<Telemetry::ProcessedStack::Module> mModules;
+  std::vector<Stack> mStacks;
+};
+
+size_t
+CombinedStacks::GetModuleCount() const {
+  return mModules.size();
+}
+
+const Telemetry::ProcessedStack::Module&
+CombinedStacks::GetModule(unsigned aIndex) const {
+  return mModules[aIndex];
+}
+
+void
+CombinedStacks::AddStack(const Telemetry::ProcessedStack& aStack) {
+  mStacks.resize(mStacks.size() + 1);
+  CombinedStacks::Stack& adjustedStack = mStacks.back();
+
+  size_t stackSize = aStack.GetStackSize();
+  for (int i = 0; i < stackSize; ++i) {
+    const Telemetry::ProcessedStack::Frame& frame = aStack.GetFrame(i);
+    uint16_t modIndex;
+    if (frame.mModIndex == std::numeric_limits<uint16_t>::max()) {
+      modIndex = frame.mModIndex;
+    } else {
+      const Telemetry::ProcessedStack::Module& module =
+        aStack.GetModule(frame.mModIndex);
+      std::vector<Telemetry::ProcessedStack::Module>::iterator modIterator =
+        std::find(mModules.begin(), mModules.end(), module);
+      if (modIterator == mModules.end()) {
+        mModules.push_back(module);
+        modIndex = mModules.size() - 1;
+      } else {
+        modIndex = modIterator - mModules.begin();
+      }
+    }
+    Telemetry::ProcessedStack::Frame adjustedFrame = { frame.mOffset, modIndex };
+    adjustedStack.push_back(adjustedFrame);
+  }
+}
+
+const CombinedStacks::Stack&
+CombinedStacks::GetStack(unsigned aIndex) const {
+  return mStacks[aIndex];
+}
+
+size_t
+CombinedStacks::GetStackCount() const {
+  return mStacks.size();
+}
+
+size_t
+CombinedStacks::SizeOfExcludingThis() const {
+  // This is a crude approximation. We would like to do something like
+  // aMallocSizeOf(&mModules[0]), but on linux aMallocSizeOf will call
+  // malloc_usable_size which is only safe on the pointers returned by malloc.
+  // While it works on current libstdc++, it is better to be safe and not assume
+  // that &vec[0] points to one. We could use a custom allocator, but
+  // it doesn't seem worth it.
+  size_t n = 0;
+  n += mModules.capacity() * sizeof(Telemetry::ProcessedStack::Module);
+  n += mStacks.capacity() * sizeof(Stack);
+  for (std::vector<Stack>::const_iterator i = mStacks.begin(),
+         e = mStacks.end(); i != e; ++i) {
+    const Stack& s = *i;
+    n += s.capacity() * sizeof(Telemetry::ProcessedStack::Frame);
+  }
+  return n;
+}
+
+class HangReports {
+public:
+  size_t SizeOfExcludingThis() const;
+  void AddHang(const Telemetry::ProcessedStack& aStack, uint32_t aDuration);
+  size_t GetStackCount() const;
+  const CombinedStacks::Stack& GetStack(unsigned aIndex) const;
+  uint32_t GetDuration(unsigned aIndex) const;
+  size_t GetModuleCount() const;
+  const Telemetry::ProcessedStack::Module& GetModule(unsigned aIndex) const;
+private:
+  CombinedStacks mStacks;
+  std::vector<uint32_t> mDurations;
+};
+
+void
+HangReports::AddHang(const Telemetry::ProcessedStack& aStack, uint32_t aDuration) {
+  mStacks.AddStack(aStack);
+  mDurations.push_back(aDuration);
+}
+
+size_t
+HangReports::SizeOfExcludingThis() const {
+  size_t n = 0;
+  n += mStacks.SizeOfExcludingThis();
+  // This is a crude approximation. See comment on
+  // CombinedStacks::SizeOfExcludingThis.
+  n += mDurations.capacity() * sizeof(uint32_t);
+  return n;
+}
+
+size_t
+HangReports::GetModuleCount() const {
+  return mStacks.GetModuleCount();
+}
+
+const Telemetry::ProcessedStack::Module&
+HangReports::GetModule(unsigned aIndex) const {
+  return mStacks.GetModule(aIndex);
+}
+
+uint32_t
+HangReports::GetDuration(unsigned aIndex) const {
+  return mDurations[aIndex];
+}
+
+const CombinedStacks::Stack&
+HangReports::GetStack(unsigned aIndex) const {
+  return mStacks.GetStack(aIndex);
+}
+
+size_t
+HangReports::GetStackCount() const {
+  MOZ_ASSERT(mDurations.size() == mStacks.GetStackCount());
+  return mStacks.GetStackCount();
+}
+
 class TelemetryImpl MOZ_FINAL : public nsITelemetry
 {
   NS_DECL_ISUPPORTS
   NS_DECL_NSITELEMETRY
 
 public:
   TelemetryImpl();
   ~TelemetryImpl();
@@ -115,20 +254,16 @@ public:
     uint32_t hitCount;
     uint32_t totalTime;
   };
   struct StmtStats {
     struct Stat mainThread;
     struct Stat otherThreads;
   };
   typedef nsBaseHashtableET<nsCStringHashKey, StmtStats> SlowSQLEntryType;
-  struct HangReport {
-    uint32_t duration;
-    Telemetry::ProcessedStack mStack;
-  };
 
 private:
   // We don't need to poke inside any of our hashtables for more
   // information, so we just have One Function To Size Them All.
   template<typename EntryType>
   struct impl {
     static size_t SizeOfEntryExcludingThis(EntryType *,
                                            nsMallocSizeOfFun,
@@ -186,17 +321,17 @@ private:
   bool mCanRecord;
   static TelemetryImpl *sTelemetry;
   AutoHashtable<SlowSQLEntryType> mPrivateSQL;
   AutoHashtable<SlowSQLEntryType> mSanitizedSQL;
   // This gets marked immutable in debug builds, so we can't use
   // AutoHashtable here.
   nsTHashtable<nsCStringHashKey> mTrackedDBs;
   Mutex mHashMutex;
-  nsTArray<HangReport> mHangReports;
+  HangReports mHangReports;
   Mutex mHangReportsMutex;
   nsIMemoryReporter *mMemoryReporter;
 };
 
 TelemetryImpl*  TelemetryImpl::sTelemetry = NULL;
 
 NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(TelemetryMallocSizeOf, "telemetry")
 
@@ -211,17 +346,17 @@ TelemetryImpl::SizeOfIncludingThis(nsMal
   n += mHistogramMap.SizeOfExcludingThis(impl<CharPtrEntryType>::SizeOfEntryExcludingThis,
                                          aMallocSizeOf);
   n += mPrivateSQL.SizeOfExcludingThis(impl<SlowSQLEntryType>::SizeOfEntryExcludingThis,
                                        aMallocSizeOf);
   n += mSanitizedSQL.SizeOfExcludingThis(impl<SlowSQLEntryType>::SizeOfEntryExcludingThis,
                                          aMallocSizeOf);
   n += mTrackedDBs.SizeOfExcludingThis(impl<nsCStringHashKey>::SizeOfEntryExcludingThis,
                                        aMallocSizeOf);
-  n += mHangReports.SizeOfExcludingThis(aMallocSizeOf);
+  n += mHangReports.SizeOfExcludingThis();
   return n;
 }
 
 int64_t
 TelemetryImpl::GetTelemetryMemoryUsed()
 {
   int64_t n = 0;
   if (sTelemetry) {
@@ -1088,30 +1223,30 @@ TelemetryImpl::GetChromeHangs(JSContext 
   MutexAutoLock hangReportMutex(mHangReportsMutex);
   JSObject *reportArray = JS_NewArrayObject(cx, 0, nullptr);
   if (!reportArray) {
     return NS_ERROR_FAILURE;
   }
   *ret = OBJECT_TO_JSVAL(reportArray);
 
   // Each hang report is an object in the 'chromeHangs' array
-  for (size_t i = 0; i < mHangReports.Length(); ++i) {
-    Telemetry::ProcessedStack &stack = mHangReports[i].mStack;
+  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)) {
       return NS_ERROR_FAILURE;
     }
 
     // Record the hang duration (expressed in seconds)
     JSBool ok = JS_DefineProperty(cx, reportObj, "duration",
-                                  INT_TO_JSVAL(mHangReports[i].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)
     JSObject *pcArray = JS_NewArrayObject(cx, 0, nullptr);
@@ -1119,20 +1254,20 @@ TelemetryImpl::GetChromeHangs(JSContext 
       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.GetStackSize();
+    const uint32_t pcCount = stack.size();
     for (size_t pcIndex = 0; pcIndex < pcCount; ++pcIndex) {
       nsAutoCString pcString;
-      const Telemetry::ProcessedStack::Frame &Frame = stack.GetFrame(pcIndex);
+      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;
@@ -1146,21 +1281,21 @@ TelemetryImpl::GetChromeHangs(JSContext 
     }
     ok = JS_DefineProperty(cx, reportObj, "memoryMap",
                            OBJECT_TO_JSVAL(moduleArray),
                            NULL, NULL, JSPROP_ENUMERATE);
     if (!ok) {
       return NS_ERROR_FAILURE;
     }
 
-    const uint32_t moduleCount = stack.GetNumModules();
+    const uint32_t moduleCount = (i == 0) ? mHangReports.GetModuleCount() : 0;
     for (size_t moduleIndex = 0; moduleIndex < moduleCount; ++moduleIndex) {
       // Current module
       const Telemetry::ProcessedStack::Module &module =
-        stack.GetModule(moduleIndex);
+        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;
@@ -1487,31 +1622,17 @@ void
 TelemetryImpl::RecordChromeHang(uint32_t duration,
                                 Telemetry::ProcessedStack &aStack)
 {
   if (!sTelemetry || !sTelemetry->mCanRecord)
     return;
 
   MutexAutoLock hangReportMutex(sTelemetry->mHangReportsMutex);
 
-  // Only report the modules which changed since the first hang report
-  if (sTelemetry->mHangReports.Length()) {
-    Telemetry::ProcessedStack &firstStack =
-      sTelemetry->mHangReports[0].mStack;
-    for (size_t i = 0; i < aStack.GetNumModules(); ++i) {
-      const Telemetry::ProcessedStack::Module &module = aStack.GetModule(i);
-      if (firstStack.HasModule(module)) {
-        aStack.RemoveModule(i);
-        --i;
-      }
-    }
-  }
-
-  HangReport newReport = { duration, aStack };
-  sTelemetry->mHangReports.AppendElement(newReport);
+  sTelemetry->mHangReports.AddHang(aStack, duration);
 }
 #endif
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(TelemetryImpl, nsITelemetry)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsITelemetry, TelemetryImpl::CreateTelemetryInstance)
 
 #define NS_TELEMETRY_CID \
   {0xaea477f2, 0xb3a2, 0x469c, {0xaa, 0x29, 0x0a, 0x82, 0xd1, 0x32, 0xb8, 0x29}}
@@ -1625,25 +1746,16 @@ size_t ProcessedStack::GetNumModules() c
 }
 
 const ProcessedStack::Module &ProcessedStack::GetModule(unsigned aIndex) const
 {
   MOZ_ASSERT(aIndex < mModules.size());
   return mModules[aIndex];
 }
 
-bool ProcessedStack::HasModule(const Module &aModule) const {
-  return mModules.end() !=
-    std::find(mModules.begin(), mModules.end(), aModule);
-}
-
-void ProcessedStack::RemoveModule(unsigned aIndex) {
-  mModules.erase(mModules.begin() + aIndex);
-}
-
 void ProcessedStack::AddModule(const Module &aModule)
 {
   mModules.push_back(aModule);
 }
 
 void ProcessedStack::Clear() {
   mModules.clear();
   mStack.clear();