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/JSONWriter.h"
#include "mozilla/Likely.h" #include "mozilla/Likely.h"
#include "mozilla/MemoryReporting.h" #include "mozilla/MemoryReporting.h"
#include "mozilla/SegmentedVector.h"
// CodeAddressService is defined entirely in the header, so this does not make // CodeAddressService is defined entirely in the header, so this does not make
// DMD depend on XPCOM's object file. // 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 // Like "Live", but for each live block it also outputs: zero or more
// report stacks. This mode is good for identifying where memory reporters // report stacks. This mode is good for identifying where memory reporters
// should be added. This is the default mode. // 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 char* mDMDEnvVar; // a saved copy, for later printing
@ -357,6 +363,7 @@ public:
bool IsLiveMode() const { return mMode == Live; } bool IsLiveMode() const { return mMode == Live; }
bool IsDarkMatterMode() const { return mMode == DarkMatter; } bool IsDarkMatterMode() const { return mMode == DarkMatter; }
bool IsCumulativeMode() const { return mMode == Cumulative; }
const char* DMDEnvVar() const { return mDMDEnvVar; } const char* DMDEnvVar() const { return mDMDEnvVar; }
@ -438,10 +445,10 @@ public:
}; };
// This lock must be held while manipulating global state such as // This lock must be held while manipulating global state such as
// gStackTraceTable, gLiveBlockTable, etc. Note that gOptions is *not* // gStackTraceTable, gLiveBlockTable, gDeadBlockList. Note that gOptions is
// protected by this lock because it is only written to by Options(), which is // *not* protected by this lock because it is only written to by Options(),
// only invoked at start-up and in ResetEverything(), which is only used by // which is only invoked at start-up and in ResetEverything(), which is only
// SmokeDMD.cpp. // used by SmokeDMD.cpp.
static Mutex* gStateLock = nullptr; static Mutex* gStateLock = nullptr;
class AutoLockState class AutoLockState
@ -850,10 +857,10 @@ class LiveBlock
public: public:
LiveBlock(const void* aPtr, size_t aReqSize, LiveBlock(const void* aPtr, size_t aReqSize,
const StackTrace* aAllocStackTrace, bool aIsSampled) const StackTrace* aAllocStackTrace, bool aIsSampled)
: mPtr(aPtr), : mPtr(aPtr)
mReqSize(aReqSize), , mReqSize(aReqSize)
mAllocStackTrace_mIsSampled(aAllocStackTrace, aIsSampled), , mAllocStackTrace_mIsSampled(aAllocStackTrace, aIsSampled)
mReportStackTrace_mReportedOnAlloc() // all fields get zeroed , mReportStackTrace_mReportedOnAlloc() // all fields get zeroed
{ {
MOZ_ASSERT(aAllocStackTrace); MOZ_ASSERT(aAllocStackTrace);
} }
@ -982,6 +989,60 @@ public:
typedef js::HashSet<LiveBlock, LiveBlock, InfallibleAllocPolicy> LiveBlockTable; typedef js::HashSet<LiveBlock, LiveBlock, InfallibleAllocPolicy> LiveBlockTable;
static LiveBlockTable* gLiveBlockTable = nullptr; 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 // 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.) // stack trace is live if it's used by one of the live blocks.)
static void static void
@ -996,6 +1057,10 @@ GatherUsedStackTraces(StackTraceSet& aStackTraces)
for (auto r = gLiveBlockTable->all(); !r.empty(); r.popFront()) { for (auto r = gLiveBlockTable->all(); !r.empty(); r.popFront()) {
r.front().AddStackTracesToTable(aStackTraces); 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. // 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 static void
FreeCallback(void* aPtr, Thread* aT) FreeCallback(void* aPtr, Thread* aT, DeadBlock* aDeadBlock)
{ {
if (!aPtr) { if (!aPtr) {
return; return;
@ -1072,7 +1137,17 @@ FreeCallback(void* aPtr, Thread* aT)
AutoLockState lock; AutoLockState lock;
AutoBlockIntercepts block(aT); 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) { if (gStackTraceTable->count() > gGCStackTraceTableWhenSizeExceeds) {
GCStackTraces(); GCStackTraces();
@ -1168,15 +1243,22 @@ replace_realloc(void* aOldPtr, size_t aSize)
// the realloc to avoid races, just like in replace_free(). // the realloc to avoid races, just like in replace_free().
// Nb: This does an unnecessary hashtable remove+add if the block doesn't // Nb: This does an unnecessary hashtable remove+add if the block doesn't
// move, but doing better isn't worth the effort. // move, but doing better isn't worth the effort.
FreeCallback(aOldPtr, t); DeadBlock db;
FreeCallback(aOldPtr, t, &db);
void* ptr = gMallocTable->realloc(aOldPtr, aSize); void* ptr = gMallocTable->realloc(aOldPtr, aSize);
if (ptr) { if (ptr) {
AllocCallback(ptr, aSize, t); AllocCallback(ptr, aSize, t);
if (gOptions->IsCumulativeMode() && db.AllocStackTrace()) {
AutoLockState lock;
gDeadBlockList->InfallibleAppend(db);
}
} else { } else {
// If realloc fails, we re-insert the old pointer. It will look like it // If realloc fails, we undo the prior operations by re-inserting the old
// was allocated for the first time here, which is untrue, and the slop // pointer into the live block table. We don't have to do anything with the
// bytes will be zero, which may be untrue. But this case is rare and // dead block list because the dead block hasn't yet been inserted. The
// doing better isn't worth the effort. // 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); AllocCallback(aOldPtr, gMallocTable->malloc_usable_size(aOldPtr), t);
} }
return ptr; return ptr;
@ -1219,7 +1301,12 @@ replace_free(void* aPtr)
// Do the actual free after updating the table. Otherwise, another thread // 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 // could call malloc and get the freed block and update the table, and then
// our update here would remove the newly-malloc'd block. // 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); gMallocTable->free(aPtr);
} }
@ -1323,6 +1410,8 @@ Options::Options(const char* aDMDEnvVar)
mMode = Options::Live; mMode = Options::Live;
} else if (strcmp(arg, "--mode=dark-matter") == 0) { } else if (strcmp(arg, "--mode=dark-matter") == 0) {
mMode = Options::DarkMatter; mMode = Options::DarkMatter;
} else if (strcmp(arg, "--mode=cumulative") == 0) {
mMode = Options::Cumulative;
} else if (GetLong(arg, "--sample-below", 1, mSampleBelowSize.mMax, } else if (GetLong(arg, "--sample-below", 1, mSampleBelowSize.mMax,
&myLong)) { &myLong)) {
@ -1359,7 +1448,7 @@ Options::BadArg(const char* aArg)
StatusMsg("\n"); StatusMsg("\n");
StatusMsg("The following options are allowed; defaults are shown in [].\n"); StatusMsg("The following options are allowed; defaults are shown in [].\n");
StatusMsg(" --mode=<mode> Profiling mode [dark-matter]\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", StatusMsg(" --sample-below=<1..%d> Sample blocks smaller than this [%d]\n",
int(mSampleBelowSize.mMax), int(mSampleBelowSize.mMax),
int(mSampleBelowSize.mDefault)); int(mSampleBelowSize.mDefault));
@ -1430,6 +1519,12 @@ Init(const malloc_table_t* aMallocTable)
gLiveBlockTable = InfallibleAllocPolicy::new_<LiveBlockTable>(); gLiveBlockTable = InfallibleAllocPolicy::new_<LiveBlockTable>();
gLiveBlockTable->init(8192); 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; gIsDMDInitialized = true;
@ -1516,6 +1611,8 @@ SizeOfInternal(Sizes* aSizes)
gStackTraceTable->sizeOfIncludingThis(MallocSizeOf); gStackTraceTable->sizeOfIncludingThis(MallocSizeOf);
aSizes->mLiveBlockTable = gLiveBlockTable->sizeOfIncludingThis(MallocSizeOf); aSizes->mLiveBlockTable = gLiveBlockTable->sizeOfIncludingThis(MallocSizeOf);
aSizes->mDeadBlockList = gDeadBlockList->SizeOfIncludingThis(MallocSizeOf);
} }
void void
@ -1644,6 +1741,8 @@ AnalyzeImpl(UniquePtr<JSONWriteFunc> aWriter)
mode = "live"; mode = "live";
} else if (gOptions->IsDarkMatterMode()) { } else if (gOptions->IsDarkMatterMode()) {
mode = "dark-matter"; mode = "dark-matter";
} else if (gOptions->IsCumulativeMode()) {
mode = "cumulative";
} else { } else {
MOZ_ASSERT(false); MOZ_ASSERT(false);
mode = "(unknown DMD mode)"; mode = "(unknown DMD mode)";
@ -1659,6 +1758,7 @@ AnalyzeImpl(UniquePtr<JSONWriteFunc> aWriter)
writer.StartArrayProperty("blockList"); writer.StartArrayProperty("blockList");
{ {
// Live blocks.
for (auto r = gLiveBlockTable->all(); !r.empty(); r.popFront()) { for (auto r = gLiveBlockTable->all(); !r.empty(); r.popFront()) {
const LiveBlock& b = r.front(); const LiveBlock& b = r.front();
b.AddStackTracesToTable(usedStackTraces); b.AddStackTracesToTable(usedStackTraces);
@ -1688,6 +1788,24 @@ AnalyzeImpl(UniquePtr<JSONWriteFunc> aWriter)
} }
writer.EndObject(); 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(); writer.EndArray();
@ -1761,6 +1879,10 @@ AnalyzeImpl(UniquePtr<JSONWriteFunc> aWriter)
Show(gLiveBlockTable->capacity(), buf2, kBufLen), Show(gLiveBlockTable->capacity(), buf2, kBufLen),
Show(gLiveBlockTable->count(), buf3, 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(" }\n");
StatusMsg(" Data structures that are destroyed after Dump() ends {\n"); StatusMsg(" Data structures that are destroyed after Dump() ends {\n");
@ -1819,6 +1941,7 @@ DMDFuncs::ResetEverything(const char* aOptions)
// Clear all existing blocks. // Clear all existing blocks.
gLiveBlockTable->clear(); gLiveBlockTable->clear();
gDeadBlockList->Clear();
gSmallBlockActualSizeCounter = 0; gSmallBlockActualSizeCounter = 0;
} }

View File

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

View File

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

View File

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

View File

@ -2,9 +2,11 @@
support-files = support-files =
full-empty-live-expected.txt full-empty-live-expected.txt
full-empty-dark-matter-expected.txt full-empty-dark-matter-expected.txt
full-empty-cumulative-expected.txt
full-unsampled1-live-expected.txt full-unsampled1-live-expected.txt
full-unsampled1-dark-matter-expected.txt full-unsampled1-dark-matter-expected.txt
full-unsampled2-dark-matter-expected.txt full-unsampled2-dark-matter-expected.txt
full-unsampled2-cumulative-expected.txt
full-sampled-live-expected.txt full-sampled-live-expected.txt
script-max-frames.json script-max-frames.json
script-max-frames-8-expected.txt script-max-frames-8-expected.txt

View File

@ -893,7 +893,7 @@ class RunProgram(MachCommandBase):
@CommandArgumentGroup('DMD') @CommandArgumentGroup('DMD')
@CommandArgument('--dmd', action='store_true', group='DMD', @CommandArgument('--dmd', action='store_true', group='DMD',
help='Enable DMD. The following arguments have no effect without this.') 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\'.') help='Profiling mode. The default is \'dark-matter\'.')
@CommandArgument('--sample-below', default=None, type=str, group='DMD', @CommandArgument('--sample-below', default=None, type=str, group='DMD',
help='Sample blocks smaller than this. Use 1 for no sampling. The default is 4093.') help='Sample blocks smaller than this. Use 1 for no sampling. The default is 4093.')

View File

@ -956,6 +956,10 @@ public:
sizes.mLiveBlockTable, sizes.mLiveBlockTable,
"Memory used by DMD's live block table."); "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 #undef REPORT
return NS_OK; return NS_OK;