Bug 1094552 (part 5) - DMD: choose the profiling mode at start-up. r=mccr8.
authorNicholas Nethercote <nnethercote@mozilla.com>
Mon, 08 Dec 2014 14:45:14 -0800
changeset 218944 c5229ba7f507cbde260d189782eda1120edb4353
parent 218943 3b926e37e776c0d015cebf461c4319193ef4cf89
child 218945 9fae0441be6665f1bc2fcca42adca87dfd990616
push id52688
push usernnethercote@mozilla.com
push dateWed, 10 Dec 2014 01:26:49 +0000
treeherdermozilla-inbound@9fae0441be66 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmccr8
bugs1094552
milestone37.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 1094552 (part 5) - DMD: choose the profiling mode at start-up. r=mccr8. This patch moves profiling mode selection from post-processing (in dmd.py) to DMD start-up. This will make it easier to add new kinds of profiling, such as cumulative heap profiling. Specifically: - There's a new --mode option. |LiveWithReports| is the default, as it is currently. - dmd.py's --ignore-reports option is gone. - There's a new |mode| field in the JSON output. - Reports-related operations are now no-ops if DMD isn't in LiveWithReports mode. - Diffs are only allowed for output files that have the same mode. - A new function ResetEverything() replaces the SetSampleBelowSize() and ClearBlocks(), which were used by the test to change DMD options. - The tests in SmokeDMD.cpp are split up so they can be run multiple times, in different modes. The exact combinations of tests and modes has been changed a bit.
memory/replace/dmd/DMD.cpp
memory/replace/dmd/DMD.h
memory/replace/dmd/dmd.py
memory/replace/dmd/test/SmokeDMD.cpp
memory/replace/dmd/test/full-empty-dark-matter-expected.txt
memory/replace/dmd/test/full-empty-live-expected.txt
memory/replace/dmd/test/full-heap-empty-expected.txt
memory/replace/dmd/test/full-heap-sampled-expected.txt
memory/replace/dmd/test/full-heap-unsampled1-expected.txt
memory/replace/dmd/test/full-heap-unsampled2-expected.txt
memory/replace/dmd/test/full-reports-empty-expected.txt
memory/replace/dmd/test/full-reports-sampled-expected.txt
memory/replace/dmd/test/full-reports-unsampled1-expected.txt
memory/replace/dmd/test/full-reports-unsampled2-expected.txt
memory/replace/dmd/test/full-sampled-live-expected.txt
memory/replace/dmd/test/full-unsampled1-dark-matter-expected.txt
memory/replace/dmd/test/full-unsampled1-live-expected.txt
memory/replace/dmd/test/full-unsampled2-dark-matter-expected.txt
memory/replace/dmd/test/script-diff-basic-expected.txt
memory/replace/dmd/test/script-diff-dark-matter-expected.txt
memory/replace/dmd/test/script-diff-dark-matter1.json
memory/replace/dmd/test/script-diff-dark-matter2.json
memory/replace/dmd/test/script-diff-live-expected.txt
memory/replace/dmd/test/script-diff-live1.json
memory/replace/dmd/test/script-diff-live2.json
memory/replace/dmd/test/script-diff-options-expected.txt
memory/replace/dmd/test/script-diff1.json
memory/replace/dmd/test/script-diff2.json
memory/replace/dmd/test/script-ignore-alloc-fns-expected.txt
memory/replace/dmd/test/script-ignore-alloc-fns.json
memory/replace/dmd/test/script-max-frames-1-expected.txt
memory/replace/dmd/test/script-max-frames-3-expected.txt
memory/replace/dmd/test/script-max-frames-8-expected.txt
memory/replace/dmd/test/script-max-frames.json
memory/replace/dmd/test/script-sort-by-req-expected.txt
memory/replace/dmd/test/script-sort-by-slop-expected.txt
memory/replace/dmd/test/script-sort-by-usable-expected.txt
memory/replace/dmd/test/script-sort-by.json.gz
memory/replace/dmd/test/test_dmd.js
memory/replace/dmd/test/xpcshell.ini
python/mozbuild/mozbuild/mach_commands.py
--- a/memory/replace/dmd/DMD.cpp
+++ b/memory/replace/dmd/DMD.cpp
@@ -300,38 +300,74 @@ class Options
     const T mDefault;
     const T mMax;
     T       mActual;
     NumOption(T aDefault, T aMax)
       : mDefault(aDefault), mMax(aMax), mActual(aDefault)
     {}
   };
 
+  // DMD has several modes. These modes affect what data is recorded and
+  // written to the output file, and the written data affects the
+  // post-processing that dmd.py can do.
+  //
+  // Users specify the mode as soon as DMD starts. This leads to minimal memory
+  // usage and log file size. It has the disadvantage that is inflexible -- if
+  // you want to change modes you have to re-run DMD. But in practice changing
+  // modes seems to be rare, so it's not much of a problem.
+  //
+  // An alternative possibility would be to always record and output *all* the
+  // information needed for all modes. This would let you choose the mode when
+  // running dmd.py, and so you could do multiple kinds of profiling on a
+  // single DMD run. But if you are only interested in one of the simpler
+  // modes, you'd pay the price of (a) increased memory usage and (b) *very*
+  // large log files.
+  //
+  // Finally, another alternative possibility would be to do mode selection
+  // partly at DMD startup or recording, and then partly in dmd.py. This would
+  // give some extra flexibility at moderate memory and file size cost. But
+  // certain mode pairs wouldn't work, which would be confusing.
+  //
+  enum Mode
+  {
+    // For each live block, this mode outputs: size (usable and slop),
+    // allocation stack, and whether it's sampled. This mode is good for live
+    // heap profiling.
+    Live,
+
+    // Like "Live", but for each live block it also outputs: zero or more
+    // report stacks. This mode is good for identifying where memory reporters
+    // should be added. This is the default mode.
+    DarkMatter
+  };
+
   char* mDMDEnvVar;   // a saved copy, for later printing
 
-  NumOption<size_t>   mSampleBelowSize;
+  Mode mMode;
+  NumOption<size_t> mSampleBelowSize;
   NumOption<uint32_t> mMaxFrames;
   bool mShowDumpStats;
 
   void BadArg(const char* aArg);
   static const char* ValueIfMatch(const char* aArg, const char* aOptionName);
   static bool GetLong(const char* aArg, const char* aOptionName,
                       long aMin, long aMax, long* aValue);
   static bool GetBool(const char* aArg, const char* aOptionName, bool* aValue);
 
 public:
   explicit Options(const char* aDMDEnvVar);
 
+  bool IsLiveMode()       const { return mMode == Live; }
+  bool IsDarkMatterMode() const { return mMode == DarkMatter; }
+
   const char* DMDEnvVar() const { return mDMDEnvVar; }
 
   size_t SampleBelowSize() const { return mSampleBelowSize.mActual; }
   size_t MaxFrames()       const { return mMaxFrames.mActual; }
   size_t ShowDumpStats()   const { return mShowDumpStats; }
-
-  void SetSampleBelowSize(size_t aSize) { mSampleBelowSize.mActual = aSize; }
 };
 
 static Options *gOptions;
 
 //---------------------------------------------------------------------------
 // The global lock
 //---------------------------------------------------------------------------
 
@@ -396,18 +432,21 @@ public:
     MOZ_ASSERT(mIsLocked);
     mIsLocked = false;
     MutexBase::Unlock();
   }
 
   bool IsLocked() { return mIsLocked; }
 };
 
-// This lock must be held while manipulating global state, such as
-// gStackTraceTable, gLiveBlockTable, etc.
+// This lock must be held while manipulating global state such as
+// gStackTraceTable, gLiveBlockTable, etc. Note that gOptions is *not*
+// protected by this lock because it is only written to by Options(), which is
+// only invoked at start-up and in ResetEverything(), which is only used by
+// SmokeDMD.cpp.
 static Mutex* gStateLock = nullptr;
 
 class AutoLockState
 {
   DISALLOW_COPY_AND_ASSIGN(AutoLockState);
 
 public:
   AutoLockState()  { gStateLock->Lock(); }
@@ -775,37 +814,42 @@ public:
     mUint |= (tag & kTagMask);
   }
 
   T Ptr() const { return reinterpret_cast<T>(mUint & kPtrMask); }
 
   bool Tag() const { return bool(mUint & kTagMask); }
 };
 
-// A live heap block.
+// A live heap block. Stores both basic data and data about reports, if we're
+// in DarkMatter mode.
 class LiveBlock
 {
   const void*  mPtr;
   const size_t mReqSize;    // size requested
 
   // Ptr: |mAllocStackTrace| - stack trace where this block was allocated.
   // Tag bit 0: |mIsSampled| - was this block sampled? (if so, slop == 0).
+  //
+  // Only used in DarkMatter mode.
   TaggedPtr<const StackTrace* const>
     mAllocStackTrace_mIsSampled;
 
   // 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
   //   Analyze(). Only relevant if |mReportStackTrace| is non-nullptr.
   //
   // |mPtr| is used as the key in LiveBlockTable, so it's ok for this member
   // to be |mutable|.
+  //
+  // Only used in DarkMatter mode.
   mutable TaggedPtr<const StackTrace*> mReportStackTrace_mReportedOnAlloc[2];
 
 public:
   LiveBlock(const void* aPtr, size_t aReqSize,
             const StackTrace* aAllocStackTrace, bool aIsSampled)
     : mPtr(aPtr),
       mReqSize(aReqSize),
       mAllocStackTrace_mIsSampled(aAllocStackTrace, aIsSampled),
@@ -836,71 +880,80 @@ public:
 
   const StackTrace* AllocStackTrace() const
   {
     return mAllocStackTrace_mIsSampled.Ptr();
   }
 
   const StackTrace* ReportStackTrace1() const
   {
+    MOZ_ASSERT(gOptions->IsDarkMatterMode());
     return mReportStackTrace_mReportedOnAlloc[0].Ptr();
   }
 
   const StackTrace* ReportStackTrace2() const
   {
+    MOZ_ASSERT(gOptions->IsDarkMatterMode());
     return mReportStackTrace_mReportedOnAlloc[1].Ptr();
   }
 
   bool ReportedOnAlloc1() const
   {
+    MOZ_ASSERT(gOptions->IsDarkMatterMode());
     return mReportStackTrace_mReportedOnAlloc[0].Tag();
   }
 
   bool ReportedOnAlloc2() const
   {
+    MOZ_ASSERT(gOptions->IsDarkMatterMode());
     return mReportStackTrace_mReportedOnAlloc[1].Tag();
   }
 
   void AddStackTracesToTable(StackTraceSet& aStackTraces) const
   {
     aStackTraces.put(AllocStackTrace());  // never null
-    const StackTrace* st;
-    if ((st = ReportStackTrace1())) {     // may be null
-      aStackTraces.put(st);
-    }
-    if ((st = ReportStackTrace2())) {     // may be null
-      aStackTraces.put(st);
+    if (gOptions->IsDarkMatterMode()) {
+      const StackTrace* st;
+      if ((st = ReportStackTrace1())) {     // may be null
+        aStackTraces.put(st);
+      }
+      if ((st = ReportStackTrace2())) {     // may be null
+        aStackTraces.put(st);
+      }
     }
   }
 
   uint32_t NumReports() const
   {
+    MOZ_ASSERT(gOptions->IsDarkMatterMode());
     if (ReportStackTrace2()) {
       MOZ_ASSERT(ReportStackTrace1());
       return 2;
     }
     if (ReportStackTrace1()) {
       return 1;
     }
     return 0;
   }
 
   // This is |const| thanks to the |mutable| fields above.
   void Report(Thread* aT, bool aReportedOnAlloc) const
   {
+    MOZ_ASSERT(gOptions->IsDarkMatterMode());
     // We don't bother recording reports after the 2nd one.
     uint32_t numReports = NumReports();
     if (numReports < 2) {
       mReportStackTrace_mReportedOnAlloc[numReports].Set(StackTrace::Get(aT),
                                                          aReportedOnAlloc);
     }
   }
 
   void UnreportIfNotReportedOnAlloc() const
   {
+    MOZ_ASSERT(gOptions->IsDarkMatterMode());
     if (!ReportedOnAlloc1() && !ReportedOnAlloc2()) {
       mReportStackTrace_mReportedOnAlloc[0].Set(nullptr, 0);
       mReportStackTrace_mReportedOnAlloc[1].Set(nullptr, 0);
 
     } else if (!ReportedOnAlloc1() && ReportedOnAlloc2()) {
       // Shift the 2nd report down to the 1st one.
       mReportStackTrace_mReportedOnAlloc[0] =
         mReportStackTrace_mReportedOnAlloc[1];
@@ -1231,20 +1284,21 @@ Options::GetBool(const char* aArg, const
 // - Why that size?  Because it's *much* faster but only moderately less precise
 //   than a size of 1.
 // - Why prime?  Because it makes our sampling more random.  If we used a size
 //   of 4096, for example, then our alloc counter would only take on even
 //   values, because jemalloc always rounds up requests sizes.  In contrast, a
 //   prime size will explore all possible values of the alloc counter.
 //
 Options::Options(const char* aDMDEnvVar)
-  : mDMDEnvVar(InfallibleAllocPolicy::strdup_(aDMDEnvVar)),
-    mSampleBelowSize(4093, 100 * 100 * 1000),
-    mMaxFrames(StackTrace::MaxFrames, StackTrace::MaxFrames),
-    mShowDumpStats(false)
+  : mDMDEnvVar(InfallibleAllocPolicy::strdup_(aDMDEnvVar))
+  , mMode(DarkMatter)
+  , mSampleBelowSize(4093, 100 * 100 * 1000)
+  , mMaxFrames(StackTrace::MaxFrames, StackTrace::MaxFrames)
+  , mShowDumpStats(false)
 {
   char* e = mDMDEnvVar;
   if (strcmp(e, "1") != 0) {
     bool isEnd = false;
     while (!isEnd) {
       // Consume leading whitespace.
       while (isspace(*e)) {
         e++;
@@ -1260,17 +1314,23 @@ Options::Options(const char* aDMDEnvVar)
       }
       char replacedChar = *e;
       isEnd = replacedChar == '\0';
       *e = '\0';
 
       // Handle arg
       long myLong;
       bool myBool;
-      if (GetLong(arg, "--sample-below", 1, mSampleBelowSize.mMax, &myLong)) {
+      if (strcmp(arg, "--mode=live") == 0) {
+        mMode = Options::Live;
+      } else if (strcmp(arg, "--mode=dark-matter") == 0) {
+        mMode = Options::DarkMatter;
+
+      } else if (GetLong(arg, "--sample-below", 1, mSampleBelowSize.mMax,
+                 &myLong)) {
         mSampleBelowSize.mActual = myLong;
 
       } else if (GetLong(arg, "--max-frames", 1, mMaxFrames.mMax, &myLong)) {
         mMaxFrames.mActual = myLong;
 
       } else if (GetBool(arg, "--show-dump-stats", &myBool)) {
         mShowDumpStats = myBool;
 
@@ -1293,16 +1353,18 @@ Options::BadArg(const char* aArg)
 {
   StatusMsg("\n");
   StatusMsg("Bad entry in the $DMD environment variable: '%s'.\n", aArg);
   StatusMsg("\n");
   StatusMsg("$DMD must be a whitespace-separated list of |--option=val|\n");
   StatusMsg("entries.\n");
   StatusMsg("\n");
   StatusMsg("The following options are allowed;  defaults are shown in [].\n");
+  StatusMsg("  --mode=<mode>                Profiling mode [dark-matter]\n");
+  StatusMsg("      where <mode> is one of: live, dark-matter\n");
   StatusMsg("  --sample-below=<1..%d> Sample blocks smaller than this [%d]\n",
             int(mSampleBelowSize.mMax),
             int(mSampleBelowSize.mDefault));
   StatusMsg("                               (prime numbers are recommended)\n");
   StatusMsg("  --max-frames=<1..%d>         Max. depth of stack traces [%d]\n",
             int(mMaxFrames.mMax),
             int(mMaxFrames.mDefault));
   StatusMsg("  --show-dump-stats=<yes|no>   Show stats about dumps? [no]\n");
@@ -1369,23 +1431,23 @@ Init(const malloc_table_t* aMallocTable)
     gLiveBlockTable = InfallibleAllocPolicy::new_<LiveBlockTable>();
     gLiveBlockTable->init(8192);
   }
 
   gIsDMDInitialized = true;
 }
 
 //---------------------------------------------------------------------------
-// DMD reporting and unreporting
+// Block reporting and unreporting
 //---------------------------------------------------------------------------
 
 static void
 ReportHelper(const void* aPtr, bool aReportedOnAlloc)
 {
-  if (!aPtr) {
+  if (!gOptions->IsDarkMatterMode() || !aPtr) {
     return;
   }
 
   Thread* t = Thread::Fetch();
 
   AutoBlockIntercepts block(t);
   AutoLockState lock;
 
@@ -1411,22 +1473,19 @@ DMDFuncs::ReportOnAlloc(const void* aPtr
   ReportHelper(aPtr, /* onAlloc */ true);
 }
 
 //---------------------------------------------------------------------------
 // DMD output
 //---------------------------------------------------------------------------
 
 // The version number of the output format. Increment this if you make
-// backwards-incompatible changes to the format.
-//
-// Version history:
-// - 1: The original format (bug 1044709).
-//
-static const int kOutputVersionNumber = 1;
+// backwards-incompatible changes to the format. See DMD.h for the version
+// history.
+static const int kOutputVersionNumber = 2;
 
 // Note that, unlike most SizeOf* functions, this function does not take a
 // |mozilla::MallocSizeOf| 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!
 //
 // SizeOfInternal should be called while you're holding the state lock and
 // while intercepts are blocked; SizeOf acquires the lock and blocks
@@ -1467,33 +1526,37 @@ DMDFuncs::SizeOf(Sizes* aSizes)
   AutoBlockIntercepts block(Thread::Fetch());
   AutoLockState lock;
   SizeOfInternal(aSizes);
 }
 
 void
 DMDFuncs::ClearReports()
 {
+  if (!gOptions->IsDarkMatterMode()) {
+    return;
+  }
+
   AutoLockState lock;
 
   // 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 (auto r = gLiveBlockTable->all(); !r.empty(); r.popFront()) {
     r.front().UnreportIfNotReportedOnAlloc();
   }
 }
 
 class ToIdStringConverter MOZ_FINAL
 {
 public:
   ToIdStringConverter() : mNextId(0) { mIdMap.init(512); }
 
-  // Converts a pointer to a unique ID. Reuses the existing ID for the pointer if
-  // it's been seen before.
+  // Converts a pointer to a unique ID. Reuses the existing ID for the pointer
+  // if it's been seen before.
   const char* ToIdString(const void* aPtr)
   {
     uint32_t id;
     PointerIdMap::AddPtr p = mIdMap.lookupForAdd(aPtr);
     if (!p) {
       id = mNextId++;
       (void)mIdMap.add(p, aPtr, id);
     } else {
@@ -1571,16 +1634,26 @@ AnalyzeImpl(UniquePtr<JSONWriteFunc> aWr
   JSONWriter writer(Move(aWriter));
   writer.Start();
   {
     writer.IntProperty("version", kOutputVersionNumber);
 
     writer.StartObjectProperty("invocation");
     {
       writer.StringProperty("dmdEnvVar", gOptions->DMDEnvVar());
+      const char* mode;
+      if (gOptions->IsLiveMode()) {
+        mode = "live";
+      } else if (gOptions->IsDarkMatterMode()) {
+        mode = "dark-matter";
+      } else {
+        MOZ_ASSERT(false);
+        mode = "(unknown DMD mode)";
+      }
+      writer.StringProperty("mode", mode);
       writer.IntProperty("sampleBelowSize", gOptions->SampleBelowSize());
     }
     writer.EndObject();
 
     StatusMsg("  Constructing the heap block list...\n");
 
     ToIdStringConverter isc;
 
@@ -1594,17 +1667,18 @@ AnalyzeImpl(UniquePtr<JSONWriteFunc> aWr
         {
           if (!b.IsSampled()) {
             writer.IntProperty("req", b.ReqSize());
             if (b.SlopSize() > 0) {
               writer.IntProperty("slop", b.SlopSize());
             }
           }
           writer.StringProperty("alloc", isc.ToIdString(b.AllocStackTrace()));
-          if (b.NumReports() > 0) {
+          if (gOptions->IsDarkMatterMode() && b.NumReports() > 0) {
+            MOZ_ASSERT(gOptions->IsDarkMatterMode());
             writer.StartArrayProperty("reps");
             {
               if (b.ReportStackTrace1()) {
                 writer.StringElement(isc.ToIdString(b.ReportStackTrace1()));
               }
               if (b.ReportStackTrace2()) {
                 writer.StringElement(isc.ToIdString(b.ReportStackTrace2()));
               }
@@ -1730,22 +1804,23 @@ DMDFuncs::Analyze(UniquePtr<JSONWriteFun
   ClearReports();
 }
 
 //---------------------------------------------------------------------------
 // Testing
 //---------------------------------------------------------------------------
 
 void
-DMDFuncs::SetSampleBelowSize(size_t aSize)
+DMDFuncs::ResetEverything(const char* aOptions)
 {
-  gOptions->SetSampleBelowSize(aSize);
-}
+  AutoLockState lock;
 
-void
-DMDFuncs::ClearBlocks()
-{
+  // Reset options.
+  InfallibleAllocPolicy::delete_(gOptions);
+  gOptions = InfallibleAllocPolicy::new_<Options>(aOptions);
+
+  // Clear all existing blocks.
   gLiveBlockTable->clear();
   gSmallBlockActualSizeCounter = 0;
 }
 
 }   // namespace dmd
 }   // namespace mozilla
--- a/memory/replace/dmd/DMD.h
+++ b/memory/replace/dmd/DMD.h
@@ -46,19 +46,17 @@ struct DMDFuncs
   virtual void ClearReports();
 
   virtual void Analyze(UniquePtr<JSONWriteFunc>);
 
   virtual void SizeOf(Sizes*);
 
   virtual void StatusMsg(const char*, va_list);
 
-  virtual void SetSampleBelowSize(size_t);
-
-  virtual void ClearBlocks();
+  virtual void ResetEverything(const char*);
 
 #ifndef REPLACE_MALLOC_IMPL
   // We deliberately don't use ReplaceMalloc::GetDMDFuncs here, because if we
   // did, the following would happen.
   // - The code footprint of each call to Get() larger as GetDMDFuncs ends
   //   up inlined.
   // - When no replace-malloc library is loaded, the number of instructions
   //   executed is equivalent, but don't necessarily fit in the same cache
@@ -141,23 +139,31 @@ ClearReports()
 // The following sample output contains comments that explain the format and
 // design choices. The output files can be quite large, so a number of
 // decisions were made to minimize size, such as using short property names and
 // omitting properties whenever possible.
 //
 // {
 //   // The version number of the format, which will be incremented each time
 //   // backwards-incompatible changes are made. A mandatory integer.
-//   "version": 1,
+//   //
+//   // Version history:
+//   // - 1: The original format. Implemented in bug 1044709.
+//   // - 2: Added the "mode" field under "invocation". Added in bug 1094552.
+//   "version": 2,
 //
 //   // Information about how DMD was invoked. A mandatory object.
 //   "invocation": {
 //     // The contents of the $DMD environment variable. A mandatory string.
 //     "dmdEnvVar": "1",
 //
+//     // The profiling mode. A mandatory string taking one of the following
+//     // values: "live", "dark-matter".
+//     "mode": "dark-matter",
+//
 //     // The value of the --sample-below-size option. A mandatory integer.
 //     "sampleBelowSize": 4093
 //   },
 //
 //   // Details of all analyzed heap blocks. A mandatory array.
 //   "blockList": [
 //     // An example of a non-sampled heap block.
 //     {
@@ -177,18 +183,19 @@ ClearReports()
 //       "alloc": "A"
 //     },
 //
 //     // An example of a sampled heap block.
 //     {
 //       "alloc": "B",
 //
 //       // One or more stack traces at which this heap block was reported by a
-//       // memory reporter. An optional array. The elements are strings that
-//       // index into the "traceTable" object.
+//       // memory reporter. An optional array that will only be present in
+//       // "dark-matter" mode. The elements are strings that index into
+//       // the "traceTable" object.
 //       "reps": ["C"]
 //     }
 //   ],
 //
 //   // The stack traces referenced by elements of the "blockList" array. This
 //   // could be an array, but making it an object makes it easier to see
 //   // which stacks correspond to which references in the "blockList" array.
 //   "traceTable": {
@@ -216,21 +223,23 @@ ClearReports()
 //     // that output stack traces in a conventional non-shared format.
 //     "D": "#00: foo (Foo.cpp:123)",
 //     "E": "#00: bar (Bar.cpp:234)",
 //     "F": "#00: baz (Baz.cpp:345)",
 //     "G": "#00: quux (Quux.cpp:456)",
 //     "H": "#00: quuux (Quux.cpp:567)"
 //   }
 // }
+//
 // Implementation note: normally, this wouldn't be templated, but in that case,
 // the function is compiled, which makes the destructor for the UniquePtr fire
 // up, and that needs JSONWriteFunc to be fully defined. That, in turn,
 // requires to include JSONWriter.h, which includes double-conversion.h, which
 // ends up breaking various things built with -Werror for various reasons.
+//
 template <typename JSONWriteFunc>
 inline void
 Analyze(UniquePtr<JSONWriteFunc> aWriteFunc)
 {
   DMDFuncs* funcs = DMDFuncs::Get();
   if (funcs) {
     funcs->Analyze(Move(aWriteFunc));
   }
@@ -262,33 +271,25 @@ StatusMsg(const char* aFmt, ...)
 
 // Indicates whether or not DMD is running.
 inline bool
 IsRunning()
 {
   return !!DMDFuncs::Get();
 }
 
-// Sets the sample-below size. Only used for testing purposes.
+// Resets all DMD options and then sets new ones according to those specified
+// in |aOptions|. Also clears all recorded data about allocations. Only used
+// for testing purposes.
 inline void
-SetSampleBelowSize(size_t aSize)
+ResetEverything(const char* aOptions)
 {
   DMDFuncs* funcs = DMDFuncs::Get();
   if (funcs) {
-    funcs->SetSampleBelowSize(aSize);
-  }
-}
-
-// Clears all records of live allocations. Only used for testing purposes.
-inline void
-ClearBlocks()
-{
-  DMDFuncs* funcs = DMDFuncs::Get();
-  if (funcs) {
-    funcs->ClearBlocks();
+    funcs->ResetEverything(aOptions);
   }
 }
 #endif
 
 } // namespace dmd
 } // namespace mozilla
 
 #endif /* DMD_h___ */
--- a/memory/replace/dmd/dmd.py
+++ b/memory/replace/dmd/dmd.py
@@ -15,17 +15,17 @@ import json
 import os
 import platform
 import re
 import shutil
 import sys
 import tempfile
 
 # The DMD output version this script handles.
-outputVersion = 1
+outputVersion = 2
 
 # If --ignore-alloc-fns is specified, stack frames containing functions that
 # match these strings will be removed from the *start* of stack traces. (Once
 # we hit a non-matching frame, any subsequent frames won't be removed even if
 # they do match.)
 allocatorFns = [
     'replace_malloc',
     'replace_calloc',
@@ -179,20 +179,16 @@ variable is used to find breakpad symbol
     p = argparse.ArgumentParser(description=description)
 
     p.add_argument('-o', '--output', type=argparse.FileType('w'),
                    help='output file; stdout if unspecified')
 
     p.add_argument('-f', '--max-frames', type=range_1_24,
                    help='maximum number of frames to consider in each trace')
 
-    p.add_argument('-r', '--ignore-reports', action='store_true',
-                   help='ignore memory reports data; useful if you just ' +
-                        'want basic heap profiling')
-
     p.add_argument('-s', '--sort-by', choices=sortByChoices.keys(),
                    default=sortByChoices.keys()[0],
                    help='sort the records by a particular metric')
 
     p.add_argument('-a', '--ignore-alloc-fns', action='store_true',
                    help='ignore allocation functions at the start of traces')
 
     p.add_argument('--no-fix-stacks', action='store_true',
@@ -273,21 +269,25 @@ def getDigestFromFile(args, inputFile):
         j = json.load(f)
 
     if j['version'] != outputVersion:
         raise Exception("'version' property isn't '{:d}'".format(outputVersion))
 
     # Extract the main parts of the JSON object.
     invocation = j['invocation']
     dmdEnvVar = invocation['dmdEnvVar']
+    mode = invocation['mode']
     sampleBelowSize = invocation['sampleBelowSize']
     blockList = j['blockList']
     traceTable = j['traceTable']
     frameTable = j['frameTable']
 
+    if not mode in ['live', 'dark-matter']:
+        raise Exception("bad 'mode' property: '{:s}'".format(mode))
+
     heapIsSampled = sampleBelowSize > 1     # is sampling present?
 
     # Remove allocation functions at the start of traces.
     if args.ignore_alloc_fns:
         # Build a regexp that matches every function in allocatorFns.
         escapedAllocatorFns = map(re.escape, allocatorFns)
         fn_re = re.compile('|'.join(escapedAllocatorFns))
 
@@ -331,19 +331,19 @@ def getDigestFromFile(args, inputFile):
         desc = []
         for n, frameKey in enumerate(traceTable[traceKey], start=1):
             desc.append(fmt.format(n, frameTable[frameKey][3:]))
         return desc
 
     # Aggregate blocks into records. All sufficiently similar blocks go into a
     # single record.
 
-    if args.ignore_reports:
+    if mode == 'live':
         liveRecords = collections.defaultdict(Record)
-    else:
+    elif mode == 'dark-matter':
         unreportedRecords    = collections.defaultdict(Record)
         onceReportedRecords  = collections.defaultdict(Record)
         twiceReportedRecords = collections.defaultdict(Record)
 
     heapUsableSize = 0
     heapBlocks = 0
 
     for block in blockList:
@@ -364,20 +364,20 @@ def getDigestFromFile(args, inputFile):
         # and we trim the final frame of each they should be considered
         # equivalent because the untrimmed frame descriptions (D1 and D2)
         # match.
         def makeRecordKeyPart(traceKey):
             return str(map(lambda frameKey: frameTable[frameKey],
                            traceTable[traceKey]))
 
         allocatedAtTraceKey = block['alloc']
-        if args.ignore_reports:
+        if mode == 'live':
             recordKey = makeRecordKeyPart(allocatedAtTraceKey)
             records = liveRecords
-        else:
+        elif mode == 'dark-matter':
             recordKey = makeRecordKeyPart(allocatedAtTraceKey)
             if 'reps' in block:
                 reportedAtTraceKeys = block['reps']
                 for reportedAtTraceKey in reportedAtTraceKeys:
                     recordKey += makeRecordKeyPart(reportedAtTraceKey)
                 if len(reportedAtTraceKeys) == 1:
                     records = onceReportedRecords
                 else:
@@ -409,34 +409,35 @@ def getDigestFromFile(args, inputFile):
         record.slopSize   += slopSize
         record.usableSize += usableSize
         record.isSampled   = record.isSampled or isSampled
         if record.allocatedAtDesc == None:
             record.allocatedAtDesc = \
                 buildTraceDescription(traceTable, frameTable,
                                       allocatedAtTraceKey)
 
-        if args.ignore_reports:
+        if mode == 'live':
             pass
-        else:
+        elif mode == 'dark-matter':
             if 'reps' in block and record.reportedAtDescs == []:
                 f = lambda k: buildTraceDescription(traceTable, frameTable, k)
                 record.reportedAtDescs = map(f, reportedAtTraceKeys)
         record.usableSizes[(usableSize, isSampled)] += 1
 
     # All the processed data for a single DMD file is called a "digest".
     digest = {}
     digest['dmdEnvVar'] = dmdEnvVar
+    digest['mode'] = mode
     digest['sampleBelowSize'] = sampleBelowSize
     digest['heapUsableSize'] = heapUsableSize
     digest['heapBlocks'] = heapBlocks
     digest['heapIsSampled'] = heapIsSampled
-    if args.ignore_reports:
+    if mode == 'live':
         digest['liveRecords'] = liveRecords
-    else:
+    elif mode == 'dark-matter':
         digest['unreportedRecords'] = unreportedRecords
         digest['onceReportedRecords'] = onceReportedRecords
         digest['twiceReportedRecords'] = twiceReportedRecords
     return digest
 
 
 def diffRecords(args, records1, records2):
     records3 = {}
@@ -459,44 +460,49 @@ def diffRecords(args, records1, records2
     for k in records2:
         # This record is present only in records2.
         records3[k] = records2[k]
 
     return records3
 
 
 def diffDigests(args, d1, d2):
+    if (d1['mode'] != d2['mode']):
+        raise Exception("the input files have different 'mode' properties")
+
     d3 = {}
     d3['dmdEnvVar'] = (d1['dmdEnvVar'], d2['dmdEnvVar'])
+    d3['mode'] = d1['mode']
     d3['sampleBelowSize'] = (d1['sampleBelowSize'], d2['sampleBelowSize'])
     d3['heapUsableSize'] = d2['heapUsableSize'] - d1['heapUsableSize']
     d3['heapBlocks']     = d2['heapBlocks']     - d1['heapBlocks']
     d3['heapIsSampled']  = d2['heapIsSampled'] or d1['heapIsSampled']
-    if args.ignore_reports:
+    if d1['mode'] == 'live':
         d3['liveRecords'] = diffRecords(args, d1['liveRecords'],
                                               d2['liveRecords'])
-    else:
+    elif d1['mode'] == 'dark-matter':
         d3['unreportedRecords']    = diffRecords(args, d1['unreportedRecords'],
                                                        d2['unreportedRecords'])
         d3['onceReportedRecords']  = diffRecords(args, d1['onceReportedRecords'],
                                                        d2['onceReportedRecords'])
         d3['twiceReportedRecords'] = diffRecords(args, d1['twiceReportedRecords'],
                                                        d2['twiceReportedRecords'])
     return d3
 
 
 def printDigest(args, digest):
     dmdEnvVar       = digest['dmdEnvVar']
+    mode            = digest['mode']
     sampleBelowSize = digest['sampleBelowSize']
     heapUsableSize  = digest['heapUsableSize']
     heapIsSampled   = digest['heapIsSampled']
     heapBlocks      = digest['heapBlocks']
-    if args.ignore_reports:
+    if mode == 'live':
         liveRecords = digest['liveRecords']
-    else:
+    elif mode == 'dark-matter':
         unreportedRecords    = digest['unreportedRecords']
         onceReportedRecords  = digest['onceReportedRecords']
         twiceReportedRecords = digest['twiceReportedRecords']
 
     separator = '#' + '-' * 65 + '\n'
 
     def number(n, isSampled):
         '''Format a number, with comma as a separator and a '~' prefix if it's
@@ -578,29 +584,29 @@ def printDigest(args, digest):
                         if count > 1:
                             out(' x {:,d}'.format(count), end='')
                         isFirst = False
                 out()
 
             out('  {:4.2f}% of the heap ({:4.2f}% cumulative)'.
                 format(perc(record.usableSize, heapUsableSize),
                        perc(kindCumulativeUsableSize, heapUsableSize)))
-            if args.ignore_reports:
+            if mode == 'live':
                 pass
-            else:
+            elif mode == 'dark-matter':
                 out('  {:4.2f}% of {:} ({:4.2f}% cumulative)'.
                     format(perc(record.usableSize, kindUsableSize),
                            recordKind,
                            perc(kindCumulativeUsableSize, kindUsableSize)))
             out('  Allocated at {')
             printStack(record.allocatedAtDesc)
             out('  }')
-            if args.ignore_reports:
+            if mode == 'live':
                 pass
-            else:
+            elif mode == 'dark-matter':
                 for n, reportedAtDesc in enumerate(record.reportedAtDescs):
                     again = 'again ' if n > 0 else ''
                     out('  Reported {:}at {{'.format(again))
                     printStack(reportedAtDesc)
                     out('  }')
             out('}\n')
 
         return (kindUsableSize, kindBlocks)
@@ -620,37 +626,37 @@ def printDigest(args, digest):
     # Print invocation(s).
     if type(dmdEnvVar) is not tuple:
         printInvocation('', dmdEnvVar, sampleBelowSize)
     else:
         printInvocation(' 1', dmdEnvVar[0], sampleBelowSize[0])
         printInvocation(' 2', dmdEnvVar[1], sampleBelowSize[1])
 
     # Print records.
-    if args.ignore_reports:
+    if mode == 'live':
         liveUsableSize, liveBlocks = \
             printRecords('live', liveRecords, heapUsableSize)
-    else:
+    elif mode == 'dark-matter':
         twiceReportedUsableSize, twiceReportedBlocks = \
             printRecords('twice-reported', twiceReportedRecords, heapUsableSize)
 
         unreportedUsableSize, unreportedBlocks = \
             printRecords('unreported', unreportedRecords, heapUsableSize)
 
         onceReportedUsableSize, onceReportedBlocks = \
             printRecords('once-reported', onceReportedRecords, heapUsableSize)
 
     # Print summary.
     out(separator)
     out('Summary {')
-    if args.ignore_reports:
+    if mode == 'live':
         out('  Total: {:} bytes in {:} blocks'.
             format(number(liveUsableSize, heapIsSampled),
                    number(liveBlocks, heapIsSampled)))
-    else:
+    elif mode == 'dark-matter':
         fmt = '  {:15} {:>12} bytes ({:6.2f}%) in {:>7} blocks ({:6.2f}%)'
         out(fmt.
             format('Total:',
                    number(heapUsableSize, heapIsSampled),
                    100,
                    number(heapBlocks, heapIsSampled),
                    100))
         out(fmt.
--- a/memory/replace/dmd/test/SmokeDMD.cpp
+++ b/memory/replace/dmd/test/SmokeDMD.cpp
@@ -84,250 +84,280 @@ void Foo(int aSeven)
   UseItOrLoseIt(a[2], aSeven);
 
   Report(a[3]);                     // reported
 
   // a[4], a[5] unreported
 }
 
 void
+TestEmpty(const char* aTestName, const char* aMode)
+{
+  char filename[128];
+  sprintf(filename, "full-%s-%s.json", aTestName, aMode);
+  auto f = MakeUnique<FpWriteFunc>(filename);
+
+  char options[128];
+  sprintf(options, "--mode=%s --sample-below=1", aMode);
+  ResetEverything(options);
+
+  // Zero for everything.
+  Analyze(Move(f));
+}
+
+void
+TestUnsampled(const char* aTestName, int aNum, const char* aMode, int aSeven)
+{
+  char filename[128];
+  sprintf(filename, "full-%s%d-%s.json", aTestName, aNum, aMode);
+  auto f = MakeUnique<FpWriteFunc>(filename);
+
+  // The --show-dump-stats=yes is there just to give that option some basic
+  // testing, e.g. ensure it doesn't crash. It's hard to test much beyond that.
+  char options[128];
+  sprintf(options, "--mode=%s --sample-below=1 --show-dump-stats=yes", aMode);
+  ResetEverything(options);
+
+  // Analyze 1: 1 freed, 9 out of 10 unreported.
+  // Analyze 2: still present and unreported.
+  int i;
+  char* a = nullptr;
+  for (i = 0; i < aSeven + 3; i++) {
+      a = (char*) malloc(100);
+      UseItOrLoseIt(a, aSeven);
+  }
+  free(a);
+
+  // Note: 8 bytes is the smallest requested size that gives consistent
+  // behaviour across all platforms with jemalloc.
+  // Analyze 1: reported.
+  // Analyze 2: thrice-reported.
+  char* a2 = (char*) malloc(8);
+  Report(a2);
+
+  // Analyze 1: reported.
+  // Analyze 2: reportedness carries over, due to ReportOnAlloc.
+  char* b = (char*) malloc(10);
+  ReportOnAlloc(b);
+
+  // ReportOnAlloc, then freed.
+  // Analyze 1: freed, irrelevant.
+  // Analyze 2: freed, irrelevant.
+  char* b2 = (char*) malloc(1);
+  ReportOnAlloc(b2);
+  free(b2);
+
+  // Analyze 1: reported 4 times.
+  // Analyze 2: freed, irrelevant.
+  char* c = (char*) calloc(10, 3);
+  Report(c);
+  for (int i = 0; i < aSeven - 4; i++) {
+    Report(c);
+  }
+
+  // Analyze 1: ignored.
+  // Analyze 2: irrelevant.
+  Report((void*)(intptr_t)i);
+
+  // jemalloc rounds this up to 8192.
+  // Analyze 1: reported.
+  // Analyze 2: freed.
+  char* e = (char*) malloc(4096);
+  e = (char*) realloc(e, 4097);
+  Report(e);
+
+  // First realloc is like malloc;  second realloc is shrinking.
+  // Analyze 1: reported.
+  // Analyze 2: 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.
+  // Analyze 1: reported.
+  // Analyze 2: freed, irrelevant.
+  char* e3 = (char*) realloc(nullptr, 1023);
+//e3 = (char*) realloc(e3, 0);
+  MOZ_ASSERT(e3);
+  Report(e3);
+
+  // Analyze 1: freed, irrelevant.
+  // Analyze 2: freed, irrelevant.
+  char* f1 = (char*) malloc(64);
+  free(f1);
+
+  // Analyze 1: ignored.
+  // Analyze 2: irrelevant.
+  Report((void*)(intptr_t)0x0);
+
+  // Analyze 1: mixture of reported and unreported.
+  // Analyze 2: all unreported.
+  Foo(aSeven);
+
+  // Analyze 1: twice-reported.
+  // Analyze 2: twice-reported.
+  char* g1 = (char*) malloc(77);
+  ReportOnAlloc(g1);
+  ReportOnAlloc(g1);
+
+  // Analyze 1: mixture of reported and unreported.
+  // Analyze 2: all unreported.
+  // Nb: this Foo() call is deliberately not adjacent to the previous one. See
+  // the comment about adjacent calls in Foo() for more details.
+  Foo(aSeven);
+
+  // Analyze 1: twice-reported.
+  // Analyze 2: once-reported.
+  char* g2 = (char*) malloc(78);
+  Report(g2);
+  ReportOnAlloc(g2);
+
+  // Analyze 1: twice-reported.
+  // Analyze 2: once-reported.
+  char* g3 = (char*) malloc(79);
+  ReportOnAlloc(g3);
+  Report(g3);
+
+  // All the odd-ball ones.
+  // Analyze 1: all unreported.
+  // Analyze 2: all freed, irrelevant.
+  // XXX: no memalign on Mac
+//void* w = memalign(64, 65);           // rounds up to 128
+//UseItOrLoseIt(w, aSeven);
+
+  // XXX: posix_memalign doesn't work on B2G
+//void* x;
+//posix_memalign(&y, 128, 129);         // rounds up to 256
+//UseItOrLoseIt(x, aSeven);
+
+  // XXX: valloc doesn't work on Windows.
+//void* y = valloc(1);                  // rounds up to 4096
+//UseItOrLoseIt(y, aSeven);
+
+  // XXX: C11 only
+//void* z = aligned_alloc(64, 256);
+//UseItOrLoseIt(z, aSeven);
+
+  if (aNum == 1) {
+    // Analyze 1.
+    Analyze(Move(f));
+  }
+
+  ClearReports();
+
+  //---------
+
+  Report(a2);
+  Report(a2);
+  free(c);
+  free(e);
+  Report(e2);
+  free(e3);
+//free(w);
+//free(x);
+//free(y);
+//free(z);
+
+  if (aNum == 2) {
+    // Analyze 2.
+    Analyze(Move(f));
+  }
+}
+
+void
+TestSampled(const char* aTestName, const char* aMode, int aSeven)
+{
+  char filename[128];
+  sprintf(filename, "full-%s-%s.json", aTestName, aMode);
+  auto f = MakeUnique<FpWriteFunc>(filename);
+
+  char options[128];
+  sprintf(options, "--mode=%s --sample-below=128", aMode);
+  ResetEverything(options);
+
+  char* s;
+
+  // This equals the sample size, and so is reported exactly.  It should be
+  // listed before records of the same size that are sampled.
+  s = (char*) malloc(128);
+  UseItOrLoseIt(s, aSeven);
+
+  // This exceeds the sample size, and so is reported exactly.
+  s = (char*) malloc(160);
+  UseItOrLoseIt(s, aSeven);
+
+  // These together constitute exactly one sample.
+  for (int i = 0; i < aSeven + 9; i++) {
+    s = (char*) malloc(8);
+    UseItOrLoseIt(s, aSeven);
+  }
+
+  // These fall 8 bytes short of a full sample.
+  for (int i = 0; i < aSeven + 8; i++) {
+    s = (char*) malloc(8);
+    UseItOrLoseIt(s, aSeven);
+  }
+
+  // This exceeds the sample size, and so is recorded exactly.
+  s = (char*) malloc(256);
+  UseItOrLoseIt(s, aSeven);
+
+  // This gets more than to a full sample from the |i < aSeven + 8| loop above.
+  s = (char*) malloc(96);
+  UseItOrLoseIt(s, aSeven);
+
+  // This gets to another full sample.
+  for (int i = 0; i < aSeven - 2; i++) {
+    s = (char*) malloc(8);
+    UseItOrLoseIt(s, aSeven);
+  }
+
+  // This allocates 16, 32, ..., 128 bytes, which results in a heap block
+  // record that contains a mix of sample and non-sampled blocks, and so should
+  // be printed with '~' signs.
+  for (int i = 1; i <= aSeven + 1; i++) {
+    s = (char*) malloc(i * 16);
+    UseItOrLoseIt(s, aSeven);
+  }
+
+  // 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.
+
+  Analyze(Move(f));
+}
+
+void
 RunTests()
 {
-  // These files are written to $CWD.
-  auto f1 = MakeUnique<FpWriteFunc>("full-empty.json");
-  auto f2 = MakeUnique<FpWriteFunc>("full-unsampled1.json");
-  auto f3 = MakeUnique<FpWriteFunc>("full-unsampled2.json");
-  auto f4 = MakeUnique<FpWriteFunc>("full-sampled.json");
-
   // This test relies on the compiler not doing various optimizations, such as
   // eliding unused malloc() calls or unrolling loops with fixed iteration
   // counts. So we compile it with -O0 (or equivalent), which probably prevents
   // that. We also use the following variable for various loop iteration
   // counts, just in case compilers might unroll very small loops even with
   // -O0.
   int seven = 7;
 
   // Make sure that DMD is actually running; it is initialized on the first
   // allocation.
   int *x = (int*)malloc(100);
   UseItOrLoseIt(x, seven);
   MOZ_RELEASE_ASSERT(IsRunning());
 
-  // The first part of this test requires sampling to be disabled.
-  SetSampleBelowSize(1);
-
-  // The file manipulations above may have done some heap allocations.
-  // Clear all knowledge of existing blocks to give us a clean slate.
-  ClearBlocks();
-
-  //---------
-
-  // Analyze 1.  Zero for everything.
-  Analyze(Move(f1));
-
-  //---------
-
-  // Analyze 2: 1 freed, 9 out of 10 unreported.
-  // Analyze 3: still present and unreported.
-  int i;
-  char* a = nullptr;
-  for (i = 0; i < seven + 3; i++) {
-      a = (char*) malloc(100);
-      UseItOrLoseIt(a, seven);
-  }
-  free(a);
-
-  // Note: 8 bytes is the smallest requested size that gives consistent
-  // behaviour across all platforms with jemalloc.
-  // Analyze 2: reported.
-  // Analyze 3: thrice-reported.
-  char* a2 = (char*) malloc(8);
-  Report(a2);
-
-  // Analyze 2: reported.
-  // Analyze 3: reportedness carries over, due to ReportOnAlloc.
-  char* b = (char*) malloc(10);
-  ReportOnAlloc(b);
-
-  // ReportOnAlloc, then freed.
-  // Analyze 2: freed, irrelevant.
-  // Analyze 3: freed, irrelevant.
-  char* b2 = (char*) malloc(1);
-  ReportOnAlloc(b2);
-  free(b2);
-
-  // Analyze 2: reported 4 times.
-  // Analyze 3: freed, irrelevant.
-  char* c = (char*) calloc(10, 3);
-  Report(c);
-  for (int i = 0; i < seven - 4; i++) {
-    Report(c);
-  }
+  // Please keep this in sync with run_test in test_dmd.js.
 
-  // Analyze 2: ignored.
-  // Analyze 3: irrelevant.
-  Report((void*)(intptr_t)i);
-
-  // jemalloc rounds this up to 8192.
-  // Analyze 2: reported.
-  // Analyze 3: freed.
-  char* e = (char*) malloc(4096);
-  e = (char*) realloc(e, 4097);
-  Report(e);
-
-  // First realloc is like malloc;  second realloc is shrinking.
-  // Analyze 2: reported.
-  // Analyze 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.
-  // Analyze 2: reported.
-  // Analyze 3: freed, irrelevant.
-  char* e3 = (char*) realloc(nullptr, 1023);
-//e3 = (char*) realloc(e3, 0);
-  MOZ_ASSERT(e3);
-  Report(e3);
-
-  // Analyze 2: freed, irrelevant.
-  // Analyze 3: freed, irrelevant.
-  char* f = (char*) malloc(64);
-  free(f);
-
-  // Analyze 2: ignored.
-  // Analyze 3: irrelevant.
-  Report((void*)(intptr_t)0x0);
-
-  // Analyze 2: mixture of reported and unreported.
-  // Analyze 3: all unreported.
-  Foo(seven);
-
-  // Analyze 2: twice-reported.
-  // Analyze 3: twice-reported.
-  char* g1 = (char*) malloc(77);
-  ReportOnAlloc(g1);
-  ReportOnAlloc(g1);
-
-  // Analyze 2: mixture of reported and unreported.
-  // Analyze 3: all unreported.
-  // Nb: this Foo() call is deliberately not adjacent to the previous one. See
-  // the comment about adjacent calls in Foo() for more details.
-  Foo(seven);
+  TestEmpty("empty", "live");
+  TestEmpty("empty", "dark-matter");
 
-  // Analyze 2: twice-reported.
-  // Analyze 3: once-reported.
-  char* g2 = (char*) malloc(78);
-  Report(g2);
-  ReportOnAlloc(g2);
-
-  // Analyze 2: twice-reported.
-  // Analyze 3: once-reported.
-  char* g3 = (char*) malloc(79);
-  ReportOnAlloc(g3);
-  Report(g3);
-
-  // All the odd-ball ones.
-  // Analyze 2: all unreported.
-  // Analyze 3: all freed, irrelevant.
-  // XXX: no memalign on Mac
-//void* w = memalign(64, 65);           // rounds up to 128
-//UseItOrLoseIt(w, seven);
-
-  // XXX: posix_memalign doesn't work on B2G
-//void* x;
-//posix_memalign(&y, 128, 129);         // rounds up to 256
-//UseItOrLoseIt(x, seven);
-
-  // XXX: valloc doesn't work on Windows.
-//void* y = valloc(1);                  // rounds up to 4096
-//UseItOrLoseIt(y, seven);
-
-  // XXX: C11 only
-//void* z = aligned_alloc(64, 256);
-//UseItOrLoseIt(z, seven);
-
-  // Analyze 2.
-  Analyze(Move(f2));
-
-  //---------
-
-  Report(a2);
-  Report(a2);
-  free(c);
-  free(e);
-  Report(e2);
-  free(e3);
-//free(w);
-//free(x);
-//free(y);
-//free(z);
-
-  // Analyze 3.
-  Analyze(Move(f3));
-
-  //---------
+  TestUnsampled("unsampled", 1, "live",        seven);
+  TestUnsampled("unsampled", 1, "dark-matter", seven);
 
-  // The first part of this test requires sampling to be disabled.
-  SetSampleBelowSize(128);
-
-  // Clear all knowledge of existing blocks to give us a clean slate.
-  ClearBlocks();
-
-  char* s;
-
-  // This equals the sample size, and so is reported exactly.  It should be
-  // listed before records of the same size that are sampled.
-  s = (char*) malloc(128);
-  UseItOrLoseIt(s, seven);
-
-  // This exceeds the sample size, and so is reported exactly.
-  s = (char*) malloc(160);
-  UseItOrLoseIt(s, seven);
-
-  // These together constitute exactly one sample.
-  for (int i = 0; i < seven + 9; i++) {
-    s = (char*) malloc(8);
-    UseItOrLoseIt(s, seven);
-  }
+  TestUnsampled("unsampled", 2, "dark-matter", seven);
 
-  // These fall 8 bytes short of a full sample.
-  for (int i = 0; i < seven + 8; i++) {
-    s = (char*) malloc(8);
-    UseItOrLoseIt(s, seven);
-  }
-
-  // This exceeds the sample size, and so is recorded exactly.
-  s = (char*) malloc(256);
-  UseItOrLoseIt(s, seven);
-
-  // This gets more than to a full sample from the |i < seven + 8| loop above.
-  s = (char*) malloc(96);
-  UseItOrLoseIt(s, seven);
-
-  // This gets to another full sample.
-  for (int i = 0; i < seven - 2; i++) {
-    s = (char*) malloc(8);
-    UseItOrLoseIt(s, seven);
-  }
-
-  // This allocates 16, 32, ..., 128 bytes, which results in a heap block
-  // record that contains a mix of sample and non-sampled blocks, and so should
-  // be printed with '~' signs.
-  for (int i = 1; i <= seven + 1; i++) {
-    s = (char*) malloc(i * 16);
-    UseItOrLoseIt(s, seven);
-  }
-
-  // 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.
-
-  // Analyze 4.
-  Analyze(Move(f4));
+  TestSampled("sampled", "live", seven);
 }
 
 int main()
 {
   RunTests();
 
   return 0;
 }
rename from memory/replace/dmd/test/full-reports-empty-expected.txt
rename to memory/replace/dmd/test/full-empty-dark-matter-expected.txt
--- a/memory/replace/dmd/test/full-reports-empty-expected.txt
+++ b/memory/replace/dmd/test/full-empty-dark-matter-expected.txt
@@ -1,13 +1,13 @@
 #-----------------------------------------------------------------
-# dmd.py --filter-stacks-for-testing -o full-reports-empty-actual.txt full-empty.json
+# dmd.py --filter-stacks-for-testing -o full-empty-dark-matter-actual.txt full-empty-dark-matter.json
 
 Invocation {
-  $DMD = '1'
+  $DMD = '--mode=dark-matter --sample-below=1'
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 # no twice-reported heap blocks
 
 #-----------------------------------------------------------------
rename from memory/replace/dmd/test/full-heap-empty-expected.txt
rename to memory/replace/dmd/test/full-empty-live-expected.txt
--- a/memory/replace/dmd/test/full-heap-empty-expected.txt
+++ b/memory/replace/dmd/test/full-empty-live-expected.txt
@@ -1,13 +1,13 @@
 #-----------------------------------------------------------------
-# dmd.py --filter-stacks-for-testing -o full-heap-empty-actual.txt --ignore-reports full-empty.json
+# dmd.py --filter-stacks-for-testing -o full-empty-live-actual.txt full-empty-live.json
 
 Invocation {
-  $DMD = '1'
+  $DMD = '--mode=live --sample-below=1'
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 # no live heap blocks
 
 #-----------------------------------------------------------------
deleted file mode 100644
--- a/memory/replace/dmd/test/full-heap-unsampled2-expected.txt
+++ /dev/null
@@ -1,100 +0,0 @@
-#-----------------------------------------------------------------
-# dmd.py --filter-stacks-for-testing -o full-heap-unsampled2-actual.txt --ignore-reports full-unsampled2.json
-
-Invocation {
-  $DMD = '1'
-  Sample-below size = 1
-}
-
-#-----------------------------------------------------------------
-
-Live {
-  9 blocks in heap block record 1 of 9
-  1,008 bytes (900 requested / 108 slop)
-  Individual block sizes: 112 x 9
-  35.49% of the heap (35.49% cumulative)
-  Allocated at {
-    #01: ... DMD.cpp ...
-  }
-}
-
-Live {
-  6 blocks in heap block record 2 of 9
-  528 bytes (528 requested / 0 slop)
-  Individual block sizes: 128; 112; 96; 80; 64; 48
-  18.59% of the heap (54.08% cumulative)
-  Allocated at {
-    #01: ... DMD.cpp ...
-  }
-}
-
-Live {
-  6 blocks in heap block record 3 of 9
-  528 bytes (528 requested / 0 slop)
-  Individual block sizes: 128; 112; 96; 80; 64; 48
-  18.59% of the heap (72.68% cumulative)
-  Allocated at {
-    #01: ... 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 {
-    #01: ... 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 {
-    #01: ... 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 {
-    #01: ... 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 {
-    #01: ... 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 {
-    #01: ... DMD.cpp ...
-  }
-}
-
-Live {
-  1 block in heap block record 9 of 9
-  8 bytes (8 requested / 0 slop)
-  0.28% of the heap (100.00% cumulative)
-  Allocated at {
-    #01: ... DMD.cpp ...
-  }
-}
-
-#-----------------------------------------------------------------
-
-Summary {
-  Total: 2,840 bytes in 27 blocks
-}
-
deleted file mode 100644
--- a/memory/replace/dmd/test/full-reports-sampled-expected.txt
+++ /dev/null
@@ -1,98 +0,0 @@
-#-----------------------------------------------------------------
-# dmd.py --filter-stacks-for-testing -o full-reports-sampled-actual.txt full-sampled.json
-
-Invocation {
-  $DMD = '1'
-  Sample-below size = 128
-}
-
-#-----------------------------------------------------------------
-
-# no twice-reported heap blocks
-
-#-----------------------------------------------------------------
-
-Unreported {
-  ~4 blocks in heap block record 1 of 7
-  ~512 bytes (~512 requested / ~0 slop)
-  Individual block sizes: ~128 x 3; 128
-  35.56% of the heap (35.56% cumulative)
-  35.56% of unreported (35.56% cumulative)
-  Allocated at {
-    #01: ... DMD.cpp ...
-  }
-}
-
-Unreported {
-  1 block in heap block record 2 of 7
-  256 bytes (256 requested / 0 slop)
-  17.78% of the heap (53.33% cumulative)
-  17.78% of unreported (53.33% cumulative)
-  Allocated at {
-    #01: ... DMD.cpp ...
-  }
-}
-
-Unreported {
-  1 block in heap block record 3 of 7
-  160 bytes (160 requested / 0 slop)
-  11.11% of the heap (64.44% cumulative)
-  11.11% of unreported (64.44% cumulative)
-  Allocated at {
-    #01: ... DMD.cpp ...
-  }
-}
-
-Unreported {
-  1 block in heap block record 4 of 7
-  128 bytes (128 requested / 0 slop)
-  8.89% of the heap (73.33% cumulative)
-  8.89% of unreported (73.33% cumulative)
-  Allocated at {
-    #01: ... DMD.cpp ...
-  }
-}
-
-Unreported {
-  ~1 block in heap block record 5 of 7
-  ~128 bytes (~128 requested / ~0 slop)
-  8.89% of the heap (82.22% cumulative)
-  8.89% of unreported (82.22% cumulative)
-  Allocated at {
-    #01: ... DMD.cpp ...
-  }
-}
-
-Unreported {
-  ~1 block in heap block record 6 of 7
-  ~128 bytes (~128 requested / ~0 slop)
-  8.89% of the heap (91.11% cumulative)
-  8.89% of unreported (91.11% cumulative)
-  Allocated at {
-    #01: ... DMD.cpp ...
-  }
-}
-
-Unreported {
-  ~1 block in heap block record 7 of 7
-  ~128 bytes (~128 requested / ~0 slop)
-  8.89% of the heap (100.00% cumulative)
-  8.89% of unreported (100.00% cumulative)
-  Allocated at {
-    #01: ... DMD.cpp ...
-  }
-}
-
-#-----------------------------------------------------------------
-
-# no once-reported heap blocks
-
-#-----------------------------------------------------------------
-
-Summary {
-  Total:                ~1,440 bytes (100.00%) in     ~10 blocks (100.00%)
-  Unreported:           ~1,440 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%)
-}
-
rename from memory/replace/dmd/test/full-heap-sampled-expected.txt
rename to memory/replace/dmd/test/full-sampled-live-expected.txt
--- a/memory/replace/dmd/test/full-heap-sampled-expected.txt
+++ b/memory/replace/dmd/test/full-sampled-live-expected.txt
@@ -1,13 +1,13 @@
 #-----------------------------------------------------------------
-# dmd.py --filter-stacks-for-testing -o full-heap-sampled-actual.txt --ignore-reports full-sampled.json
+# dmd.py --filter-stacks-for-testing -o full-sampled-live-actual.txt full-sampled-live.json
 
 Invocation {
-  $DMD = '1'
+  $DMD = '--mode=live --sample-below=128'
   Sample-below size = 128
 }
 
 #-----------------------------------------------------------------
 
 Live {
   ~4 blocks in heap block record 1 of 7
   ~512 bytes (~512 requested / ~0 slop)
rename from memory/replace/dmd/test/full-reports-unsampled1-expected.txt
rename to memory/replace/dmd/test/full-unsampled1-dark-matter-expected.txt
--- a/memory/replace/dmd/test/full-reports-unsampled1-expected.txt
+++ b/memory/replace/dmd/test/full-unsampled1-dark-matter-expected.txt
@@ -1,13 +1,13 @@
 #-----------------------------------------------------------------
-# dmd.py --filter-stacks-for-testing -o full-reports-unsampled1-actual.txt full-unsampled1.json
+# dmd.py --filter-stacks-for-testing -o full-unsampled1-dark-matter-actual.txt full-unsampled1-dark-matter.json
 
 Invocation {
-  $DMD = '1'
+  $DMD = '--mode=dark-matter --sample-below=1 --show-dump-stats=yes'
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Twice-reported {
   1 block in heap block record 1 of 4
   80 bytes (79 requested / 1 slop)
rename from memory/replace/dmd/test/full-heap-unsampled1-expected.txt
rename to memory/replace/dmd/test/full-unsampled1-live-expected.txt
--- a/memory/replace/dmd/test/full-heap-unsampled1-expected.txt
+++ b/memory/replace/dmd/test/full-unsampled1-live-expected.txt
@@ -1,13 +1,13 @@
 #-----------------------------------------------------------------
-# dmd.py --filter-stacks-for-testing -o full-heap-unsampled1-actual.txt --ignore-reports full-unsampled1.json
+# dmd.py --filter-stacks-for-testing -o full-unsampled1-live-actual.txt full-unsampled1-live.json
 
 Invocation {
-  $DMD = '1'
+  $DMD = '--mode=live --sample-below=1 --show-dump-stats=yes'
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Live {
   1 block in heap block record 1 of 12
   8,192 bytes (4,097 requested / 4,095 slop)
rename from memory/replace/dmd/test/full-reports-unsampled2-expected.txt
rename to memory/replace/dmd/test/full-unsampled2-dark-matter-expected.txt
--- a/memory/replace/dmd/test/full-reports-unsampled2-expected.txt
+++ b/memory/replace/dmd/test/full-unsampled2-dark-matter-expected.txt
@@ -1,13 +1,13 @@
 #-----------------------------------------------------------------
-# dmd.py --filter-stacks-for-testing -o full-reports-unsampled2-actual.txt full-unsampled2.json
+# dmd.py --filter-stacks-for-testing -o full-unsampled2-dark-matter-actual.txt full-unsampled2-dark-matter.json
 
 Invocation {
-  $DMD = '1'
+  $DMD = '--mode=dark-matter --sample-below=1 --show-dump-stats=yes'
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Twice-reported {
   1 block in heap block record 1 of 2
   80 bytes (77 requested / 3 slop)
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/script-diff-dark-matter-expected.txt
@@ -0,0 +1,127 @@
+#-----------------------------------------------------------------
+# dmd.py --filter-stacks-for-testing -o script-diff-dark-matter-actual.txt script-diff-dark-matter1.json script-diff-dark-matter2.json
+
+Invocation 1 {
+  $DMD = '--sample-below=127'
+  Sample-below size = 127
+}
+
+Invocation 2 {
+  $DMD = '--sample-below=63'
+  Sample-below size = 63
+}
+
+#-----------------------------------------------------------------
+
+Twice-reported {
+  ~-1 blocks in heap block record 1 of 1
+  ~-1,088 bytes (~-1,064 requested / ~-24 slop)
+  Individual block sizes: -1,024; ~-127; ~63
+  15.46% of the heap (15.46% cumulative)
+  100.00% of twice-reported (100.00% cumulative)
+  Allocated at {
+    #01: F (F.cpp:99)
+  }
+  Reported at {
+    #01: R1 (R1.cpp:99)
+  }
+  Reported again at {
+    #01: R2 (R2.cpp:99)
+  }
+}
+
+#-----------------------------------------------------------------
+
+Unreported {
+  4 blocks in heap block record 1 of 5
+  16,384 bytes (16,384 requested / 0 slop)
+  Individual block sizes: 4,096 x 4
+  -232.76% of the heap (-232.76% cumulative)
+  371.01% of unreported (371.01% cumulative)
+  Allocated at {
+    #01: E (E.cpp:99)
+  }
+}
+
+Unreported {
+  ~7 blocks in heap block record 2 of 5
+  ~-11,968 bytes (~-12,016 requested / ~48 slop)
+  Individual block sizes: -15,360; 2,048; 512 x 2; 128; ~-127; 64 x 4; ~63
+  170.02% of the heap (-62.74% cumulative)
+  -271.01% of unreported (100.00% cumulative)
+  Allocated at {
+    #01: F (F.cpp:99)
+  }
+}
+
+Unreported {
+  0 blocks in heap block record 3 of 5
+  0 bytes (-384 requested / 384 slop)
+  Individual block sizes: (no change)
+  -0.00% of the heap (-62.74% cumulative)
+  0.00% of unreported (100.00% cumulative)
+  Allocated at {
+    #01: C (C.cpp:99)
+  }
+}
+
+Unreported {
+  -2 blocks in heap block record 4 of 5
+  0 bytes (0 requested / 0 slop)
+  Individual block sizes: 8,192 x 2; -4,096 x 4
+  -0.00% of the heap (-62.74% cumulative)
+  0.00% of unreported (100.00% cumulative)
+  Allocated at {
+    #01: B (B.cpp:99)
+  }
+}
+
+Unreported {
+  0 blocks in heap block record 5 of 5
+  0 bytes (0 requested / 0 slop)
+  Individual block sizes: 20,480; -16,384; -8,192; 4,096
+  -0.00% of the heap (-62.74% cumulative)
+  0.00% of unreported (100.00% cumulative)
+  Allocated at {
+    #01: G (G.cpp:99)
+  }
+}
+
+#-----------------------------------------------------------------
+
+Once-reported {
+  -3 blocks in heap block record 1 of 2
+  -10,240 bytes (-10,192 requested / -48 slop)
+  Individual block sizes: -4,096 x 2; -2,048
+  145.48% of the heap (145.48% cumulative)
+  98.77% of once-reported (98.77% cumulative)
+  Allocated at {
+    #01: D (D.cpp:99)
+  }
+  Reported at {
+    #01: R1 (R1.cpp:99)
+  }
+}
+
+Once-reported {
+  ~-1 blocks in heap block record 2 of 2
+  ~-127 bytes (~-151 requested / ~24 slop)
+  1.80% of the heap (147.28% cumulative)
+  1.23% of once-reported (100.00% cumulative)
+  Allocated at {
+    #01: F (F.cpp:99)
+  }
+  Reported at {
+    #01: R1 (R1.cpp:99)
+  }
+}
+
+#-----------------------------------------------------------------
+
+Summary {
+  Total:               ~-7,039 bytes (100.00%) in      ~4 blocks (100.00%)
+  Unreported:           ~4,416 bytes (-62.74%) in      ~9 blocks (225.00%)
+  Once-reported:      ~-10,367 bytes (147.28%) in     ~-4 blocks (-100.00%)
+  Twice-reported:      ~-1,088 bytes ( 15.46%) in     ~-1 blocks (-25.00%)
+}
+
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/script-diff-dark-matter1.json
@@ -0,0 +1,63 @@
+{
+ "version": 2,
+ "invocation": {
+  "dmdEnvVar": "--sample-below=127",
+  "mode": "dark-matter",
+  "sampleBelowSize": 127
+ },
+ "blockList": [
+  {"req": 4096, "alloc": "A"},
+  {"req": 4096, "alloc": "A"},
+  {"req": 4096, "alloc": "A"},
+  {"req": 4096, "alloc": "A"},
+
+  {"req": 4096, "alloc": "B"},
+  {"req": 4096, "alloc": "B"},
+  {"req": 4096, "alloc": "B"},
+  {"req": 4096, "alloc": "B"},
+
+  {"req": 4096, "alloc": "C"},
+  {"req": 4096, "alloc": "C"},
+  {"req": 4096, "alloc": "C"},
+  {"req": 4096, "alloc": "C"},
+
+  {"req": 4096,             "alloc": "D", "reps": ["R1"]},
+  {"req": 4096,             "alloc": "D", "reps": ["R1"]},
+  {"req": 2000, "slop": 48, "alloc": "D", "reps": ["R1"]},
+
+  {"req": 15360,            "alloc": "F"},
+  {"req": 512,              "alloc": "F"},
+  {"req": 512,              "alloc": "F"},
+  {                         "alloc": "F"},
+  {"req": 1024,             "alloc": "F", "reps": ["R1"]},
+  {                         "alloc": "F", "reps": ["R1"]},
+  {"req": 1000, "slop": 24, "alloc": "F", "reps": ["R1", "R2"]},
+  {                         "alloc": "F", "reps": ["R1", "R2"]},
+
+  {"req": 4096,            "alloc": "G"},
+  {"req": 8192,            "alloc": "G"},
+  {"req": 16384,           "alloc": "G"}
+ ],
+ "traceTable": {
+  "A": ["AA"],
+  "B": ["BB"],
+  "C": ["CC"],
+  "D": ["DD"],
+  "E": ["EE"],
+  "F": ["FF"],
+  "G": ["GG"],
+  "R1": ["RR1"],
+  "R2": ["RR2"]
+ },
+ "frameTable": {
+  "AA": "#00: A (A.cpp:99)",
+  "BB": "#00: B (B.cpp:99)",
+  "CC": "#00: C (C.cpp:99)",
+  "DD": "#00: D (D.cpp:99)",
+  "EE": "#00: E (E.cpp:99)",
+  "FF": "#00: F (F.cpp:99)",
+  "GG": "#00: G (G.cpp:99)",
+  "RR1": "#00: R1 (R1.cpp:99)",
+  "RR2": "#00: R2 (R2.cpp:99)"
+ }
+}
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/script-diff-dark-matter2.json
@@ -0,0 +1,67 @@
+{
+ "version": 2,
+ "invocation": {
+  "dmdEnvVar": "--sample-below=63",
+  "mode": "dark-matter",
+  "sampleBelowSize": 63
+ },
+ "blockList": [
+  {"req": 4096, "alloc": "A"},
+  {"req": 4096, "alloc": "A"},
+  {"req": 4096, "alloc": "A"},
+  {"req": 4096, "alloc": "A"},
+
+  {"req": 8192, "alloc": "B"},
+  {"req": 8192, "alloc": "B"},
+
+  {"req": 4000, "slop": 96, "alloc": "C"},
+  {"req": 4000, "slop": 96, "alloc": "C"},
+  {"req": 4000, "slop": 96, "alloc": "C"},
+  {"req": 4000, "slop": 96, "alloc": "C"},
+
+  {"req": 4096, "alloc": "E"},
+  {"req": 4096, "alloc": "E"},
+  {"req": 4096, "alloc": "E"},
+  {"req": 4096, "alloc": "E"},
+
+  {"req": 2000, "slop": 48, "alloc": "F"},
+  {"req": 1000, "slop": 24, "alloc": "F", "reps": ["R1"]},
+  {"req": 512,              "alloc": "F"},
+  {"req": 512,              "alloc": "F"},
+  {"req": 512,              "alloc": "F"},
+  {"req": 512,              "alloc": "F"},
+  {"req": 128,              "alloc": "F"},
+  {                         "alloc": "F", "reps": ["R1", "R2"]},
+  {"req": 64,               "alloc": "F"},
+  {"req": 64,               "alloc": "F"},
+  {"req": 64,               "alloc": "F"},
+  {"req": 64,               "alloc": "F"},
+  {                         "alloc": "F"},
+
+  {"req": 4096,            "alloc": "G"},
+  {"req": 4096,            "alloc": "G"},
+  {"req": 20480,           "alloc": "G"}
+ ],
+ "traceTable": {
+  "A": ["AA"],
+  "B": ["BB"],
+  "C": ["CC"],
+  "D": ["DD"],
+  "E": ["EE"],
+  "F": ["FF"],
+  "G": ["GG"],
+  "R1": ["RR1"],
+  "R2": ["RR2"]
+ },
+ "frameTable": {
+  "AA": "#00: A (A.cpp:99)",
+  "BB": "#00: B (B.cpp:99)",
+  "CC": "#00: C (C.cpp:99)",
+  "DD": "#00: D (D.cpp:99)",
+  "EE": "#00: E (E.cpp:99)",
+  "FF": "#00: F (F.cpp:99)",
+  "GG": "#00: G (G.cpp:99)",
+  "RR1": "#00: R1 (R1.cpp:99)",
+  "RR2": "#00: R2 (R2.cpp:99)"
+ }
+}
rename from memory/replace/dmd/test/script-diff-basic-expected.txt
rename to memory/replace/dmd/test/script-diff-live-expected.txt
--- a/memory/replace/dmd/test/script-diff-basic-expected.txt
+++ b/memory/replace/dmd/test/script-diff-live-expected.txt
@@ -1,127 +1,81 @@
 #-----------------------------------------------------------------
-# dmd.py --filter-stacks-for-testing -o script-diff-basic-actual.txt script-diff1.json script-diff2.json
+# dmd.py --filter-stacks-for-testing -o script-diff-live-actual.txt script-diff-live1.json script-diff-live2.json
 
 Invocation 1 {
   $DMD = '--sample-below=127'
   Sample-below size = 127
 }
 
 Invocation 2 {
   $DMD = '--sample-below=63'
   Sample-below size = 63
 }
 
 #-----------------------------------------------------------------
 
-Twice-reported {
-  ~-1 blocks in heap block record 1 of 1
-  ~-1,088 bytes (~-1,064 requested / ~-24 slop)
-  Individual block sizes: -1,024; ~-127; ~63
-  15.46% of the heap (15.46% cumulative)
-  100.00% of twice-reported (100.00% cumulative)
-  Allocated at {
-    #01: F (F.cpp:99)
-  }
-  Reported at {
-    #01: R1 (R1.cpp:99)
-  }
-  Reported again at {
-    #01: R2 (R2.cpp:99)
-  }
-}
-
-#-----------------------------------------------------------------
-
-Unreported {
-  4 blocks in heap block record 1 of 5
+Live {
+  4 blocks in heap block record 1 of 6
   16,384 bytes (16,384 requested / 0 slop)
   Individual block sizes: 4,096 x 4
   -232.76% of the heap (-232.76% cumulative)
-  371.01% of unreported (371.01% cumulative)
   Allocated at {
     #01: E (E.cpp:99)
   }
 }
 
-Unreported {
-  ~7 blocks in heap block record 2 of 5
-  ~-11,968 bytes (~-12,016 requested / ~48 slop)
-  Individual block sizes: -15,360; 2,048; 512 x 2; 128; ~-127; 64 x 4; ~63
-  170.02% of the heap (-62.74% cumulative)
-  -271.01% of unreported (100.00% cumulative)
+Live {
+  ~5 blocks in heap block record 2 of 6
+  ~-13,183 bytes (~-13,231 requested / ~48 slop)
+  Individual block sizes: -15,360; 2,048; -1,024; 512 x 2; 128; ~-127 x 3; 64 x 4; ~63 x 2
+  187.29% of the heap (-45.48% cumulative)
   Allocated at {
     #01: F (F.cpp:99)
   }
 }
 
-Unreported {
-  0 blocks in heap block record 3 of 5
+Live {
+  -3 blocks in heap block record 3 of 6
+  -10,240 bytes (-10,192 requested / -48 slop)
+  Individual block sizes: -4,096 x 2; -2,048
+  145.48% of the heap (100.00% cumulative)
+  Allocated at {
+    #01: D (D.cpp:99)
+  }
+}
+
+Live {
+  0 blocks in heap block record 4 of 6
   0 bytes (-384 requested / 384 slop)
   Individual block sizes: (no change)
-  -0.00% of the heap (-62.74% cumulative)
-  0.00% of unreported (100.00% cumulative)
+  -0.00% of the heap (100.00% cumulative)
   Allocated at {
     #01: C (C.cpp:99)
   }
 }
 
-Unreported {
-  -2 blocks in heap block record 4 of 5
-  0 bytes (0 requested / 0 slop)
-  Individual block sizes: 8,192 x 2; -4,096 x 4
-  -0.00% of the heap (-62.74% cumulative)
-  0.00% of unreported (100.00% cumulative)
-  Allocated at {
-    #01: B (B.cpp:99)
-  }
-}
-
-Unreported {
-  0 blocks in heap block record 5 of 5
+Live {
+  0 blocks in heap block record 5 of 6
   0 bytes (0 requested / 0 slop)
   Individual block sizes: 20,480; -16,384; -8,192; 4,096
-  -0.00% of the heap (-62.74% cumulative)
-  0.00% of unreported (100.00% cumulative)
+  -0.00% of the heap (100.00% cumulative)
   Allocated at {
     #01: G (G.cpp:99)
   }
 }
 
-#-----------------------------------------------------------------
-
-Once-reported {
-  -3 blocks in heap block record 1 of 2
-  -10,240 bytes (-10,192 requested / -48 slop)
-  Individual block sizes: -4,096 x 2; -2,048
-  145.48% of the heap (145.48% cumulative)
-  98.77% of once-reported (98.77% cumulative)
+Live {
+  -2 blocks in heap block record 6 of 6
+  0 bytes (0 requested / 0 slop)
+  Individual block sizes: 8,192 x 2; -4,096 x 4
+  -0.00% of the heap (100.00% cumulative)
   Allocated at {
-    #01: D (D.cpp:99)
-  }
-  Reported at {
-    #01: R1 (R1.cpp:99)
-  }
-}
-
-Once-reported {
-  ~-1 blocks in heap block record 2 of 2
-  ~-127 bytes (~-151 requested / ~24 slop)
-  1.80% of the heap (147.28% cumulative)
-  1.23% of once-reported (100.00% cumulative)
-  Allocated at {
-    #01: F (F.cpp:99)
-  }
-  Reported at {
-    #01: R1 (R1.cpp:99)
+    #01: B (B.cpp:99)
   }
 }
 
 #-----------------------------------------------------------------
 
 Summary {
-  Total:               ~-7,039 bytes (100.00%) in      ~4 blocks (100.00%)
-  Unreported:           ~4,416 bytes (-62.74%) in      ~9 blocks (225.00%)
-  Once-reported:      ~-10,367 bytes (147.28%) in     ~-4 blocks (-100.00%)
-  Twice-reported:      ~-1,088 bytes ( 15.46%) in     ~-1 blocks (-25.00%)
+  Total: ~-7,039 bytes in ~4 blocks
 }
 
rename from memory/replace/dmd/test/script-diff1.json
rename to memory/replace/dmd/test/script-diff-live1.json
--- a/memory/replace/dmd/test/script-diff1.json
+++ b/memory/replace/dmd/test/script-diff-live1.json
@@ -1,12 +1,13 @@
 {
- "version": 1,
+ "version": 2,
  "invocation": {
   "dmdEnvVar": "--sample-below=127",
+  "mode": "live",
   "sampleBelowSize": 127
  },
  "blockList": [
   {"req": 4096, "alloc": "A"},
   {"req": 4096, "alloc": "A"},
   {"req": 4096, "alloc": "A"},
   {"req": 4096, "alloc": "A"},
 
@@ -15,28 +16,28 @@
   {"req": 4096, "alloc": "B"},
   {"req": 4096, "alloc": "B"},
 
   {"req": 4096, "alloc": "C"},
   {"req": 4096, "alloc": "C"},
   {"req": 4096, "alloc": "C"},
   {"req": 4096, "alloc": "C"},
 
-  {"req": 4096,             "alloc": "D", "reps": ["R1"]},
-  {"req": 4096,             "alloc": "D", "reps": ["R1"]},
-  {"req": 2000, "slop": 48, "alloc": "D", "reps": ["R1"]},
+  {"req": 4096,             "alloc": "D"},
+  {"req": 4096,             "alloc": "D"},
+  {"req": 2000, "slop": 48, "alloc": "D"},
 
   {"req": 15360,            "alloc": "F"},
   {"req": 512,              "alloc": "F"},
   {"req": 512,              "alloc": "F"},
   {                         "alloc": "F"},
-  {"req": 1024,             "alloc": "F", "reps": ["R1"]},
-  {                         "alloc": "F", "reps": ["R1"]},
-  {"req": 1000, "slop": 24, "alloc": "F", "reps": ["R1", "R2"]},
-  {                         "alloc": "F", "reps": ["R1", "R2"]},
+  {"req": 1024,             "alloc": "F"},
+  {                         "alloc": "F"},
+  {"req": 1000, "slop": 24, "alloc": "F"},
+  {                         "alloc": "F"},
 
   {"req": 4096,            "alloc": "G"},
   {"req": 8192,            "alloc": "G"},
   {"req": 16384,           "alloc": "G"}
  ],
  "traceTable": {
   "A": ["AA"],
   "B": ["BB"],
rename from memory/replace/dmd/test/script-diff2.json
rename to memory/replace/dmd/test/script-diff-live2.json
--- a/memory/replace/dmd/test/script-diff2.json
+++ b/memory/replace/dmd/test/script-diff-live2.json
@@ -1,12 +1,13 @@
 {
- "version": 1,
+ "version": 2,
  "invocation": {
   "dmdEnvVar": "--sample-below=63",
+  "mode": "live",
   "sampleBelowSize": 63
  },
  "blockList": [
   {"req": 4096, "alloc": "A"},
   {"req": 4096, "alloc": "A"},
   {"req": 4096, "alloc": "A"},
   {"req": 4096, "alloc": "A"},
 
@@ -19,23 +20,23 @@
   {"req": 4000, "slop": 96, "alloc": "C"},
 
   {"req": 4096, "alloc": "E"},
   {"req": 4096, "alloc": "E"},
   {"req": 4096, "alloc": "E"},
   {"req": 4096, "alloc": "E"},
 
   {"req": 2000, "slop": 48, "alloc": "F"},
-  {"req": 1000, "slop": 24, "alloc": "F", "reps": ["R1"]},
+  {"req": 1000, "slop": 24, "alloc": "F"},
   {"req": 512,              "alloc": "F"},
   {"req": 512,              "alloc": "F"},
   {"req": 512,              "alloc": "F"},
   {"req": 512,              "alloc": "F"},
   {"req": 128,              "alloc": "F"},
-  {                         "alloc": "F", "reps": ["R1", "R2"]},
+  {                         "alloc": "F"},
   {"req": 64,               "alloc": "F"},
   {"req": 64,               "alloc": "F"},
   {"req": 64,               "alloc": "F"},
   {"req": 64,               "alloc": "F"},
   {                         "alloc": "F"},
 
   {"req": 4096,            "alloc": "G"},
   {"req": 4096,            "alloc": "G"},
deleted file mode 100644
--- a/memory/replace/dmd/test/script-diff-options-expected.txt
+++ /dev/null
@@ -1,81 +0,0 @@
-#-----------------------------------------------------------------
-# dmd.py --filter-stacks-for-testing -o script-diff-options-actual.txt --ignore-reports script-diff1.json script-diff2.json
-
-Invocation 1 {
-  $DMD = '--sample-below=127'
-  Sample-below size = 127
-}
-
-Invocation 2 {
-  $DMD = '--sample-below=63'
-  Sample-below size = 63
-}
-
-#-----------------------------------------------------------------
-
-Live {
-  4 blocks in heap block record 1 of 6
-  16,384 bytes (16,384 requested / 0 slop)
-  Individual block sizes: 4,096 x 4
-  -232.76% of the heap (-232.76% cumulative)
-  Allocated at {
-    #01: E (E.cpp:99)
-  }
-}
-
-Live {
-  ~5 blocks in heap block record 2 of 6
-  ~-13,183 bytes (~-13,231 requested / ~48 slop)
-  Individual block sizes: -15,360; 2,048; -1,024; 512 x 2; 128; ~-127 x 3; 64 x 4; ~63 x 2
-  187.29% of the heap (-45.48% cumulative)
-  Allocated at {
-    #01: F (F.cpp:99)
-  }
-}
-
-Live {
-  -3 blocks in heap block record 3 of 6
-  -10,240 bytes (-10,192 requested / -48 slop)
-  Individual block sizes: -4,096 x 2; -2,048
-  145.48% of the heap (100.00% cumulative)
-  Allocated at {
-    #01: D (D.cpp:99)
-  }
-}
-
-Live {
-  0 blocks in heap block record 4 of 6
-  0 bytes (-384 requested / 384 slop)
-  Individual block sizes: (no change)
-  -0.00% of the heap (100.00% cumulative)
-  Allocated at {
-    #01: C (C.cpp:99)
-  }
-}
-
-Live {
-  0 blocks in heap block record 5 of 6
-  0 bytes (0 requested / 0 slop)
-  Individual block sizes: 20,480; -16,384; -8,192; 4,096
-  -0.00% of the heap (100.00% cumulative)
-  Allocated at {
-    #01: G (G.cpp:99)
-  }
-}
-
-Live {
-  -2 blocks in heap block record 6 of 6
-  0 bytes (0 requested / 0 slop)
-  Individual block sizes: 8,192 x 2; -4,096 x 4
-  -0.00% of the heap (100.00% cumulative)
-  Allocated at {
-    #01: B (B.cpp:99)
-  }
-}
-
-#-----------------------------------------------------------------
-
-Summary {
-  Total: ~-7,039 bytes in ~4 blocks
-}
-
--- a/memory/replace/dmd/test/script-ignore-alloc-fns-expected.txt
+++ b/memory/replace/dmd/test/script-ignore-alloc-fns-expected.txt
@@ -1,10 +1,10 @@
 #-----------------------------------------------------------------
-# dmd.py --filter-stacks-for-testing -o script-ignore-alloc-fns-actual.txt --ignore-reports --ignore-alloc-fns script-ignore-alloc-fns.json
+# dmd.py --filter-stacks-for-testing -o script-ignore-alloc-fns-actual.txt --ignore-alloc-fns script-ignore-alloc-fns.json
 
 Invocation {
   $DMD = '1'
   Sample-below size = 2500
 }
 
 #-----------------------------------------------------------------
 
--- a/memory/replace/dmd/test/script-ignore-alloc-fns.json
+++ b/memory/replace/dmd/test/script-ignore-alloc-fns.json
@@ -1,12 +1,13 @@
 {
- "version": 1,
+ "version": 2,
  "invocation": {
   "dmdEnvVar": "1",
+  "mode": "live",
   "sampleBelowSize": 2500
  },
  "blockList": [
   {"req": 1048576,           "alloc": "A"},
   {"req": 65536,             "alloc": "B"},
   {"req": 8000, "slop": 192, "alloc": "C"},
   {                          "alloc": "D"}
  ],
--- a/memory/replace/dmd/test/script-max-frames-1-expected.txt
+++ b/memory/replace/dmd/test/script-max-frames-1-expected.txt
@@ -1,10 +1,10 @@
 #-----------------------------------------------------------------
-# dmd.py --filter-stacks-for-testing -o script-max-frames-1-actual.txt --ignore-reports --max-frames=1 script-max-frames.json
+# dmd.py --filter-stacks-for-testing -o script-max-frames-1-actual.txt --max-frames=1 script-max-frames.json
 
 Invocation {
   $DMD = '1'
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
--- a/memory/replace/dmd/test/script-max-frames-3-expected.txt
+++ b/memory/replace/dmd/test/script-max-frames-3-expected.txt
@@ -1,10 +1,10 @@
 #-----------------------------------------------------------------
-# dmd.py --filter-stacks-for-testing -o script-max-frames-3-actual.txt --ignore-reports --max-frames=3 --no-fix-stacks script-max-frames.json
+# dmd.py --filter-stacks-for-testing -o script-max-frames-3-actual.txt --max-frames=3 --no-fix-stacks script-max-frames.json
 
 Invocation {
   $DMD = '1'
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
--- a/memory/replace/dmd/test/script-max-frames-8-expected.txt
+++ b/memory/replace/dmd/test/script-max-frames-8-expected.txt
@@ -1,10 +1,10 @@
 #-----------------------------------------------------------------
-# dmd.py --filter-stacks-for-testing -o script-max-frames-8-actual.txt --ignore-reports --max-frames=8 script-max-frames.json
+# dmd.py --filter-stacks-for-testing -o script-max-frames-8-actual.txt --max-frames=8 script-max-frames.json
 
 Invocation {
   $DMD = '1'
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
--- a/memory/replace/dmd/test/script-max-frames.json
+++ b/memory/replace/dmd/test/script-max-frames.json
@@ -1,12 +1,13 @@
 {
- "version": 1,
+ "version": 2,
  "invocation": {
   "dmdEnvVar": "1",
+  "mode": "live",
   "sampleBelowSize": 1
  },
  "blockList": [
   {"req": 4096, "alloc": "A"},
   {"req": 128, "alloc": "B"},
   {"req": 100, "slop":12, "alloc": "C"},
   {"req": 80, "alloc": "D"}
  ],
--- a/memory/replace/dmd/test/script-sort-by-req-expected.txt
+++ b/memory/replace/dmd/test/script-sort-by-req-expected.txt
@@ -1,10 +1,10 @@
 #-----------------------------------------------------------------
-# dmd.py --filter-stacks-for-testing -o script-sort-by-req-actual.txt --ignore-reports --sort-by=req --no-fix-stacks script-sort-by.json.gz
+# dmd.py --filter-stacks-for-testing -o script-sort-by-req-actual.txt --sort-by=req --no-fix-stacks script-sort-by.json.gz
 
 Invocation {
   $DMD = '1'
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
--- a/memory/replace/dmd/test/script-sort-by-slop-expected.txt
+++ b/memory/replace/dmd/test/script-sort-by-slop-expected.txt
@@ -1,10 +1,10 @@
 #-----------------------------------------------------------------
-# dmd.py --filter-stacks-for-testing -o script-sort-by-slop-actual.txt --ignore-reports --sort-by=slop script-sort-by.json.gz
+# dmd.py --filter-stacks-for-testing -o script-sort-by-slop-actual.txt --sort-by=slop script-sort-by.json.gz
 
 Invocation {
   $DMD = '1'
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
--- a/memory/replace/dmd/test/script-sort-by-usable-expected.txt
+++ b/memory/replace/dmd/test/script-sort-by-usable-expected.txt
@@ -1,10 +1,10 @@
 #-----------------------------------------------------------------
-# dmd.py --filter-stacks-for-testing -o script-sort-by-usable-actual.txt --ignore-reports --sort-by=usable script-sort-by.json.gz
+# dmd.py --filter-stacks-for-testing -o script-sort-by-usable-actual.txt --sort-by=usable script-sort-by.json.gz
 
 Invocation {
   $DMD = '1'
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
index f8b602a7359a80984fffd76099696733288788a8..fb767c74adca6ee27c176fea47d23aff1e65ef89
GIT binary patch
literal 295
zc$@(z0oeW@iwFoe3UpKg19M|?X>fEcb8m8VEn;~tYIARH0L_xkPJ}QNh4+1mo9hM>
zV#+ufg<adYbEk1*SahH!jfG+%m`LW`ZDB;<0d%_J{O~nR&OiWr<yviP%wQLLsq5Yr
zvYVWN0AN{_4|V?{oi{MSICzyURggyaYI0gxHAZn|?B|pI3SmOv6o^M-i}y#}cHVpS
z)d8LQ@ZWoy9%4Xgd>&W~L;Y{!`xyM$#EblvQtE$ijcvk9HZZ=1T}U}OjE`EKO?lDY
z(S7=h_Udkl7HjlBmI>rxM4gj`dX`6{erCzSoRBfh$Nlk9a(<R#ltN&BUg4XQ)dmm<
tctvRj3?g<@G)<PK*VCLipF<82Uxz}>p@2xtp}&fp$Tzm8!XZ@y0012MjGh1h
--- a/memory/replace/dmd/test/test_dmd.js
+++ b/memory/replace/dmd/test/test_dmd.js
@@ -123,61 +123,73 @@ function run_test() {
   // in-place (to fix stacks) when it runs dmd.py, and that's not safe to do
   // asynchronously.
 
   gEnv.set("DMD", "1");
   gEnv.set(gEnv.get("DMD_PRELOAD_VAR"), gEnv.get("DMD_PRELOAD_VALUE"));
 
   runProcess(gDmdTestFile, []);
 
-  let fullTestNames = ["empty", "unsampled1", "unsampled2", "sampled"];
-  for (let i = 0; i < fullTestNames.length; i++) {
-      let name = fullTestNames[i];
-      jsonFile = FileUtils.getFile("CurWorkD", ["full-" + name + ".json"]);
-      test("full-heap-" + name, ["--ignore-reports", jsonFile.path])
-      test("full-reports-" + name, [jsonFile.path])
-      jsonFile.remove(true);
+  function test2(aTestName, aMode) {
+    let name = "full-" + aTestName + "-" + aMode;
+    jsonFile = FileUtils.getFile("CurWorkD", [name + ".json"]);
+    test(name, [jsonFile.path]);
+    jsonFile.remove(true);
   }
 
+  // Please keep this in sync with RunTests() in SmokeDMD.cpp.
+
+  test2("empty", "live");
+  test2("empty", "dark-matter");
+
+  test2("unsampled1", "live");
+  test2("unsampled1", "dark-matter");
+
+  test2("unsampled2", "dark-matter");
+
+  test2("sampled", "live");
+
   // These tests only test the post-processing script. They use hand-written
   // JSON files as input. Ideally the JSON files would contain comments
   // explaining how they work, but JSON doesn't allow comments, so I've put
   // explanations here.
 
   // This just tests that stack traces of various lengths are truncated
   // appropriately. The number of records in the output is different for each
   // of the tested values.
   jsonFile = FileUtils.getFile("CurWorkD", ["script-max-frames.json"]);
   test("script-max-frames-8",
-       ["--ignore-reports", "--max-frames=8", jsonFile.path]);
+       ["--max-frames=8", jsonFile.path]);
   test("script-max-frames-3",
-       ["--ignore-reports", "--max-frames=3", "--no-fix-stacks",
-        jsonFile.path]);
+       ["--max-frames=3", "--no-fix-stacks", jsonFile.path]);
   test("script-max-frames-1",
-       ["--ignore-reports", "--max-frames=1", jsonFile.path]);
+       ["--max-frames=1", jsonFile.path]);
 
   // This file has three records that are shown in a different order for each
   // of the different sort values. It also tests the handling of gzipped JSON
   // files.
   jsonFile = FileUtils.getFile("CurWorkD", ["script-sort-by.json.gz"]);
   test("script-sort-by-usable",
-       ["--ignore-reports", "--sort-by=usable", jsonFile.path]);
+       ["--sort-by=usable", jsonFile.path]);
   test("script-sort-by-req",
-       ["--ignore-reports", "--sort-by=req", "--no-fix-stacks", jsonFile.path]);
+       ["--sort-by=req", "--no-fix-stacks", jsonFile.path]);
   test("script-sort-by-slop",
-       ["--ignore-reports", "--sort-by=slop", jsonFile.path]);
+       ["--sort-by=slop", jsonFile.path]);
 
   // This file has several real stack traces taken from Firefox execution, each
   // of which tests a different allocator function (or functions).
   jsonFile = FileUtils.getFile("CurWorkD", ["script-ignore-alloc-fns.json"]);
   test("script-ignore-alloc-fns",
-       ["--ignore-reports", "--ignore-alloc-fns", jsonFile.path]);
+       ["--ignore-alloc-fns", jsonFile.path]);
 
-  // This tests diffs. The first invocation has no options, the second has
-  // several.
-  jsonFile  = FileUtils.getFile("CurWorkD", ["script-diff1.json"]);
-  jsonFile2 = FileUtils.getFile("CurWorkD", ["script-diff2.json"]);
-  test("script-diff-basic",
+  // This tests "live"-mode diffs.
+  jsonFile  = FileUtils.getFile("CurWorkD", ["script-diff-live1.json"]);
+  jsonFile2 = FileUtils.getFile("CurWorkD", ["script-diff-live2.json"]);
+  test("script-diff-live",
        [jsonFile.path, jsonFile2.path]);
-  test("script-diff-options",
-       ["--ignore-reports", jsonFile.path, jsonFile2.path]);
+
+  // This tests "dark-matter"-mode diffs.
+  jsonFile  = FileUtils.getFile("CurWorkD", ["script-diff-dark-matter1.json"]);
+  jsonFile2 = FileUtils.getFile("CurWorkD", ["script-diff-dark-matter2.json"]);
+  test("script-diff-dark-matter",
+       [jsonFile.path, jsonFile2.path]);
 }
 
--- a/memory/replace/dmd/test/xpcshell.ini
+++ b/memory/replace/dmd/test/xpcshell.ini
@@ -1,31 +1,31 @@
 [DEFAULT]
 support-files =
-  full-heap-empty-expected.txt
-  full-heap-unsampled1-expected.txt
-  full-heap-unsampled2-expected.txt
-  full-heap-sampled-expected.txt
-  full-reports-empty-expected.txt
-  full-reports-unsampled1-expected.txt
-  full-reports-unsampled2-expected.txt
-  full-reports-sampled-expected.txt
+  full-empty-live-expected.txt
+  full-empty-dark-matter-expected.txt
+  full-unsampled1-live-expected.txt
+  full-unsampled1-dark-matter-expected.txt
+  full-unsampled2-dark-matter-expected.txt
+  full-sampled-live-expected.txt
   script-max-frames.json
   script-max-frames-8-expected.txt
   script-max-frames-3-expected.txt
   script-max-frames-1-expected.txt
   script-sort-by.json.gz
   script-sort-by-usable-expected.txt
   script-sort-by-req-expected.txt
   script-sort-by-slop-expected.txt
   script-ignore-alloc-fns.json
   script-ignore-alloc-fns-expected.txt
-  script-diff1.json
-  script-diff2.json
-  script-diff-basic-expected.txt
-  script-diff-options-expected.txt
+  script-diff-live1.json
+  script-diff-live2.json
+  script-diff-live-expected.txt
+  script-diff-dark-matter1.json
+  script-diff-dark-matter2.json
+  script-diff-dark-matter-expected.txt
 
 # Bug 1077230 explains why this test is disabled on Mac 10.6.
 # Bug 1076446 comment 20 explains why this test is only enabled on Windows 5.1
 # (WinXP) and 6.1 (Win7), but not 6.2 (Win8).
 [test_dmd.js]
 dmd = true
 run-if = os == 'linux' || os == 'mac' && os_version != '10.6' || os == 'win' && (os_version == '5.1' || os_version == '6.1')
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -888,24 +888,26 @@ class RunProgram(MachCommandBase):
     # "continue" to (safely) resume execution.  There are ways to implement
     # automatic resuming; see the bug.
     @CommandArgument('--slowscript', action='store_true', group='debugging',
         help='Do not set the JS_DISABLE_SLOW_SCRIPT_SIGNALS env variable; when not set, recoverable but misleading SIGSEGV instances may occur in Ion/Odin JIT code.')
 
     @CommandArgumentGroup('DMD')
     @CommandArgument('--dmd', action='store_true', group='DMD',
         help='Enable DMD. The following arguments have no effect without this.')
+    @CommandArgument('--mode', choices=['live', 'dark-matter'], group='DMD',
+         help='Profiling mode. The default is \'dark-matter\'.')
     @CommandArgument('--sample-below', default=None, type=str, group='DMD',
         help='Sample blocks smaller than this. Use 1 for no sampling. The default is 4093.')
     @CommandArgument('--max-frames', default=None, type=str, group='DMD',
         help='The maximum depth of stack traces. The default and maximum is 24.')
     @CommandArgument('--show-dump-stats', action='store_true', group='DMD',
         help='Show stats when doing dumps.')
     def run(self, params, remote, background, noprofile, debug, debugger,
-        debugparams, slowscript, dmd, sample_below, max_frames,
+        debugparams, slowscript, dmd, mode, sample_below, max_frames,
         show_dump_stats):
 
         try:
             binpath = self.get_binary_path('app')
         except Exception as e:
             print("It looks like your program isn't built.",
                 "You can run |mach build| to build it.")
             print(e)
@@ -962,16 +964,18 @@ class RunProgram(MachCommandBase):
             extra_env['MOZ_CRASHREPORTER_DISABLE'] = '1'
 
             # Prepend the debugger args.
             args = [self.debuggerInfo.path] + self.debuggerInfo.args + args
 
         if dmd:
             dmd_params = []
 
+            if mode:
+                dmd_params.append('--mode=' + mode)
             if sample_below:
                 dmd_params.append('--sample-below=' + sample_below)
             if max_frames:
                 dmd_params.append('--max-frames=' + max_frames)
             if show_dump_stats:
                 dmd_params.append('--show-dump-stats=yes')
 
             if dmd_params: