Bug 1121830 - DMD: add "num" property to blocks in the output. r=mccr8.
authorNicholas Nethercote <nnethercote@mozilla.com>
Thu, 15 Jan 2015 20:38:38 -0800
changeset 251259 3bf791ce96e8be1c02b4c9bb67c107e7f6dc5170
parent 251258 add393f947f302cf0214b3cd963f7cc009b53aac
child 251260 0bbf63496d8d7cbb6803aab4c8bed5f8b0595478
push id4610
push userjlund@mozilla.com
push dateMon, 30 Mar 2015 18:32:55 +0000
treeherdermozilla-beta@4df54044d9ef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmccr8
bugs1121830
milestone38.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1121830 - DMD: add "num" property to blocks in the output. r=mccr8. The new "num" property lets identical blocks be aggregated in the output. This patch only uses the "num" property for dead blocks, because that's where the greatest potential benefit lies, but it could be used for live blocks as well. On one test case (a complex PDF file) running with --mode=cumulative --sample-below=1 this patch had the following effects. - Change in running speed was negligible. - Compressed output file size dropped from 8.8 to 5.0 MB. - Compressed output file size dropped from 297 to 50 MB. - dmd.py runtime (without stack fixing) dropped from 30 to 8 seconds.
memory/replace/dmd/DMD.cpp
memory/replace/dmd/DMD.h
memory/replace/dmd/dmd.py
memory/replace/dmd/test/SmokeDMD.cpp
memory/replace/dmd/test/full-unsampled2-cumulative-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-live1.json
memory/replace/dmd/test/script-diff-live2.json
memory/replace/dmd/test/script-ignore-alloc-fns.json
memory/replace/dmd/test/script-max-frames.json
memory/replace/dmd/test/script-sort-by.json.gz
xpcom/base/nsMemoryReporterManager.cpp
--- a/memory/replace/dmd/DMD.cpp
+++ b/memory/replace/dmd/DMD.cpp
@@ -33,17 +33,16 @@
 #include "js/Vector.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/HashFunctions.h"
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/JSONWriter.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MemoryReporting.h"
-#include "mozilla/SegmentedVector.h"
 
 // CodeAddressService is defined entirely in the header, so this does not make
 // DMD depend on XPCOM's object file.
 #include "CodeAddressService.h"
 
 // replace_malloc.h needs to be included before replace_malloc_bridge.h,
 // which DMD.h includes, so DMD.h needs to be included after replace_malloc.h.
 // MOZ_REPLACE_ONLY_MEMALIGN saves us from having to define
@@ -440,17 +439,17 @@ public:
     mIsLocked = false;
     MutexBase::Unlock();
   }
 
   bool IsLocked() { return mIsLocked; }
 };
 
 // This lock must be held while manipulating global state such as
-// gStackTraceTable, gLiveBlockTable, gDeadBlockList. Note that gOptions is
+// gStackTraceTable, gLiveBlockTable, gDeadBlockTable. Note that gOptions is
 // *not* protected by this lock because it is only written to by Options(),
 // which is only invoked at start-up and in ResetEverything(), which is only
 // used by SmokeDMD.cpp.
 static Mutex* gStateLock = nullptr;
 
 class AutoLockState
 {
   DISALLOW_COPY_AND_ASSIGN(AutoLockState);
@@ -1031,40 +1030,74 @@ public:
   {
     return mAllocStackTrace_mIsSampled.Ptr();
   }
 
   void AddStackTracesToTable(StackTraceSet& aStackTraces) const
   {
     aStackTraces.put(AllocStackTrace());  // never null
   }
+
+  // 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();
+  }
 };
 
-static const size_t kDeadBlockListSegmentSize = 16384;
-typedef SegmentedVector<DeadBlock, kDeadBlockListSegmentSize,
-                        InfallibleAllocPolicy> DeadBlockList;
-static DeadBlockList* gDeadBlockList = nullptr;
+// 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;
+static DeadBlockTable* gDeadBlockTable = nullptr;
+
+// Add the dead block to the dead block table, if that's appropriate.
+void MaybeAddToDeadBlockTable(const DeadBlock& aDb)
+{
+  if (gOptions->IsCumulativeMode() && aDb.AllocStackTrace()) {
+    AutoLockState lock;
+    if (DeadBlockTable::AddPtr p = gDeadBlockTable->lookupForAdd(aDb)) {
+      p->value() += 1;
+    } else {
+      gDeadBlockTable->add(p, aDb, 1);
+    }
+  }
+}
 
 // Add a pointer to each live stack trace into the given StackTraceSet.  (A
 // stack trace is live if it's used by one of the live blocks.)
 static void
 GatherUsedStackTraces(StackTraceSet& aStackTraces)
 {
   MOZ_ASSERT(gStateLock->IsLocked());
   MOZ_ASSERT(Thread::Fetch()->InterceptsAreBlocked());
 
   aStackTraces.finish();
   aStackTraces.init(512);
 
   for (auto r = gLiveBlockTable->all(); !r.empty(); r.popFront()) {
     r.front().AddStackTracesToTable(aStackTraces);
   }
 
-  for (auto iter = gDeadBlockList->Iter(); !iter.Done(); iter.Next()) {
-    iter.Get().AddStackTracesToTable(aStackTraces);
+  for (auto r = gDeadBlockTable->all(); !r.empty(); r.popFront()) {
+    r.front().key().AddStackTracesToTable(aStackTraces);
   }
 }
 
 // Delete stack traces that we aren't using, and compact our hashtable.
 static void
 GCStackTraces()
 {
   MOZ_ASSERT(gStateLock->IsLocked());
@@ -1243,20 +1276,17 @@ replace_realloc(void* aOldPtr, size_t aS
   // the realloc to avoid races, just like in replace_free().
   // Nb: This does an unnecessary hashtable remove+add if the block doesn't
   // move, but doing better isn't worth the effort.
   DeadBlock db;
   FreeCallback(aOldPtr, t, &db);
   void* ptr = gMallocTable->realloc(aOldPtr, aSize);
   if (ptr) {
     AllocCallback(ptr, aSize, t);
-    if (gOptions->IsCumulativeMode() && db.AllocStackTrace()) {
-      AutoLockState lock;
-      gDeadBlockList->InfallibleAppend(db);
-    }
+    MaybeAddToDeadBlockTable(db);
   } else {
     // If realloc fails, we undo the prior operations by re-inserting the old
     // pointer into the live block table. We don't have to do anything with the
     // dead block list because the dead block hasn't yet been inserted. The
     // block will end up looking like it was allocated for the first time here,
     // which is untrue, and the slop bytes will be zero, which may be untrue.
     // But this case is rare and doing better isn't worth the effort.
     AllocCallback(aOldPtr, gMallocTable->malloc_usable_size(aOldPtr), t);
@@ -1298,20 +1328,17 @@ replace_free(void* aPtr)
     return InfallibleAllocPolicy::free_(aPtr);
   }
 
   // Do the actual free after updating the table.  Otherwise, another thread
   // could call malloc and get the freed block and update the table, and then
   // our update here would remove the newly-malloc'd block.
   DeadBlock db;
   FreeCallback(aPtr, t, &db);
-  if (gOptions->IsCumulativeMode() && db.AllocStackTrace()) {
-    AutoLockState lock;
-    gDeadBlockList->InfallibleAppend(db);
-  }
+  MaybeAddToDeadBlockTable(db);
   gMallocTable->free(aPtr);
 }
 
 namespace mozilla {
 namespace dmd {
 
 //---------------------------------------------------------------------------
 // Options (Part 2)
@@ -1519,21 +1546,21 @@ Init(const malloc_table_t* aMallocTable)
     AutoLockState lock;
 
     gStackTraceTable = InfallibleAllocPolicy::new_<StackTraceTable>();
     gStackTraceTable->init(8192);
 
     gLiveBlockTable = InfallibleAllocPolicy::new_<LiveBlockTable>();
     gLiveBlockTable->init(8192);
 
-    // Create this even if the mode isn't Cumulative, in case the mode is
-    // changed later on (as is done by SmokeDMD.cpp, for example). It's tiny
-    // when empty, so space isn't a concern.
-    gDeadBlockList =
-      InfallibleAllocPolicy::new_<DeadBlockList>(kDeadBlockListSegmentSize);
+    // Create this even if the mode isn't Cumulative (albeit with a small
+    // size), in case the mode is changed later on (as is done by SmokeDMD.cpp,
+    // for example).
+    gDeadBlockTable = InfallibleAllocPolicy::new_<DeadBlockTable>();
+    gDeadBlockTable->init(gOptions->IsCumulativeMode() ? 8192 : 4);
   }
 
   gIsDMDInitialized = true;
 }
 
 //---------------------------------------------------------------------------
 // Block reporting and unreporting
 //---------------------------------------------------------------------------
@@ -1574,17 +1601,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 = 3;
+static const int kOutputVersionNumber = 4;
 
 // 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
@@ -1611,17 +1638,17 @@ SizeOfInternal(Sizes* aSizes)
     }
   }
 
   aSizes->mStackTraceTable =
     gStackTraceTable->sizeOfIncludingThis(MallocSizeOf);
 
   aSizes->mLiveBlockTable = gLiveBlockTable->sizeOfIncludingThis(MallocSizeOf);
 
-  aSizes->mDeadBlockList = gDeadBlockList->SizeOfIncludingThis(MallocSizeOf);
+  aSizes->mDeadBlockTable = gDeadBlockTable->sizeOfIncludingThis(MallocSizeOf);
 }
 
 void
 DMDFuncs::SizeOf(Sizes* aSizes)
 {
   aSizes->Clear();
 
   AutoBlockIntercepts block(Thread::Fetch());
@@ -1796,29 +1823,36 @@ AnalyzeImpl(UniquePtr<JSONWriteFunc> aWr
             }
             writer.EndArray();
           }
         }
         writer.EndObject();
       }
 
       // Dead blocks.
-      for (auto iter = gDeadBlockList->Iter(); !iter.Done(); iter.Next()) {
-        const DeadBlock& b = iter.Get();
+      for (auto r = gDeadBlockTable->all(); !r.empty(); r.popFront()) {
+        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.StringProperty("alloc", isc.ToIdString(b.AllocStackTrace()));
+
+          if (num > 1) {
+            writer.IntProperty("num", num);
+          }
         }
         writer.EndObject();
       }
     }
     writer.EndArray();
 
     StatusMsg("  Constructing the stack trace table...\n");
 
@@ -1885,19 +1919,20 @@ AnalyzeImpl(UniquePtr<JSONWriteFunc> aWr
       Show(gStackTraceTable->capacity(), buf2, kBufLen),
       Show(gStackTraceTable->count(),    buf3, kBufLen));
 
     StatusMsg("      Live block table:     %10s bytes (%s entries, %s used)\n",
       Show(sizes.mLiveBlockTable,       buf1, kBufLen),
       Show(gLiveBlockTable->capacity(), buf2, kBufLen),
       Show(gLiveBlockTable->count(),    buf3, kBufLen));
 
-    StatusMsg("      Dead block list:      %10s bytes (%s entries)\n",
-      Show(sizes.mDeadBlockList,     buf1, kBufLen),
-      Show(gDeadBlockList->Length(), buf2, kBufLen));
+    StatusMsg("      Dead block table:     %10s bytes (%s entries, %s used)\n",
+      Show(sizes.mDeadBlockTable,       buf1, kBufLen),
+      Show(gDeadBlockTable->capacity(), buf2, kBufLen),
+      Show(gDeadBlockTable->count(),    buf3, kBufLen));
 
     StatusMsg("    }\n");
     StatusMsg("    Data structures that are destroyed after Dump() ends {\n");
 
     StatusMsg("      Location service:      %10s bytes\n",
       Show(locService->SizeOfIncludingThis(MallocSizeOf), buf1, kBufLen));
     StatusMsg("      Used stack traces set: %10s bytes\n",
       Show(usedStackTraces.sizeOfExcludingThis(MallocSizeOf), buf1, kBufLen));
@@ -1947,14 +1982,14 @@ DMDFuncs::ResetEverything(const char* aO
   AutoLockState lock;
 
   // Reset options.
   InfallibleAllocPolicy::delete_(gOptions);
   gOptions = InfallibleAllocPolicy::new_<Options>(aOptions);
 
   // Clear all existing blocks.
   gLiveBlockTable->clear();
-  gDeadBlockList->Clear();
+  gDeadBlockTable->clear();
   gSmallBlockActualSizeCounter = 0;
 }
 
 }   // namespace dmd
 }   // namespace mozilla
--- a/memory/replace/dmd/DMD.h
+++ b/memory/replace/dmd/DMD.h
@@ -24,17 +24,17 @@ class JSONWriteFunc;
 namespace dmd {
 
 struct Sizes
 {
   size_t mStackTracesUsed;
   size_t mStackTracesUnused;
   size_t mStackTraceTable;
   size_t mLiveBlockTable;
-  size_t mDeadBlockList;
+  size_t mDeadBlockTable;
 
   Sizes() { Clear(); }
   void Clear() { memset(this, 0, sizeof(Sizes)); }
 };
 
 // See further below for a description of each method. The DMDFuncs class
 // should contain a virtual method for each of them (except IsRunning,
 // which can be inferred from the DMDFuncs singleton existing).
@@ -142,21 +142,23 @@ 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: The original format. Implemented in bug 1044709.
-//   // - 2: Added the "mode" property under "invocation". Added in bug 1094552.
-//   // - 3: The "dmdEnvVar" property under "invocation" can now be |null| if
-//   //      the |DMD| environment variable is not defined. Done in bug 1100851.
-//   "version": 3,
+//   // - 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,
 //
 //   // Information about how DMD was invoked. A mandatory object.
 //   "invocation": {
 //     // The contents of the $DMD environment variable. A string, or |null| is
 //     // $DMD is undefined.
 //     "dmdEnvVar": "--mode=dark-matter",
 //
 //     // The profiling mode. A mandatory string taking one of the following
@@ -179,17 +181,23 @@ ClearReports()
 //
 //       // 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.
 //       "slop": 512,
 //
 //       // The stack trace at which the block was allocated. A mandatory
 //       // string which indexes into the "traceTable" object.
-//       "alloc": "A"
+//       "alloc": "A",
+//
+//       // 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
 //     },
 //
 //     // 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
@@ -228,21 +236,22 @@ ClearReports()
 //     "D": "#00: foo (Foo.cpp:123)",
 //     "E": "#00: bar (Bar.cpp:234)",
 //     "F": "#00: baz (Baz.cpp:345)",
 //     "G": "#00: quux (Quux.cpp:456)",
 //     "H": "#00: quuux (Quux.cpp:567)"
 //   }
 // }
 //
-// Implementation note: normally, this wouldn't be templated, but in that case,
-// the function is compiled, which makes the destructor for the UniquePtr fire
-// up, and that needs JSONWriteFunc to be fully defined. That, in turn,
-// requires to include JSONWriter.h, which includes double-conversion.h, which
-// ends up breaking various things built with -Werror for various reasons.
+// 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
+// -Werror for various reasons.
 //
 template <typename JSONWriteFunc>
 inline void
 Analyze(UniquePtr<JSONWriteFunc> aWriteFunc)
 {
   DMDFuncs* funcs = DMDFuncs::Get();
   if (funcs) {
     funcs->Analyze(Move(aWriteFunc));
--- a/memory/replace/dmd/dmd.py
+++ b/memory/replace/dmd/dmd.py
@@ -15,17 +15,17 @@ import json
 import os
 import platform
 import re
 import shutil
 import sys
 import tempfile
 
 # The DMD output version this script handles.
-outputVersion = 3
+outputVersion = 4
 
 # 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',
@@ -403,37 +403,42 @@ def getDigestFromFile(args, inputFile):
         else:
             # sampled
             reqSize = sampleBelowSize
             if 'slop' in block:
                 raise Exception("'slop' property in sampled block'")
             slopSize = 0
             isSampled = True
 
-        usableSize = reqSize + slopSize
-        heapUsableSize += usableSize
-        heapBlocks += 1
+        if 'num' in block:
+            num = block['num']
+        else:
+            num = 1
 
-        record.numBlocks  += 1
-        record.reqSize    += reqSize
-        record.slopSize   += slopSize
-        record.usableSize += usableSize
+        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)] += 1
+        record.usableSizes[(usableSize, isSampled)] += 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
--- a/memory/replace/dmd/test/SmokeDMD.cpp
+++ b/memory/replace/dmd/test/SmokeDMD.cpp
@@ -255,16 +255,21 @@ TestUnsampled(const char* aTestName, int
   free(e);
   Report(e2);
   free(e3);
 //free(w);
 //free(x);
 //free(y);
 //free(z);
 
+  // Do some allocations that will only show up in cumulative mode.
+  for (int i = 0; i < 100; i++) {
+    free(malloc(128));
+  }
+
   if (aNum == 2) {
     // Analyze 2.
     Analyze(Move(f));
   }
 }
 
 void
 TestSampled(const char* aTestName, const char* aMode, int aSeven)
--- a/memory/replace/dmd/test/full-unsampled2-cumulative-expected.txt
+++ b/memory/replace/dmd/test/full-unsampled2-cumulative-expected.txt
@@ -5,160 +5,170 @@ Invocation {
   $DMD = '--mode=cumulative --sample-below=1 --show-dump-stats=yes'
   Mode = 'cumulative'
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Cumulative {
-  1 block in heap block record 1 of 16
-  8,192 bytes (7,169 requested / 1,023 slop)
-  47.10% of the heap (47.10% cumulative)
+  100 blocks in heap block record 1 of 17
+  12,800 bytes (12,800 requested / 0 slop)
+  Individual block sizes: 128 x 100
+  42.40% of the heap (42.40% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Cumulative {
-  1 block in heap block record 2 of 16
-  4,096 bytes (4,096 requested / 0 slop)
-  23.55% of the heap (70.65% cumulative)
+  1 block in heap block record 2 of 17
+  8,192 bytes (7,169 requested / 1,023 slop)
+  27.13% of the heap (69.53% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Cumulative {
-  10 blocks in heap block record 3 of 16
-  1,120 bytes (1,000 requested / 120 slop)
-  Individual block sizes: 112 x 10
-  6.44% of the heap (77.09% cumulative)
+  1 block in heap block record 3 of 17
+  4,096 bytes (4,096 requested / 0 slop)
+  13.57% of the heap (83.09% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Cumulative {
-  1 block in heap block record 4 of 16
-  1,024 bytes (1,024 requested / 0 slop)
-  5.89% of the heap (82.98% cumulative)
+  10 blocks in heap block record 4 of 17
+  1,120 bytes (1,000 requested / 120 slop)
+  Individual block sizes: 112 x 10
+  3.71% of the heap (86.80% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Cumulative {
-  1 block in heap block record 5 of 16
-  1,024 bytes (1,023 requested / 1 slop)
-  5.89% of the heap (88.87% cumulative)
+  1 block in heap block record 5 of 17
+  1,024 bytes (1,024 requested / 0 slop)
+  3.39% of the heap (90.20% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Cumulative {
-  6 blocks in heap block record 6 of 16
-  528 bytes (528 requested / 0 slop)
-  Individual block sizes: 128; 112; 96; 80; 64; 48
-  3.04% of the heap (91.90% cumulative)
+  1 block in heap block record 6 of 17
+  1,024 bytes (1,023 requested / 1 slop)
+  3.39% of the heap (93.59% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Cumulative {
-  6 blocks in heap block record 7 of 16
+  6 blocks in heap block record 7 of 17
   528 bytes (528 requested / 0 slop)
   Individual block sizes: 128; 112; 96; 80; 64; 48
-  3.04% of the heap (94.94% cumulative)
+  1.75% of the heap (95.34% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Cumulative {
-  1 block in heap block record 8 of 16
-  512 bytes (512 requested / 0 slop)
-  2.94% of the heap (97.88% cumulative)
+  6 blocks in heap block record 8 of 17
+  528 bytes (528 requested / 0 slop)
+  Individual block sizes: 128; 112; 96; 80; 64; 48
+  1.75% of the heap (97.09% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Cumulative {
-  1 block in heap block record 9 of 16
-  80 bytes (79 requested / 1 slop)
-  0.46% of the heap (98.34% cumulative)
+  1 block in heap block record 9 of 17
+  512 bytes (512 requested / 0 slop)
+  1.70% of the heap (98.78% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Cumulative {
-  1 block in heap block record 10 of 16
-  80 bytes (78 requested / 2 slop)
-  0.46% of the heap (98.80% cumulative)
+  1 block in heap block record 10 of 17
+  80 bytes (79 requested / 1 slop)
+  0.26% of the heap (99.05% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Cumulative {
-  1 block in heap block record 11 of 16
-  80 bytes (77 requested / 3 slop)
-  0.46% of the heap (99.26% cumulative)
+  1 block in heap block record 11 of 17
+  80 bytes (78 requested / 2 slop)
+  0.26% of the heap (99.31% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Cumulative {
-  1 block in heap block record 12 of 16
-  64 bytes (64 requested / 0 slop)
-  0.37% of the heap (99.63% cumulative)
+  1 block in heap block record 12 of 17
+  80 bytes (77 requested / 3 slop)
+  0.26% of the heap (99.58% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Cumulative {
-  1 block in heap block record 13 of 16
-  32 bytes (30 requested / 2 slop)
-  0.18% of the heap (99.82% cumulative)
+  1 block in heap block record 13 of 17
+  64 bytes (64 requested / 0 slop)
+  0.21% of the heap (99.79% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Cumulative {
-  1 block in heap block record 14 of 16
-  16 bytes (10 requested / 6 slop)
-  0.09% of the heap (99.91% cumulative)
+  1 block in heap block record 14 of 17
+  32 bytes (30 requested / 2 slop)
+  0.11% of the heap (99.89% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Cumulative {
-  1 block in heap block record 15 of 16
-  8 bytes (8 requested / 0 slop)
+  1 block in heap block record 15 of 17
+  16 bytes (10 requested / 6 slop)
   0.05% of the heap (99.95% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Cumulative {
-  1 block in heap block record 16 of 16
+  1 block in heap block record 16 of 17
   8 bytes (8 requested / 0 slop)
-  0.05% of the heap (100.00% cumulative)
+  0.03% of the heap (99.97% cumulative)
+  Allocated at {
+    #01: ... DMD.cpp ...
+  }
+}
+
+Cumulative {
+  1 block in heap block record 17 of 17
+  8 bytes (8 requested / 0 slop)
+  0.03% of the heap (100.00% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 #-----------------------------------------------------------------
 
 Summary {
-  Total: 17,392 bytes in 35 blocks
+  Total: 30,192 bytes in 135 blocks
 }
 
--- a/memory/replace/dmd/test/script-diff-dark-matter1.json
+++ b/memory/replace/dmd/test/script-diff-dark-matter1.json
@@ -1,38 +1,29 @@
 {
- "version": 3,
+ "version": 4,
  "invocation": {
   "dmdEnvVar": "--mode=dark-matter --sample-below=127",
   "mode": "dark-matter",
   "sampleBelowSize": 127
  },
  "blockList": [
-  {"req": 4096, "alloc": "A"},
-  {"req": 4096, "alloc": "A"},
-  {"req": 4096, "alloc": "A"},
-  {"req": 4096, "alloc": "A"},
+  {"req": 4096, "alloc": "A", "num": 4},
 
-  {"req": 4096, "alloc": "B"},
-  {"req": 4096, "alloc": "B"},
-  {"req": 4096, "alloc": "B"},
+  {"req": 4096, "alloc": "B", "num": 3},
   {"req": 4096, "alloc": "B"},
 
-  {"req": 4096, "alloc": "C"},
-  {"req": 4096, "alloc": "C"},
-  {"req": 4096, "alloc": "C"},
-  {"req": 4096, "alloc": "C"},
+  {"req": 4096, "alloc": "C", "num": 2},
+  {"req": 4096, "alloc": "C", "num": 2},
 
-  {"req": 4096,             "alloc": "D", "reps": ["R1"]},
-  {"req": 4096,             "alloc": "D", "reps": ["R1"]},
+  {"req": 4096,             "alloc": "D", "reps": ["R1"], "num": 2},
   {"req": 2000, "slop": 48, "alloc": "D", "reps": ["R1"]},
 
   {"req": 15360,            "alloc": "F"},
-  {"req": 512,              "alloc": "F"},
-  {"req": 512,              "alloc": "F"},
+  {"req": 512,              "alloc": "F", "num": 2},
   {                         "alloc": "F"},
   {"req": 1024,             "alloc": "F", "reps": ["R1"]},
   {                         "alloc": "F", "reps": ["R1"]},
   {"req": 1000, "slop": 24, "alloc": "F", "reps": ["R1", "R2"]},
   {                         "alloc": "F", "reps": ["R1", "R2"]},
 
   {"req": 4096,            "alloc": "G"},
   {"req": 8192,            "alloc": "G"},
--- a/memory/replace/dmd/test/script-diff-dark-matter2.json
+++ b/memory/replace/dmd/test/script-diff-dark-matter2.json
@@ -1,50 +1,37 @@
 {
- "version": 3,
+ "version": 4,
  "invocation": {
   "dmdEnvVar": "--sample-below=63",
   "mode": "dark-matter",
   "sampleBelowSize": 63
  },
  "blockList": [
-  {"req": 4096, "alloc": "A"},
-  {"req": 4096, "alloc": "A"},
-  {"req": 4096, "alloc": "A"},
-  {"req": 4096, "alloc": "A"},
+  {"req": 4096, "alloc": "A", "num": 4},
 
   {"req": 8192, "alloc": "B"},
   {"req": 8192, "alloc": "B"},
 
-  {"req": 4000, "slop": 96, "alloc": "C"},
-  {"req": 4000, "slop": 96, "alloc": "C"},
-  {"req": 4000, "slop": 96, "alloc": "C"},
-  {"req": 4000, "slop": 96, "alloc": "C"},
+  {"req": 4000, "slop": 96, "alloc": "C", "num": 4},
 
-  {"req": 4096, "alloc": "E"},
-  {"req": 4096, "alloc": "E"},
-  {"req": 4096, "alloc": "E"},
-  {"req": 4096, "alloc": "E"},
+  {"req": 4096, "alloc": "E", "num": 4},
 
   {"req": 2000, "slop": 48, "alloc": "F"},
   {"req": 1000, "slop": 24, "alloc": "F", "reps": ["R1"]},
   {"req": 512,              "alloc": "F"},
   {"req": 512,              "alloc": "F"},
   {"req": 512,              "alloc": "F"},
   {"req": 512,              "alloc": "F"},
   {"req": 128,              "alloc": "F"},
   {                         "alloc": "F", "reps": ["R1", "R2"]},
-  {"req": 64,               "alloc": "F"},
-  {"req": 64,               "alloc": "F"},
-  {"req": 64,               "alloc": "F"},
-  {"req": 64,               "alloc": "F"},
+  {"req": 64,               "alloc": "F", "num": 4},
   {                         "alloc": "F"},
 
-  {"req": 4096,            "alloc": "G"},
-  {"req": 4096,            "alloc": "G"},
+  {"req": 4096,            "alloc": "G", "num": 2},
   {"req": 20480,           "alloc": "G"}
  ],
  "traceTable": {
   "A": ["AA"],
   "B": ["BB"],
   "C": ["CC"],
   "D": ["DD"],
   "E": ["EE"],
--- a/memory/replace/dmd/test/script-diff-live1.json
+++ b/memory/replace/dmd/test/script-diff-live1.json
@@ -1,30 +1,21 @@
 {
- "version": 3,
+ "version": 4,
  "invocation": {
   "dmdEnvVar": "--mode=live --sample-below=127",
   "mode": "live",
   "sampleBelowSize": 127
  },
  "blockList": [
-  {"req": 4096, "alloc": "A"},
-  {"req": 4096, "alloc": "A"},
-  {"req": 4096, "alloc": "A"},
-  {"req": 4096, "alloc": "A"},
+  {"req": 4096, "alloc": "A", "num": 4},
 
-  {"req": 4096, "alloc": "B"},
-  {"req": 4096, "alloc": "B"},
-  {"req": 4096, "alloc": "B"},
-  {"req": 4096, "alloc": "B"},
+  {"req": 4096, "alloc": "B", "num": 4},
 
-  {"req": 4096, "alloc": "C"},
-  {"req": 4096, "alloc": "C"},
-  {"req": 4096, "alloc": "C"},
-  {"req": 4096, "alloc": "C"},
+  {"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"},
--- a/memory/replace/dmd/test/script-diff-live2.json
+++ b/memory/replace/dmd/test/script-diff-live2.json
@@ -1,46 +1,35 @@
 {
- "version": 3,
+ "version": 4,
  "invocation": {
   "dmdEnvVar": "--mode=live --sample-below=63",
   "mode": "live",
   "sampleBelowSize": 63
  },
  "blockList": [
-  {"req": 4096, "alloc": "A"},
-  {"req": 4096, "alloc": "A"},
-  {"req": 4096, "alloc": "A"},
+  {"req": 4096, "alloc": "A", "num": 3},
   {"req": 4096, "alloc": "A"},
 
   {"req": 8192, "alloc": "B"},
   {"req": 8192, "alloc": "B"},
 
-  {"req": 4000, "slop": 96, "alloc": "C"},
-  {"req": 4000, "slop": 96, "alloc": "C"},
-  {"req": 4000, "slop": 96, "alloc": "C"},
-  {"req": 4000, "slop": 96, "alloc": "C"},
+  {"req": 4000, "slop": 96, "alloc": "C", "num": 4},
 
   {"req": 4096, "alloc": "E"},
   {"req": 4096, "alloc": "E"},
   {"req": 4096, "alloc": "E"},
   {"req": 4096, "alloc": "E"},
 
   {"req": 2000, "slop": 48, "alloc": "F"},
   {"req": 1000, "slop": 24, "alloc": "F"},
-  {"req": 512,              "alloc": "F"},
-  {"req": 512,              "alloc": "F"},
-  {"req": 512,              "alloc": "F"},
-  {"req": 512,              "alloc": "F"},
+  {"req": 512,              "alloc": "F", "num": 4},
   {"req": 128,              "alloc": "F"},
   {                         "alloc": "F"},
-  {"req": 64,               "alloc": "F"},
-  {"req": 64,               "alloc": "F"},
-  {"req": 64,               "alloc": "F"},
-  {"req": 64,               "alloc": "F"},
+  {"req": 64,               "alloc": "F", "num": 4},
   {                         "alloc": "F"},
 
   {"req": 4096,            "alloc": "G"},
   {"req": 4096,            "alloc": "G"},
   {"req": 20480,           "alloc": "G"}
  ],
  "traceTable": {
   "A": ["AA"],
--- a/memory/replace/dmd/test/script-ignore-alloc-fns.json
+++ b/memory/replace/dmd/test/script-ignore-alloc-fns.json
@@ -1,10 +1,10 @@
 {
- "version": 3,
+ "version": 4,
  "invocation": {
   "dmdEnvVar": null,
   "mode": "dark-matter",
   "sampleBelowSize": 2500
  },
  "blockList": [
   {"req": 1048576,           "alloc": "A"},
   {"req": 65536,             "alloc": "B"},
--- a/memory/replace/dmd/test/script-max-frames.json
+++ b/memory/replace/dmd/test/script-max-frames.json
@@ -1,10 +1,10 @@
 {
- "version": 3,
+ "version": 4,
  "invocation": {
   "dmdEnvVar": "--mode=live",
   "mode": "live",
   "sampleBelowSize": 1
  },
  "blockList": [
   {"req": 4096, "alloc": "A"},
   {"req": 128, "alloc": "B"},
index 6f0232aefcf53a6d36dd99d8d8ebefd2192326a7..79999e4dd50eaa67e251018d5020078b475b025d
GIT binary patch
literal 305
zc$@(-0nYv(iwFoYlDJd=19M|?X>fEcb8m8VEn;~tYIARH0L_uXZi6rkhVOX_%eyd%
zPHDSU1!;#Q+HJQ@yG$HX$RxBt7%(Oc@$QRhN1;zp-+q68ek*p*06SARxhpY-`_Rk0
z>|CnbW2a{Tm=)Pm*}du7H*mEoTxK3^-Wd#uk|c2MG`gs)k;b~8m;9IDn!!NX2kX+$
z=e%is@7-H_tj!k<=Fu($)Yfy#66jU=T@a#&lW`w{ues6V8Q*fw{nN&}ipE~VN?e3p
ziFv&nKeea}^W4oh5A*2Fb{?(Hi`x$j5?Xp&>(spJgEjvL6|{a70+aDxa+Fe@q#C7C
zn4GTmQS0I&fD-VA^B4pK;x4VKIErqM=a+JV5(2q|N=;BfpeE=?v4Q;oytf_<j{*Py
D)Mu3V
--- a/xpcom/base/nsMemoryReporterManager.cpp
+++ b/xpcom/base/nsMemoryReporterManager.cpp
@@ -952,17 +952,17 @@ public:
            sizes.mStackTraceTable,
            "Memory used by DMD's stack trace table.");
 
     REPORT("explicit/dmd/live-block-table",
            sizes.mLiveBlockTable,
            "Memory used by DMD's live block table.");
 
     REPORT("explicit/dmd/dead-block-list",
-           sizes.mDeadBlockList,
+           sizes.mDeadBlockTable,
            "Memory used by DMD's dead block list.");
 
 #undef REPORT
 
     return NS_OK;
   }
 
 private: