Bug 1094552 (part 6) - DMD: add support for cumulative heap profiling. r=mccr8.

By adding a new "cumulative" mode.

--HG--
extra : rebase_source : 5c851b7c594a134fae48393ff0becfd057715041
This commit is contained in:
Nicholas Nethercote 2014-10-30 20:22:47 -07:00
parent 5b5d3c27bb
commit 5cc6a39a9a
10 changed files with 361 additions and 42 deletions

View File

@ -38,6 +38,7 @@
#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.
@ -336,7 +337,12 @@ class Options
// 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
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.
Cumulative
};
char* mDMDEnvVar; // a saved copy, for later printing
@ -357,6 +363,7 @@ public:
bool IsLiveMode() const { return mMode == Live; }
bool IsDarkMatterMode() const { return mMode == DarkMatter; }
bool IsCumulativeMode() const { return mMode == Cumulative; }
const char* DMDEnvVar() const { return mDMDEnvVar; }
@ -438,10 +445,10 @@ public:
};
// This lock must be held while manipulating global state such as
// gStackTraceTable, gLiveBlockTable, etc. Note that gOptions is *not*
// protected by this lock because it is only written to by Options(), which is
// only invoked at start-up and in ResetEverything(), which is only used by
// SmokeDMD.cpp.
// gStackTraceTable, gLiveBlockTable, gDeadBlockList. 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
@ -850,10 +857,10 @@ class LiveBlock
public:
LiveBlock(const void* aPtr, size_t aReqSize,
const StackTrace* aAllocStackTrace, bool aIsSampled)
: mPtr(aPtr),
mReqSize(aReqSize),
mAllocStackTrace_mIsSampled(aAllocStackTrace, aIsSampled),
mReportStackTrace_mReportedOnAlloc() // all fields get zeroed
: mPtr(aPtr)
, mReqSize(aReqSize)
, mAllocStackTrace_mIsSampled(aAllocStackTrace, aIsSampled)
, mReportStackTrace_mReportedOnAlloc() // all fields get zeroed
{
MOZ_ASSERT(aAllocStackTrace);
}
@ -982,6 +989,60 @@ public:
typedef js::HashSet<LiveBlock, LiveBlock, InfallibleAllocPolicy> LiveBlockTable;
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;
public:
DeadBlock()
: mReqSize(0)
, mSlopSize(0)
, mAllocStackTrace_mIsSampled(nullptr, 0)
{}
explicit DeadBlock(const LiveBlock& aLb)
: mReqSize(aLb.ReqSize())
, mSlopSize(aLb.SlopSize())
, mAllocStackTrace_mIsSampled(aLb.AllocStackTrace(), aLb.IsSampled())
{
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();
}
void AddStackTracesToTable(StackTraceSet& aStackTraces) const
{
aStackTraces.put(AllocStackTrace()); // never null
}
};
static const size_t kDeadBlockListSegmentSize = 16384;
typedef SegmentedVector<DeadBlock, kDeadBlockListSegmentSize,
InfallibleAllocPolicy> DeadBlockList;
static DeadBlockList* gDeadBlockList = nullptr;
// 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
@ -996,6 +1057,10 @@ GatherUsedStackTraces(StackTraceSet& aStackTraces)
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);
}
}
// Delete stack traces that we aren't using, and compact our hashtable.
@ -1063,7 +1128,7 @@ AllocCallback(void* aPtr, size_t aReqSize, Thread* aT)
}
static void
FreeCallback(void* aPtr, Thread* aT)
FreeCallback(void* aPtr, Thread* aT, DeadBlock* aDeadBlock)
{
if (!aPtr) {
return;
@ -1072,7 +1137,17 @@ FreeCallback(void* aPtr, Thread* aT)
AutoLockState lock;
AutoBlockIntercepts block(aT);
gLiveBlockTable->remove(aPtr);
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.
}
if (gStackTraceTable->count() > gGCStackTraceTableWhenSizeExceeds) {
GCStackTraces();
@ -1168,15 +1243,22 @@ replace_realloc(void* aOldPtr, size_t aSize)
// 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.
FreeCallback(aOldPtr, t);
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);
}
} else {
// If realloc fails, we re-insert the old pointer. It will look 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.
// 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);
}
return ptr;
@ -1219,7 +1301,12 @@ replace_free(void* 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.
FreeCallback(aPtr, t);
DeadBlock db;
FreeCallback(aPtr, t, &db);
if (gOptions->IsCumulativeMode() && db.AllocStackTrace()) {
AutoLockState lock;
gDeadBlockList->InfallibleAppend(db);
}
gMallocTable->free(aPtr);
}
@ -1323,6 +1410,8 @@ Options::Options(const char* aDMDEnvVar)
mMode = Options::Live;
} else if (strcmp(arg, "--mode=dark-matter") == 0) {
mMode = Options::DarkMatter;
} else if (strcmp(arg, "--mode=cumulative") == 0) {
mMode = Options::Cumulative;
} else if (GetLong(arg, "--sample-below", 1, mSampleBelowSize.mMax,
&myLong)) {
@ -1359,7 +1448,7 @@ Options::BadArg(const char* aArg)
StatusMsg("\n");
StatusMsg("The following options are allowed; defaults are shown in [].\n");
StatusMsg(" --mode=<mode> Profiling mode [dark-matter]\n");
StatusMsg(" where <mode> is one of: live, dark-matter\n");
StatusMsg(" where <mode> is one of: live, dark-matter, cumulative\n");
StatusMsg(" --sample-below=<1..%d> Sample blocks smaller than this [%d]\n",
int(mSampleBelowSize.mMax),
int(mSampleBelowSize.mDefault));
@ -1430,6 +1519,12 @@ Init(const malloc_table_t* aMallocTable)
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);
}
gIsDMDInitialized = true;
@ -1516,6 +1611,8 @@ SizeOfInternal(Sizes* aSizes)
gStackTraceTable->sizeOfIncludingThis(MallocSizeOf);
aSizes->mLiveBlockTable = gLiveBlockTable->sizeOfIncludingThis(MallocSizeOf);
aSizes->mDeadBlockList = gDeadBlockList->SizeOfIncludingThis(MallocSizeOf);
}
void
@ -1644,6 +1741,8 @@ AnalyzeImpl(UniquePtr<JSONWriteFunc> aWriter)
mode = "live";
} else if (gOptions->IsDarkMatterMode()) {
mode = "dark-matter";
} else if (gOptions->IsCumulativeMode()) {
mode = "cumulative";
} else {
MOZ_ASSERT(false);
mode = "(unknown DMD mode)";
@ -1659,6 +1758,7 @@ AnalyzeImpl(UniquePtr<JSONWriteFunc> aWriter)
writer.StartArrayProperty("blockList");
{
// Live blocks.
for (auto r = gLiveBlockTable->all(); !r.empty(); r.popFront()) {
const LiveBlock& b = r.front();
b.AddStackTracesToTable(usedStackTraces);
@ -1688,6 +1788,24 @@ AnalyzeImpl(UniquePtr<JSONWriteFunc> aWriter)
}
writer.EndObject();
}
// Dead blocks.
for (auto iter = gDeadBlockList->Iter(); !iter.Done(); iter.Next()) {
const DeadBlock& b = iter.Get();
b.AddStackTracesToTable(usedStackTraces);
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()));
}
writer.EndObject();
}
}
writer.EndArray();
@ -1761,6 +1879,10 @@ AnalyzeImpl(UniquePtr<JSONWriteFunc> aWriter)
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(" }\n");
StatusMsg(" Data structures that are destroyed after Dump() ends {\n");
@ -1819,6 +1941,7 @@ DMDFuncs::ResetEverything(const char* aOptions)
// Clear all existing blocks.
gLiveBlockTable->clear();
gDeadBlockList->Clear();
gSmallBlockActualSizeCounter = 0;
}

View File

@ -29,6 +29,7 @@ struct Sizes
size_t mStackTracesUnused;
size_t mStackTraceTable;
size_t mLiveBlockTable;
size_t mDeadBlockList;
Sizes() { Clear(); }
void Clear() { memset(this, 0, sizeof(Sizes)); }
@ -156,7 +157,7 @@ ClearReports()
// "dmdEnvVar": "1",
//
// // The profiling mode. A mandatory string taking one of the following
// // values: "live", "dark-matter".
// // values: "live", "dark-matter", "cumulative".
// "mode": "dark-matter",
//
// // The value of the --sample-below-size option. A mandatory integer.

View File

@ -280,7 +280,7 @@ def getDigestFromFile(args, inputFile):
traceTable = j['traceTable']
frameTable = j['frameTable']
if not mode in ['live', 'dark-matter']:
if not mode in ['live', 'dark-matter', 'cumulative']:
raise Exception("bad 'mode' property: '{:s}'".format(mode))
heapIsSampled = sampleBelowSize > 1 # is sampling present?
@ -336,8 +336,8 @@ def getDigestFromFile(args, inputFile):
# Aggregate blocks into records. All sufficiently similar blocks go into a
# single record.
if mode == 'live':
liveRecords = collections.defaultdict(Record)
if mode in ['live', 'cumulative']:
liveOrCumulativeRecords = collections.defaultdict(Record)
elif mode == 'dark-matter':
unreportedRecords = collections.defaultdict(Record)
onceReportedRecords = collections.defaultdict(Record)
@ -369,9 +369,9 @@ def getDigestFromFile(args, inputFile):
traceTable[traceKey]))
allocatedAtTraceKey = block['alloc']
if mode == 'live':
if mode in ['live', 'cumulative']:
recordKey = makeRecordKeyPart(allocatedAtTraceKey)
records = liveRecords
records = liveOrCumulativeRecords
elif mode == 'dark-matter':
recordKey = makeRecordKeyPart(allocatedAtTraceKey)
if 'reps' in block:
@ -414,7 +414,7 @@ def getDigestFromFile(args, inputFile):
buildTraceDescription(traceTable, frameTable,
allocatedAtTraceKey)
if mode == 'live':
if mode in ['live', 'cumulative']:
pass
elif mode == 'dark-matter':
if 'reps' in block and record.reportedAtDescs == []:
@ -430,8 +430,8 @@ def getDigestFromFile(args, inputFile):
digest['heapUsableSize'] = heapUsableSize
digest['heapBlocks'] = heapBlocks
digest['heapIsSampled'] = heapIsSampled
if mode == 'live':
digest['liveRecords'] = liveRecords
if mode in ['live', 'cumulative']:
digest['liveOrCumulativeRecords'] = liveOrCumulativeRecords
elif mode == 'dark-matter':
digest['unreportedRecords'] = unreportedRecords
digest['onceReportedRecords'] = onceReportedRecords
@ -475,9 +475,10 @@ def diffDigests(args, d1, d2):
d3['heapUsableSize'] = d2['heapUsableSize'] - d1['heapUsableSize']
d3['heapBlocks'] = d2['heapBlocks'] - d1['heapBlocks']
d3['heapIsSampled'] = d2['heapIsSampled'] or d1['heapIsSampled']
if d1['mode'] == 'live':
d3['liveRecords'] = diffRecords(args, d1['liveRecords'],
d2['liveRecords'])
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'])
@ -495,8 +496,8 @@ def printDigest(args, digest):
heapUsableSize = digest['heapUsableSize']
heapIsSampled = digest['heapIsSampled']
heapBlocks = digest['heapBlocks']
if mode == 'live':
liveRecords = digest['liveRecords']
if mode in ['live', 'cumulative']:
liveOrCumulativeRecords = digest['liveOrCumulativeRecords']
elif mode == 'dark-matter':
unreportedRecords = digest['unreportedRecords']
onceReportedRecords = digest['onceReportedRecords']
@ -589,7 +590,7 @@ def printDigest(args, digest):
out(' {:4.2f}% of the heap ({:4.2f}% cumulative)'.
format(perc(record.usableSize, heapUsableSize),
perc(kindCumulativeUsableSize, heapUsableSize)))
if mode == 'live':
if mode in ['live', 'cumulative']:
pass
elif mode == 'dark-matter':
out(' {:4.2f}% of {:} ({:4.2f}% cumulative)'.
@ -599,7 +600,7 @@ def printDigest(args, digest):
out(' Allocated at {')
printStack(record.allocatedAtDesc)
out(' }')
if mode == 'live':
if mode in ['live', 'cumulative']:
pass
elif mode == 'dark-matter':
for n, reportedAtDesc in enumerate(record.reportedAtDescs):
@ -631,9 +632,9 @@ def printDigest(args, digest):
printInvocation(' 2', dmdEnvVar[1], sampleBelowSize[1])
# Print records.
if mode == 'live':
liveUsableSize, liveBlocks = \
printRecords('live', liveRecords, heapUsableSize)
if mode in ['live', 'cumulative']:
liveOrCumulativeUsableSize, liveOrCumulativeBlocks = \
printRecords(mode, liveOrCumulativeRecords, heapUsableSize)
elif mode == 'dark-matter':
twiceReportedUsableSize, twiceReportedBlocks = \
printRecords('twice-reported', twiceReportedRecords, heapUsableSize)
@ -647,10 +648,10 @@ def printDigest(args, digest):
# Print summary.
out(separator)
out('Summary {')
if mode == 'live':
if mode in ['live', 'cumulative']:
out(' Total: {:} bytes in {:} blocks'.
format(number(liveUsableSize, heapIsSampled),
number(liveBlocks, heapIsSampled)))
format(number(liveOrCumulativeUsableSize, heapIsSampled),
number(liveOrCumulativeBlocks, heapIsSampled)))
elif mode == 'dark-matter':
fmt = ' {:15} {:>12} bytes ({:6.2f}%) in {:>7} blocks ({:6.2f}%)'
out(fmt.

View File

@ -126,6 +126,9 @@ TestUnsampled(const char* aTestName, int aNum, const char* aMode, int aSeven)
}
free(a);
// A no-op.
free(nullptr);
// Note: 8 bytes is the smallest requested size that gives consistent
// behaviour across all platforms with jemalloc.
// Analyze 1: reported.
@ -141,7 +144,7 @@ TestUnsampled(const char* aTestName, int aNum, const char* aMode, int aSeven)
// ReportOnAlloc, then freed.
// Analyze 1: freed, irrelevant.
// Analyze 2: freed, irrelevant.
char* b2 = (char*) malloc(1);
char* b2 = (char*) malloc(8);
ReportOnAlloc(b2);
free(b2);
@ -346,11 +349,13 @@ RunTests()
TestEmpty("empty", "live");
TestEmpty("empty", "dark-matter");
TestEmpty("empty", "cumulative");
TestUnsampled("unsampled", 1, "live", seven);
TestUnsampled("unsampled", 1, "dark-matter", seven);
TestUnsampled("unsampled", 2, "dark-matter", seven);
TestUnsampled("unsampled", 2, "cumulative", seven);
TestSampled("sampled", "live", seven);
}

View File

@ -0,0 +1,18 @@
#-----------------------------------------------------------------
# dmd.py --filter-stacks-for-testing -o full-empty-cumulative-actual.txt full-empty-cumulative.json
Invocation {
$DMD = '--mode=cumulative --sample-below=1'
Sample-below size = 1
}
#-----------------------------------------------------------------
# no cumulative heap blocks
#-----------------------------------------------------------------
Summary {
Total: 0 bytes in 0 blocks
}

View File

@ -0,0 +1,163 @@
#-----------------------------------------------------------------
# dmd.py --filter-stacks-for-testing -o full-unsampled2-cumulative-actual.txt full-unsampled2-cumulative.json
Invocation {
$DMD = '--mode=cumulative --sample-below=1 --show-dump-stats=yes'
Sample-below size = 1
}
#-----------------------------------------------------------------
Cumulative {
1 block in heap block record 1 of 16
8,192 bytes (4,097 requested / 4,095 slop)
47.10% of the heap (47.10% 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)
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)
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)
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)
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)
Allocated at {
#01: ... DMD.cpp ...
}
}
Cumulative {
6 blocks in heap block record 7 of 16
528 bytes (528 requested / 0 slop)
Individual block sizes: 128; 112; 96; 80; 64; 48
3.04% of the heap (94.94% 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)
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)
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)
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)
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)
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)
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)
Allocated at {
#01: ... DMD.cpp ...
}
}
Cumulative {
1 block in heap block record 15 of 16
8 bytes (8 requested / 0 slop)
0.05% of the heap (99.95% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Cumulative {
1 block in heap block record 16 of 16
8 bytes (8 requested / 0 slop)
0.05% of the heap (100.00% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
#-----------------------------------------------------------------
Summary {
Total: 17,392 bytes in 35 blocks
}

View File

@ -139,11 +139,13 @@ function run_test() {
test2("empty", "live");
test2("empty", "dark-matter");
test2("empty", "cumulative");
test2("unsampled1", "live");
test2("unsampled1", "dark-matter");
test2("unsampled2", "dark-matter");
test2("unsampled2", "cumulative");
test2("sampled", "live");

View File

@ -2,9 +2,11 @@
support-files =
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
script-max-frames.json
script-max-frames-8-expected.txt

View File

@ -893,7 +893,7 @@ class RunProgram(MachCommandBase):
@CommandArgumentGroup('DMD')
@CommandArgument('--dmd', action='store_true', group='DMD',
help='Enable DMD. The following arguments have no effect without this.')
@CommandArgument('--mode', choices=['live', 'dark-matter'], group='DMD',
@CommandArgument('--mode', choices=['live', 'dark-matter', 'cumulative'], 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.')

View File

@ -956,6 +956,10 @@ public:
sizes.mLiveBlockTable,
"Memory used by DMD's live block table.");
REPORT("explicit/dmd/dead-block-list",
sizes.mDeadBlockList,
"Memory used by DMD's dead block list.");
#undef REPORT
return NS_OK;