Bug 819817 - DMD: cache calls to NS_DescribeCodeAddress for faster dumping. r+a=jlebar
authorNicholas Nethercote <nnethercote@mozilla.com>
Thu, 13 Dec 2012 16:58:28 -0800
changeset 122979 d69a0019cda150447e72f54f10683ceba3a03c67
parent 122978 0bb8216d88a76e633b870f53cf502a398a45021d
child 122980 cb4021304583367c50b49a6020ccf0b88d22df78
push id273
push userlsblakk@mozilla.com
push dateThu, 14 Feb 2013 23:19:38 +0000
treeherdermozilla-release@c5e807a3f8b8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs819817
milestone19.0a2
Bug 819817 - DMD: cache calls to NS_DescribeCodeAddress for faster dumping. r+a=jlebar
memory/replace/dmd/DMD.cpp
--- a/memory/replace/dmd/DMD.cpp
+++ b/memory/replace/dmd/DMD.cpp
@@ -119,18 +119,17 @@ public:
     ExitOnFailure(p);
     return p;
   }
 
   static void free_(void* aPtr) { gMallocTable->free(aPtr); }
 
   static char* strdup_(const char* aStr)
   {
-    char* s = (char*) gMallocTable->malloc(strlen(aStr) + 1);
-    ExitOnFailure(s);
+    char* s = (char*) InfallibleAllocPolicy::malloc_(strlen(aStr) + 1);
     strcpy(s, aStr);
     return s;
   }
 
   template <class T>
   static T* new_()
   {
     void* mem = malloc_(sizeof(T));
@@ -141,16 +140,25 @@ public:
   template <class T, typename P1>
   static T* new_(P1 p1)
   {
     void* mem = malloc_(sizeof(T));
     ExitOnFailure(mem);
     return new (mem) T(p1);
   }
 
+  template <class T>
+  static void delete_(T *p)
+  {
+    if (p) {
+      p->~T();
+      InfallibleAllocPolicy::free_(p);
+    }
+  }
+
   static void reportAllocOverflow() { ExitOnFailure(nullptr); }
 };
 
 // This is only needed because of the |const void*| vs |void*| arg mismatch.
 static size_t
 MallocSizeOf(const void* aPtr)
 {
   return gMallocTable->malloc_usable_size(const_cast<void*>(aPtr));
@@ -489,36 +497,202 @@ public:
   ~AutoBlockIntercepts()
   {
     MOZ_ASSERT(mT->interceptsAreBlocked());
     mT->unblockIntercepts();
   }
 };
 
 //---------------------------------------------------------------------------
+// Location service
+//---------------------------------------------------------------------------
+
+// This class is used to print details about code locations.
+class LocationService
+{
+  // WriteLocation() is the key function in this class.  It's basically a
+  // wrapper around NS_DescribeCodeAddress.
+  //
+  // However, NS_DescribeCodeAddress is very slow on some platforms, and we
+  // have lots of repeated (i.e. same PC) calls to it.  So we do some caching
+  // of results.  Each cached result includes two strings (|mFunction| and
+  // |mLibrary|), so we also optimize them for space in the following ways.
+  //
+  // - The number of distinct library names is small, e.g. a few dozen.  There
+  //   is lots of repetition, especially of libxul.  So we intern them in their
+  //   own table, which saves space over duplicating them for each cache entry.
+  //
+  // - The number of distinct function names is much higher, so we duplicate
+  //   them in each cache entry.  That's more space-efficient than interning
+  //   because entries containing single-occurrence function names are quickly
+  //   overwritten, and their copies released.  In addition, empty function
+  //   names are common, so we use nullptr to represent them compactly.
+
+  struct StringHasher
+  {
+      typedef const char* Lookup;
+
+      static uint32_t hash(const char* const& aS)
+      {
+          return HashString(aS);
+      }
+
+      static bool match(const char* const& aA, const char* const& aB)
+      {
+          return strcmp(aA, aB) == 0;
+      }
+  };
+
+  typedef js::HashSet<const char*, StringHasher, InfallibleAllocPolicy>
+          StringTable;
+
+  StringTable mLibraryStrings;
+
+  struct Entry
+  {
+    const void* mPc;        // the entry is unused if this is null
+    char*       mFunction;  // owned by the Entry;  may be null
+    const char* mLibrary;   // owned by mLibraryStrings;  never null
+                            //   in a non-empty entry is in use
+    ptrdiff_t   mLOffset;
+
+    Entry()
+      : mPc(nullptr), mFunction(nullptr), mLibrary(nullptr), mLOffset(0)
+    {}
+
+    ~Entry()
+    {
+      // We don't free mLibrary because it's externally owned.
+      InfallibleAllocPolicy::free_(mFunction);
+    }
+
+    void Replace(const void* aPc, const char* aFunction, const char* aLibrary,
+                 ptrdiff_t aLOffset)
+    {
+      mPc = aPc;
+
+      // Convert "" to nullptr.  Otherwise, make a copy of the name.
+      InfallibleAllocPolicy::free_(mFunction);
+      mFunction =
+        !aFunction[0] ? nullptr : InfallibleAllocPolicy::strdup_(aFunction);
+
+      mLibrary = aLibrary;
+      mLOffset = aLOffset;
+    }
+
+    size_t SizeOfExcludingThis() {
+      // Don't measure mLibrary because it's externally owned.
+      return MallocSizeOf(mFunction);
+    }
+  };
+
+  // A direct-mapped cache.  When doing a dump just after starting desktop
+  // Firefox (which is similar to dumping after a longer-running session,
+  // thanks to the limit on how many groups we dump), a cache with 2^24 entries
+  // (which approximates an infinite-entry cache) has a ~91% hit rate.  A cache
+  // with 2^12 entries has a ~83% hit rate, and takes up ~85 KiB (on 32-bit
+  // platforms) or ~150 KiB (on 64-bit platforms).
+  static const size_t kNumEntries = 1 << 12;
+  static const size_t kMask = kNumEntries - 1;
+  Entry mEntries[kNumEntries];
+
+  size_t mNumCacheHits;
+  size_t mNumCacheMisses;
+
+public:
+  LocationService()
+    : mEntries(), mNumCacheHits(0), mNumCacheMisses(0)
+  {
+    (void)mLibraryStrings.init(64);
+  }
+
+  void WriteLocation(const Writer& aWriter, const void* aPc)
+  {
+    MOZ_ASSERT(gStateLock->IsLocked());
+
+    uint32_t index = HashGeneric(aPc) & kMask;
+    MOZ_ASSERT(index < kNumEntries);
+    Entry& entry = mEntries[index];
+
+    MOZ_ASSERT(aPc);    // important, because null represents an empty entry
+    if (entry.mPc != aPc) {
+      mNumCacheMisses++;
+
+      // NS_DescribeCodeAddress can (on Linux) acquire a lock inside
+      // the shared library loader.  Another thread might call malloc
+      // while holding that lock (when loading a shared library).  So
+      // we have to exit gStateLock around this call.  For details, see
+      // https://bugzilla.mozilla.org/show_bug.cgi?id=363334#c3
+      nsCodeAddressDetails details;
+      {
+        AutoUnlockState unlock;
+        (void)NS_DescribeCodeAddress(const_cast<void*>(aPc), &details);
+      }
+
+      // Intern the library name.
+      const char* library = nullptr;
+      StringTable::AddPtr p = mLibraryStrings.lookupForAdd(details.library);
+      if (!p) {
+        library = InfallibleAllocPolicy::strdup_(details.library);
+        (void)mLibraryStrings.add(p, library);
+      } else {
+        library = *p;
+      }
+
+      entry.Replace(aPc, details.function, library, details.loffset);
+
+    } else {
+      mNumCacheHits++;
+    }
+
+    MOZ_ASSERT(entry.mPc == aPc);
+
+    // Use "???" for unknown functions.
+    W("   %s[%s +0x%X] %p\n", entry.mFunction ? entry.mFunction : "???",
+      entry.mLibrary, entry.mLOffset, entry.mPc);
+  }
+
+  size_t SizeOfIncludingThis()
+  {
+    size_t n = MallocSizeOf(this);
+    for (uint32_t i = 0; i < kNumEntries; i++) {
+      n += mEntries[i].SizeOfExcludingThis();
+    }
+
+    n += mLibraryStrings.sizeOfExcludingThis(MallocSizeOf);
+    for (StringTable::Range r = mLibraryStrings.all();
+         !r.empty();
+         r.popFront()) {
+      n += MallocSizeOf(r.front());
+    }
+
+    return n;
+  }
+
+  size_t CacheCapacity() const { return kNumEntries; }
+
+  size_t CacheCount() const
+  {
+    size_t n = 0;
+    for (size_t i = 0; i < kNumEntries; i++) {
+      if (mEntries[i].mPc) {
+        n++;
+      }
+    }
+    return n;
+  }
+
+  size_t NumCacheHits()   const { return mNumCacheHits; }
+  size_t NumCacheMisses() const { return mNumCacheMisses; }
+};
+
+//---------------------------------------------------------------------------
 // Stack traces
 //---------------------------------------------------------------------------
 
-static void
-PcInfo(const void* aPc, nsCodeAddressDetails* aDetails)
-{
-  // NS_DescribeCodeAddress can (on Linux) acquire a lock inside
-  // the shared library loader.  Another thread might call malloc
-  // while holding that lock (when loading a shared library).  So
-  // we have to exit gStateLock around this call.  For details, see
-  // https://bugzilla.mozilla.org/show_bug.cgi?id=363334#c3
-  {
-    AutoUnlockState unlock;
-    (void)NS_DescribeCodeAddress(const_cast<void*>(aPc), aDetails);
-  }
-  if (!aDetails->function[0]) {
-    strcpy(aDetails->function, "???");
-  }
-}
-
 class StackTrace
 {
   static const uint32_t MaxDepth = 24;
 
   uint32_t mLength;             // The number of PCs.
   void* mPcs[MaxDepth];         // The PCs themselves.
 
 public:
@@ -533,17 +707,17 @@ public:
   // and so is immortal and unmovable.
   static const StackTrace* Get(Thread* aT);
 
   void Sort()
   {
     qsort(mPcs, mLength, sizeof(mPcs[0]), StackTrace::QsortCmp);
   }
 
-  void Print(const Writer& aWriter) const;
+  void Print(const Writer& aWriter, LocationService* aLocService) const;
 
   // Hash policy.
 
   typedef StackTrace* Lookup;
 
   static uint32_t hash(const StackTrace* const& aSt)
   {
     return mozilla::HashBytes(aSt->mPcs, aSt->Size());
@@ -579,37 +753,32 @@ private:
   }
 };
 
 typedef js::HashSet<StackTrace*, StackTrace, InfallibleAllocPolicy>
         StackTraceTable;
 static StackTraceTable* gStackTraceTable = nullptr;
 
 void
-StackTrace::Print(const Writer& aWriter) const
+StackTrace::Print(const Writer& aWriter, LocationService* aLocService) const
 {
   if (mLength == 0) {
     W("   (empty)\n");
     return;
   }
 
   if (gMode == Test) {
     // Don't print anything because there's too much variation.
     W("   (stack omitted due to test mode)\n");
     return;
   }
 
   for (uint32_t i = 0; i < mLength; i++) {
-    nsCodeAddressDetails details;
     void* pc = mPcs[i];
-    PcInfo(pc, &details);
-    if (details.function[0]) {
-      W("   %s[%s +0x%X] %p\n", details.function, details.library,
-        details.loffset, pc);
-    }
+    aLocService->WriteLocation(aWriter, pc);
   }
 }
 
 /* static */ const StackTrace*
 StackTrace::Get(Thread* aT)
 {
   MOZ_ASSERT(gStateLock->IsLocked());
   MOZ_ASSERT(aT->interceptsAreBlocked());
@@ -1161,18 +1330,18 @@ class LiveBlockGroup : public LiveBlockK
   friend class FrameGroup;      // FrameGroups are created from LiveBlockGroups
 
 public:
   explicit LiveBlockGroup(const LiveBlockKey& aKey)
     : LiveBlockKey(aKey),
       BlockGroup()
   {}
 
-  void Print(const Writer& aWriter, uint32_t aM, uint32_t aN,
-             const char* aStr, const char* astr,
+  void Print(const Writer& aWriter, LocationService* aLocService,
+             uint32_t aM, uint32_t aN, const char* aStr, const char* astr,
              size_t aCategoryUsableSize, size_t aCumulativeUsableSize,
              size_t aTotalUsableSize) const;
 
   static int QsortCmp(const void* aA, const void* aB)
   {
     const LiveBlockGroup* const a =
       *static_cast<const LiveBlockGroup* const*>(aA);
     const LiveBlockGroup* const b =
@@ -1181,17 +1350,18 @@ public:
     return GroupSize::Cmp(a->mGroupSize, b->mGroupSize);
   }
 };
 
 typedef js::HashSet<LiveBlockGroup, LiveBlockGroup, InfallibleAllocPolicy>
         LiveBlockGroupTable;
 
 void
-LiveBlockGroup::Print(const Writer& aWriter, uint32_t aM, uint32_t aN,
+LiveBlockGroup::Print(const Writer& aWriter, LocationService* aLocService,
+                      uint32_t aM, uint32_t aN,
                       const char* aStr, const char* astr,
                       size_t aCategoryUsableSize, size_t aCumulativeUsableSize,
                       size_t aTotalUsableSize) const
 {
   bool showTilde = mGroupSize.IsSampled();
 
   W("%s: %s block%s in block group %s of %s\n",
     aStr,
@@ -1208,38 +1378,38 @@ LiveBlockGroup::Print(const Writer& aWri
     " %4.2f%% of %s (%4.2f%% cumulative)\n",
     Percent(mGroupSize.Usable(), aTotalUsableSize),
     Percent(aCumulativeUsableSize, aTotalUsableSize),
     Percent(mGroupSize.Usable(), aCategoryUsableSize),
     astr,
     Percent(aCumulativeUsableSize, aCategoryUsableSize));
 
   W(" Allocated at\n");
-  mAllocStackTrace->Print(aWriter);
+  mAllocStackTrace->Print(aWriter, aLocService);
 
   if (IsReported()) {
     W("\n Reported by '%s' at\n", mReporterName);
-    mReportStackTrace->Print(aWriter);
+    mReportStackTrace->Print(aWriter, aLocService);
   }
 
   W("\n");
 }
 
 // A group of one or more double-reported heap blocks with a common
 // DoubleReportBlockKey.
 class DoubleReportBlockGroup : public DoubleReportBlockKey, public BlockGroup
 {
 public:
   explicit DoubleReportBlockGroup(const DoubleReportBlockKey& aKey)
     : DoubleReportBlockKey(aKey),
       BlockGroup()
   {}
 
-  void Print(const Writer& aWriter, uint32_t aM, uint32_t aN,
-             const char* aStr, const char* astr,
+  void Print(const Writer& aWriter, LocationService* aLocService,
+             uint32_t aM, uint32_t aN, const char* aStr, const char* astr,
              size_t aCategoryUsableSize, size_t aCumulativeUsableSize,
              size_t aTotalUsableSize) const;
 
   static int QsortCmp(const void* aA, const void* aB)
   {
     const DoubleReportBlockGroup* const a =
       *static_cast<const DoubleReportBlockGroup* const*>(aA);
     const DoubleReportBlockGroup* const b =
@@ -1249,17 +1419,19 @@ public:
   }
 };
 
 typedef js::HashSet<DoubleReportBlockGroup, DoubleReportBlockGroup,
                     InfallibleAllocPolicy> DoubleReportBlockGroupTable;
 DoubleReportBlockGroupTable* gDoubleReportBlockGroupTable = nullptr;
 
 void
-DoubleReportBlockGroup::Print(const Writer& aWriter, uint32_t aM, uint32_t aN,
+DoubleReportBlockGroup::Print(const Writer& aWriter,
+                              LocationService* aLocService,
+                              uint32_t aM, uint32_t aN,
                               const char* aStr, const char* astr,
                               size_t aCategoryUsableSize,
                               size_t aCumulativeUsableSize,
                               size_t aTotalUsableSize) const
 {
   bool showTilde = mGroupSize.IsSampled();
 
   W("%s: %s block%s in block group %s of %s\n",
@@ -1269,23 +1441,23 @@ DoubleReportBlockGroup::Print(const Writ
     Show(aN, gBuf3, kBufLen));
 
   W(" %s bytes (%s requested / %s slop)\n",
     Show(mGroupSize.Usable(), gBuf1, kBufLen, showTilde),
     Show(mGroupSize.Req(),    gBuf2, kBufLen, showTilde),
     Show(mGroupSize.Slop(),   gBuf3, kBufLen, showTilde));
 
   W(" Allocated at\n");
-  mAllocStackTrace->Print(aWriter);
+  mAllocStackTrace->Print(aWriter, aLocService);
 
   W("\n Previously reported by '%s' at\n", mReporterName1);
-  mReportStackTrace1->Print(aWriter);
+  mReportStackTrace1->Print(aWriter, aLocService);
 
   W("\n Now reported by '%s' at\n", mReporterName2);
-  mReportStackTrace2->Print(aWriter);
+  mReportStackTrace2->Print(aWriter, aLocService);
 
   W("\n");
 }
 
 //---------------------------------------------------------------------------
 // Stack frame groups
 //---------------------------------------------------------------------------
 
@@ -1313,18 +1485,18 @@ public:
   // This is |const| thanks to the |mutable| fields above.
   void Add(const LiveBlockGroup& aBg) const
   {
     mNumBlocks += aBg.mNumBlocks;
     mNumBlockGroups++;
     mGroupSize.Add(aBg.mGroupSize);
   }
 
-  void Print(const Writer& aWriter, uint32_t aM, uint32_t aN,
-             const char* aStr, const char* astr,
+  void Print(const Writer& aWriter, LocationService* aLocService,
+             uint32_t aM, uint32_t aN, const char* aStr, const char* astr,
              size_t aCategoryUsableSize, size_t aCumulativeUsableSize,
              size_t aTotalUsableSize) const;
 
   static int QsortCmp(const void* aA, const void* aB)
   {
     const FrameGroup* const a = *static_cast<const FrameGroup* const*>(aA);
     const FrameGroup* const b = *static_cast<const FrameGroup* const*>(aB);
 
@@ -1349,28 +1521,25 @@ public:
 };
 
 const char* const FrameGroup::kName = "frame";
 
 typedef js::HashSet<FrameGroup, FrameGroup, InfallibleAllocPolicy>
         FrameGroupTable;
 
 void
-FrameGroup::Print(const Writer& aWriter, uint32_t aM, uint32_t aN,
-                  const char* aStr, const char* astr,
+FrameGroup::Print(const Writer& aWriter, LocationService* aLocService,
+                  uint32_t aM, uint32_t aN, const char* aStr, const char* astr,
                   size_t aCategoryUsableSize, size_t aCumulativeUsableSize,
                   size_t aTotalUsableSize) const
 {
   (void)aCumulativeUsableSize;
 
   bool showTilde = mGroupSize.IsSampled();
 
-  nsCodeAddressDetails details;
-  PcInfo(mPc, &details);
-
   W("%s: %s block%s and %s block group%s in frame group %s of %s\n",
     aStr,
     Show(mNumBlocks, gBuf1, kBufLen, showTilde), Plural(mNumBlocks),
     Show(mNumBlockGroups, gBuf2, kBufLen, showTilde), Plural(mNumBlockGroups),
     Show(aM, gBuf3, kBufLen),
     Show(aN, gBuf4, kBufLen));
 
   W(" %s bytes (%s requested / %s slop)\n",
@@ -1379,18 +1548,18 @@ FrameGroup::Print(const Writer& aWriter,
     Show(mGroupSize.Slop(),   gBuf3, kBufLen, showTilde));
 
   W(" %4.2f%% of the heap;  %4.2f%% of %s\n",
     Percent(mGroupSize.Usable(), aTotalUsableSize),
     Percent(mGroupSize.Usable(), aCategoryUsableSize),
     astr);
 
   W(" PC is\n");
-  W("   %s[%s +0x%X] %p\n\n", details.function, details.library,
-    details.loffset, mPc);
+  aLocService->WriteLocation(aWriter, mPc);
+  W("\n");
 }
 
 //---------------------------------------------------------------------------
 // DMD start-up
 //---------------------------------------------------------------------------
 
 static void RunTestMode(FILE* fp);
 static void RunStressMode(FILE* fp);
@@ -1692,17 +1861,18 @@ ReportOnAlloc(const void* aPtr, const ch
 
 //---------------------------------------------------------------------------
 // DMD output
 //---------------------------------------------------------------------------
 
 // This works for LiveBlockGroups, DoubleReportBlockGroups and FrameGroups.
 template <class TGroup>
 static void
-PrintSortedGroups(const Writer& aWriter, const char* aStr, const char* astr,
+PrintSortedGroups(const Writer& aWriter, LocationService* aLocService,
+                  const char* aStr, const char* astr,
                   const js::HashSet<TGroup, TGroup, InfallibleAllocPolicy>& aTGroupTable,
                   size_t aCategoryUsableSize, size_t aTotalUsableSize)
 {
   const char* name = TGroup::kName;
   StatusMsg("  creating and sorting %s %s group array...\n", astr, name);
 
   // Convert the table into a sorted array.
   js::Vector<const TGroup*, 0, InfallibleAllocPolicy> tgArray;
@@ -1730,46 +1900,47 @@ PrintSortedGroups(const Writer& aWriter,
   uint32_t numTGroups = tgArray.length();
 
   StatusMsg("  printing %s %s group array...\n", astr, name);
   size_t cumulativeUsableSize = 0;
   for (uint32_t i = 0; i < numTGroups; i++) {
     const TGroup* tg = tgArray[i];
     cumulativeUsableSize += tg->GroupSize().Usable();
     if (i < MaxTGroups) {
-      tg->Print(aWriter, i+1, numTGroups, aStr, astr, aCategoryUsableSize,
-                cumulativeUsableSize, aTotalUsableSize);
+      tg->Print(aWriter, aLocService, i+1, numTGroups, aStr, astr,
+                aCategoryUsableSize, cumulativeUsableSize, aTotalUsableSize);
     } else if (i == MaxTGroups) {
       W("%s: stopping after %s %s groups\n\n", aStr,
         Show(MaxTGroups, gBuf1, kBufLen), name);
     }
   }
 
   MOZ_ASSERT(aCategoryUsableSize == kNoSize ||
              aCategoryUsableSize == cumulativeUsableSize);
 }
 
 static void
 PrintSortedBlockAndFrameGroups(const Writer& aWriter,
+                               LocationService* aLocService,
                                const char* aStr, const char* astr,
                                const LiveBlockGroupTable& aLiveBlockGroupTable,
                                size_t aCategoryUsableSize,
                                size_t aTotalUsableSize)
 {
-  PrintSortedGroups(aWriter, aStr, astr, aLiveBlockGroupTable,
+  PrintSortedGroups(aWriter, aLocService, aStr, astr, aLiveBlockGroupTable,
                     aCategoryUsableSize, aTotalUsableSize);
 
   // Frame groups are totally dependent on vagaries of stack traces, so we
   // can't show them in test mode.
   if (gMode == Test) {
     return;
   }
 
   FrameGroupTable frameGroupTable;
-  frameGroupTable.init(2048);
+  (void)frameGroupTable.init(2048);
   for (LiveBlockGroupTable::Range r = aLiveBlockGroupTable.all();
        !r.empty();
        r.popFront()) {
     const LiveBlockGroup& bg = r.front();
     const StackTrace* st = bg.mAllocStackTrace;
 
     // A single PC can appear multiple times in a stack trace.  We ignore
     // duplicates by first sorting and then ignoring adjacent duplicates.
@@ -1786,17 +1957,17 @@ PrintSortedBlockAndFrameGroups(const Wri
       FrameGroupTable::AddPtr p = frameGroupTable.lookupForAdd(pc);
       if (!p) {
         FrameGroup fg(pc);
         (void)frameGroupTable.add(p, fg);
       }
       p->Add(bg);
     }
   }
-  PrintSortedGroups(aWriter, aStr, astr, frameGroupTable, kNoSize,
+  PrintSortedGroups(aWriter, aLocService, aStr, astr, frameGroupTable, kNoSize,
                     aTotalUsableSize);
 }
 
 // Note that, unlike most SizeOf* functions, this function does not take a
 // |nsMallocSizeOfFun| argument.  That's because those arguments are primarily
 // to aid DMD track heap blocks... but DMD deliberately doesn't track heap
 // blocks it allocated for itself!
 MOZ_EXPORT void
@@ -1815,29 +1986,29 @@ SizeOf(Sizes* aSizes)
 
   aSizes->mLiveBlockTable = gLiveBlockTable->sizeOfIncludingThis(MallocSizeOf);
 
   aSizes->mDoubleReportTable =
     gDoubleReportBlockGroupTable->sizeOfIncludingThis(MallocSizeOf);
 }
 
 static void
-ClearState()
+ClearGlobalState()
 {
   // Unreport all blocks, except those that were reported on allocation,
   // because they need to keep their reported marking.
   for (LiveBlockTable::Range r = gLiveBlockTable->all();
        !r.empty();
        r.popFront()) {
     r.front().UnreportIfNotReportedOnAlloc();
   }
 
   // Clear errors.
   gDoubleReportBlockGroupTable->finish();
-  gDoubleReportBlockGroupTable->init();
+  (void)gDoubleReportBlockGroupTable->init();
 }
 
 MOZ_EXPORT void
 Dump(Writer aWriter)
 {
   if (!gIsDMDRunning) {
     const char* msg = "cannot Dump();  DMD was not enabled at startup\n";
     StatusMsg("%s", msg);
@@ -1885,24 +2056,29 @@ Dump(Writer aWriter)
     anyBlocksSampled = anyBlocksSampled || b.IsSampled();
   }
   size_t totalUsableSize = unreportedUsableSize + reportedUsableSize;
 
   WriteTitle("Invocation\n");
   W("$DMD = '%s'\n", gDMDEnvVar);
   W("Sample-below size = %lld\n\n", (long long)(gSampleBelowSize));
 
-  PrintSortedGroups(aWriter, "Double-reported", "double-reported",
+  // Allocate this on the heap instead of the stack because it's fairly large.
+  LocationService* locService = InfallibleAllocPolicy::new_<LocationService>();
+
+  PrintSortedGroups(aWriter, locService, "Double-reported", "double-reported",
                     *gDoubleReportBlockGroupTable, kNoSize, kNoSize);
 
-  PrintSortedBlockAndFrameGroups(aWriter, "Unreported", "unreported",
+  PrintSortedBlockAndFrameGroups(aWriter, locService,
+                                 "Unreported", "unreported",
                                  unreportedLiveBlockGroupTable,
                                  unreportedUsableSize, totalUsableSize);
 
-  PrintSortedBlockAndFrameGroups(aWriter, "Reported", "reported",
+  PrintSortedBlockAndFrameGroups(aWriter, locService,
+                                 "Reported", "reported",
                                  reportedLiveBlockGroupTable,
                                  reportedUsableSize, totalUsableSize);
 
   bool showTilde = anyBlocksSampled;
   WriteTitle("Summary\n");
   W("Total:      %s bytes\n",
     Show(totalUsableSize, gBuf1, kBufLen, showTilde));
   W("Reported:   %s bytes (%5.2f%%)\n",
@@ -1931,41 +2107,63 @@ Dump(Writer aWriter)
       Show(gStackTraceTable->capacity(), gBuf2, kBufLen),
       Show(gStackTraceTable->count(),    gBuf3, kBufLen));
 
     W("  Live block table:    %10s bytes (%s entries, %s used)\n",
       Show(sizes.mLiveBlockTable,       gBuf1, kBufLen),
       Show(gLiveBlockTable->capacity(), gBuf2, kBufLen),
       Show(gLiveBlockTable->count(),    gBuf3, kBufLen));
 
-    W("\nData structures that are cleared after Dump() ends:\n");
+    W("\nData structures that are partly cleared after Dump() ends:\n");
 
     W("  Double-report table: %10s bytes (%s entries, %s used)\n",
       Show(sizes.mDoubleReportTable,                 gBuf1, kBufLen),
       Show(gDoubleReportBlockGroupTable->capacity(), gBuf2, kBufLen),
       Show(gDoubleReportBlockGroupTable->count(),    gBuf3, kBufLen));
 
+    W("\nData structures that are destroyed after Dump() ends:\n");
+
     size_t unreportedSize =
       unreportedLiveBlockGroupTable.sizeOfIncludingThis(MallocSizeOf);
     W("  Unreported table:    %10s bytes (%s entries, %s used)\n",
       Show(unreportedSize,                           gBuf1, kBufLen),
       Show(unreportedLiveBlockGroupTable.capacity(), gBuf2, kBufLen),
       Show(unreportedLiveBlockGroupTable.count(),    gBuf3, kBufLen));
 
     size_t reportedSize =
       reportedLiveBlockGroupTable.sizeOfIncludingThis(MallocSizeOf);
     W("  Reported table:      %10s bytes (%s entries, %s used)\n",
       Show(reportedSize,                           gBuf1, kBufLen),
       Show(reportedLiveBlockGroupTable.capacity(), gBuf2, kBufLen),
       Show(reportedLiveBlockGroupTable.count(),    gBuf3, kBufLen));
 
+    W("  Location service:    %10s bytes\n",
+      Show(locService->SizeOfIncludingThis(), gBuf1, kBufLen));
+
+    W("\nCounts:\n");
+
+    size_t hits   = locService->NumCacheHits();
+    size_t misses = locService->NumCacheMisses();
+    size_t requests = hits + misses;
+    W("  Location service:    %10s requests\n",
+      Show(requests, gBuf1, kBufLen));
+
+    size_t count    = locService->CacheCount();
+    size_t capacity = locService->CacheCapacity();
+    double hitRate   = 100 * double(hits) / requests;
+    double occupancy = 100 * double(count) / capacity;
+    W("  Location service cache:  %4.1f%% hit rate, %.1f%% occupancy at end\n",
+      hitRate, occupancy);
+
     W("\n");
   }
 
-  ClearState();
+  InfallibleAllocPolicy::delete_(locService);
+
+  ClearGlobalState();
 
   StatusMsg("}\n");
 }
 
 //---------------------------------------------------------------------------
 // Testing
 //---------------------------------------------------------------------------