Bug 1035570 (part 3) - DMD: Add DMDAnalyzeHeap(), a heap snapshot function. r=erahm,mccr8.
authorNicholas Nethercote <nnethercote@mozilla.com>
Thu, 29 May 2014 23:46:09 -0700
changeset 219264 92b7bbf5c1afa386d768e5c5a8cc7baa4d23100b
parent 219263 c58334ac43628dc5225d989576c538a4ee35a70f
child 219265 258e6048d585c4a86e98cbf6045afa34c4cf7f18
push id583
push userbhearsum@mozilla.com
push dateMon, 24 Nov 2014 19:04:58 +0000
treeherdermozilla-release@c107e74250f4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerserahm, mccr8
bugs1035570
milestone34.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 1035570 (part 3) - DMD: Add DMDAnalyzeHeap(), a heap snapshot function. r=erahm,mccr8. The patch also adds DMDAnalyzeReports() as a synonym for DMDReportAndDump(), and deprecates the latter.
dom/base/nsJSEnvironment.cpp
memory/replace/dmd/DMD.cpp
memory/replace/dmd/DMD.h
memory/replace/dmd/test-expected.dmd
testing/mochitest/tests/SimpleTest/MemoryStats.js
toolkit/components/aboutmemory/content/aboutMemory.js
xpcom/base/nsMemoryInfoDumper.cpp
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -1435,55 +1435,100 @@ static const JSFunctionSpec TraceMallocF
 #include <errno.h>
 
 namespace mozilla {
 namespace dmd {
 
 // See https://wiki.mozilla.org/Performance/MemShrink/DMD for instructions on
 // how to use DMD.
 
+static FILE *
+OpenDMDOutputFile(JSContext *cx, JS::CallArgs &args)
+{
+  JSString *str = JS::ToString(cx, args.get(0));
+  if (!str)
+    return nullptr;
+  JSAutoByteString pathname(cx, str);
+  if (!pathname)
+    return nullptr;
+
+  FILE* fp = fopen(pathname.ptr(), "w");
+  if (!fp) {
+    JS_ReportError(cx, "DMD can't open %s: %s",
+                   pathname.ptr(), strerror(errno));
+    return nullptr;
+  }
+  return fp;
+}
+
 static bool
-ReportAndDump(JSContext *cx, unsigned argc, JS::Value *vp)
+AnalyzeReports(JSContext *cx, unsigned argc, JS::Value *vp)
 {
   if (!dmd::IsRunning()) {
     JS_ReportError(cx, "DMD is not running");
     return false;
   }
 
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-  JSString *str = JS::ToString(cx, args.get(0));
-  if (!str)
-    return false;
-  JSAutoByteString pathname(cx, str);
-  if (!pathname)
-    return false;
-
-  FILE* fp = fopen(pathname.ptr(), "w");
+  FILE *fp = OpenDMDOutputFile(cx, args);
   if (!fp) {
-    JS_ReportError(cx, "DMD can't open %s: %s",
-                   pathname.ptr(), strerror(errno));
     return false;
   }
 
   dmd::ClearReports();
   dmd::RunReportersForThisProcess();
   dmd::Writer writer(FpWrite, fp);
-  dmd::Dump(writer);
+  dmd::AnalyzeReports(writer);
+
+  fclose(fp);
+
+  args.rval().setUndefined();
+  return true;
+}
+
+// This will be removed eventually.
+static bool
+ReportAndDump(JSContext *cx, unsigned argc, JS::Value *vp)
+{
+  JS_ReportWarning(cx, "DMDReportAndDump() is deprecated; "
+                   "please use DMDAnalyzeReports() instead");
+
+  return AnalyzeReports(cx, argc, vp);
+}
+
+static bool
+AnalyzeHeap(JSContext *cx, unsigned argc, JS::Value *vp)
+{
+  if (!dmd::IsRunning()) {
+    JS_ReportError(cx, "DMD is not running");
+    return false;
+  }
+
+  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+  FILE *fp = OpenDMDOutputFile(cx, args);
+  if (!fp) {
+    return false;
+  }
+
+  dmd::Writer writer(FpWrite, fp);
+  dmd::AnalyzeHeap(writer);
 
   fclose(fp);
 
   args.rval().setUndefined();
   return true;
 }
 
 } // namespace dmd
 } // namespace mozilla
 
 static const JSFunctionSpec DMDFunctions[] = {
-    JS_FS("DMDReportAndDump", dmd::ReportAndDump, 1, 0),
+    JS_FS("DMDReportAndDump",  dmd::ReportAndDump,  1, 0),
+    JS_FS("DMDAnalyzeReports", dmd::AnalyzeReports, 1, 0),
+    JS_FS("DMDAnalyzeHeap",    dmd::AnalyzeHeap,    1, 0),
     JS_FS_END
 };
 
 #endif  // defined(MOZ_DMD)
 
 #ifdef MOZ_JPROF
 
 #include <signal.h>
--- a/memory/replace/dmd/DMD.cpp
+++ b/memory/replace/dmd/DMD.cpp
@@ -39,16 +39,17 @@
 #include "mozilla/Likely.h"
 #include "mozilla/MemoryReporting.h"
 
 // MOZ_REPLACE_ONLY_MEMALIGN saves us from having to define
 // replace_{posix_memalign,aligned_alloc,valloc}.  It requires defining
 // PAGE_SIZE.  Nb: sysconf() is expensive, but it's only used for (the obsolete
 // and rarely used) valloc.
 #define MOZ_REPLACE_ONLY_MEMALIGN 1
+
 #ifdef XP_WIN
 #define PAGE_SIZE GetPageSize()
 static long GetPageSize()
 {
   SYSTEM_INFO si;
   GetSystemInfo(&si);
   return si.dwPageSize;
 }
@@ -286,17 +287,17 @@ class Options
   };
 
   enum Mode {
     Normal,   // run normally
     Test,     // do some basic correctness tests
     Stress    // do some performance stress tests
   };
 
-  char* mDMDEnvVar;   // a saved copy, for printing during Dump()
+  char* mDMDEnvVar;   // a saved copy, for later printing
 
   NumOption<size_t>   mSampleBelowSize;
   NumOption<uint32_t> mMaxFrames;
   NumOption<uint32_t> mMaxRecords;
   Mode mMode;
 
   void BadArg(const char* aArg);
   static const char* ValueIfMatch(const char* aArg, const char* aOptionName);
@@ -605,17 +606,18 @@ class LocationService
     const char* mLibrary;   // owned by mLibraryStrings;  never null
                             //   in a non-empty entry is in use
     ptrdiff_t   mLOffset;
     char*       mFileName;  // owned by the Entry; may be null
     uint32_t    mLineNo:31;
     uint32_t    mInUse:1;   // is the entry used?
 
     Entry()
-      : mPc(0), mFunction(nullptr), mLibrary(nullptr), mLOffset(0), mFileName(nullptr), mLineNo(0), mInUse(0)
+      : mPc(0), mFunction(nullptr), mLibrary(nullptr), mLOffset(0),
+        mFileName(nullptr), mLineNo(0), mInUse(0)
     {}
 
     ~Entry()
     {
       // We don't free mLibrary because it's externally owned.
       InfallibleAllocPolicy::free_(mFunction);
       InfallibleAllocPolicy::free_(mFileName);
     }
@@ -643,22 +645,22 @@ class LocationService
     }
 
     size_t SizeOfExcludingThis() {
       // Don't measure mLibrary because it's externally owned.
       return MallocSizeOf(mFunction) + MallocSizeOf(mFileName);
     }
   };
 
-  // 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 records 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).
+  // A direct-mapped cache.  When doing AnalyzeReports just after starting
+  // desktop Firefox (which is similar to analyzing after a longer-running
+  // session, thanks to the limit on how many records we print), 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:
@@ -788,17 +790,17 @@ public:
   uint32_t Size() const { return mLength * sizeof(mPcs[0]); }
 
   // The stack trace returned by this function is interned in gStackTraceTable,
   // and so is immortal and unmovable.
   static const StackTrace* Get(Thread* aT);
 
   void Sort()
   {
-    qsort(mPcs, mLength, sizeof(mPcs[0]), StackTrace::QsortCmp);
+    qsort(mPcs, mLength, sizeof(mPcs[0]), StackTrace::Cmp);
   }
 
   void Print(const Writer& aWriter, LocationService* aLocService) const;
 
   // Hash policy.
 
   typedef StackTrace* Lookup;
 
@@ -818,17 +820,17 @@ private:
   static void StackWalkCallback(void* aPc, void* aSp, void* aClosure)
   {
     StackTrace* st = (StackTrace*) aClosure;
     MOZ_ASSERT(st->mLength < MaxFrames);
     st->mPcs[st->mLength] = aPc;
     st->mLength++;
   }
 
-  static int QsortCmp(const void* aA, const void* aB)
+  static int Cmp(const void* aA, const void* aB)
   {
     const void* const a = *static_cast<const void* const*>(aA);
     const void* const b = *static_cast<const void* const*>(aB);
     if (a < b) return -1;
     if (a > b) return  1;
     return 0;
   }
 };
@@ -839,17 +841,17 @@ static StackTraceTable* gStackTraceTable
 
 // We won't GC the stack trace table until it this many elements.
 static uint32_t gGCStackTraceTableWhenSizeExceeds = 4 * 1024;
 
 void
 StackTrace::Print(const Writer& aWriter, LocationService* aLocService) const
 {
   if (mLength == 0) {
-    W("   (empty)\n");  // StackTrace::Get() must have failed
+    W("    (empty)\n");  // StackTrace::Get() must have failed
     return;
   }
 
   for (uint32_t i = 0; i < mLength; i++) {
     aLocService->WriteLocation(aWriter, Pc(i));
   }
 }
 
@@ -968,18 +970,18 @@ class Block
   TaggedPtr<const StackTrace* const>
     mAllocStackTrace_mSampled;
 
   // This array has two elements because we record at most two reports of a
   // block.
   // - Ptr: |mReportStackTrace| - stack trace where this block was reported.
   //   nullptr if not reported.
   // - Tag bit 0: |mReportedOnAlloc| - was the block reported immediately on
-  //   allocation?  If so, DMD must not clear the report at the end of Dump().
-  //   Only relevant if |mReportStackTrace| is non-nullptr.
+  //   allocation?  If so, DMD must not clear the report at the end of
+  //   AnalyzeReports(). Only relevant if |mReportStackTrace| is non-nullptr.
   //
   // |mPtr| is used as the key in BlockTable, so it's ok for this member
   // to be |mutable|.
   mutable TaggedPtr<const StackTrace*> mReportStackTrace_mReportedOnAlloc[2];
 
 public:
   Block(const void* aPtr, size_t aReqSize, const StackTrace* aAllocStackTrace,
         bool aSampled)
@@ -1412,17 +1414,17 @@ public:
 
   void Add(const RecordSize& aRecordSize)
   {
     mReq  += aRecordSize.Req();
     mSlop += aRecordSize.Slop();
     mSampled = mSampled || aRecordSize.IsSampled();
   }
 
-  static int Cmp(const RecordSize& aA, const RecordSize& aB)
+  static int CmpByUsable(const RecordSize& aA, const RecordSize& aB)
   {
     // Primary sort: put bigger usable sizes first.
     if (aA.Usable() > aB.Usable()) return -1;
     if (aA.Usable() < aB.Usable()) return  1;
 
     // Secondary sort: put bigger requested sizes first.
     if (aA.Req() > aB.Req()) return -1;
     if (aA.Req() < aB.Req()) return  1;
@@ -1459,34 +1461,36 @@ public:
   {
     mNumBlocks++;
     mRecordSize.Add(aB);
   }
 
   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)
+             size_t aTotalUsableSize, bool aShowCategoryPercentage,
+             bool aShowReportedAt) const;
+
+  static int CmpByUsable(const void* aA, const void* aB)
   {
     const Record* const a = *static_cast<const Record* const*>(aA);
     const Record* const b = *static_cast<const Record* const*>(aB);
 
-    return RecordSize::Cmp(a->mRecordSize, b->mRecordSize);
+    return RecordSize::CmpByUsable(a->mRecordSize, b->mRecordSize);
   }
 };
 
 typedef js::HashSet<Record, Record, InfallibleAllocPolicy> RecordTable;
 
 void
 Record::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
+              size_t aTotalUsableSize, bool aShowCategoryPercentage,
+              bool aShowReportedAt) const
 {
   bool showTilde = mRecordSize.IsSampled();
 
   W("%s {\n", aStr);
   W("  %s block%s in heap block record %s of %s\n",
     Show(mNumBlocks, gBuf1, kBufLen, showTilde), Plural(mNumBlocks),
     Show(aM, gBuf2, kBufLen),
     Show(aN, gBuf3, kBufLen));
@@ -1495,34 +1499,38 @@ Record::Print(const Writer& aWriter, Loc
     Show(mRecordSize.Usable(), gBuf1, kBufLen, showTilde),
     Show(mRecordSize.Req(),    gBuf2, kBufLen, showTilde),
     Show(mRecordSize.Slop(),   gBuf3, kBufLen, showTilde));
 
   W("  %4.2f%% of the heap (%4.2f%% cumulative)\n",
     Percent(mRecordSize.Usable(), aTotalUsableSize),
     Percent(aCumulativeUsableSize, aTotalUsableSize));
 
-  W("  %4.2f%% of %s (%4.2f%% cumulative)\n",
-    Percent(mRecordSize.Usable(), aCategoryUsableSize),
-    astr,
-    Percent(aCumulativeUsableSize, aCategoryUsableSize));
+  if (aShowCategoryPercentage) {
+    W("  %4.2f%% of %s (%4.2f%% cumulative)\n",
+      Percent(mRecordSize.Usable(), aCategoryUsableSize),
+      astr,
+      Percent(aCumulativeUsableSize, aCategoryUsableSize));
+  }
 
   W("  Allocated at {\n");
   mAllocStackTrace->Print(aWriter, aLocService);
   W("  }\n");
 
-  if (mReportStackTrace1) {
-    W("  Reported at {\n");
-    mReportStackTrace1->Print(aWriter, aLocService);
-    W("  }\n");
-  }
-  if (mReportStackTrace2) {
-    W("  Reported again at {\n");
-    mReportStackTrace2->Print(aWriter, aLocService);
-    W("  }\n");
+  if (aShowReportedAt) {
+    if (mReportStackTrace1) {
+      W("  Reported at {\n");
+      mReportStackTrace1->Print(aWriter, aLocService);
+      W("  }\n");
+    }
+    if (mReportStackTrace2) {
+      W("  Reported again at {\n");
+      mReportStackTrace2->Print(aWriter, aLocService);
+      W("  }\n");
+    }
   }
 
   W("}\n\n");
 }
 
 //---------------------------------------------------------------------------
 // Options (Part 2)
 //---------------------------------------------------------------------------
@@ -1803,32 +1811,34 @@ ReportOnAlloc(const void* aPtr)
 }
 
 //---------------------------------------------------------------------------
 // DMD output
 //---------------------------------------------------------------------------
 
 static void
 PrintSortedRecords(const Writer& aWriter, LocationService* aLocService,
+                   int (*aCmp)(const void*, const void*),
                    const char* aStr, const char* astr,
                    const RecordTable& aRecordTable,
-                   size_t aCategoryUsableSize, size_t aTotalUsableSize)
+                   size_t aCategoryUsableSize, size_t aTotalUsableSize,
+                   bool aShowCategoryPercentage, bool aShowReportedAt)
 {
   StatusMsg("  creating and sorting %s heap block record array...\n", astr);
 
   // Convert the table into a sorted array.
   js::Vector<const Record*, 0, InfallibleAllocPolicy> recordArray;
   recordArray.reserve(aRecordTable.count());
   for (RecordTable::Range r = aRecordTable.all();
        !r.empty();
        r.popFront()) {
     recordArray.infallibleAppend(&r.front());
   }
   qsort(recordArray.begin(), recordArray.length(), sizeof(recordArray[0]),
-        Record::QsortCmp);
+        aCmp);
 
   WriteSeparator();
 
   if (recordArray.length() == 0) {
     W("# no %s heap blocks\n\n", astr);
     return;
   }
 
@@ -1840,17 +1850,18 @@ PrintSortedRecords(const Writer& aWriter
   // keep adding to |cumulativeUsableSize|.
   uint32_t numRecords = recordArray.length();
   uint32_t maxRecords = gOptions->MaxRecords();
   for (uint32_t i = 0; i < numRecords; i++) {
     const Record* r = recordArray[i];
     cumulativeUsableSize += r->GetRecordSize().Usable();
     if (i < maxRecords) {
       r->Print(aWriter, aLocService, i+1, numRecords, aStr, astr,
-               aCategoryUsableSize, cumulativeUsableSize, aTotalUsableSize);
+               aCategoryUsableSize, cumulativeUsableSize, aTotalUsableSize,
+               aShowCategoryPercentage, aShowReportedAt);
     } else if (i == maxRecords) {
       W("# %s: stopping after %s heap block records\n\n", aStr,
         Show(maxRecords, gBuf1, kBufLen));
     }
   }
   MOZ_ASSERT(aCategoryUsableSize == cumulativeUsableSize);
 }
 
@@ -1905,179 +1916,297 @@ SizeOf(Sizes* aSizes)
     return;
   }
 
   AutoBlockIntercepts block(Thread::Fetch());
   AutoLockState lock;
   SizeOfInternal(aSizes);
 }
 
-void
-ClearReportsInternal()
-{
-  MOZ_ASSERT(gStateLock->IsLocked());
-
-  // Unreport all blocks that were marked reported by a memory reporter.  This
-  // excludes those that were reported on allocation, because they need to keep
-  // their reported marking.
-  for (BlockTable::Range r = gBlockTable->all(); !r.empty(); r.popFront()) {
-    r.front().UnreportIfNotReportedOnAlloc();
-  }
-}
-
 MOZ_EXPORT void
 ClearReports()
 {
   if (!gIsDMDRunning) {
     return;
   }
 
   AutoLockState lock;
-  ClearReportsInternal();
+
+  // Unreport all blocks that were marked reported by a memory reporter.  This
+  // excludes those that were reported on allocation, because they need to keep
+  // their reported marking.
+  for (BlockTable::Range r = gBlockTable->all(); !r.empty(); r.popFront()) {
+    r.front().UnreportIfNotReportedOnAlloc();
+  }
 }
 
 MOZ_EXPORT bool
 IsRunning()
 {
   return gIsDMDRunning;
 }
 
-MOZ_EXPORT void
-Dump(Writer aWriter)
+// AnalyzeReports() and AnalyzeHeap() have a lot in common. This abstract class
+// encapsulates the operations that are not shared.
+class Analyzer
+{
+public:
+  virtual const char* AnalyzeFunctionName() const = 0;
+
+  virtual RecordTable* ProcessBlock(const Block& aBlock) = 0;
+
+  virtual void PrintRecords(const Writer& aWriter,
+                            LocationService* aLocService) const = 0;
+  virtual void PrintSummary(const Writer& aWriter, bool aShowTilde) const = 0;
+  virtual void PrintStats(const Writer& aWriter) const = 0;
+
+  struct RecordKindData
+  {
+    RecordTable mRecordTable;
+    size_t mUsableSize;
+    size_t mNumBlocks;
+
+    RecordKindData(size_t aN)
+      : mUsableSize(0), mNumBlocks(0)
+    {
+      mRecordTable.init(aN);
+    }
+
+    void processBlock(const Block& aBlock)
+    {
+      mUsableSize += aBlock.UsableSize();
+      mNumBlocks++;
+    }
+  };
+};
+
+class ReportsAnalyzer MOZ_FINAL : public Analyzer
+{
+  RecordKindData mUnreported;
+  RecordKindData mOnceReported;
+  RecordKindData mTwiceReported;
+
+  size_t mTotalUsableSize;
+  size_t mTotalNumBlocks;
+
+public:
+  ReportsAnalyzer()
+    : mUnreported(1024), mOnceReported(1024), mTwiceReported(0),
+      mTotalUsableSize(0), mTotalNumBlocks(0)
+  {}
+
+  ~ReportsAnalyzer()
+  {
+    ClearReports();
+  }
+
+  virtual const char* AnalyzeFunctionName() const { return "AnalyzeReports"; }
+
+  virtual RecordTable* ProcessBlock(const Block& aBlock)
+  {
+    RecordKindData* data;
+    uint32_t numReports = aBlock.NumReports();
+    if (numReports == 0) {
+      data = &mUnreported;
+    } else if (numReports == 1) {
+      data = &mOnceReported;
+    } else {
+      MOZ_ASSERT(numReports == 2);
+      data = &mTwiceReported;
+    }
+    data->processBlock(aBlock);
+
+    mTotalUsableSize += aBlock.UsableSize();
+    mTotalNumBlocks++;
+
+    return &data->mRecordTable;
+  }
+
+  virtual void PrintRecords(const Writer& aWriter,
+                            LocationService* aLocService) const
+  {
+    PrintSortedRecords(aWriter, aLocService, Record::CmpByUsable,
+                       "Twice-reported", "twice-reported",
+                       mTwiceReported.mRecordTable,
+                       mTwiceReported.mUsableSize, mTotalUsableSize,
+                       /* showCategoryPercentage = */ true,
+                       /* showReportedAt = */ true);
+
+    PrintSortedRecords(aWriter, aLocService, Record::CmpByUsable,
+                       "Unreported", "unreported",
+                       mUnreported.mRecordTable,
+                       mUnreported.mUsableSize, mTotalUsableSize,
+                       /* showCategoryPercentage = */ true,
+                       /* showReportedAt = */ true);
+
+    PrintSortedRecords(aWriter, aLocService, Record::CmpByUsable,
+                       "Once-reported", "once-reported",
+                       mOnceReported.mRecordTable,
+                       mOnceReported.mUsableSize, mTotalUsableSize,
+                       /* showCategoryPercentage = */ true,
+                       /* showReportedAt = */ true);
+  }
+
+  virtual void PrintSummary(const Writer& aWriter, bool aShowTilde) const
+  {
+    W("  Total:          %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n",
+      Show(mTotalUsableSize, gBuf1, kBufLen, aShowTilde),
+      100.0,
+      Show(mTotalNumBlocks,  gBuf2, kBufLen, aShowTilde),
+      100.0);
+
+    W("  Unreported:     %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n",
+      Show(mUnreported.mUsableSize, gBuf1, kBufLen, aShowTilde),
+      Percent(mUnreported.mUsableSize, mTotalUsableSize),
+      Show(mUnreported.mNumBlocks, gBuf2, kBufLen, aShowTilde),
+      Percent(mUnreported.mNumBlocks, mTotalNumBlocks));
+
+    W("  Once-reported:  %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n",
+      Show(mOnceReported.mUsableSize, gBuf1, kBufLen, aShowTilde),
+      Percent(mOnceReported.mUsableSize, mTotalUsableSize),
+      Show(mOnceReported.mNumBlocks, gBuf2, kBufLen, aShowTilde),
+      Percent(mOnceReported.mNumBlocks, mTotalNumBlocks));
+
+    W("  Twice-reported: %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n",
+      Show(mTwiceReported.mUsableSize, gBuf1, kBufLen, aShowTilde),
+      Percent(mTwiceReported.mUsableSize, mTotalUsableSize),
+      Show(mTwiceReported.mNumBlocks, gBuf2, kBufLen, aShowTilde),
+      Percent(mTwiceReported.mNumBlocks, mTotalNumBlocks));
+  }
+
+  virtual void PrintStats(const Writer& aWriter) const
+  {
+    size_t unreportedSize =
+      mUnreported.mRecordTable.sizeOfIncludingThis(MallocSizeOf);
+    W("    Unreported table:     %10s bytes (%s entries, %s used)\n",
+      Show(unreportedSize,                      gBuf1, kBufLen),
+      Show(mUnreported.mRecordTable.capacity(), gBuf2, kBufLen),
+      Show(mUnreported.mRecordTable.count(),    gBuf3, kBufLen));
+
+    size_t onceReportedSize =
+      mOnceReported.mRecordTable.sizeOfIncludingThis(MallocSizeOf);
+    W("    Once-reported table:  %10s bytes (%s entries, %s used)\n",
+      Show(onceReportedSize,                      gBuf1, kBufLen),
+      Show(mOnceReported.mRecordTable.capacity(), gBuf2, kBufLen),
+      Show(mOnceReported.mRecordTable.count(),    gBuf3, kBufLen));
+
+    size_t twiceReportedSize =
+      mTwiceReported.mRecordTable.sizeOfIncludingThis(MallocSizeOf);
+    W("    Twice-reported table: %10s bytes (%s entries, %s used)\n",
+      Show(twiceReportedSize,                      gBuf1, kBufLen),
+      Show(mTwiceReported.mRecordTable.capacity(), gBuf2, kBufLen),
+      Show(mTwiceReported.mRecordTable.count(),    gBuf3, kBufLen));
+  }
+};
+
+class HeapAnalyzer MOZ_FINAL : public Analyzer
+{
+  RecordKindData mLive;
+
+public:
+  HeapAnalyzer() : mLive(1024) {}
+
+  virtual const char* AnalyzeFunctionName() const { return "AnalyzeHeap"; }
+
+  virtual RecordTable* ProcessBlock(const Block& aBlock)
+  {
+    mLive.processBlock(aBlock);
+
+    return &mLive.mRecordTable;
+  }
+
+  virtual void PrintRecords(const Writer& aWriter,
+                            LocationService* aLocService) const
+  {
+    size_t totalUsableSize = mLive.mUsableSize;
+    PrintSortedRecords(aWriter, aLocService, Record::CmpByUsable,
+                       "Live", "live", mLive.mRecordTable, totalUsableSize,
+                       mLive.mUsableSize,
+                       /* showReportedAt = */ false,
+                       /* showCategoryPercentage = */ false);
+  }
+
+  virtual void PrintSummary(const Writer& aWriter, bool aShowTilde) const
+  {
+    W("  Total: %s bytes in %s blocks\n",
+      Show(mLive.mUsableSize, gBuf1, kBufLen, aShowTilde),
+      Show(mLive.mNumBlocks,  gBuf2, kBufLen, aShowTilde));
+  }
+
+  virtual void PrintStats(const Writer& aWriter) const
+  {
+    size_t liveSize = mLive.mRecordTable.sizeOfIncludingThis(MallocSizeOf);
+    W("    Live table:           %10s bytes (%s entries, %s used)\n",
+      Show(liveSize,                      gBuf1, kBufLen),
+      Show(mLive.mRecordTable.capacity(), gBuf2, kBufLen),
+      Show(mLive.mRecordTable.count(),    gBuf3, kBufLen));
+  }
+};
+
+static void
+AnalyzeImpl(Analyzer *aAnalyzer, const Writer& aWriter)
 {
   if (!gIsDMDRunning) {
-    const char* msg = "cannot Dump();  DMD was not enabled at startup\n";
-    StatusMsg("%s", msg);
-    W("%s", msg);
     return;
   }
 
   AutoBlockIntercepts block(Thread::Fetch());
   AutoLockState lock;
 
-  static int dumpCount = 1;
-  StatusMsg("Dump %d {\n", dumpCount++);
+  static int analysisCount = 1;
+  StatusMsg("%s %d {\n", aAnalyzer->AnalyzeFunctionName(), analysisCount++);
 
   StatusMsg("  gathering heap block records...\n");
 
-  RecordTable unreportedRecordTable;
-  (void)unreportedRecordTable.init(1024);
-  size_t unreportedUsableSize = 0;
-  size_t unreportedNumBlocks = 0;
-
-  RecordTable onceReportedRecordTable;
-  (void)onceReportedRecordTable.init(1024);
-  size_t onceReportedUsableSize = 0;
-  size_t onceReportedNumBlocks = 0;
-
-  RecordTable twiceReportedRecordTable;
-  (void)twiceReportedRecordTable.init(0);
-  size_t twiceReportedUsableSize = 0;
-  size_t twiceReportedNumBlocks = 0;
-
   bool anyBlocksSampled = false;
 
   for (BlockTable::Range r = gBlockTable->all(); !r.empty(); r.popFront()) {
     const Block& b = r.front();
-
-    RecordTable* table;
-    uint32_t numReports = b.NumReports();
-    if (numReports == 0) {
-      unreportedUsableSize += b.UsableSize();
-      unreportedNumBlocks++;
-      table = &unreportedRecordTable;
-    } else if (numReports == 1) {
-      onceReportedUsableSize += b.UsableSize();
-      onceReportedNumBlocks++;
-      table = &onceReportedRecordTable;
-    } else {
-      MOZ_ASSERT(numReports == 2);
-      twiceReportedUsableSize += b.UsableSize();
-      twiceReportedNumBlocks++;
-      table = &twiceReportedRecordTable;
-    }
+    RecordTable* table = aAnalyzer->ProcessBlock(b);
+
     RecordKey key(b);
     RecordTable::AddPtr p = table->lookupForAdd(key);
     if (!p) {
       Record tr(b);
       (void)table->add(p, tr);
     }
     p->Add(b);
 
     anyBlocksSampled = anyBlocksSampled || b.IsSampled();
   }
-  size_t totalUsableSize =
-    unreportedUsableSize + onceReportedUsableSize + twiceReportedUsableSize;
-  size_t totalNumBlocks =
-    unreportedNumBlocks + onceReportedNumBlocks + twiceReportedNumBlocks;
 
   WriteSeparator();
   W("Invocation {\n");
   W("  $DMD = '%s'\n", gOptions->DMDEnvVar());
+  W("  Function = %s\n", aAnalyzer->AnalyzeFunctionName());
   W("  Sample-below size = %lld\n", (long long)(gOptions->SampleBelowSize()));
   W("}\n\n");
 
   // Allocate this on the heap instead of the stack because it's fairly large.
   LocationService* locService = InfallibleAllocPolicy::new_<LocationService>();
 
-  PrintSortedRecords(aWriter, locService,
-                     "Twice-reported", "twice-reported",
-                     twiceReportedRecordTable,
-                     twiceReportedUsableSize, totalUsableSize);
-
-  PrintSortedRecords(aWriter, locService,
-                     "Unreported", "unreported",
-                     unreportedRecordTable,
-                     unreportedUsableSize, totalUsableSize);
-
-  PrintSortedRecords(aWriter, locService,
-                     "Once-reported", "once-reported",
-                     onceReportedRecordTable,
-                     onceReportedUsableSize, totalUsableSize);
-
-  bool showTilde = anyBlocksSampled;
+  aAnalyzer->PrintRecords(aWriter, locService);
 
   WriteSeparator();
   W("Summary {\n");
 
-  W("  Total:          %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n",
-    Show(totalUsableSize, gBuf1, kBufLen, showTilde),
-    100.0,
-    Show(totalNumBlocks,  gBuf2, kBufLen, showTilde),
-    100.0);
-
-  W("  Unreported:     %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n",
-    Show(unreportedUsableSize, gBuf1, kBufLen, showTilde),
-    Percent(unreportedUsableSize, totalUsableSize),
-    Show(unreportedNumBlocks, gBuf2, kBufLen, showTilde),
-    Percent(unreportedNumBlocks, totalNumBlocks));
-
-  W("  Once-reported:  %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n",
-    Show(onceReportedUsableSize, gBuf1, kBufLen, showTilde),
-    Percent(onceReportedUsableSize, totalUsableSize),
-    Show(onceReportedNumBlocks, gBuf2, kBufLen, showTilde),
-    Percent(onceReportedNumBlocks, totalNumBlocks));
-
-  W("  Twice-reported: %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n",
-    Show(twiceReportedUsableSize, gBuf1, kBufLen, showTilde),
-    Percent(twiceReportedUsableSize, totalUsableSize),
-    Show(twiceReportedNumBlocks, gBuf2, kBufLen, showTilde),
-    Percent(twiceReportedNumBlocks, totalNumBlocks));
+  bool showTilde = anyBlocksSampled;
+  aAnalyzer->PrintSummary(aWriter, showTilde);
 
   W("}\n\n");
 
   // Stats are non-deterministic, so don't show them in test mode.
   if (!gOptions->IsTestMode()) {
     Sizes sizes;
     SizeOfInternal(&sizes);
 
     WriteSeparator();
     W("Execution measurements {\n");
 
-    W("  Data structures that persist after Dump() ends:\n");
+    W("  Data structures that persist after Dump() ends {\n");
 
     W("    Used stack traces:    %10s bytes\n",
       Show(sizes.mStackTracesUsed, gBuf1, kBufLen));
 
     W("    Unused stack traces:  %10s bytes\n",
       Show(sizes.mStackTracesUnused, gBuf1, kBufLen));
 
     W("    Stack trace table:    %10s bytes (%s entries, %s used)\n",
@@ -2085,66 +2214,62 @@ Dump(Writer aWriter)
       Show(gStackTraceTable->capacity(), gBuf2, kBufLen),
       Show(gStackTraceTable->count(),    gBuf3, kBufLen));
 
     W("    Block table:          %10s bytes (%s entries, %s used)\n",
       Show(sizes.mBlockTable,       gBuf1, kBufLen),
       Show(gBlockTable->capacity(), gBuf2, kBufLen),
       Show(gBlockTable->count(),    gBuf3, kBufLen));
 
-    W("\n  Data structures that are destroyed after Dump() ends:\n");
-
-    size_t unreportedSize =
-      unreportedRecordTable.sizeOfIncludingThis(MallocSizeOf);
-    W("    Unreported table:     %10s bytes (%s entries, %s used)\n",
-      Show(unreportedSize,                   gBuf1, kBufLen),
-      Show(unreportedRecordTable.capacity(), gBuf2, kBufLen),
-      Show(unreportedRecordTable.count(),    gBuf3, kBufLen));
-
-    size_t onceReportedSize =
-      onceReportedRecordTable.sizeOfIncludingThis(MallocSizeOf);
-    W("    Once-reported table:  %10s bytes (%s entries, %s used)\n",
-      Show(onceReportedSize,                   gBuf1, kBufLen),
-      Show(onceReportedRecordTable.capacity(), gBuf2, kBufLen),
-      Show(onceReportedRecordTable.count(),    gBuf3, kBufLen));
-
-    size_t twiceReportedSize =
-      twiceReportedRecordTable.sizeOfIncludingThis(MallocSizeOf);
-    W("    Twice-reported table: %10s bytes (%s entries, %s used)\n",
-      Show(twiceReportedSize,                   gBuf1, kBufLen),
-      Show(twiceReportedRecordTable.capacity(), gBuf2, kBufLen),
-      Show(twiceReportedRecordTable.count(),    gBuf3, kBufLen));
+    W("  }\n");
+    W("  Data structures that are destroyed after Dump() ends {\n");
+
+    aAnalyzer->PrintStats(aWriter);
 
     W("    Location service:     %10s bytes\n",
       Show(locService->SizeOfIncludingThis(), gBuf1, kBufLen));
 
-    W("\n  Counts:\n");
+    W("  }\n");
+    W("  Counts {\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();
     W("    Location service cache:  "
       "%4.1f%% hit rate, %.1f%% occupancy at end\n",
       Percent(hits, requests), Percent(count, capacity));
 
+    W("  }\n");
     W("}\n\n");
   }
 
   InfallibleAllocPolicy::delete_(locService);
 
-  ClearReportsInternal(); // Use internal version, we already have the lock.
-
   StatusMsg("}\n");
 }
 
+MOZ_EXPORT void
+AnalyzeReports(const Writer& aWriter)
+{
+  ReportsAnalyzer aAnalyzer;
+  AnalyzeImpl(&aAnalyzer, aWriter);
+}
+
+MOZ_EXPORT void
+AnalyzeHeap(const Writer& aWriter)
+{
+  HeapAnalyzer analyzer;
+  AnalyzeImpl(&analyzer, aWriter);
+}
+
 //---------------------------------------------------------------------------
 // Testing
 //---------------------------------------------------------------------------
 
 // This function checks that heap blocks that have the same stack trace but
 // different (or no) reporters get aggregated separately.
 void foo()
 {
@@ -2175,147 +2300,150 @@ UseItOrLoseIt(void* a)
 static void
 RunTestMode(FILE* fp)
 {
   Writer writer(FpWrite, fp);
 
   // The first part of this test requires sampling to be disabled.
   gOptions->SetSampleBelowSize(1);
 
-  // Dump 1.  Zero for everything.
-  Dump(writer);
-
-  // Dump 2: 1 freed, 9 out of 10 unreported.
-  // Dump 3: still present and unreported.
+  // AnalyzeReports 1.  Zero for everything.
+  AnalyzeReports(writer);
+  AnalyzeHeap(writer);
+
+  // AnalyzeReports 2: 1 freed, 9 out of 10 unreported.
+  // AnalyzeReports 3: still present and unreported.
   int i;
   char* a;
   for (i = 0; i < 10; i++) {
       a = (char*) malloc(100);
       UseItOrLoseIt(a);
   }
   free(a);
 
   // Min-sized block.
-  // Dump 2: reported.
-  // Dump 3: thrice-reported.
+  // AnalyzeReports 2: reported.
+  // AnalyzeReports 3: thrice-reported.
   char* a2 = (char*) malloc(0);
   Report(a2);
 
   // Operator new[].
-  // Dump 2: reported.
-  // Dump 3: reportedness carries over, due to ReportOnAlloc.
+  // AnalyzeReports 2: reported.
+  // AnalyzeReports 3: reportedness carries over, due to ReportOnAlloc.
   char* b = new char[10];
   ReportOnAlloc(b);
 
   // ReportOnAlloc, then freed.
-  // Dump 2: freed, irrelevant.
-  // Dump 3: freed, irrelevant.
+  // AnalyzeReports 2: freed, irrelevant.
+  // AnalyzeReports 3: freed, irrelevant.
   char* b2 = new char;
   ReportOnAlloc(b2);
   free(b2);
 
-  // Dump 2: reported 4 times.
-  // Dump 3: freed, irrelevant.
+  // AnalyzeReports 2: reported 4 times.
+  // AnalyzeReports 3: freed, irrelevant.
   char* c = (char*) calloc(10, 3);
   Report(c);
   for (int i = 0; i < 3; i++) {
     Report(c);
   }
 
-  // Dump 2: ignored.
-  // Dump 3: irrelevant.
+  // AnalyzeReports 2: ignored.
+  // AnalyzeReports 3: irrelevant.
   Report((void*)(intptr_t)i);
 
   // jemalloc rounds this up to 8192.
-  // Dump 2: reported.
-  // Dump 3: freed.
+  // AnalyzeReports 2: reported.
+  // AnalyzeReports 3: freed.
   char* e = (char*) malloc(4096);
   e = (char*) realloc(e, 4097);
   Report(e);
 
   // First realloc is like malloc;  second realloc is shrinking.
-  // Dump 2: reported.
-  // Dump 3: re-reported.
+  // AnalyzeReports 2: reported.
+  // AnalyzeReports 3: re-reported.
   char* e2 = (char*) realloc(nullptr, 1024);
   e2 = (char*) realloc(e2, 512);
   Report(e2);
 
   // First realloc is like malloc;  second realloc creates a min-sized block.
   // XXX: on Windows, second realloc frees the block.
-  // Dump 2: reported.
-  // Dump 3: freed, irrelevant.
+  // AnalyzeReports 2: reported.
+  // AnalyzeReports 3: freed, irrelevant.
   char* e3 = (char*) realloc(nullptr, 1023);
 //e3 = (char*) realloc(e3, 0);
   MOZ_ASSERT(e3);
   Report(e3);
 
-  // Dump 2: freed, irrelevant.
-  // Dump 3: freed, irrelevant.
+  // AnalyzeReports 2: freed, irrelevant.
+  // AnalyzeReports 3: freed, irrelevant.
   char* f = (char*) malloc(64);
   free(f);
 
-  // Dump 2: ignored.
-  // Dump 3: irrelevant.
+  // AnalyzeReports 2: ignored.
+  // AnalyzeReports 3: irrelevant.
   Report((void*)(intptr_t)0x0);
 
-  // Dump 2: mixture of reported and unreported.
-  // Dump 3: all unreported.
+  // AnalyzeReports 2: mixture of reported and unreported.
+  // AnalyzeReports 3: all unreported.
   foo();
   foo();
 
-  // Dump 2: twice-reported.
-  // Dump 3: twice-reported.
+  // AnalyzeReports 2: twice-reported.
+  // AnalyzeReports 3: twice-reported.
   char* g1 = (char*) malloc(77);
   ReportOnAlloc(g1);
   ReportOnAlloc(g1);
 
-  // Dump 2: twice-reported.
-  // Dump 3: once-reported.
+  // AnalyzeReports 2: twice-reported.
+  // AnalyzeReports 3: once-reported.
   char* g2 = (char*) malloc(78);
   Report(g2);
   ReportOnAlloc(g2);
 
-  // Dump 2: twice-reported.
-  // Dump 3: once-reported.
+  // AnalyzeReports 2: twice-reported.
+  // AnalyzeReports 3: once-reported.
   char* g3 = (char*) malloc(79);
   ReportOnAlloc(g3);
   Report(g3);
 
   // All the odd-ball ones.
-  // Dump 2: all unreported.
-  // Dump 3: all freed, irrelevant.
+  // AnalyzeReports 2: all unreported.
+  // AnalyzeReports 3: all freed, irrelevant.
   // XXX: no memalign on Mac
 //void* x = memalign(64, 65);           // rounds up to 128
 //UseItOrLoseIt(x);
   // XXX: posix_memalign doesn't work on B2G
 //void* y;
 //posix_memalign(&y, 128, 129);         // rounds up to 256
 //UseItOrLoseIt(y);
   // XXX: valloc doesn't work on Windows.
 //void* z = valloc(1);                  // rounds up to 4096
 //UseItOrLoseIt(z);
 //aligned_alloc(64, 256);               // XXX: C11 only
 
-  // Dump 2.
-  Dump(writer);
+  // AnalyzeReports 2.
+  AnalyzeReports(writer);
+  AnalyzeHeap(writer);
 
   //---------
 
   Report(a2);
   Report(a2);
   free(c);
   free(e);
   Report(e2);
   free(e3);
 //free(x);
 //free(y);
 //free(z);
 
-  // Dump 3.
-  Dump(writer);
+  // AnalyzeReports 3.
+  AnalyzeReports(writer);
+  AnalyzeHeap(writer);
 
   //---------
 
   // Clear all knowledge of existing blocks to give us a clean slate.
   gBlockTable->clear();
 
   gOptions->SetSampleBelowSize(128);
 
@@ -2368,18 +2496,19 @@ RunTestMode(FILE* fp)
     s = (char*) malloc(i * 16);
     UseItOrLoseIt(s);
   }
   MOZ_ASSERT(gSmallBlockActualSizeCounter == 64);
 
   // At the end we're 64 bytes into the current sample so we report ~1,424
   // bytes of allocation overall, which is 64 less than the real value 1,488.
 
-  // Dump 4.
-  Dump(writer);
+  // AnalyzeReports 4.
+  AnalyzeReports(writer);
+  AnalyzeHeap(writer);
 }
 
 //---------------------------------------------------------------------------
 // Stress testing microbenchmark
 //---------------------------------------------------------------------------
 
 // This stops otherwise-unused variables from being optimized away.
 static void
@@ -2445,13 +2574,13 @@ RunStressMode(FILE* fp)
   Writer writer(FpWrite, fp);
 
   // Disable sampling for maximum stress.
   gOptions->SetSampleBelowSize(1);
 
   stress1(); stress1(); stress1(); stress1(); stress1();
   stress1(); stress1(); stress1(); stress1(); stress1();
 
-  Dump(writer);
+  AnalyzeReports(writer);
 }
 
 }   // namespace dmd
 }   // namespace mozilla
--- a/memory/replace/dmd/DMD.h
+++ b/memory/replace/dmd/DMD.h
@@ -38,32 +38,38 @@ private:
   WriterFun mWriterFun;
   void*     mWriteState;
 };
 
 // Clears existing reportedness data from any prior runs of the memory
 // reporters.  The following sequence should be used.
 // - ClearReports()
 // - run the memory reporters
-// - Dump()
+// - AnalyzeReports()
 // This sequence avoids spurious twice-reported warnings.
 MOZ_EXPORT void
 ClearReports();
 
-// Checks which heap blocks have been reported, and dumps a human-readable
+// Determines which heap blocks have been reported, and dumps a human-readable
 // summary (via |aWrite|).  If |aWrite| is nullptr it will dump to stderr.
-// Beware:  this output may have very long lines.
+// Beware: this output may have very long lines.
 MOZ_EXPORT void
-Dump(Writer aWriter);
+AnalyzeReports(const Writer& aWriter);
 
-// A useful |WriterFun|.  If |fp| is a FILE* you want |Dump|'s output to be
-// written to, call:
+// Measures all heap blocks, and dumps a human-readable summary (via |aWrite|).
+// If |aWrite| is nullptr it will dump to stderr.  Beware: this output may
+// have very long lines.
+MOZ_EXPORT void
+AnalyzeHeap(const Writer& aWriter);
+
+// A useful |WriterFun|.  For example, if |fp| is a FILE* you want
+// |AnalyzeReports|'s output to be written to, call:
 //
 //   dmd::Writer writer(FpWrite, fp);
-//   dmd::Dump(writer);
+//   dmd::AnalyzeReports(writer);
 MOZ_EXPORT void
 FpWrite(void* aFp, const char* aFmt, va_list aAp);
 
 struct Sizes
 {
   size_t mStackTracesUsed;
   size_t mStackTracesUnused;
   size_t mStackTraceTable;
--- a/memory/replace/dmd/test-expected.dmd
+++ b/memory/replace/dmd/test-expected.dmd
@@ -1,12 +1,13 @@
 #-----------------------------------------------------------------
 
 Invocation {
   $DMD = '--mode=test'
+  Function = AnalyzeReports
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 # no twice-reported heap blocks
 
 #-----------------------------------------------------------------
@@ -25,16 +26,35 @@ Summary {
   Once-reported:             0 bytes (  0.00%) in       0 blocks (  0.00%)
   Twice-reported:            0 bytes (  0.00%) in       0 blocks (  0.00%)
 }
 
 #-----------------------------------------------------------------
 
 Invocation {
   $DMD = '--mode=test'
+  Function = AnalyzeHeap
+  Sample-below size = 1
+}
+
+#-----------------------------------------------------------------
+
+# no live heap blocks
+
+#-----------------------------------------------------------------
+
+Summary {
+  Total: 0 bytes in 0 blocks
+}
+
+#-----------------------------------------------------------------
+
+Invocation {
+  $DMD = '--mode=test'
+  Function = AnalyzeReports
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Twice-reported {
   1 block in heap block record 1 of 4
   80 bytes (79 requested / 1 slop)
@@ -284,16 +304,141 @@ Summary {
   Once-reported:        10,584 bytes ( 87.56%) in      13 blocks ( 43.33%)
   Twice-reported:          272 bytes (  2.25%) in       4 blocks ( 13.33%)
 }
 
 #-----------------------------------------------------------------
 
 Invocation {
   $DMD = '--mode=test'
+  Function = AnalyzeHeap
+  Sample-below size = 1
+}
+
+#-----------------------------------------------------------------
+
+Live {
+  1 block in heap block record 1 of 12
+  8,192 bytes (4,097 requested / 4,095 slop)
+  67.77% of the heap (67.77% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 2 of 12
+  1,024 bytes (1,023 requested / 1 slop)
+  8.47% of the heap (76.24% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  9 blocks in heap block record 3 of 12
+  1,008 bytes (900 requested / 108 slop)
+  8.34% of the heap (84.58% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  6 blocks in heap block record 4 of 12
+  528 bytes (528 requested / 0 slop)
+  4.37% of the heap (88.95% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  6 blocks in heap block record 5 of 12
+  528 bytes (528 requested / 0 slop)
+  4.37% of the heap (93.32% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 6 of 12
+  512 bytes (512 requested / 0 slop)
+  4.24% of the heap (97.55% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 7 of 12
+  80 bytes (79 requested / 1 slop)
+  0.66% of the heap (98.21% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 8 of 12
+  80 bytes (78 requested / 2 slop)
+  0.66% of the heap (98.87% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 9 of 12
+  80 bytes (77 requested / 3 slop)
+  0.66% of the heap (99.54% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 10 of 12
+  32 bytes (30 requested / 2 slop)
+  0.26% of the heap (99.80% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 11 of 12
+  16 bytes (10 requested / 6 slop)
+  0.13% of the heap (99.93% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 12 of 12
+  8 bytes (0 requested / 8 slop)
+  0.07% of the heap (100.00% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+#-----------------------------------------------------------------
+
+Summary {
+  Total: 12,088 bytes in 30 blocks
+}
+
+#-----------------------------------------------------------------
+
+Invocation {
+  $DMD = '--mode=test'
+  Function = AnalyzeReports
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Twice-reported {
   1 block in heap block record 1 of 2
   80 bytes (77 requested / 3 slop)
@@ -420,16 +565,114 @@ Summary {
   Once-reported:           688 bytes ( 24.23%) in       4 blocks ( 14.81%)
   Twice-reported:           88 bytes (  3.10%) in       2 blocks (  7.41%)
 }
 
 #-----------------------------------------------------------------
 
 Invocation {
   $DMD = '--mode=test'
+  Function = AnalyzeHeap
+  Sample-below size = 1
+}
+
+#-----------------------------------------------------------------
+
+Live {
+  9 blocks in heap block record 1 of 9
+  1,008 bytes (900 requested / 108 slop)
+  35.49% of the heap (35.49% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  6 blocks in heap block record 2 of 9
+  528 bytes (528 requested / 0 slop)
+  18.59% of the heap (54.08% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  6 blocks in heap block record 3 of 9
+  528 bytes (528 requested / 0 slop)
+  18.59% of the heap (72.68% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 4 of 9
+  512 bytes (512 requested / 0 slop)
+  18.03% of the heap (90.70% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 5 of 9
+  80 bytes (79 requested / 1 slop)
+  2.82% of the heap (93.52% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 6 of 9
+  80 bytes (78 requested / 2 slop)
+  2.82% of the heap (96.34% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 7 of 9
+  80 bytes (77 requested / 3 slop)
+  2.82% of the heap (99.15% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 8 of 9
+  16 bytes (10 requested / 6 slop)
+  0.56% of the heap (99.72% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 9 of 9
+  8 bytes (0 requested / 8 slop)
+  0.28% of the heap (100.00% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+#-----------------------------------------------------------------
+
+Summary {
+  Total: 2,840 bytes in 27 blocks
+}
+
+#-----------------------------------------------------------------
+
+Invocation {
+  $DMD = '--mode=test'
+  Function = AnalyzeReports
   Sample-below size = 128
 }
 
 #-----------------------------------------------------------------
 
 # no twice-reported heap blocks
 
 #-----------------------------------------------------------------
@@ -512,8 +755,87 @@ Unreported {
 
 Summary {
   Total:                ~1,424 bytes (100.00%) in     ~10 blocks (100.00%)
   Unreported:           ~1,424 bytes (100.00%) in     ~10 blocks (100.00%)
   Once-reported:            ~0 bytes (  0.00%) in      ~0 blocks (  0.00%)
   Twice-reported:           ~0 bytes (  0.00%) in      ~0 blocks (  0.00%)
 }
 
+#-----------------------------------------------------------------
+
+Invocation {
+  $DMD = '--mode=test'
+  Function = AnalyzeHeap
+  Sample-below size = 128
+}
+
+#-----------------------------------------------------------------
+
+Live {
+  ~4 blocks in heap block record 1 of 7
+  ~512 bytes (~512 requested / ~0 slop)
+  35.96% of the heap (35.96% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 2 of 7
+  256 bytes (256 requested / 0 slop)
+  17.98% of the heap (53.93% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 3 of 7
+  144 bytes (144 requested / 0 slop)
+  10.11% of the heap (64.04% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 4 of 7
+  128 bytes (128 requested / 0 slop)
+  8.99% of the heap (73.03% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  ~1 block in heap block record 5 of 7
+  ~128 bytes (~128 requested / ~0 slop)
+  8.99% of the heap (82.02% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  ~1 block in heap block record 6 of 7
+  ~128 bytes (~128 requested / ~0 slop)
+  8.99% of the heap (91.01% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  ~1 block in heap block record 7 of 7
+  ~128 bytes (~128 requested / ~0 slop)
+  8.99% of the heap (100.00% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+#-----------------------------------------------------------------
+
+Summary {
+  Total: ~1,424 bytes in ~10 blocks
+}
+
--- a/testing/mochitest/tests/SimpleTest/MemoryStats.js
+++ b/testing/mochitest/tests/SimpleTest/MemoryStats.js
@@ -88,16 +88,25 @@ MemoryStats.dump = function (logger,
         logger.info(testURL + " | MEMDUMP-START " + dumpfile);
         var md = MemoryStats._getService("@mozilla.org/memory-info-dumper;1",
                                          "nsIMemoryInfoDumper");
         md.dumpMemoryReportsToNamedFile(dumpfile, function () {
             logger.info("TEST-INFO | " + testURL + " | MEMDUMP-END");
         }, null, /* anonymize = */ false);
     }
 
+    // This is the old, deprecated function.
     if (dumpDMD && typeof(DMDReportAndDump) != undefined) {
+        var basename = "dmd-" + testNumber + "-deprecated.txt";
+        var dumpfile = MemoryStats.constructPathname(dumpOutputDirectory,
+                                                     basename);
+        logger.info(testURL + " | DMD-DUMP-deprecated " + dumpfile);
+        DMDReportAndDump(dumpfile);
+    }
+
+    if (dumpDMD && typeof(DMDAnalyzeReports) != undefined) {
         var basename = "dmd-" + testNumber + ".txt";
         var dumpfile = MemoryStats.constructPathname(dumpOutputDirectory,
                                                      basename);
         logger.info(testURL + " | DMD-DUMP " + dumpfile);
-        DMDReportAndDump(dumpfile);
+        DMDAnalyzeReports(dumpfile);
     }
 };
--- a/toolkit/components/aboutmemory/content/aboutMemory.js
+++ b/toolkit/components/aboutmemory/content/aboutMemory.js
@@ -48,17 +48,18 @@ XPCOMUtils.defineLazyGetter(this, "nsGzi
 
 let gMgr = Cc["@mozilla.org/memory-reporter-manager;1"]
              .getService(Ci.nsIMemoryReporterManager);
 
 const gUnnamedProcessStr = "Main Process";
 
 let gIsDiff = false;
 
-const DMDFile = "out.dmd";
+const gAnalyzeReportsFile = "reports.dmd";
+const gAnalyzeHeapFile    = "heap.dmd";
 
 //---------------------------------------------------------------------------
 
 // Forward slashes in URLs in paths are represented with backslashes to avoid
 // being mistaken for path separators.  Paths/names where this hasn't been
 // undone are prefixed with "unsafe"; the rest are prefixed with "safe".
 function flipBackslashes(aUnsafeStr)
 {
@@ -293,17 +294,20 @@ function onLoad()
 
   const GCAndCCLogDesc = "Save garbage collection log and concise cycle " +
                          "collection log.\n" +
                          "WARNING: These logs may be large (>1GB).";
   const GCAndCCAllLogDesc = "Save garbage collection log and verbose cycle " +
                             "collection log.\n" +
                             "WARNING: These logs may be large (>1GB).";
 
-  const DMDEnabledDesc = "Run DMD analysis and save it to '" + DMDFile + "'.\n";
+  const AnalyzeReportsDesc = "Analyze memory reports coverage and save the "
+                             "output to '" + gAnalyzeReportsFile + "'.\n";
+  const AnalyzeHeapDesc = "Analyze heap usage and save the output to '" +
+                          gAnalyzeHeapFile + "'.\n";
   const DMDDisabledDesc = "DMD is not running. Please re-start with $DMD and " +
                           "the other relevant environment variables set " +
                           "appropriately.";
 
   let ops = appendElement(header, "div", "");
 
   let row1 = appendElement(ops, "div", "opsRow");
 
@@ -354,22 +358,30 @@ function onLoad()
 
   // Three cases here:
   // - DMD is disabled (i.e. not built): don't show the button.
   // - DMD is enabled but is not running: show the button, but disable it.
   // - DMD is enabled and is running: show the button and enable it.
   if (gMgr.isDMDEnabled) {
     let row5 = appendElement(ops, "div", "opsRow");
 
-    appendElementWithText(row5, "div", "opsRowLabel", "Save DMD output");
-    let enableButton = gMgr.isDMDRunning;
-    let dmdButton =
-      appendButton(row5, enableButton ? DMDEnabledDesc : DMDDisabledDesc,
-                   doDMD, "Save", "dmdButton");
-    dmdButton.disabled = !enableButton;
+    appendElementWithText(row5, "div", "opsRowLabel", "DMD operations");
+    let enableButtons = gMgr.isDMDRunning;
+
+    let analyzeReportsButton =
+      appendButton(row5,
+                   enableButtons ? AnalyzeReportsDesc : DMDDisabledDesc,
+                   doAnalyzeReports, "Analyze reports");
+    analyzeReportsButton.disabled = !enableButtons;
+
+    let analyzeHeapButton =
+      appendButton(row5,
+                   enableButtons ? AnalyzeHeapDesc : DMDDisabledDesc,
+                   doAnalyzeHeap, "Analyze heap");
+    analyzeHeapButton.disabled = !enableButtons;
   }
 
   // Generate the main div, where content ("section" divs) will go.  It's
   // hidden at first.
 
   gMain = appendElement(document.body, 'div', '');
   gMain.id = 'mainDiv';
 
@@ -440,22 +452,34 @@ function saveGCLogAndConciseCCLog()
   dumpGCLogAndCCLog(false);
 }
 
 function saveGCLogAndVerboseCCLog()
 {
   dumpGCLogAndCCLog(true);
 }
 
-function doDMD()
+function doAnalyzeReports()
 {
   updateMainAndFooter('Saving DMD output...', HIDE_FOOTER);
   try {
-    let x = DMDReportAndDump('out.dmd');
-    updateMainAndFooter('Saved DMD output to ' + DMDFile, HIDE_FOOTER);
+    let x = DMDAnalyzeReports(gAnalyzeReportsFile);
+    updateMainAndFooter('Saved DMD output to ' + gAnalyzeReportsFile,
+                        HIDE_FOOTER);
+  } catch (ex) {
+    updateMainAndFooter(ex.toString(), HIDE_FOOTER);
+  }
+}
+
+function doAnalyzeHeap()
+{
+  updateMainAndFooter('Saving DMD output...', HIDE_FOOTER);
+  try {
+    let x = DMDAnalyzeHeap(gAnalyzeHeapFile);
+    updateMainAndFooter('Saved DMD output to ' + gAnalyzeHeapFile, HIDE_FOOTER);
   } catch (ex) {
     updateMainAndFooter(ex.toString(), HIDE_FOOTER);
   }
 }
 
 function dumpGCLogAndCCLog(aVerbose)
 {
   let dumper = Cc["@mozilla.org/memory-info-dumper;1"]
--- a/xpcom/base/nsMemoryInfoDumper.cpp
+++ b/xpcom/base/nsMemoryInfoDumper.cpp
@@ -716,20 +716,20 @@ nsresult
 nsMemoryInfoDumper::DumpDMDToFile(FILE* aFile)
 {
   nsRefPtr<nsGZFileWriter> dmdWriter = new nsGZFileWriter();
   nsresult rv = dmdWriter->InitANSIFileDesc(aFile);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  // Dump DMD output to the file.
+  // Dump DMD's memory reports analysis to the file.
   DMDWriteState state(dmdWriter);
   dmd::Writer w(DMDWrite, &state);
-  dmd::Dump(w);
+  dmd::AnalyzeReports(w);
 
   rv = dmdWriter->Finish();
   NS_WARN_IF(NS_FAILED(rv));
   return rv;
 }
 
 nsresult
 nsMemoryInfoDumper::DumpDMD(const nsAString& aIdentifier)