mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
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:
parent
5b5d3c27bb
commit
5cc6a39a9a
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
18
memory/replace/dmd/test/full-empty-cumulative-expected.txt
Normal file
18
memory/replace/dmd/test/full-empty-cumulative-expected.txt
Normal 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
|
||||||
|
}
|
||||||
|
|
163
memory/replace/dmd/test/full-unsampled2-cumulative-expected.txt
Normal file
163
memory/replace/dmd/test/full-unsampled2-cumulative-expected.txt
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -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");
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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.')
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user