Bug 1253512 (part 1) - Overhaul DMD's "sampling". r=erahm.
authorNicholas Nethercote <nnethercote@mozilla.com>
Wed, 24 Feb 2016 14:42:22 +1100
changeset 290774 ce540d9af1cbd53127811c72bdbe0fd278eb224a
parent 290773 d9f4607812aec2639be5a70963edc41a7f7600a2
child 290775 5f7d9726c2ffb569007e416ca940df658a7b8500
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerserahm
bugs1253512
milestone48.0a1
Bug 1253512 (part 1) - Overhaul DMD's "sampling". r=erahm. DMD currently uses a very hacky form of "sampling" by default to avoid recording stack traces for all blocks. This makes DMD run faster than when it records all stack traces. This patch changes the sampling method used; in fact, it avoids "sampling" at all. The existence of all heap blocks is now recorded exactly, but by default we only record an allocation stack for each heap block if a Bernoulli trial succeeds. This choice works well because getting the stack trace is ~100x slower than recording the block's existence. Overall, this approach is simpler and it also gives better output -- the choice of which blocks to record allocation stacks for is mathematically sound, no stack trace gets blamed for allocations it didn't do, and block counts and sizes are now always exact. Other specific things changed. - All notion of sampling is removed from the various data structures. - The --sample-below option is removed in favour of --stacks={partial,full}. - The format of the JSON output file has changed. - The names of various test files have changed to reflect concept changes.
memory/replace/dmd/DMD.cpp
memory/replace/dmd/DMD.h
memory/replace/dmd/block_analyzer.py
memory/replace/dmd/dmd.py
memory/replace/dmd/test/SmokeDMD.cpp
memory/replace/dmd/test/basic-scan-32-expected.txt
memory/replace/dmd/test/basic-scan-64-expected.txt
memory/replace/dmd/test/complete-empty-cumulative-expected.txt
memory/replace/dmd/test/complete-empty-dark-matter-expected.txt
memory/replace/dmd/test/complete-empty-live-expected.txt
memory/replace/dmd/test/complete-full1-dark-matter-expected.txt
memory/replace/dmd/test/complete-full1-live-expected.txt
memory/replace/dmd/test/complete-full2-cumulative-expected.txt
memory/replace/dmd/test/complete-full2-dark-matter-expected.txt
memory/replace/dmd/test/complete-partial-live-expected.txt
memory/replace/dmd/test/full-empty-cumulative-expected.txt
memory/replace/dmd/test/full-empty-dark-matter-expected.txt
memory/replace/dmd/test/full-empty-live-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-cumulative-expected.txt
memory/replace/dmd/test/full-unsampled2-dark-matter-expected.txt
memory/replace/dmd/test/scan-test.py
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-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-num-blocks-expected.txt
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
@@ -29,16 +29,17 @@
 
 #include "nscore.h"
 #include "mozilla/StackWalk.h"
 
 #include "js/HashTable.h"
 #include "js/Vector.h"
 
 #include "mozilla/Assertions.h"
+#include "mozilla/FastBernoulliTrial.h"
 #include "mozilla/HashFunctions.h"
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/JSONWriter.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MemoryReporting.h"
 
 // CodeAddressService is defined entirely in the header, so this does not make
 // DMD depend on XPCOM's object file.
@@ -219,34 +220,32 @@ public:
     strcpy(s, aStr);
     return s;
   }
 
   template <class T>
   static T* new_()
   {
     void* mem = malloc_(sizeof(T));
-    ExitOnFailure(mem);
     return new (mem) T;
   }
 
   template <class T, typename P1>
-  static T* new_(P1 p1)
+  static T* new_(P1 aP1)
   {
     void* mem = malloc_(sizeof(T));
-    ExitOnFailure(mem);
-    return new (mem) T(p1);
+    return new (mem) T(aP1);
   }
 
   template <class T>
-  static void delete_(T *p)
+  static void delete_(T* aPtr)
   {
-    if (p) {
-      p->~T();
-      InfallibleAllocPolicy::free_(p);
+    if (aPtr) {
+      aPtr->~T();
+      InfallibleAllocPolicy::free_(aPtr);
     }
   }
 
   static void reportAllocOverflow() { ExitOnFailure(nullptr); }
   bool checkSimulatedOOM() const { return true; }
 };
 
 // This is only needed because of the |const void*| vs |void*| arg mismatch.
@@ -349,65 +348,79 @@ class Options
   // 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
+  enum class 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.
+    // For each live block, this mode outputs: size (usable and slop) and
+    // (possibly) and allocation stack. 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,
 
     // Like "Live", but also outputs the same data for dead blocks. This mode
     // does cumulative heap profiling, which is good for identifying where large
-    // amounts of short-lived allocations occur.
+    // amounts of short-lived allocations ("heap churn") occur.
     Cumulative,
 
     // Like "Live", but this mode also outputs for each live block the address
     // of the block and the values contained in the blocks. This mode is useful
     // for investigating leaks, by helping to figure out which blocks refer to
-    // other blocks. This mode disables sampling.
+    // other blocks. This mode force-enables full stacks coverage.
     Scan
   };
 
+  // With full stacks, every heap block gets a stack trace recorded for it.
+  // This is complete but slow.
+  //
+  // With partial stacks, not all heap blocks will get a stack trace recorded.
+  // A Bernoulli trial (see mfbt/FastBernoulliTrial.h for details) is performed
+  // for each heap block to decide if it gets one. Because bigger heap blocks
+  // are more likely to get a stack trace, even though most heap *blocks* won't
+  // get a stack trace, most heap *bytes* will.
+  enum class Stacks
+  {
+    Full,
+    Partial
+  };
+
   char* mDMDEnvVar;   // a saved copy, for later printing
 
   Mode mMode;
-  NumOption<size_t> mSampleBelowSize;
+  Stacks mStacks;
   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; }
-  bool IsCumulativeMode() const { return mMode == Cumulative; }
-  bool IsScanMode()       const { return mMode == Scan; }
+  bool IsLiveMode()       const { return mMode == Mode::Live; }
+  bool IsDarkMatterMode() const { return mMode == Mode::DarkMatter; }
+  bool IsCumulativeMode() const { return mMode == Mode::Cumulative; }
+  bool IsScanMode()       const { return mMode == Mode::Scan; }
 
   const char* ModeString() const;
 
   const char* DMDEnvVar() const { return mDMDEnvVar; }
 
-  size_t SampleBelowSize() const { return mSampleBelowSize.mActual; }
+  bool DoFullStacks()      const { return mStacks == Stacks::Full; }
   size_t ShowDumpStats()   const { return mShowDumpStats; }
 };
 
 static Options *gOptions;
 
 //---------------------------------------------------------------------------
 // The global lock
 //---------------------------------------------------------------------------
@@ -846,22 +859,19 @@ public:
 
 // 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;
+  // The stack trace where this block was allocated, or nullptr if we didn't
+  // record one.
+  const StackTrace* const mAllocStackTrace;
 
   // 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.
@@ -869,48 +879,35 @@ class LiveBlock
   // |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)
+            const StackTrace* aAllocStackTrace)
     : mPtr(aPtr)
     , mReqSize(aReqSize)
-    , mAllocStackTrace_mIsSampled(aAllocStackTrace, aIsSampled)
+    , mAllocStackTrace(aAllocStackTrace)
     , mReportStackTrace_mReportedOnAlloc()     // all fields get zeroed
-  {
-    MOZ_ASSERT(aAllocStackTrace);
-  }
+  {}
 
   const void* Address() const { return mPtr; }
 
   size_t ReqSize() const { return mReqSize; }
 
-  // Sampled blocks always have zero slop.
   size_t SlopSize() const
   {
-    return IsSampled() ? 0 : MallocSizeOf(mPtr) - mReqSize;
-  }
-
-  size_t UsableSize() const
-  {
-    return IsSampled() ? mReqSize : MallocSizeOf(mPtr);
-  }
-
-  bool IsSampled() const
-  {
-    return mAllocStackTrace_mIsSampled.Tag();
+    return MallocSizeOf(mPtr) - mReqSize;
   }
 
   const StackTrace* AllocStackTrace() const
   {
-    return mAllocStackTrace_mIsSampled.Ptr();
+    return mAllocStackTrace;
   }
 
   const StackTrace* ReportStackTrace1() const
   {
     MOZ_ASSERT(gOptions->IsDarkMatterMode());
     return mReportStackTrace_mReportedOnAlloc[0].Ptr();
   }
 
@@ -929,24 +926,25 @@ public:
   bool ReportedOnAlloc2() const
   {
     MOZ_ASSERT(gOptions->IsDarkMatterMode());
     return mReportStackTrace_mReportedOnAlloc[1].Tag();
   }
 
   void AddStackTracesToTable(StackTraceSet& aStackTraces) const
   {
-    MOZ_ALWAYS_TRUE(aStackTraces.put(AllocStackTrace()));  // never null
+    if (AllocStackTrace()) {
+      MOZ_ALWAYS_TRUE(aStackTraces.put(AllocStackTrace()));
+    }
     if (gOptions->IsDarkMatterMode()) {
-      const StackTrace* st;
-      if ((st = ReportStackTrace1())) {     // may be null
-        MOZ_ALWAYS_TRUE(aStackTraces.put(st));
+      if (ReportStackTrace1()) {
+        MOZ_ALWAYS_TRUE(aStackTraces.put(ReportStackTrace1()));
       }
-      if ((st = ReportStackTrace2())) {     // may be null
-        MOZ_ALWAYS_TRUE(aStackTraces.put(st));
+      if (ReportStackTrace2()) {
+        MOZ_ALWAYS_TRUE(aStackTraces.put(ReportStackTrace2()));
       }
     }
   }
 
   uint32_t NumReports() const
   {
     MOZ_ASSERT(gOptions->IsDarkMatterMode());
     if (ReportStackTrace2()) {
@@ -1008,75 +1006,66 @@ typedef js::HashSet<LiveBlock, LiveBlock
 static LiveBlockTable* gLiveBlockTable = nullptr;
 
 // A freed heap block.
 class DeadBlock
 {
   const size_t mReqSize;    // size requested
   const size_t mSlopSize;   // slop above size requested
 
-  // Ptr: |mAllocStackTrace| - stack trace where this block was allocated.
-  // Tag bit 0: |mIsSampled| - was this block sampled? (if so, slop == 0).
-  TaggedPtr<const StackTrace* const>
-    mAllocStackTrace_mIsSampled;
+  // The stack trace where this block was allocated.
+  const StackTrace* const mAllocStackTrace;
 
 public:
   DeadBlock()
     : mReqSize(0)
     , mSlopSize(0)
-    , mAllocStackTrace_mIsSampled(nullptr, 0)
+    , mAllocStackTrace(nullptr)
   {}
 
   explicit DeadBlock(const LiveBlock& aLb)
     : mReqSize(aLb.ReqSize())
     , mSlopSize(aLb.SlopSize())
-    , mAllocStackTrace_mIsSampled(aLb.AllocStackTrace(), aLb.IsSampled())
+    , mAllocStackTrace(aLb.AllocStackTrace())
   {
     MOZ_ASSERT(AllocStackTrace());
-    MOZ_ASSERT_IF(IsSampled(), SlopSize() == 0);
   }
 
   ~DeadBlock() {}
 
   size_t ReqSize()    const { return mReqSize; }
   size_t SlopSize()   const { return mSlopSize; }
-  size_t UsableSize() const { return mReqSize + mSlopSize; }
-
-  bool IsSampled() const
-  {
-    return mAllocStackTrace_mIsSampled.Tag();
-  }
 
   const StackTrace* AllocStackTrace() const
   {
-    return mAllocStackTrace_mIsSampled.Ptr();
+    return mAllocStackTrace;
   }
 
   void AddStackTracesToTable(StackTraceSet& aStackTraces) const
   {
-    MOZ_ALWAYS_TRUE(aStackTraces.put(AllocStackTrace()));  // never null
+    if (AllocStackTrace()) {
+      MOZ_ALWAYS_TRUE(aStackTraces.put(AllocStackTrace()));
+    }
   }
 
   // Hash policy.
 
   typedef DeadBlock Lookup;
 
   static uint32_t hash(const DeadBlock& aB)
   {
     return mozilla::HashGeneric(aB.ReqSize(),
                                 aB.SlopSize(),
-                                aB.IsSampled(),
                                 aB.AllocStackTrace());
   }
 
   static bool match(const DeadBlock& aA, const DeadBlock& aB)
   {
     return aA.ReqSize() == aB.ReqSize() &&
            aA.SlopSize() == aB.SlopSize() &&
-           aA.IsSampled() == aB.IsSampled() &&
            aA.AllocStackTrace() == aB.AllocStackTrace();
   }
 };
 
 // For each unique DeadBlock value we store a count of how many actual dead
 // blocks have that value.
 typedef js::HashMap<DeadBlock, size_t, DeadBlock, InfallibleAllocPolicy>
   DeadBlockTable;
@@ -1139,49 +1128,55 @@ GCStackTraces()
   // this GC finished.
   gGCStackTraceTableWhenSizeExceeds = 2 * gStackTraceTable->count();
 }
 
 //---------------------------------------------------------------------------
 // malloc/free callbacks
 //---------------------------------------------------------------------------
 
-static size_t gSmallBlockActualSizeCounter = 0;
+static FastBernoulliTrial* gBernoulli;
+
+// In testing, a probability of 0.003 resulted in ~25% of heap blocks getting
+// a stack trace and ~80% of heap bytes getting a stack trace. (This is
+// possible because big heap blocks are more likely to get a stack trace.)
+//
+// We deliberately choose not to give the user control over this probability
+// (other than effectively setting it to 1 via --stacks=full) because it's
+// quite inscrutable and generally the user just wants "faster and imprecise"
+// or "slower and precise".
+//
+// The random number seeds are arbitrary and were obtained from random.org. If
+// you change them you'll need to change the tests as well, because their
+// expected output is based on the particular sequence of trial results that we
+// get with these seeds.
+static void
+ResetBernoulli()
+{
+  new (gBernoulli) FastBernoulliTrial(0.003, 0x8e26eeee166bc8ca,
+                                             0x56820f304a9c9ae0);
+}
 
 static void
 AllocCallback(void* aPtr, size_t aReqSize, Thread* aT)
 {
   if (!aPtr) {
     return;
   }
 
   AutoLockState lock;
   AutoBlockIntercepts block(aT);
 
   size_t actualSize = gMallocTable->malloc_usable_size(aPtr);
-  size_t sampleBelowSize = gOptions->SampleBelowSize();
 
-  if (actualSize < sampleBelowSize) {
-    // If this allocation is smaller than the sample-below size, increment the
-    // cumulative counter.  Then, if that counter now exceeds the sample size,
-    // blame this allocation for |sampleBelowSize| bytes.  This precludes the
-    // measurement of slop.
-    gSmallBlockActualSizeCounter += actualSize;
-    if (gSmallBlockActualSizeCounter >= sampleBelowSize) {
-      gSmallBlockActualSizeCounter -= sampleBelowSize;
-
-      LiveBlock b(aPtr, sampleBelowSize, StackTrace::Get(aT),
-                  /* isSampled */ true);
-      MOZ_ALWAYS_TRUE(gLiveBlockTable->putNew(aPtr, b));
-    }
-  } else {
-    // If this block size is larger than the sample size, record it exactly.
-    LiveBlock b(aPtr, aReqSize, StackTrace::Get(aT), /* isSampled */ false);
-    MOZ_ALWAYS_TRUE(gLiveBlockTable->putNew(aPtr, b));
-  }
+  // We may or may not record the allocation stack trace, depending on the
+  // options and the outcome of a Bernoulli trial.
+  bool getTrace = gOptions->DoFullStacks() || gBernoulli->trial(actualSize);
+  LiveBlock b(aPtr, aReqSize, getTrace ? StackTrace::Get(aT) : nullptr);
+  MOZ_ALWAYS_TRUE(gLiveBlockTable->putNew(aPtr, b));
 }
 
 static void
 FreeCallback(void* aPtr, Thread* aT, DeadBlock* aDeadBlock)
 {
   if (!aPtr) {
     return;
   }
@@ -1191,19 +1186,18 @@ FreeCallback(void* aPtr, Thread* aT, Dea
 
   if (LiveBlockTable::Ptr lb = gLiveBlockTable->lookup(aPtr)) {
     if (gOptions->IsCumulativeMode()) {
       // Copy it out so it can be added to the dead block list later.
       new (aDeadBlock) DeadBlock(*lb);
     }
     gLiveBlockTable->remove(lb);
   } else {
-    // We have no record of the block. Do nothing. Either:
-    // - We're sampling and we skipped this block. This is likely.
-    // - It's a bogus pointer.
+    // We have no record of the block. It must be a bogus pointer, or one that
+    // DMD wasn't able to see allocated. This should be extremely rare.
   }
 
   if (gStackTraceTable->count() > gGCStackTraceTableWhenSizeExceeds) {
     GCStackTraces();
   }
 }
 
 //---------------------------------------------------------------------------
@@ -1408,29 +1402,21 @@ Options::GetBool(const char* aArg, const
     if (strcmp(optionValue, "no") == 0) {
       *aValue = false;
       return true;
     }
   }
   return false;
 }
 
-// The sample-below default is a prime number close to 4096.
-// - 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(aDMDEnvVar ? InfallibleAllocPolicy::strdup_(aDMDEnvVar)
                           : nullptr)
-  , mMode(DarkMatter)
-  , mSampleBelowSize(4093, 100 * 100 * 1000)
+  , mMode(Mode::DarkMatter)
+  , mStacks(Stacks::Partial)
   , mShowDumpStats(false)
 {
   // It's no longer necessary to set the DMD env var to "1" if you want default
   // options (you can leave it undefined) but we still accept "1" for
   // backwards compatibility.
   char* e = mDMDEnvVar;
   if (e && strcmp(e, "1") != 0) {
     bool isEnd = false;
@@ -1448,30 +1434,30 @@ Options::Options(const char* aDMDEnvVar)
       while (!isspace(*e) && *e != '\0') {
         e++;
       }
       char replacedChar = *e;
       isEnd = replacedChar == '\0';
       *e = '\0';
 
       // Handle arg
-      long myLong;
       bool myBool;
       if (strcmp(arg, "--mode=live") == 0) {
-        mMode = Options::Live;
+        mMode = Mode::Live;
       } else if (strcmp(arg, "--mode=dark-matter") == 0) {
-        mMode = Options::DarkMatter;
+        mMode = Mode::DarkMatter;
       } else if (strcmp(arg, "--mode=cumulative") == 0) {
-        mMode = Options::Cumulative;
+        mMode = Mode::Cumulative;
       } else if (strcmp(arg, "--mode=scan") == 0) {
-        mMode = Options::Scan;
+        mMode = Mode::Scan;
 
-      } else if (GetLong(arg, "--sample-below", 1, mSampleBelowSize.mMax,
-                 &myLong)) {
-        mSampleBelowSize.mActual = myLong;
+      } else if (strcmp(arg, "--stacks=full") == 0) {
+        mStacks = Stacks::Full;
+      } else if (strcmp(arg, "--stacks=partial") == 0) {
+        mStacks = Stacks::Partial;
 
       } else if (GetBool(arg, "--show-dump-stats", &myBool)) {
         mShowDumpStats = myBool;
 
       } else if (strcmp(arg, "") == 0) {
         // This can only happen if there is trailing whitespace.  Ignore.
         MOZ_ASSERT(isEnd);
 
@@ -1479,41 +1465,41 @@ Options::Options(const char* aDMDEnvVar)
         BadArg(arg);
       }
 
       // Undo the temporary isolation.
       *e = replacedChar;
     }
   }
 
-  if (mMode == Options::Scan) {
-    mSampleBelowSize.mActual = 1;
+  if (mMode == Mode::Scan) {
+    mStacks = Stacks::Full;
   }
 }
 
 void
 Options::BadArg(const char* aArg)
 {
   StatusMsg("\n");
   StatusMsg("Bad entry in the $DMD environment variable: '%s'.\n", aArg);
   StatusMsg("See the output of |mach help run| for the allowed options.\n");
   exit(1);
 }
 
 const char*
 Options::ModeString() const
 {
   switch (mMode) {
-  case Live:
+  case Mode::Live:
     return "live";
-  case DarkMatter:
+  case Mode::DarkMatter:
     return "dark-matter";
-  case Cumulative:
+  case Mode::Cumulative:
     return "cumulative";
-  case Scan:
+  case Mode::Scan:
     return "scan";
   default:
     MOZ_ASSERT(false);
     return "(unknown DMD mode)";
   }
 }
 
 //---------------------------------------------------------------------------
@@ -1558,17 +1544,19 @@ Init(const malloc_table_t* aMallocTable)
   // just call MozStackWalk, because that calls StackWalkInitCriticalAddress().
   // See the comment above StackWalkInitCriticalAddress() for more details.
   (void)MozStackWalk(NopStackWalkCallback, /* skipFrames */ 0,
                      /* maxFrames */ 1, nullptr, 0, nullptr);
 #endif
 
   gStateLock = InfallibleAllocPolicy::new_<Mutex>();
 
-  gSmallBlockActualSizeCounter = 0;
+  gBernoulli = (FastBernoulliTrial*)
+    InfallibleAllocPolicy::malloc_(sizeof(FastBernoulliTrial));
+  ResetBernoulli();
 
   DMD_CREATE_TLS_INDEX(gTlsIndex);
 
   {
     AutoLockState lock;
 
     gStackTraceTable = InfallibleAllocPolicy::new_<StackTraceTable>();
     MOZ_ALWAYS_TRUE(gStackTraceTable->init(8192));
@@ -1601,20 +1589,19 @@ ReportHelper(const void* aPtr, bool aRep
   Thread* t = Thread::Fetch();
 
   AutoBlockIntercepts block(t);
   AutoLockState lock;
 
   if (LiveBlockTable::Ptr p = gLiveBlockTable->lookup(aPtr)) {
     p->Report(t, aReportedOnAlloc);
   } else {
-    // We have no record of the block.  Do nothing.  Either:
-    // - We're sampling and we skipped this block.  This is likely.
-    // - It's a bogus pointer.  This is unlikely because Report() is almost
-    //   always called in conjunction with a malloc_size_of-style function.
+    // We have no record of the block. It must be a bogus pointer. This should
+    // be extremely rare because Report() is almost always called in
+    // conjunction with a malloc_size_of-style function.
   }
 }
 
 void
 DMDFuncs::Report(const void* aPtr)
 {
   ReportHelper(aPtr, /* onAlloc */ false);
 }
@@ -1627,17 +1614,17 @@ DMDFuncs::ReportOnAlloc(const void* aPtr
 
 //---------------------------------------------------------------------------
 // DMD output
 //---------------------------------------------------------------------------
 
 // The version number of the output format. Increment this if you make
 // backwards-incompatible changes to the format. See DMD.h for the version
 // history.
-static const int kOutputVersionNumber = 4;
+static const int kOutputVersionNumber = 5;
 
 // 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
@@ -1724,27 +1711,23 @@ public:
   }
 
   size_t sizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
   {
     return mIdMap.sizeOfExcludingThis(aMallocSizeOf);
   }
 
 private:
-  // This function converts an integer to base-32. |aBuf| must have space for at
-  // least eight chars, which is the space needed to hold 'Dffffff' (including
-  // the terminating null char), which is the base-32 representation of
-  // 0xffffffff.
-  //
-  // We use base-32 values for indexing into the traceTable and the frameTable,
-  // for the following reasons.
+  // This function converts an integer to base-32. We use base-32 values for
+  // indexing into the traceTable and the frameTable, for the following reasons.
   //
   // - Base-32 gives more compact indices than base-16.
   //
-  // - 32 is a power-of-two, which makes the necessary div/mod calculations fast.
+  // - 32 is a power-of-two, which makes the necessary div/mod calculations
+  //   fast.
   //
   // - We can (and do) choose non-numeric digits for base-32. When
   //   inspecting/debugging the JSON output, non-numeric indices are easier to
   //   search for than numeric indices.
   //
   char* Base32(uint32_t aN)
   {
     static const char digits[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef";
@@ -1760,16 +1743,20 @@ private:
       aN /= 32;
     } while (aN);
 
     return b;
   }
 
   PointerIdMap mIdMap;
   uint32_t mNextId;
+
+  // |mIdBuf| must have space for at least eight chars, which is the space
+  // needed to hold 'Dffffff' (including the terminating null char), which is
+  // the base-32 representation of 0xffffffff.
   static const size_t kIdBufLen = 16;
   char mIdBuf[kIdBufLen];
 };
 
 // Helper class for converting a pointer value to a string.
 class ToStringConverter
 {
 public:
@@ -1781,18 +1768,16 @@ public:
 
 private:
   char kPtrBuf[32];
 };
 
 static void
 WriteBlockContents(JSONWriter& aWriter, const LiveBlock& aBlock)
 {
-  MOZ_ASSERT(!aBlock.IsSampled(), "Sampled blocks do not have accurate sizes");
-
   size_t numWords = aBlock.ReqSize() / sizeof(uintptr_t*);
   if (numWords == 0) {
     return;
   }
 
   aWriter.StartArrayProperty("contents", aWriter.SingleLineStyle);
   {
     const uintptr_t** block = (const uintptr_t**)aBlock.Address();
@@ -1834,17 +1819,16 @@ AnalyzeImpl(UniquePtr<JSONWriteFunc> aWr
       const char* var = gOptions->DMDEnvVar();
       if (var) {
         writer.StringProperty("dmdEnvVar", var);
       } else {
         writer.NullProperty("dmdEnvVar");
       }
 
       writer.StringProperty("mode", gOptions->ModeString());
-      writer.IntProperty("sampleBelowSize", gOptions->SampleBelowSize());
     }
     writer.EndObject();
 
     StatusMsg("  Constructing the heap block list...\n");
 
     ToIdStringConverter isc;
     ToStringConverter sc;
 
@@ -1852,27 +1836,29 @@ AnalyzeImpl(UniquePtr<JSONWriteFunc> aWr
     {
       // Live blocks.
       for (auto r = gLiveBlockTable->all(); !r.empty(); r.popFront()) {
         const LiveBlock& b = r.front();
         b.AddStackTracesToTable(usedStackTraces);
 
         writer.StartObjectElement(writer.SingleLineStyle);
         {
-          if (!b.IsSampled()) {
-            if (gOptions->IsScanMode()) {
-              writer.StringProperty("addr", sc.ToPtrString(b.Address()));
-              WriteBlockContents(writer, b);
-            }
-            writer.IntProperty("req", b.ReqSize());
-            if (b.SlopSize() > 0) {
-              writer.IntProperty("slop", b.SlopSize());
-            }
+          if (gOptions->IsScanMode()) {
+            writer.StringProperty("addr", sc.ToPtrString(b.Address()));
+            WriteBlockContents(writer, b);
           }
-          writer.StringProperty("alloc", isc.ToIdString(b.AllocStackTrace()));
+          writer.IntProperty("req", b.ReqSize());
+          if (b.SlopSize() > 0) {
+            writer.IntProperty("slop", b.SlopSize());
+          }
+
+          if (b.AllocStackTrace()) {
+            writer.StringProperty("alloc", isc.ToIdString(b.AllocStackTrace()));
+          }
+
           if (gOptions->IsDarkMatterMode() && b.NumReports() > 0) {
             writer.StartArrayProperty("reps");
             {
               if (b.ReportStackTrace1()) {
                 writer.StringElement(isc.ToIdString(b.ReportStackTrace1()));
               }
               if (b.ReportStackTrace2()) {
                 writer.StringElement(isc.ToIdString(b.ReportStackTrace2()));
@@ -1889,23 +1875,23 @@ AnalyzeImpl(UniquePtr<JSONWriteFunc> aWr
         const DeadBlock& b = r.front().key();
         b.AddStackTracesToTable(usedStackTraces);
 
         size_t num = r.front().value();
         MOZ_ASSERT(num > 0);
 
         writer.StartObjectElement(writer.SingleLineStyle);
         {
-          if (!b.IsSampled()) {
-            writer.IntProperty("req", b.ReqSize());
-            if (b.SlopSize() > 0) {
-              writer.IntProperty("slop", b.SlopSize());
-            }
+          writer.IntProperty("req", b.ReqSize());
+          if (b.SlopSize() > 0) {
+            writer.IntProperty("slop", b.SlopSize());
           }
-          writer.StringProperty("alloc", isc.ToIdString(b.AllocStackTrace()));
+          if (b.AllocStackTrace()) {
+            writer.StringProperty("alloc", isc.ToIdString(b.AllocStackTrace()));
+          }
 
           if (num > 1) {
             writer.IntProperty("num", num);
           }
         }
         writer.EndObject();
       }
     }
@@ -2040,13 +2026,16 @@ DMDFuncs::ResetEverything(const char* aO
 
   // Reset options.
   InfallibleAllocPolicy::delete_(gOptions);
   gOptions = InfallibleAllocPolicy::new_<Options>(aOptions);
 
   // Clear all existing blocks.
   gLiveBlockTable->clear();
   gDeadBlockTable->clear();
-  gSmallBlockActualSizeCounter = 0;
+
+  // Reset gBernoulli to a deterministic state. (Its current state depends on
+  // all previous trials.)
+  ResetBernoulli();
 }
 
 } // namespace dmd
 } // namespace mozilla
--- a/memory/replace/dmd/DMD.h
+++ b/memory/replace/dmd/DMD.h
@@ -149,94 +149,81 @@ ClearReports()
 // 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 history:
-//   // - 1: Bug 1044709. The original format.
-//   // - 2: Bug 1094552. Added the "mode" property under "invocation".
-//   // - 3: Bug 1100851. The "dmdEnvVar" property under "invocation" can now
-//   //      be |null| if the |DMD| environment variable is not defined.
-//   // - 4: Bug 1121830. Added the "num" property in "blockList" object.
-//   //
-//   "version": 4,
+//   // - 1: Bug 1044709
+//   // - 2: Bug 1094552
+//   // - 3: Bug 1100851
+//   // - 4: Bug 1121830
+//   // - 5: Bug 1253512
+//   "version": 5,
 //
 //   // Information about how DMD was invoked. A mandatory object.
 //   "invocation": {
-//     // The contents of the $DMD environment variable. A string, or |null| is
+//     // The contents of the $DMD environment variable. A string, or |null| if
 //     // $DMD is undefined.
 //     "dmdEnvVar": "--mode=dark-matter",
 //
 //     // The profiling mode. A mandatory string taking one of the following
-//     // values: "live", "dark-matter", "cumulative".
+//     // values: "live", "dark-matter", "cumulative", "scan".
 //     "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.
+//     // An example of a heap block.
 //     {
-//       // Requested size, in bytes. In non-sampled blocks this is a
-//       // mandatory integer. In sampled blocks this is not present, and the
-//       // requested size is equal to the "sampleBelowSize" value. Therefore,
-//       // the block is sampled if and only if this property is absent.
+//       // Requested size, in bytes. This is a mandatory integer.
 //       "req": 3584,
 //
 //       // Requested slop size, in bytes. This is mandatory if it is non-zero,
-//       // but omitted otherwise. Because sampled blocks never have slop, this
-//       // property is never present for non-sampled blocks.
+//       // but omitted otherwise.
 //       "slop": 512,
 //
-//       // The stack trace at which the block was allocated. A mandatory
-//       // string which indexes into the "traceTable" object.
+//       // The stack trace at which the block was allocated. An optional
+//       // string that indexes into the "traceTable" object. If omitted, no
+//       // allocation stack trace was recorded for the block.
 //       "alloc": "A",
 //
+//       // One or more stack traces at which this heap block was reported by a
+//       // 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": ["B"]
+//
 //       // The number of heap blocks with exactly the above properties. This
 //       // is mandatory if it is greater than one, but omitted otherwise.
 //       // (Blocks with identical properties don't have to be aggregated via
 //       // this property, but it can greatly reduce output file size.)
 //       "num": 5,
 //
-//       // The address of the block. This is mandatory in "scan" mode for
-//       // non-sampled blocks, but omitted otherwise.
+//       // The address of the block. This is mandatory in "scan" mode, but
+//       // omitted otherwise.
 //       "addr": "4e4e4e4e",
 //
 //       // The contents of the block, read one word at a time. This is
-//       // mandatory in "scan" mode for non-sampled blocks at least one word
-//       // long, but omitted otherwise.
+//       // mandatory in "scan" mode for blocks at least one word long, but
+//       // omitted otherwise.
 //       "contents": ["0", "6", "7f7f7f7f", "0"]
-//     },
-//
-//     // 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 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": {
 //     // Each property corresponds to a stack trace mentioned in the "blocks"
 //     // object. Each element is an index into the "frameTable" object.
 //     "A": ["D", "E"],
-//     "B": ["D", "F"],
-//     "C": ["G", "H"]
+//     "B": ["F", "G"]
 //   },
 //
 //   // The stack frames referenced by the "traceTable" object. The
 //   // descriptions can be quite long, so they are stored separately from the
 //   // "traceTable" object so that each one only has to be written once.
 //   // This could also be an array, but again, making it an object makes it
 //   // easier to see which frames correspond to which references in the
 //   // "traceTable" object.
@@ -247,18 +234,17 @@ ClearReports()
 //     // scripts (e.g. fix_linux_stack.py), which require a frame number at
 //     // the start. Because each stack frame description in this table can
 //     // be shared between multiple stack traces, we use a dummy value of
 //     // #00. The proper frame number can be reconstructed later by scripts
 //     // 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)"
+//     "G": "#00: quux (Quux.cpp:456)"
 //   }
 // }
 //
 // Implementation note: normally, this function 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
--- a/memory/replace/dmd/block_analyzer.py
+++ b/memory/replace/dmd/block_analyzer.py
@@ -12,17 +12,17 @@
 import json
 import gzip
 import sys
 import argparse
 import re
 
 
 # The DMD output version this script handles.
-outputVersion = 4
+outputVersion = 5
 
 # 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 = [
     'malloc (',
     'replace_malloc',
@@ -218,20 +218,16 @@ def loadGraph(options):
 
     with opener(options.dmd_log_file_name, 'rb') as f:
         j = json.load(f)
 
     if j['version'] != outputVersion:
         raise Exception("'version' property isn't '{:d}'".format(outputVersion))
 
     invocation = j['invocation']
-    sampleBelowSize = invocation['sampleBelowSize']
-    heapIsSampled = sampleBelowSize > 1
-    if heapIsSampled:
-        raise Exception("Heap analysis is not going to work with sampled blocks.")
 
     block_list = j['blockList']
     blocks = {}
 
     for json_block in block_list:
         blocks[int(json_block['addr'], 16)] = BlockData(json_block)
 
     traceTable = j['traceTable']
--- a/memory/replace/dmd/dmd.py
+++ b/memory/replace/dmd/dmd.py
@@ -16,17 +16,17 @@ import os
 import platform
 import re
 import shutil
 import sys
 import tempfile
 from bisect import bisect_right
 
 # The DMD output version this script handles.
-outputVersion = 4
+outputVersion = 5
 
 # 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 = [
     # Matches malloc, replace_malloc, moz_xmalloc, vpx_malloc, js_malloc, pod_malloc, malloc_zone_*, g_malloc.
     'malloc',
@@ -50,17 +50,16 @@ class Record(object):
     traces. It can also be used to represent the difference between two
     records.'''
 
     def __init__(self):
         self.numBlocks = 0
         self.reqSize = 0
         self.slopSize = 0
         self.usableSize = 0
-        self.isSampled = False
         self.allocatedAtDesc = None
         self.reportedAtDescs = []
         self.usableSizes = collections.defaultdict(int)
 
     def isZero(self, args):
         return self.numBlocks == 0 and \
                self.reqSize == 0 and \
                self.slopSize == 0 and \
@@ -69,77 +68,68 @@ class Record(object):
 
     def negate(self):
         self.numBlocks = -self.numBlocks
         self.reqSize = -self.reqSize
         self.slopSize = -self.slopSize
         self.usableSize = -self.usableSize
 
         negatedUsableSizes = collections.defaultdict(int)
-        for (usableSize, isSampled), count in self.usableSizes.items():
-            negatedUsableSizes[(-usableSize, isSampled)] = count
+        for usableSize, count in self.usableSizes.items():
+            negatedUsableSizes[-usableSize] = count
         self.usableSizes = negatedUsableSizes
 
     def subtract(self, r):
         # We should only be calling this on records with matching stack traces.
         # Check this.
         assert self.allocatedAtDesc == r.allocatedAtDesc
         assert self.reportedAtDescs == r.reportedAtDescs
 
         self.numBlocks -= r.numBlocks
         self.reqSize -= r.reqSize
         self.slopSize -= r.slopSize
         self.usableSize -= r.usableSize
-        self.isSampled = self.isSampled or r.isSampled
 
         usableSizes1 = self.usableSizes
         usableSizes2 = r.usableSizes
         usableSizes3 = collections.defaultdict(int)
-        for usableSize, isSampled in usableSizes1:
-            counts1 = usableSizes1[usableSize, isSampled]
-            if (usableSize, isSampled) in usableSizes2:
-                counts2 = usableSizes2[usableSize, isSampled]
-                del usableSizes2[usableSize, isSampled]
+        for usableSize in usableSizes1:
+            counts1 = usableSizes1[usableSize]
+            if usableSize in usableSizes2:
+                counts2 = usableSizes2[usableSize]
+                del usableSizes2[usableSize]
                 counts3 = counts1 - counts2
                 if counts3 != 0:
                     if counts3 < 0:
                         usableSize = -usableSize
                         counts3 = -counts3
-                    usableSizes3[usableSize, isSampled] = counts3
+                    usableSizes3[usableSize] = counts3
             else:
-                usableSizes3[usableSize, isSampled] = counts1
+                usableSizes3[usableSize] = counts1
 
-        for usableSize, isSampled in usableSizes2:
-            usableSizes3[-usableSize, isSampled] = \
-                usableSizes2[usableSize, isSampled]
+        for usableSize in usableSizes2:
+            usableSizes3[-usableSize] = usableSizes2[usableSize]
 
         self.usableSizes = usableSizes3
 
     @staticmethod
-    def cmpByIsSampled(r1, r2):
-        # Treat sampled as smaller than non-sampled.
-        return cmp(r2.isSampled, r1.isSampled)
-
-    @staticmethod
     def cmpByUsableSize(r1, r2):
-        # Sort by usable size, then req size, then by isSampled.
+        # Sort by usable size, then by req size.
         return cmp(abs(r1.usableSize), abs(r2.usableSize)) or \
                Record.cmpByReqSize(r1, r2)
 
     @staticmethod
     def cmpByReqSize(r1, r2):
-        # Sort by req size, then by isSampled.
-        return cmp(abs(r1.reqSize), abs(r2.reqSize)) or \
-               Record.cmpByIsSampled(r1, r2)
+        # Sort by req size.
+        return cmp(abs(r1.reqSize), abs(r2.reqSize))
 
     @staticmethod
     def cmpBySlopSize(r1, r2):
-        # Sort by slop size, then by isSampled.
-        return cmp(abs(r1.slopSize), abs(r2.slopSize)) or \
-               Record.cmpByIsSampled(r1, r2)
+        # Sort by slop size.
+        return cmp(abs(r1.slopSize), abs(r2.slopSize))
 
     @staticmethod
     def cmpByNumBlocks(r1, r2):
         # Sort by block counts, then by usable size.
         return cmp(abs(r1.numBlocks), abs(r2.numBlocks)) or \
                Record.cmpByUsableSize(r1, r2)
 
 
@@ -274,30 +264,36 @@ def getDigestFromFile(args, inputFile):
 
     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']
 
+    # Insert the necessary entries for unrecorded stack traces. Note that 'ut'
+    # and 'uf' will not overlap with any keys produced by DMD's
+    # ToIdStringConverter::Base32() function.
+    unrecordedTraceID = 'ut'
+    unrecordedFrameID = 'uf'
+    traceTable[unrecordedTraceID] = [unrecordedFrameID]
+    frameTable[unrecordedFrameID] = \
+        '#00: (no stack trace recorded due to --stacks=partial)'
+
     # For the purposes of this script, 'scan' behaves like 'live'.
     if mode == 'scan':
         mode = 'live'
 
     if not mode in ['live', 'dark-matter', 'cumulative']:
         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))
 
         # Remove allocator fns from each stack trace.
         for traceKey, frameKeys in traceTable.items():
@@ -383,17 +379,17 @@ def getDigestFromFile(args, inputFile):
             if traceKey in recordKeyPartCache:
                 return recordKeyPartCache[traceKey]
 
             recordKeyPart = str(map(lambda frameKey: frameTable[frameKey],
                                     traceTable[traceKey]))
             recordKeyPartCache[traceKey] = recordKeyPart
             return recordKeyPart
 
-        allocatedAtTraceKey = block['alloc']
+        allocatedAtTraceKey = block.get('alloc', unrecordedTraceID)
         if mode in ['live', 'cumulative']:
             recordKey = makeRecordKeyPart(allocatedAtTraceKey)
             records = liveOrCumulativeRecords
         elif mode == 'dark-matter':
             recordKey = makeRecordKeyPart(allocatedAtTraceKey)
             if 'reps' in block:
                 reportedAtTraceKeys = block['reps']
                 for reportedAtTraceKey in reportedAtTraceKeys:
@@ -402,64 +398,54 @@ def getDigestFromFile(args, inputFile):
                     records = onceReportedRecords
                 else:
                     records = twiceReportedRecords
             else:
                 records = unreportedRecords
 
         record = records[recordKey]
 
-        if 'req' in block:
-            # not sampled
-            reqSize = block['req']
-            slopSize = block.get('slop', 0)
-            isSampled = False
-        else:
-            # sampled
-            reqSize = sampleBelowSize
-            if 'slop' in block:
-                raise Exception("'slop' property in sampled block'")
-            slopSize = 0
-            isSampled = True
+        if 'req' not in block:
+            raise Exception("'req' property missing in block'")
+
+        reqSize = block['req']
+        slopSize = block.get('slop', 0)
 
         if 'num' in block:
             num = block['num']
         else:
             num = 1
 
         usableSize = reqSize + slopSize
         heapUsableSize += num * usableSize
         heapBlocks += num
 
         record.numBlocks  += num
         record.reqSize    += num * reqSize
         record.slopSize   += num * slopSize
         record.usableSize += num * usableSize
-        record.isSampled   = record.isSampled or isSampled
         if record.allocatedAtDesc == None:
             record.allocatedAtDesc = \
                 buildTraceDescription(traceTable, frameTable,
                                       allocatedAtTraceKey)
 
         if mode in ['live', 'cumulative']:
             pass
         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)] += num
+        record.usableSizes[usableSize] += num
 
     # 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 mode in ['live', 'cumulative']:
         digest['liveOrCumulativeRecords'] = liveOrCumulativeRecords
     elif mode == 'dark-matter':
         digest['unreportedRecords'] = unreportedRecords
         digest['onceReportedRecords'] = onceReportedRecords
         digest['twiceReportedRecords'] = twiceReportedRecords
     return digest
 
@@ -491,20 +477,18 @@ def diffRecords(args, records1, records2
 
 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 d1['mode'] in ['live', 'cumulative']:
         d3['liveOrCumulativeRecords'] = \
             diffRecords(args, d1['liveOrCumulativeRecords'],
                               d2['liveOrCumulativeRecords'])
     elif d1['mode'] == 'dark-matter':
         d3['unreportedRecords']    = diffRecords(args, d1['unreportedRecords'],
                                                        d2['unreportedRecords'])
         d3['onceReportedRecords']  = diffRecords(args, d1['onceReportedRecords'],
@@ -512,33 +496,30 @@ def diffDigests(args, d1, d2):
         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 mode in ['live', 'cumulative']:
         liveOrCumulativeRecords = digest['liveOrCumulativeRecords']
     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
-        sampled.'''
-        return '{:}{:,d}'.format('~' if isSampled else '', n)
+    def number(n):
+        '''Format a number with comma as a separator.'''
+        return '{:,d}'.format(n)
 
     def perc(m, n):
         return 0 if n == 0 else (100 * m / n)
 
     def plural(n):
         return '' if n == 1 else 's'
 
     # Prints to stdout, or to file if -o/--output was specified.
@@ -573,45 +554,42 @@ def printDigest(args, digest):
             # Stop printing at the |maxRecord|th record.
             if i == maxRecord:
                 out('# {:}: stopping after {:,d} heap block records\n'.
                     format(RecordKind, i))
                 break
 
             kindCumulativeUsableSize += record.usableSize
 
-            isSampled = record.isSampled
-
             out(RecordKind + ' {')
             out('  {:} block{:} in heap block record {:,d} of {:,d}'.
-                format(number(record.numBlocks, isSampled),
+                format(number(record.numBlocks),
                        plural(record.numBlocks), i, numRecords))
             out('  {:} bytes ({:} requested / {:} slop)'.
-                format(number(record.usableSize, isSampled),
-                       number(record.reqSize, isSampled),
-                       number(record.slopSize, isSampled)))
+                format(number(record.usableSize),
+                       number(record.reqSize),
+                       number(record.slopSize)))
 
-            abscmp = lambda ((usableSize1, _1a), _1b), \
-                            ((usableSize2, _2a), _2b): \
+            abscmp = lambda (usableSize1, _1), (usableSize2, _2): \
                             cmp(abs(usableSize1), abs(usableSize2))
             usableSizes = sorted(record.usableSizes.items(), cmp=abscmp,
                                  reverse=True)
 
             hasSingleBlock = len(usableSizes) == 1 and usableSizes[0][1] == 1
 
             if not hasSingleBlock:
                 out('  Individual block sizes: ', end='')
                 if len(usableSizes) == 0:
                     out('(no change)', end='')
                 else:
                     isFirst = True
-                    for (usableSize, isSampled), count in usableSizes:
+                    for usableSize, count in usableSizes:
                         if not isFirst:
                             out('; ', end='')
-                        out('{:}'.format(number(usableSize, isSampled)), end='')
+                        out('{:}'.format(number(usableSize)), end='')
                         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)))
@@ -633,37 +611,36 @@ def printDigest(args, digest):
                     out('  Reported {:}at {{'.format(again))
                     printStack(reportedAtDesc)
                     out('  }')
             out('}\n')
 
         return (kindUsableSize, kindBlocks)
 
 
-    def printInvocation(n, dmdEnvVar, mode, sampleBelowSize):
+    def printInvocation(n, dmdEnvVar, mode):
         out('Invocation{:} {{'.format(n))
         if dmdEnvVar == None:
             out('  $DMD is undefined')
         else:
             out('  $DMD = \'' + dmdEnvVar + '\'')
         out('  Mode = \'' + mode + '\'')
-        out('  Sample-below size = ' + str(sampleBelowSize))
         out('}\n')
 
     # Print command line. Strip dirs so the output is deterministic, which is
     # needed for testing.
     out(separator, end='')
     out('# ' + ' '.join(map(os.path.basename, sys.argv)) + '\n')
 
     # Print invocation(s).
     if type(dmdEnvVar) is not tuple:
-        printInvocation('', dmdEnvVar, mode, sampleBelowSize)
+        printInvocation('', dmdEnvVar, mode)
     else:
-        printInvocation(' 1', dmdEnvVar[0], mode, sampleBelowSize[0])
-        printInvocation(' 2', dmdEnvVar[1], mode, sampleBelowSize[1])
+        printInvocation(' 1', dmdEnvVar[0], mode)
+        printInvocation(' 2', dmdEnvVar[1], mode)
 
     # Print records.
     if mode in ['live', 'cumulative']:
         liveOrCumulativeUsableSize, liveOrCumulativeBlocks = \
             printRecords(mode, liveOrCumulativeRecords, heapUsableSize)
     elif mode == 'dark-matter':
         twiceReportedUsableSize, twiceReportedBlocks = \
             printRecords('twice-reported', twiceReportedRecords, heapUsableSize)
@@ -674,43 +651,43 @@ def printDigest(args, digest):
         onceReportedUsableSize, onceReportedBlocks = \
             printRecords('once-reported', onceReportedRecords, heapUsableSize)
 
     # Print summary.
     out(separator)
     out('Summary {')
     if mode in ['live', 'cumulative']:
         out('  Total: {:} bytes in {:} blocks'.
-            format(number(liveOrCumulativeUsableSize, heapIsSampled),
-                   number(liveOrCumulativeBlocks, heapIsSampled)))
+            format(number(liveOrCumulativeUsableSize),
+                   number(liveOrCumulativeBlocks)))
     elif mode == 'dark-matter':
         fmt = '  {:15} {:>12} bytes ({:6.2f}%) in {:>7} blocks ({:6.2f}%)'
         out(fmt.
             format('Total:',
-                   number(heapUsableSize, heapIsSampled),
+                   number(heapUsableSize),
                    100,
-                   number(heapBlocks, heapIsSampled),
+                   number(heapBlocks),
                    100))
         out(fmt.
             format('Unreported:',
-                   number(unreportedUsableSize, heapIsSampled),
+                   number(unreportedUsableSize),
                    perc(unreportedUsableSize, heapUsableSize),
-                   number(unreportedBlocks, heapIsSampled),
+                   number(unreportedBlocks),
                    perc(unreportedBlocks, heapBlocks)))
         out(fmt.
             format('Once-reported:',
-                   number(onceReportedUsableSize, heapIsSampled),
+                   number(onceReportedUsableSize),
                    perc(onceReportedUsableSize, heapUsableSize),
-                   number(onceReportedBlocks, heapIsSampled),
+                   number(onceReportedBlocks),
                    perc(onceReportedBlocks, heapBlocks)))
         out(fmt.
             format('Twice-reported:',
-                   number(twiceReportedUsableSize, heapIsSampled),
+                   number(twiceReportedUsableSize),
                    perc(twiceReportedUsableSize, heapUsableSize),
-                   number(twiceReportedBlocks, heapIsSampled),
+                   number(twiceReportedBlocks),
                    perc(twiceReportedBlocks, heapBlocks)))
     out('}\n')
 
 
 #############################
 # Pretty printer for DMD JSON
 #############################
 
@@ -830,18 +807,16 @@ def clampBlockList(args, inputFileName, 
     with opener(inputFileName, 'rb') as f:
         j = json.load(f)
 
     if j['version'] != outputVersion:
         raise Exception("'version' property isn't '{:d}'".format(outputVersion))
 
     # Check that the invocation is reasonable for contents clamping.
     invocation = j['invocation']
-    if invocation['sampleBelowSize'] > 1:
-        raise Exception("Heap analysis is not going to work with sampled blocks.")
     if invocation['mode'] != 'scan':
         raise Exception("Log was taken in mode " + invocation['mode'] + " not scan")
 
     sys.stderr.write('Creating block range list.\n')
     blockList = j['blockList']
     blockRanges = []
     for block in blockList:
         blockRanges.append(AddrRange(block['addr'], block['req']))
--- a/memory/replace/dmd/test/SmokeDMD.cpp
+++ b/memory/replace/dmd/test/SmokeDMD.cpp
@@ -87,38 +87,38 @@ void Foo(int aSeven)
 
   // a[4], a[5] unreported
 }
 
 void
 TestEmpty(const char* aTestName, const char* aMode)
 {
   char filename[128];
-  sprintf(filename, "full-%s-%s.json", aTestName, aMode);
+  sprintf(filename, "complete-%s-%s.json", aTestName, aMode);
   auto f = MakeUnique<FpWriteFunc>(filename);
 
   char options[128];
-  sprintf(options, "--mode=%s --sample-below=1", aMode);
+  sprintf(options, "--mode=%s --stacks=full", aMode);
   ResetEverything(options);
 
   // Zero for everything.
   Analyze(Move(f));
 }
 
 void
-TestUnsampled(const char* aTestName, int aNum, const char* aMode, int aSeven)
+TestFull(const char* aTestName, int aNum, const char* aMode, int aSeven)
 {
   char filename[128];
-  sprintf(filename, "full-%s%d-%s.json", aTestName, aNum, aMode);
+  sprintf(filename, "complete-%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);
+  sprintf(options, "--mode=%s --stacks=full --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);
@@ -267,74 +267,57 @@ TestUnsampled(const char* aTestName, int
 
   if (aNum == 2) {
     // Analyze 2.
     Analyze(Move(f));
   }
 }
 
 void
-TestSampled(const char* aTestName, const char* aMode, int aSeven)
+TestPartial(const char* aTestName, const char* aMode, int aSeven)
 {
   char filename[128];
-  sprintf(filename, "full-%s-%s.json", aTestName, aMode);
+  sprintf(filename, "complete-%s-%s.json", aTestName, aMode);
   auto f = MakeUnique<FpWriteFunc>(filename);
 
   char options[128];
-  sprintf(options, "--mode=%s --sample-below=128", aMode);
+  sprintf(options, "--mode=%s", aMode);
   ResetEverything(options);
 
+  int kTenThousand = aSeven + 9993;
   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);
+  // The output of this function is deterministic but it relies on the
+  // probability and seeds given to the FastBernoulliTrial instance in
+  // ResetBernoulli(). If they change, the output will change too.
 
-  // 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++) {
+  // Expected fraction with stacks: (1 - (1 - 0.003) ** 8) = 0.0237
+  // So we expect about 0.0237 * 10000 == 237.
+  // We actually get 258.
+  for (int i = 0; i < kTenThousand; 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);
+  // Expected fraction with stacks: (1 - (1 - 0.003) ** 128) = 0.3193,
+  // So we expect about 0.3193 * 10000 == 3193.
+  // We actually get 3150.
+  for (int i = 0; i < kTenThousand; i++) {
+    s = (char*) malloc(128);
     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);
+  // Expected fraction with stacks: (1 - (1 - 0.003) ** 1024) = 0.9539,
+  // So we expect about 0.9539 * 10000 == 9539.
+  // We actually get 9539.
+  for (int i = 0; i < kTenThousand; i++) {
+    s = (char*) malloc(1024);
     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
 TestScan(int aSeven)
 {
   auto f = MakeUnique<FpWriteFunc>("basic-scan.json");
 
@@ -372,23 +355,23 @@ RunTests()
   MOZ_RELEASE_ASSERT(IsRunning());
 
   // Please keep this in sync with run_test in test_dmd.js.
 
   TestEmpty("empty", "live");
   TestEmpty("empty", "dark-matter");
   TestEmpty("empty", "cumulative");
 
-  TestUnsampled("unsampled", 1, "live",        seven);
-  TestUnsampled("unsampled", 1, "dark-matter", seven);
+  TestFull("full", 1, "live",        seven);
+  TestFull("full", 1, "dark-matter", seven);
 
-  TestUnsampled("unsampled", 2, "dark-matter", seven);
-  TestUnsampled("unsampled", 2, "cumulative",  seven);
+  TestFull("full", 2, "dark-matter", seven);
+  TestFull("full", 2, "cumulative",  seven);
 
-  TestSampled("sampled", "live", seven);
+  TestPartial("partial", "live", seven);
 
   TestScan(seven);
 }
 
 int main()
 {
   RunTests();
 
--- a/memory/replace/dmd/test/basic-scan-32-expected.txt
+++ b/memory/replace/dmd/test/basic-scan-32-expected.txt
@@ -1,15 +1,14 @@
 #-----------------------------------------------------------------
 # dmd.py --filter-stacks-for-testing -o basic-scan-32-actual.txt --clamp-contents basic-scan.json
 
 Invocation {
   $DMD = '--mode=scan'
   Mode = 'live'
-  Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Live {
   1 block in heap block record 1 of 1
   32 bytes (24 requested / 8 slop)
   100.00% of the heap (100.00% cumulative)
--- a/memory/replace/dmd/test/basic-scan-64-expected.txt
+++ b/memory/replace/dmd/test/basic-scan-64-expected.txt
@@ -1,15 +1,14 @@
 #-----------------------------------------------------------------
 # dmd.py --filter-stacks-for-testing -o basic-scan-64-actual.txt --clamp-contents basic-scan.json
 
 Invocation {
   $DMD = '--mode=scan'
   Mode = 'live'
-  Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Live {
   1 block in heap block record 1 of 1
   48 bytes (48 requested / 0 slop)
   100.00% of the heap (100.00% cumulative)
rename from memory/replace/dmd/test/full-empty-cumulative-expected.txt
rename to memory/replace/dmd/test/complete-empty-cumulative-expected.txt
--- a/memory/replace/dmd/test/full-empty-cumulative-expected.txt
+++ b/memory/replace/dmd/test/complete-empty-cumulative-expected.txt
@@ -1,15 +1,14 @@
 #-----------------------------------------------------------------
-# dmd.py --filter-stacks-for-testing -o full-empty-cumulative-actual.txt full-empty-cumulative.json
+# dmd.py --filter-stacks-for-testing -o complete-empty-cumulative-actual.txt complete-empty-cumulative.json
 
 Invocation {
-  $DMD = '--mode=cumulative --sample-below=1'
+  $DMD = '--mode=cumulative --stacks=full'
   Mode = 'cumulative'
-  Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 # no cumulative heap blocks
 
 #-----------------------------------------------------------------
 
rename from memory/replace/dmd/test/full-empty-dark-matter-expected.txt
rename to memory/replace/dmd/test/complete-empty-dark-matter-expected.txt
--- a/memory/replace/dmd/test/full-empty-dark-matter-expected.txt
+++ b/memory/replace/dmd/test/complete-empty-dark-matter-expected.txt
@@ -1,15 +1,14 @@
 #-----------------------------------------------------------------
-# dmd.py --filter-stacks-for-testing -o full-empty-dark-matter-actual.txt full-empty-dark-matter.json
+# dmd.py --filter-stacks-for-testing -o complete-empty-dark-matter-actual.txt complete-empty-dark-matter.json
 
 Invocation {
-  $DMD = '--mode=dark-matter --sample-below=1'
+  $DMD = '--mode=dark-matter --stacks=full'
   Mode = 'dark-matter'
-  Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 # no twice-reported heap blocks
 
 #-----------------------------------------------------------------
 
rename from memory/replace/dmd/test/full-empty-live-expected.txt
rename to memory/replace/dmd/test/complete-empty-live-expected.txt
--- a/memory/replace/dmd/test/full-empty-live-expected.txt
+++ b/memory/replace/dmd/test/complete-empty-live-expected.txt
@@ -1,15 +1,14 @@
 #-----------------------------------------------------------------
-# dmd.py --filter-stacks-for-testing -o full-empty-live-actual.txt full-empty-live.json
+# dmd.py --filter-stacks-for-testing -o complete-empty-live-actual.txt complete-empty-live.json
 
 Invocation {
-  $DMD = '--mode=live --sample-below=1'
+  $DMD = '--mode=live --stacks=full'
   Mode = 'live'
-  Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 # no live heap blocks
 
 #-----------------------------------------------------------------
 
rename from memory/replace/dmd/test/full-unsampled1-dark-matter-expected.txt
rename to memory/replace/dmd/test/complete-full1-dark-matter-expected.txt
--- a/memory/replace/dmd/test/full-unsampled1-dark-matter-expected.txt
+++ b/memory/replace/dmd/test/complete-full1-dark-matter-expected.txt
@@ -1,15 +1,14 @@
 #-----------------------------------------------------------------
-# dmd.py --filter-stacks-for-testing -o full-unsampled1-dark-matter-actual.txt full-unsampled1-dark-matter.json
+# dmd.py --filter-stacks-for-testing -o complete-full1-dark-matter-actual.txt complete-full1-dark-matter.json
 
 Invocation {
-  $DMD = '--mode=dark-matter --sample-below=1 --show-dump-stats=yes'
+  $DMD = '--mode=dark-matter --stacks=full --show-dump-stats=yes'
   Mode = 'dark-matter'
-  Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Twice-reported {
   1 block in heap block record 1 of 4
   80 bytes (79 requested / 1 slop)
   0.66% of the heap (0.66% cumulative)
rename from memory/replace/dmd/test/full-unsampled1-live-expected.txt
rename to memory/replace/dmd/test/complete-full1-live-expected.txt
--- a/memory/replace/dmd/test/full-unsampled1-live-expected.txt
+++ b/memory/replace/dmd/test/complete-full1-live-expected.txt
@@ -1,15 +1,14 @@
 #-----------------------------------------------------------------
-# dmd.py --filter-stacks-for-testing -o full-unsampled1-live-actual.txt full-unsampled1-live.json
+# dmd.py --filter-stacks-for-testing -o complete-full1-live-actual.txt complete-full1-live.json
 
 Invocation {
-  $DMD = '--mode=live --sample-below=1 --show-dump-stats=yes'
+  $DMD = '--mode=live --stacks=full --show-dump-stats=yes'
   Mode = 'live'
-  Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Live {
   1 block in heap block record 1 of 12
   8,192 bytes (7,169 requested / 1,023 slop)
   67.77% of the heap (67.77% cumulative)
rename from memory/replace/dmd/test/full-unsampled2-cumulative-expected.txt
rename to memory/replace/dmd/test/complete-full2-cumulative-expected.txt
--- a/memory/replace/dmd/test/full-unsampled2-cumulative-expected.txt
+++ b/memory/replace/dmd/test/complete-full2-cumulative-expected.txt
@@ -1,15 +1,14 @@
 #-----------------------------------------------------------------
-# dmd.py --filter-stacks-for-testing -o full-unsampled2-cumulative-actual.txt full-unsampled2-cumulative.json
+# dmd.py --filter-stacks-for-testing -o complete-full2-cumulative-actual.txt complete-full2-cumulative.json
 
 Invocation {
-  $DMD = '--mode=cumulative --sample-below=1 --show-dump-stats=yes'
+  $DMD = '--mode=cumulative --stacks=full --show-dump-stats=yes'
   Mode = 'cumulative'
-  Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Cumulative {
   100 blocks in heap block record 1 of 17
   12,800 bytes (12,800 requested / 0 slop)
   Individual block sizes: 128 x 100
rename from memory/replace/dmd/test/full-unsampled2-dark-matter-expected.txt
rename to memory/replace/dmd/test/complete-full2-dark-matter-expected.txt
--- a/memory/replace/dmd/test/full-unsampled2-dark-matter-expected.txt
+++ b/memory/replace/dmd/test/complete-full2-dark-matter-expected.txt
@@ -1,15 +1,14 @@
 #-----------------------------------------------------------------
-# dmd.py --filter-stacks-for-testing -o full-unsampled2-dark-matter-actual.txt full-unsampled2-dark-matter.json
+# dmd.py --filter-stacks-for-testing -o complete-full2-dark-matter-actual.txt complete-full2-dark-matter.json
 
 Invocation {
-  $DMD = '--mode=dark-matter --sample-below=1 --show-dump-stats=yes'
+  $DMD = '--mode=dark-matter --stacks=full --show-dump-stats=yes'
   Mode = 'dark-matter'
-  Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Twice-reported {
   1 block in heap block record 1 of 2
   80 bytes (77 requested / 3 slop)
   2.82% of the heap (2.82% cumulative)
rename from memory/replace/dmd/test/full-sampled-live-expected.txt
rename to memory/replace/dmd/test/complete-partial-live-expected.txt
--- a/memory/replace/dmd/test/full-sampled-live-expected.txt
+++ b/memory/replace/dmd/test/complete-partial-live-expected.txt
@@ -1,81 +1,56 @@
 #-----------------------------------------------------------------
-# dmd.py --filter-stacks-for-testing -o full-sampled-live-actual.txt full-sampled-live.json
+# dmd.py --filter-stacks-for-testing -o complete-partial-live-actual.txt complete-partial-live.json
 
 Invocation {
-  $DMD = '--mode=live --sample-below=128'
+  $DMD = '--mode=live'
   Mode = 'live'
-  Sample-below size = 128
 }
 
 #-----------------------------------------------------------------
 
 Live {
-  ~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)
-  Allocated at {
-    #01: ... DMD.cpp ...
-  }
-}
-
-Live {
-  1 block in heap block record 2 of 7
-  256 bytes (256 requested / 0 slop)
-  17.78% of the heap (53.33% cumulative)
+  9,539 blocks in heap block record 1 of 4
+  9,767,936 bytes (9,767,936 requested / 0 slop)
+  Individual block sizes: 1,024 x 9,539
+  84.21% of the heap (84.21% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Live {
-  1 block in heap block record 3 of 7
-  160 bytes (160 requested / 0 slop)
-  11.11% of the heap (64.44% cumulative)
+  17,053 blocks in heap block record 2 of 4
+  1,426,800 bytes (1,426,800 requested / 0 slop)
+  Individual block sizes: 1,024 x 461; 128 x 6,850; 8 x 9,742
+  12.30% of the heap (96.51% cumulative)
   Allocated at {
-    #01: ... DMD.cpp ...
+    #01: (no stack trace recorded due to --stacks=partial)
   }
 }
 
 Live {
-  1 block in heap block record 4 of 7
-  128 bytes (128 requested / 0 slop)
-  8.89% of the heap (73.33% cumulative)
+  3,150 blocks in heap block record 3 of 4
+  403,200 bytes (403,200 requested / 0 slop)
+  Individual block sizes: 128 x 3,150
+  3.48% of the heap (99.98% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Live {
-  ~1 block in heap block record 5 of 7
-  ~128 bytes (~128 requested / ~0 slop)
-  8.89% of the heap (82.22% cumulative)
-  Allocated at {
-    #01: ... DMD.cpp ...
-  }
-}
-
-Live {
-  ~1 block in heap block record 6 of 7
-  ~128 bytes (~128 requested / ~0 slop)
-  8.89% of the heap (91.11% cumulative)
-  Allocated at {
-    #01: ... DMD.cpp ...
-  }
-}
-
-Live {
-  ~1 block in heap block record 7 of 7
-  ~128 bytes (~128 requested / ~0 slop)
-  8.89% of the heap (100.00% cumulative)
+  258 blocks in heap block record 4 of 4
+  2,064 bytes (2,064 requested / 0 slop)
+  Individual block sizes: 8 x 258
+  0.02% of the heap (100.00% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 #-----------------------------------------------------------------
 
 Summary {
-  Total: ~1,440 bytes in ~10 blocks
+  Total: 11,600,000 bytes in 30,000 blocks
 }
 
--- a/memory/replace/dmd/test/scan-test.py
+++ b/memory/replace/dmd/test/scan-test.py
@@ -9,17 +9,17 @@
 from __future__ import print_function, division
 
 import argparse
 import gzip
 import json
 import sys
 
 # The DMD output version this script handles.
-outputVersion = 4
+outputVersion = 5
 
 
 def parseCommandLine():
     description = '''
 Ensure that DMD heap scan mode creates the correct output when run with SmokeDMD.
 This is only for testing. Input files can be gzipped.
 '''
     p = argparse.ArgumentParser(description=description)
@@ -56,20 +56,16 @@ def main():
         raise Exception("'version' property isn't '{:d}'".format(outputVersion))
 
     invocation = j['invocation']
 
     mode = invocation['mode']
     if mode != 'scan':
         raise Exception("bad 'mode' property: '{:s}'".format(mode))
 
-    sampleBelowSize = invocation['sampleBelowSize']
-    if sampleBelowSize != 1:
-        raise Exception("Expected sampleBelowSize of 1 but got " + sampleBelowSize)
-
     blockList = j['blockList']
 
     if len(blockList) != 1:
         raise Exception("Expected only one block")
 
     b = blockList[0]
 
     # The expected values are based on hard-coded values in SmokeDMD.cpp.
--- a/memory/replace/dmd/test/script-diff-dark-matter-expected.txt
+++ b/memory/replace/dmd/test/script-diff-dark-matter-expected.txt
@@ -1,29 +1,27 @@
 #-----------------------------------------------------------------
 # 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 = '--mode=dark-matter --sample-below=127'
+  $DMD = '--mode=dark-matter'
   Mode = 'dark-matter'
-  Sample-below size = 127
 }
 
 Invocation 2 {
-  $DMD = '--sample-below=63'
+  $DMD is undefined
   Mode = 'dark-matter'
-  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
+  -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)
   }
@@ -41,19 +39,19 @@ Unreported {
   -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
+  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 {
@@ -80,17 +78,17 @@ Unreported {
 
 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)
+    #01: (no stack trace recorded due to --stacks=partial)
   }
 }
 
 #-----------------------------------------------------------------
 
 Once-reported {
   -3 blocks in heap block record 1 of 2
   -10,240 bytes (-10,192 requested / -48 slop)
@@ -101,29 +99,29 @@ Once-reported {
     #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 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%)
+  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%)
 }
 
--- a/memory/replace/dmd/test/script-diff-dark-matter1.json
+++ b/memory/replace/dmd/test/script-diff-dark-matter1.json
@@ -1,54 +1,51 @@
 {
- "version": 4,
+ "version": 5,
  "invocation": {
-  "dmdEnvVar": "--mode=dark-matter --sample-below=127",
-  "mode": "dark-matter",
-  "sampleBelowSize": 127
+  "dmdEnvVar": "--mode=dark-matter",
+  "mode": "dark-matter"
  },
  "blockList": [
   {"req": 4096, "alloc": "A", "num": 4},
 
   {"req": 4096, "alloc": "B", "num": 3},
   {"req": 4096, "alloc": "B"},
 
   {"req": 4096, "alloc": "C", "num": 2},
   {"req": 4096, "alloc": "C", "num": 2},
 
   {"req": 4096,             "alloc": "D", "reps": ["R1"], "num": 2},
   {"req": 2000, "slop": 48, "alloc": "D", "reps": ["R1"]},
 
   {"req": 15360,            "alloc": "F"},
   {"req": 512,              "alloc": "F", "num": 2},
-  {                         "alloc": "F"},
+  {"req": 127,              "alloc": "F"},
   {"req": 1024,             "alloc": "F", "reps": ["R1"]},
-  {                         "alloc": "F", "reps": ["R1"]},
+  {"req": 127,              "alloc": "F", "reps": ["R1"]},
   {"req": 1000, "slop": 24, "alloc": "F", "reps": ["R1", "R2"]},
-  {                         "alloc": "F", "reps": ["R1", "R2"]},
+  {"req": 127,              "alloc": "F", "reps": ["R1", "R2"]},
 
-  {"req": 4096,            "alloc": "G"},
-  {"req": 8192,            "alloc": "G"},
-  {"req": 16384,           "alloc": "G"}
+  {"req": 4096 },
+  {"req": 8192 },
+  {"req": 16384 }
  ],
  "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)"
  }
 }
--- a/memory/replace/dmd/test/script-diff-dark-matter2.json
+++ b/memory/replace/dmd/test/script-diff-dark-matter2.json
@@ -1,14 +1,13 @@
 {
- "version": 4,
+ "version": 5,
  "invocation": {
-  "dmdEnvVar": "--sample-below=63",
-  "mode": "dark-matter",
-  "sampleBelowSize": 63
+  "dmdEnvVar": null,
+  "mode": "dark-matter"
  },
  "blockList": [
   {"req": 4096, "alloc": "A", "num": 4},
 
   {"req": 8192, "alloc": "B"},
   {"req": 8192, "alloc": "B"},
 
   {"req": 4000, "slop": 96, "alloc": "C", "num": 4},
@@ -17,38 +16,36 @@
 
   {"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": 63,               "alloc": "F", "reps": ["R1", "R2"]},
   {"req": 64,               "alloc": "F", "num": 4},
-  {                         "alloc": "F"},
+  {"req": 63,               "alloc": "F"},
 
-  {"req": 4096,            "alloc": "G", "num": 2},
-  {"req": 20480,           "alloc": "G"}
+  {"req": 4096, "num": 2 },
+  {"req": 20480 }
  ],
  "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)"
  }
 }
--- a/memory/replace/dmd/test/script-diff-live-expected.txt
+++ b/memory/replace/dmd/test/script-diff-live-expected.txt
@@ -1,39 +1,37 @@
 #-----------------------------------------------------------------
 # dmd.py --filter-stacks-for-testing -o script-diff-live-actual.txt script-diff-live1.json script-diff-live2.json
 
 Invocation 1 {
-  $DMD = '--mode=live --sample-below=127'
+  $DMD = '--mode=live'
   Mode = 'live'
-  Sample-below size = 127
 }
 
 Invocation 2 {
-  $DMD = '--mode=live --sample-below=63'
+  $DMD = '--mode=live --stacks=partial'
   Mode = 'live'
-  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
+  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
@@ -56,28 +54,28 @@ Live {
 }
 
 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)
+    #01: (no stack trace recorded due to --stacks=partial)
   }
 }
 
 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
+  Total: -7,039 bytes in 4 blocks
 }
 
--- a/memory/replace/dmd/test/script-diff-live1.json
+++ b/memory/replace/dmd/test/script-diff-live1.json
@@ -1,54 +1,51 @@
 {
- "version": 4,
+ "version": 5,
  "invocation": {
-  "dmdEnvVar": "--mode=live --sample-below=127",
-  "mode": "live",
-  "sampleBelowSize": 127
+  "dmdEnvVar": "--mode=live",
+  "mode": "live"
  },
  "blockList": [
   {"req": 4096, "alloc": "A", "num": 4},
 
   {"req": 4096, "alloc": "B", "num": 4},
 
   {"req": 4096, "alloc": "C", "num": 4},
 
   {"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": 127,              "alloc": "F"},
   {"req": 1024,             "alloc": "F"},
-  {                         "alloc": "F"},
+  {"req": 127,              "alloc": "F"},
   {"req": 1000, "slop": 24, "alloc": "F"},
-  {                         "alloc": "F"},
+  {"req": 127,              "alloc": "F"},
 
-  {"req": 4096,            "alloc": "G"},
-  {"req": 8192,            "alloc": "G"},
-  {"req": 16384,           "alloc": "G"}
+  {"req": 4096 },
+  {"req": 8192 },
+  {"req": 16384 }
  ],
  "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)"
  }
 }
--- a/memory/replace/dmd/test/script-diff-live2.json
+++ b/memory/replace/dmd/test/script-diff-live2.json
@@ -1,14 +1,13 @@
 {
- "version": 4,
+ "version": 5,
  "invocation": {
-  "dmdEnvVar": "--mode=live --sample-below=63",
-  "mode": "live",
-  "sampleBelowSize": 63
+  "dmdEnvVar": "--mode=live --stacks=partial",
+  "mode": "live"
  },
  "blockList": [
   {"req": 4096, "alloc": "A", "num": 3},
   {"req": 4096, "alloc": "A"},
 
   {"req": 8192, "alloc": "B"},
   {"req": 8192, "alloc": "B"},
 
@@ -18,39 +17,37 @@
   {"req": 4096, "alloc": "E"},
   {"req": 4096, "alloc": "E"},
   {"req": 4096, "alloc": "E"},
 
   {"req": 2000, "slop": 48, "alloc": "F"},
   {"req": 1000, "slop": 24, "alloc": "F"},
   {"req": 512,              "alloc": "F", "num": 4},
   {"req": 128,              "alloc": "F"},
-  {                         "alloc": "F"},
+  {"req": 63,               "alloc": "F"},
   {"req": 64,               "alloc": "F", "num": 4},
-  {                         "alloc": "F"},
+  {"req": 63,               "alloc": "F"},
 
-  {"req": 4096,            "alloc": "G"},
-  {"req": 4096,            "alloc": "G"},
-  {"req": 20480,           "alloc": "G"}
+  {"req": 4096 },
+  {"req": 4096 },
+  {"req": 20480 }
  ],
  "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)"
  }
 }
--- a/memory/replace/dmd/test/script-ignore-alloc-fns-expected.txt
+++ b/memory/replace/dmd/test/script-ignore-alloc-fns-expected.txt
@@ -1,15 +1,14 @@
 #-----------------------------------------------------------------
 # dmd.py --filter-stacks-for-testing -o script-ignore-alloc-fns-actual.txt --ignore-alloc-fns script-ignore-alloc-fns.json
 
 Invocation {
   $DMD is undefined
   Mode = 'dark-matter'
-  Sample-below size = 2500
 }
 
 #-----------------------------------------------------------------
 
 # no twice-reported heap blocks
 
 #-----------------------------------------------------------------
 
@@ -40,18 +39,18 @@ Unreported {
   0.73% of unreported (99.78% cumulative)
   Allocated at {
     #01: mozilla::Vector::growStorageBy(unsigned long) (Vector.h:802)
     #02: D (D.cpp:99)
   }
 }
 
 Unreported {
-  ~1 block in heap block record 4 of 4
-  ~2,500 bytes (~2,500 requested / ~0 slop)
+  1 block in heap block record 4 of 4
+  2,500 bytes (2,500 requested / 0 slop)
   0.22% of the heap (100.00% cumulative)
   0.22% of unreported (100.00% cumulative)
   Allocated at {
     #01: g_type_create_instance (/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0)
     #02: not_an_alloc_function_so_alloc_functions_below_here_will_not_be_stripped (blah)
     #03: replace_posix_memalign (replace_malloc.h:120)
     #04: ??? (/lib/x86_64-linux-gnu/libglib-2.0.so.0)
     #05: another_non_alloc_function (blah)
@@ -60,14 +59,14 @@ Unreported {
 
 #-----------------------------------------------------------------
 
 # no once-reported heap blocks
 
 #-----------------------------------------------------------------
 
 Summary {
-  Total:            ~1,124,804 bytes (100.00%) in      ~4 blocks (100.00%)
-  Unreported:       ~1,124,804 bytes (100.00%) in      ~4 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%)
+  Total:             1,124,804 bytes (100.00%) in       4 blocks (100.00%)
+  Unreported:        1,124,804 bytes (100.00%) in       4 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%)
 }
 
--- a/memory/replace/dmd/test/script-ignore-alloc-fns.json
+++ b/memory/replace/dmd/test/script-ignore-alloc-fns.json
@@ -1,20 +1,19 @@
 {
- "version": 4,
+ "version": 5,
  "invocation": {
   "dmdEnvVar": null,
-  "mode": "dark-matter",
-  "sampleBelowSize": 2500
+  "mode": "dark-matter"
  },
  "blockList": [
   {"req": 1048576,           "alloc": "A"},
   {"req": 65536,             "alloc": "B"},
   {"req": 8000, "slop": 192, "alloc": "C"},
-  {                          "alloc": "D"}
+  {"req": 2500,              "alloc": "D"}
  ],
  "traceTable": {
   "A": ["AA", "AB", "AC", "AD"],
   "B": ["BA", "BB", "BC"],
   "C": ["CA", "CB", "CC", "CD"],
   "D": ["DA", "DB", "DD", "DD", "DE", "DF", "DG", "DH", "DI", "DJ"]
  },
  "frameTable": {
--- a/memory/replace/dmd/test/script-max-frames-1-expected.txt
+++ b/memory/replace/dmd/test/script-max-frames-1-expected.txt
@@ -1,15 +1,14 @@
 #-----------------------------------------------------------------
 # dmd.py --filter-stacks-for-testing -o script-max-frames-1-actual.txt --max-frames=1 script-max-frames.json
 
 Invocation {
-  $DMD = '--mode=live'
+  $DMD = '--mode=live --stacks=full'
   Mode = 'live'
-  Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Live {
   4 blocks in heap block record 1 of 1
   4,416 bytes (4,404 requested / 12 slop)
   Individual block sizes: 4,096; 128; 112; 80
--- a/memory/replace/dmd/test/script-max-frames-3-expected.txt
+++ b/memory/replace/dmd/test/script-max-frames-3-expected.txt
@@ -1,15 +1,14 @@
 #-----------------------------------------------------------------
 # 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 = '--mode=live'
+  $DMD = '--mode=live --stacks=full'
   Mode = 'live'
-  Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Live {
   2 blocks in heap block record 1 of 3
   4,224 bytes (4,224 requested / 0 slop)
   Individual block sizes: 4,096; 128
--- a/memory/replace/dmd/test/script-max-frames-8-expected.txt
+++ b/memory/replace/dmd/test/script-max-frames-8-expected.txt
@@ -1,15 +1,14 @@
 #-----------------------------------------------------------------
 # dmd.py --filter-stacks-for-testing -o script-max-frames-8-actual.txt --max-frames=8 script-max-frames.json
 
 Invocation {
-  $DMD = '--mode=live'
+  $DMD = '--mode=live --stacks=full'
   Mode = 'live'
-  Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Live {
   1 block in heap block record 1 of 4
   4,096 bytes (4,096 requested / 0 slop)
   92.75% of the heap (92.75% cumulative)
--- a/memory/replace/dmd/test/script-max-frames.json
+++ b/memory/replace/dmd/test/script-max-frames.json
@@ -1,14 +1,13 @@
 {
- "version": 4,
+ "version": 5,
  "invocation": {
-  "dmdEnvVar": "--mode=live",
-  "mode": "live",
-  "sampleBelowSize": 1
+  "dmdEnvVar": "--mode=live --stacks=full",
+  "mode": "live"
  },
  "blockList": [
   {"req": 4096, "alloc": "A"},
   {"req": 128, "alloc": "B"},
   {"req": 100, "slop":12, "alloc": "C"},
   {"req": 80, "alloc": "D"}
  ],
  "traceTable": {
--- a/memory/replace/dmd/test/script-sort-by-num-blocks-expected.txt
+++ b/memory/replace/dmd/test/script-sort-by-num-blocks-expected.txt
@@ -1,15 +1,14 @@
 #-----------------------------------------------------------------
 # dmd.py --filter-stacks-for-testing -o script-sort-by-num-blocks-actual.txt --sort-by=num-blocks script-sort-by.json.gz
 
 Invocation {
   $DMD = '--mode=live'
   Mode = 'live'
-  Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Live {
   8 blocks in heap block record 1 of 3
   16,384 bytes (8,200 requested / 8,184 slop)
   Individual block sizes: 2,048 x 8
--- a/memory/replace/dmd/test/script-sort-by-req-expected.txt
+++ b/memory/replace/dmd/test/script-sort-by-req-expected.txt
@@ -1,15 +1,14 @@
 #-----------------------------------------------------------------
 # 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 = '--mode=live'
   Mode = 'live'
-  Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Live {
   5 blocks in heap block record 1 of 3
   16,392 bytes (16,392 requested / 0 slop)
   Individual block sizes: 4,096 x 4; 8
--- a/memory/replace/dmd/test/script-sort-by-slop-expected.txt
+++ b/memory/replace/dmd/test/script-sort-by-slop-expected.txt
@@ -1,15 +1,14 @@
 #-----------------------------------------------------------------
 # dmd.py --filter-stacks-for-testing -o script-sort-by-slop-actual.txt --sort-by=slop script-sort-by.json.gz
 
 Invocation {
   $DMD = '--mode=live'
   Mode = 'live'
-  Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Live {
   8 blocks in heap block record 1 of 3
   16,384 bytes (8,200 requested / 8,184 slop)
   Individual block sizes: 2,048 x 8
--- a/memory/replace/dmd/test/script-sort-by-usable-expected.txt
+++ b/memory/replace/dmd/test/script-sort-by-usable-expected.txt
@@ -1,15 +1,14 @@
 #-----------------------------------------------------------------
 # dmd.py --filter-stacks-for-testing -o script-sort-by-usable-actual.txt --sort-by=usable script-sort-by.json.gz
 
 Invocation {
   $DMD = '--mode=live'
   Mode = 'live'
-  Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Live {
   5 blocks in heap block record 1 of 3
   16,400 bytes (12,016 requested / 4,384 slop)
   Individual block sizes: 4,096 x 4; 16
index 79999e4dd50eaa67e251018d5020078b475b025d..fa7da08c25e2f3035981abce07f30da693014258
GIT binary patch
literal 292
zc$@(w0o(o`iwFo+TkBQ;19M|?X>fEcb8m8VEn;~tYIARH0L_uVZi6ro#&<u(>9SCX
zL{8E~0jYy+b?aDlsTdq^B~<>^P!`0y_w1w;c!GNJcfapG%Xb3<?2T>ny22PvA<4Yz
z>r{8Q%D@0HE3@aSf76x<xZRd@W}b??HyAqVqyxUfV00PRqE0_w^0p)JPNo5^`J($H
zkIo^WE{MAxfuySM(jnJ4880EwnHveu_>pr?t!+^^r1q;=iHop*V&0$qrxs^rp1b+s
zG><+U=h5nKaF>xm;)dQ?otjsDEzIxYgd0B!fr;Og^is;ZR9-5D$$e`dR+p;)mw+42
qV-OICUD`Bp6g}J)FXaR!1ab+LnxKL}O^}{qBl`jQstzJ|0ssKJMT|`V
--- a/memory/replace/dmd/test/test_dmd.js
+++ b/memory/replace/dmd/test/test_dmd.js
@@ -123,64 +123,65 @@ function scanTest(aJsonFilePath, aExtraA
   ].concat(aExtraArgs);
 
   return runProcess(new FileUtils.File(gPythonName), args) == 0;
 }
 
 function run_test() {
   let jsonFile, jsonFile2;
 
-  // These tests do full end-to-end testing of DMD, i.e. both the C++ code that
-  // generates the JSON output, and the script that post-processes that output.
+  // These tests do complete end-to-end testing of DMD, i.e. both the C++ code
+  // that generates the JSON output, and the script that post-processes that
+  // output.
   //
-  // Run these synchronously, because test() updates the full*.json files
+  // Run these synchronously, because test() updates the complete*.json files
   // in-place (to fix stacks) when it runs dmd.py, and that's not safe to do
   // asynchronously.
 
   gEnv.set(gEnv.get("DMD_PRELOAD_VAR"), gEnv.get("DMD_PRELOAD_VALUE"));
 
   runProcess(gDmdTestFile, []);
 
   function test2(aTestName, aMode) {
-    let name = "full-" + aTestName + "-" + aMode;
+    let name = "complete-" + 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("empty", "cumulative");
 
-  test2("unsampled1", "live");
-  test2("unsampled1", "dark-matter");
+  test2("full1", "live");
+  test2("full1", "dark-matter");
 
-  test2("unsampled2", "dark-matter");
-  test2("unsampled2", "cumulative");
+  test2("full2", "dark-matter");
+  test2("full2", "cumulative");
 
-  test2("sampled", "live");
+  test2("partial", "live");
 
   // Heap scan testing.
   jsonFile = FileUtils.getFile("CurWorkD", ["basic-scan.json"]);
   ok(scanTest(jsonFile.path), "Basic scan test");
 
   let is64Bit = Components.classes["@mozilla.org/xre/app-info;1"]
                           .getService(Components.interfaces.nsIXULRuntime).is64Bit;
   let basicScanFileName = "basic-scan-" + (is64Bit ? "64" : "32");
   test(basicScanFileName, ["--clamp-contents", jsonFile.path]);
   ok(scanTest(jsonFile.path, ["--clamp-contents"]), "Scan with address clamping");
+
   // Run the generic test a second time to ensure that the first time produced
   // valid JSON output. "--clamp-contents" is passed in so we don't have to have
   // more variants of the files.
   test(basicScanFileName, ["--clamp-contents", jsonFile.path]);
   jsonFile.remove(true);
 
-
   // 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.
--- a/memory/replace/dmd/test/xpcshell.ini
+++ b/memory/replace/dmd/test/xpcshell.ini
@@ -1,20 +1,20 @@
 [DEFAULT]
 support-files =
   basic-scan-32-expected.txt
   basic-scan-64-expected.txt
-  full-empty-live-expected.txt
-  full-empty-dark-matter-expected.txt
-  full-empty-cumulative-expected.txt
-  full-unsampled1-live-expected.txt
-  full-unsampled1-dark-matter-expected.txt
-  full-unsampled2-dark-matter-expected.txt
-  full-unsampled2-cumulative-expected.txt
-  full-sampled-live-expected.txt
+  complete-empty-live-expected.txt
+  complete-empty-dark-matter-expected.txt
+  complete-empty-cumulative-expected.txt
+  complete-full1-live-expected.txt
+  complete-full1-dark-matter-expected.txt
+  complete-full2-dark-matter-expected.txt
+  complete-full2-cumulative-expected.txt
+  complete-partial-live-expected.txt
   scan-test.py
   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
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -1116,22 +1116,22 @@ class RunProgram(MachCommandBase):
     @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', 'cumulative', 'scan'], 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('--stacks', choices=['partial', 'full'], group='DMD',
+        help='Allocation stack trace coverage. The default is \'partial\'.')
     @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, mode, sample_below, show_dump_stats):
+        debugparams, slowscript, dmd, mode, stacks, show_dump_stats):
 
         if conditions.is_android(self):
             # Running Firefox for Android is completely different
             if dmd:
                 print("DMD is not supported for Firefox for Android")
                 return 1
             from mozrunner.devices.android_device import verify_android_device, run_firefox_for_android
             if not (debug or debugger or debugparams):
@@ -1202,18 +1202,18 @@ class RunProgram(MachCommandBase):
             # 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 stacks:
+                dmd_params.append('--stacks=' + stacks)
             if show_dump_stats:
                 dmd_params.append('--show-dump-stats=yes')
 
             bin_dir = os.path.dirname(binpath)
             lib_name = self.substs['DLL_PREFIX'] + 'dmd' + self.substs['DLL_SUFFIX']
             dmd_lib = os.path.join(bin_dir, lib_name)
             if not os.path.exists(dmd_lib):
                 print("Please build with |--enable-dmd| to use DMD.")