Bug 1035570 (part 3) - DMD: Add DMDAnalyzeHeap(), a heap snapshot function. r=erahm,mccr8.

The patch also adds DMDAnalyzeReports() as a synonym for DMDReportAndDump(),
and deprecates the latter.

--HG--
extra : rebase_source : 651246aa7a0a301f804c124f25beb0e8ed6cd67f
This commit is contained in:
Nicholas Nethercote 2014-05-29 23:46:09 -07:00
Родитель c375f811f6
Коммит c07ebda3e2
7 изменённых файлов: 764 добавлений и 229 удалений

Просмотреть файл

@ -1440,8 +1440,27 @@ namespace dmd {
// See https://wiki.mozilla.org/Performance/MemShrink/DMD for instructions on
// how to use DMD.
static FILE *
OpenDMDOutputFile(JSContext *cx, JS::CallArgs &args)
{
JSString *str = JS::ToString(cx, args.get(0));
if (!str)
return nullptr;
JSAutoByteString pathname(cx, str);
if (!pathname)
return nullptr;
FILE* fp = fopen(pathname.ptr(), "w");
if (!fp) {
JS_ReportError(cx, "DMD can't open %s: %s",
pathname.ptr(), strerror(errno));
return nullptr;
}
return fp;
}
static bool
ReportAndDump(JSContext *cx, unsigned argc, JS::Value *vp)
AnalyzeReports(JSContext *cx, unsigned argc, JS::Value *vp)
{
if (!dmd::IsRunning()) {
JS_ReportError(cx, "DMD is not running");
@ -1449,24 +1468,48 @@ ReportAndDump(JSContext *cx, unsigned argc, JS::Value *vp)
}
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
JSString *str = JS::ToString(cx, args.get(0));
if (!str)
return false;
JSAutoByteString pathname(cx, str);
if (!pathname)
return false;
FILE* fp = fopen(pathname.ptr(), "w");
FILE *fp = OpenDMDOutputFile(cx, args);
if (!fp) {
JS_ReportError(cx, "DMD can't open %s: %s",
pathname.ptr(), strerror(errno));
return false;
}
dmd::ClearReports();
dmd::RunReportersForThisProcess();
dmd::Writer writer(FpWrite, fp);
dmd::Dump(writer);
dmd::AnalyzeReports(writer);
fclose(fp);
args.rval().setUndefined();
return true;
}
// This will be removed eventually.
static bool
ReportAndDump(JSContext *cx, unsigned argc, JS::Value *vp)
{
JS_ReportWarning(cx, "DMDReportAndDump() is deprecated; "
"please use DMDAnalyzeReports() instead");
return AnalyzeReports(cx, argc, vp);
}
static bool
AnalyzeHeap(JSContext *cx, unsigned argc, JS::Value *vp)
{
if (!dmd::IsRunning()) {
JS_ReportError(cx, "DMD is not running");
return false;
}
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
FILE *fp = OpenDMDOutputFile(cx, args);
if (!fp) {
return false;
}
dmd::Writer writer(FpWrite, fp);
dmd::AnalyzeHeap(writer);
fclose(fp);
@ -1478,7 +1521,9 @@ ReportAndDump(JSContext *cx, unsigned argc, JS::Value *vp)
} // namespace mozilla
static const JSFunctionSpec DMDFunctions[] = {
JS_FS("DMDReportAndDump", dmd::ReportAndDump, 1, 0),
JS_FS("DMDReportAndDump", dmd::ReportAndDump, 1, 0),
JS_FS("DMDAnalyzeReports", dmd::AnalyzeReports, 1, 0),
JS_FS("DMDAnalyzeHeap", dmd::AnalyzeHeap, 1, 0),
JS_FS_END
};

Просмотреть файл

@ -44,6 +44,7 @@
// PAGE_SIZE. Nb: sysconf() is expensive, but it's only used for (the obsolete
// and rarely used) valloc.
#define MOZ_REPLACE_ONLY_MEMALIGN 1
#ifdef XP_WIN
#define PAGE_SIZE GetPageSize()
static long GetPageSize()
@ -291,7 +292,7 @@ class Options
Stress // do some performance stress tests
};
char* mDMDEnvVar; // a saved copy, for printing during Dump()
char* mDMDEnvVar; // a saved copy, for later printing
NumOption<size_t> mSampleBelowSize;
NumOption<uint32_t> mMaxFrames;
@ -610,7 +611,8 @@ class LocationService
uint32_t mInUse:1; // is the entry used?
Entry()
: mPc(0), mFunction(nullptr), mLibrary(nullptr), mLOffset(0), mFileName(nullptr), mLineNo(0), mInUse(0)
: mPc(0), mFunction(nullptr), mLibrary(nullptr), mLOffset(0),
mFileName(nullptr), mLineNo(0), mInUse(0)
{}
~Entry()
@ -648,12 +650,12 @@ class LocationService
}
};
// A direct-mapped cache. When doing a dump just after starting desktop
// Firefox (which is similar to dumping after a longer-running session,
// thanks to the limit on how many records we dump), a cache with 2^24
// entries (which approximates an infinite-entry cache) has a ~91% hit rate.
// A cache with 2^12 entries has a ~83% hit rate, and takes up ~85 KiB (on
// 32-bit platforms) or ~150 KiB (on 64-bit platforms).
// A direct-mapped cache. When doing AnalyzeReports just after starting
// desktop Firefox (which is similar to analyzing after a longer-running
// session, thanks to the limit on how many records we print), a cache with
// 2^24 entries (which approximates an infinite-entry cache) has a ~91% hit
// rate. A cache with 2^12 entries has a ~83% hit rate, and takes up ~85 KiB
// (on 32-bit platforms) or ~150 KiB (on 64-bit platforms).
static const size_t kNumEntries = 1 << 12;
static const size_t kMask = kNumEntries - 1;
Entry mEntries[kNumEntries];
@ -793,7 +795,7 @@ public:
void Sort()
{
qsort(mPcs, mLength, sizeof(mPcs[0]), StackTrace::QsortCmp);
qsort(mPcs, mLength, sizeof(mPcs[0]), StackTrace::Cmp);
}
void Print(const Writer& aWriter, LocationService* aLocService) const;
@ -823,7 +825,7 @@ private:
st->mLength++;
}
static int QsortCmp(const void* aA, const void* aB)
static int Cmp(const void* aA, const void* aB)
{
const void* const a = *static_cast<const void* const*>(aA);
const void* const b = *static_cast<const void* const*>(aB);
@ -844,7 +846,7 @@ void
StackTrace::Print(const Writer& aWriter, LocationService* aLocService) const
{
if (mLength == 0) {
W(" (empty)\n"); // StackTrace::Get() must have failed
W(" (empty)\n"); // StackTrace::Get() must have failed
return;
}
@ -973,8 +975,8 @@ class Block
// - Ptr: |mReportStackTrace| - stack trace where this block was reported.
// nullptr if not reported.
// - Tag bit 0: |mReportedOnAlloc| - was the block reported immediately on
// allocation? If so, DMD must not clear the report at the end of Dump().
// Only relevant if |mReportStackTrace| is non-nullptr.
// allocation? If so, DMD must not clear the report at the end of
// AnalyzeReports(). Only relevant if |mReportStackTrace| is non-nullptr.
//
// |mPtr| is used as the key in BlockTable, so it's ok for this member
// to be |mutable|.
@ -1417,7 +1419,7 @@ public:
mSampled = mSampled || aRecordSize.IsSampled();
}
static int Cmp(const RecordSize& aA, const RecordSize& aB)
static int CmpByUsable(const RecordSize& aA, const RecordSize& aB)
{
// Primary sort: put bigger usable sizes first.
if (aA.Usable() > aB.Usable()) return -1;
@ -1464,14 +1466,15 @@ public:
void Print(const Writer& aWriter, LocationService* aLocService,
uint32_t aM, uint32_t aN, const char* aStr, const char* astr,
size_t aCategoryUsableSize, size_t aCumulativeUsableSize,
size_t aTotalUsableSize) const;
size_t aTotalUsableSize, bool aShowCategoryPercentage,
bool aShowReportedAt) const;
static int QsortCmp(const void* aA, const void* aB)
static int CmpByUsable(const void* aA, const void* aB)
{
const Record* const a = *static_cast<const Record* const*>(aA);
const Record* const b = *static_cast<const Record* const*>(aB);
return RecordSize::Cmp(a->mRecordSize, b->mRecordSize);
return RecordSize::CmpByUsable(a->mRecordSize, b->mRecordSize);
}
};
@ -1481,7 +1484,8 @@ void
Record::Print(const Writer& aWriter, LocationService* aLocService,
uint32_t aM, uint32_t aN, const char* aStr, const char* astr,
size_t aCategoryUsableSize, size_t aCumulativeUsableSize,
size_t aTotalUsableSize) const
size_t aTotalUsableSize, bool aShowCategoryPercentage,
bool aShowReportedAt) const
{
bool showTilde = mRecordSize.IsSampled();
@ -1500,24 +1504,28 @@ Record::Print(const Writer& aWriter, LocationService* aLocService,
Percent(mRecordSize.Usable(), aTotalUsableSize),
Percent(aCumulativeUsableSize, aTotalUsableSize));
W(" %4.2f%% of %s (%4.2f%% cumulative)\n",
Percent(mRecordSize.Usable(), aCategoryUsableSize),
astr,
Percent(aCumulativeUsableSize, aCategoryUsableSize));
if (aShowCategoryPercentage) {
W(" %4.2f%% of %s (%4.2f%% cumulative)\n",
Percent(mRecordSize.Usable(), aCategoryUsableSize),
astr,
Percent(aCumulativeUsableSize, aCategoryUsableSize));
}
W(" Allocated at {\n");
mAllocStackTrace->Print(aWriter, aLocService);
W(" }\n");
if (mReportStackTrace1) {
W(" Reported at {\n");
mReportStackTrace1->Print(aWriter, aLocService);
W(" }\n");
}
if (mReportStackTrace2) {
W(" Reported again at {\n");
mReportStackTrace2->Print(aWriter, aLocService);
W(" }\n");
if (aShowReportedAt) {
if (mReportStackTrace1) {
W(" Reported at {\n");
mReportStackTrace1->Print(aWriter, aLocService);
W(" }\n");
}
if (mReportStackTrace2) {
W(" Reported again at {\n");
mReportStackTrace2->Print(aWriter, aLocService);
W(" }\n");
}
}
W("}\n\n");
@ -1808,9 +1816,11 @@ ReportOnAlloc(const void* aPtr)
static void
PrintSortedRecords(const Writer& aWriter, LocationService* aLocService,
int (*aCmp)(const void*, const void*),
const char* aStr, const char* astr,
const RecordTable& aRecordTable,
size_t aCategoryUsableSize, size_t aTotalUsableSize)
size_t aCategoryUsableSize, size_t aTotalUsableSize,
bool aShowCategoryPercentage, bool aShowReportedAt)
{
StatusMsg(" creating and sorting %s heap block record array...\n", astr);
@ -1823,7 +1833,7 @@ PrintSortedRecords(const Writer& aWriter, LocationService* aLocService,
recordArray.infallibleAppend(&r.front());
}
qsort(recordArray.begin(), recordArray.length(), sizeof(recordArray[0]),
Record::QsortCmp);
aCmp);
WriteSeparator();
@ -1845,7 +1855,8 @@ PrintSortedRecords(const Writer& aWriter, LocationService* aLocService,
cumulativeUsableSize += r->GetRecordSize().Usable();
if (i < maxRecords) {
r->Print(aWriter, aLocService, i+1, numRecords, aStr, astr,
aCategoryUsableSize, cumulativeUsableSize, aTotalUsableSize);
aCategoryUsableSize, cumulativeUsableSize, aTotalUsableSize,
aShowCategoryPercentage, aShowReportedAt);
} else if (i == maxRecords) {
W("# %s: stopping after %s heap block records\n\n", aStr,
Show(maxRecords, gBuf1, kBufLen));
@ -1910,10 +1921,14 @@ SizeOf(Sizes* aSizes)
SizeOfInternal(aSizes);
}
void
ClearReportsInternal()
MOZ_EXPORT void
ClearReports()
{
MOZ_ASSERT(gStateLock->IsLocked());
if (!gIsDMDRunning) {
return;
}
AutoLockState lock;
// Unreport all blocks that were marked reported by a memory reporter. This
// excludes those that were reported on allocation, because they need to keep
@ -1923,77 +1938,230 @@ ClearReportsInternal()
}
}
MOZ_EXPORT void
ClearReports()
{
if (!gIsDMDRunning) {
return;
}
AutoLockState lock;
ClearReportsInternal();
}
MOZ_EXPORT bool
IsRunning()
{
return gIsDMDRunning;
}
MOZ_EXPORT void
Dump(Writer aWriter)
// AnalyzeReports() and AnalyzeHeap() have a lot in common. This abstract class
// encapsulates the operations that are not shared.
class Analyzer
{
public:
virtual const char* AnalyzeFunctionName() const = 0;
virtual RecordTable* ProcessBlock(const Block& aBlock) = 0;
virtual void PrintRecords(const Writer& aWriter,
LocationService* aLocService) const = 0;
virtual void PrintSummary(const Writer& aWriter, bool aShowTilde) const = 0;
virtual void PrintStats(const Writer& aWriter) const = 0;
struct RecordKindData
{
RecordTable mRecordTable;
size_t mUsableSize;
size_t mNumBlocks;
RecordKindData(size_t aN)
: mUsableSize(0), mNumBlocks(0)
{
mRecordTable.init(aN);
}
void processBlock(const Block& aBlock)
{
mUsableSize += aBlock.UsableSize();
mNumBlocks++;
}
};
};
class ReportsAnalyzer MOZ_FINAL : public Analyzer
{
RecordKindData mUnreported;
RecordKindData mOnceReported;
RecordKindData mTwiceReported;
size_t mTotalUsableSize;
size_t mTotalNumBlocks;
public:
ReportsAnalyzer()
: mUnreported(1024), mOnceReported(1024), mTwiceReported(0),
mTotalUsableSize(0), mTotalNumBlocks(0)
{}
~ReportsAnalyzer()
{
ClearReports();
}
virtual const char* AnalyzeFunctionName() const { return "AnalyzeReports"; }
virtual RecordTable* ProcessBlock(const Block& aBlock)
{
RecordKindData* data;
uint32_t numReports = aBlock.NumReports();
if (numReports == 0) {
data = &mUnreported;
} else if (numReports == 1) {
data = &mOnceReported;
} else {
MOZ_ASSERT(numReports == 2);
data = &mTwiceReported;
}
data->processBlock(aBlock);
mTotalUsableSize += aBlock.UsableSize();
mTotalNumBlocks++;
return &data->mRecordTable;
}
virtual void PrintRecords(const Writer& aWriter,
LocationService* aLocService) const
{
PrintSortedRecords(aWriter, aLocService, Record::CmpByUsable,
"Twice-reported", "twice-reported",
mTwiceReported.mRecordTable,
mTwiceReported.mUsableSize, mTotalUsableSize,
/* showCategoryPercentage = */ true,
/* showReportedAt = */ true);
PrintSortedRecords(aWriter, aLocService, Record::CmpByUsable,
"Unreported", "unreported",
mUnreported.mRecordTable,
mUnreported.mUsableSize, mTotalUsableSize,
/* showCategoryPercentage = */ true,
/* showReportedAt = */ true);
PrintSortedRecords(aWriter, aLocService, Record::CmpByUsable,
"Once-reported", "once-reported",
mOnceReported.mRecordTable,
mOnceReported.mUsableSize, mTotalUsableSize,
/* showCategoryPercentage = */ true,
/* showReportedAt = */ true);
}
virtual void PrintSummary(const Writer& aWriter, bool aShowTilde) const
{
W(" Total: %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n",
Show(mTotalUsableSize, gBuf1, kBufLen, aShowTilde),
100.0,
Show(mTotalNumBlocks, gBuf2, kBufLen, aShowTilde),
100.0);
W(" Unreported: %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n",
Show(mUnreported.mUsableSize, gBuf1, kBufLen, aShowTilde),
Percent(mUnreported.mUsableSize, mTotalUsableSize),
Show(mUnreported.mNumBlocks, gBuf2, kBufLen, aShowTilde),
Percent(mUnreported.mNumBlocks, mTotalNumBlocks));
W(" Once-reported: %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n",
Show(mOnceReported.mUsableSize, gBuf1, kBufLen, aShowTilde),
Percent(mOnceReported.mUsableSize, mTotalUsableSize),
Show(mOnceReported.mNumBlocks, gBuf2, kBufLen, aShowTilde),
Percent(mOnceReported.mNumBlocks, mTotalNumBlocks));
W(" Twice-reported: %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n",
Show(mTwiceReported.mUsableSize, gBuf1, kBufLen, aShowTilde),
Percent(mTwiceReported.mUsableSize, mTotalUsableSize),
Show(mTwiceReported.mNumBlocks, gBuf2, kBufLen, aShowTilde),
Percent(mTwiceReported.mNumBlocks, mTotalNumBlocks));
}
virtual void PrintStats(const Writer& aWriter) const
{
size_t unreportedSize =
mUnreported.mRecordTable.sizeOfIncludingThis(MallocSizeOf);
W(" Unreported table: %10s bytes (%s entries, %s used)\n",
Show(unreportedSize, gBuf1, kBufLen),
Show(mUnreported.mRecordTable.capacity(), gBuf2, kBufLen),
Show(mUnreported.mRecordTable.count(), gBuf3, kBufLen));
size_t onceReportedSize =
mOnceReported.mRecordTable.sizeOfIncludingThis(MallocSizeOf);
W(" Once-reported table: %10s bytes (%s entries, %s used)\n",
Show(onceReportedSize, gBuf1, kBufLen),
Show(mOnceReported.mRecordTable.capacity(), gBuf2, kBufLen),
Show(mOnceReported.mRecordTable.count(), gBuf3, kBufLen));
size_t twiceReportedSize =
mTwiceReported.mRecordTable.sizeOfIncludingThis(MallocSizeOf);
W(" Twice-reported table: %10s bytes (%s entries, %s used)\n",
Show(twiceReportedSize, gBuf1, kBufLen),
Show(mTwiceReported.mRecordTable.capacity(), gBuf2, kBufLen),
Show(mTwiceReported.mRecordTable.count(), gBuf3, kBufLen));
}
};
class HeapAnalyzer MOZ_FINAL : public Analyzer
{
RecordKindData mLive;
public:
HeapAnalyzer() : mLive(1024) {}
virtual const char* AnalyzeFunctionName() const { return "AnalyzeHeap"; }
virtual RecordTable* ProcessBlock(const Block& aBlock)
{
mLive.processBlock(aBlock);
return &mLive.mRecordTable;
}
virtual void PrintRecords(const Writer& aWriter,
LocationService* aLocService) const
{
size_t totalUsableSize = mLive.mUsableSize;
PrintSortedRecords(aWriter, aLocService, Record::CmpByUsable,
"Live", "live", mLive.mRecordTable, totalUsableSize,
mLive.mUsableSize,
/* showReportedAt = */ false,
/* showCategoryPercentage = */ false);
}
virtual void PrintSummary(const Writer& aWriter, bool aShowTilde) const
{
W(" Total: %s bytes in %s blocks\n",
Show(mLive.mUsableSize, gBuf1, kBufLen, aShowTilde),
Show(mLive.mNumBlocks, gBuf2, kBufLen, aShowTilde));
}
virtual void PrintStats(const Writer& aWriter) const
{
size_t liveSize = mLive.mRecordTable.sizeOfIncludingThis(MallocSizeOf);
W(" Live table: %10s bytes (%s entries, %s used)\n",
Show(liveSize, gBuf1, kBufLen),
Show(mLive.mRecordTable.capacity(), gBuf2, kBufLen),
Show(mLive.mRecordTable.count(), gBuf3, kBufLen));
}
};
static void
AnalyzeImpl(Analyzer *aAnalyzer, const Writer& aWriter)
{
if (!gIsDMDRunning) {
const char* msg = "cannot Dump(); DMD was not enabled at startup\n";
StatusMsg("%s", msg);
W("%s", msg);
return;
}
AutoBlockIntercepts block(Thread::Fetch());
AutoLockState lock;
static int dumpCount = 1;
StatusMsg("Dump %d {\n", dumpCount++);
static int analysisCount = 1;
StatusMsg("%s %d {\n", aAnalyzer->AnalyzeFunctionName(), analysisCount++);
StatusMsg(" gathering heap block records...\n");
RecordTable unreportedRecordTable;
(void)unreportedRecordTable.init(1024);
size_t unreportedUsableSize = 0;
size_t unreportedNumBlocks = 0;
RecordTable onceReportedRecordTable;
(void)onceReportedRecordTable.init(1024);
size_t onceReportedUsableSize = 0;
size_t onceReportedNumBlocks = 0;
RecordTable twiceReportedRecordTable;
(void)twiceReportedRecordTable.init(0);
size_t twiceReportedUsableSize = 0;
size_t twiceReportedNumBlocks = 0;
bool anyBlocksSampled = false;
for (BlockTable::Range r = gBlockTable->all(); !r.empty(); r.popFront()) {
const Block& b = r.front();
RecordTable* table = aAnalyzer->ProcessBlock(b);
RecordTable* table;
uint32_t numReports = b.NumReports();
if (numReports == 0) {
unreportedUsableSize += b.UsableSize();
unreportedNumBlocks++;
table = &unreportedRecordTable;
} else if (numReports == 1) {
onceReportedUsableSize += b.UsableSize();
onceReportedNumBlocks++;
table = &onceReportedRecordTable;
} else {
MOZ_ASSERT(numReports == 2);
twiceReportedUsableSize += b.UsableSize();
twiceReportedNumBlocks++;
table = &twiceReportedRecordTable;
}
RecordKey key(b);
RecordTable::AddPtr p = table->lookupForAdd(key);
if (!p) {
@ -2004,63 +2172,24 @@ Dump(Writer aWriter)
anyBlocksSampled = anyBlocksSampled || b.IsSampled();
}
size_t totalUsableSize =
unreportedUsableSize + onceReportedUsableSize + twiceReportedUsableSize;
size_t totalNumBlocks =
unreportedNumBlocks + onceReportedNumBlocks + twiceReportedNumBlocks;
WriteSeparator();
W("Invocation {\n");
W(" $DMD = '%s'\n", gOptions->DMDEnvVar());
W(" Function = %s\n", aAnalyzer->AnalyzeFunctionName());
W(" Sample-below size = %lld\n", (long long)(gOptions->SampleBelowSize()));
W("}\n\n");
// Allocate this on the heap instead of the stack because it's fairly large.
LocationService* locService = InfallibleAllocPolicy::new_<LocationService>();
PrintSortedRecords(aWriter, locService,
"Twice-reported", "twice-reported",
twiceReportedRecordTable,
twiceReportedUsableSize, totalUsableSize);
PrintSortedRecords(aWriter, locService,
"Unreported", "unreported",
unreportedRecordTable,
unreportedUsableSize, totalUsableSize);
PrintSortedRecords(aWriter, locService,
"Once-reported", "once-reported",
onceReportedRecordTable,
onceReportedUsableSize, totalUsableSize);
bool showTilde = anyBlocksSampled;
aAnalyzer->PrintRecords(aWriter, locService);
WriteSeparator();
W("Summary {\n");
W(" Total: %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n",
Show(totalUsableSize, gBuf1, kBufLen, showTilde),
100.0,
Show(totalNumBlocks, gBuf2, kBufLen, showTilde),
100.0);
W(" Unreported: %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n",
Show(unreportedUsableSize, gBuf1, kBufLen, showTilde),
Percent(unreportedUsableSize, totalUsableSize),
Show(unreportedNumBlocks, gBuf2, kBufLen, showTilde),
Percent(unreportedNumBlocks, totalNumBlocks));
W(" Once-reported: %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n",
Show(onceReportedUsableSize, gBuf1, kBufLen, showTilde),
Percent(onceReportedUsableSize, totalUsableSize),
Show(onceReportedNumBlocks, gBuf2, kBufLen, showTilde),
Percent(onceReportedNumBlocks, totalNumBlocks));
W(" Twice-reported: %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n",
Show(twiceReportedUsableSize, gBuf1, kBufLen, showTilde),
Percent(twiceReportedUsableSize, totalUsableSize),
Show(twiceReportedNumBlocks, gBuf2, kBufLen, showTilde),
Percent(twiceReportedNumBlocks, totalNumBlocks));
bool showTilde = anyBlocksSampled;
aAnalyzer->PrintSummary(aWriter, showTilde);
W("}\n\n");
@ -2072,7 +2201,7 @@ Dump(Writer aWriter)
WriteSeparator();
W("Execution measurements {\n");
W(" Data structures that persist after Dump() ends:\n");
W(" Data structures that persist after Dump() ends {\n");
W(" Used stack traces: %10s bytes\n",
Show(sizes.mStackTracesUsed, gBuf1, kBufLen));
@ -2090,33 +2219,16 @@ Dump(Writer aWriter)
Show(gBlockTable->capacity(), gBuf2, kBufLen),
Show(gBlockTable->count(), gBuf3, kBufLen));
W("\n Data structures that are destroyed after Dump() ends:\n");
W(" }\n");
W(" Data structures that are destroyed after Dump() ends {\n");
size_t unreportedSize =
unreportedRecordTable.sizeOfIncludingThis(MallocSizeOf);
W(" Unreported table: %10s bytes (%s entries, %s used)\n",
Show(unreportedSize, gBuf1, kBufLen),
Show(unreportedRecordTable.capacity(), gBuf2, kBufLen),
Show(unreportedRecordTable.count(), gBuf3, kBufLen));
size_t onceReportedSize =
onceReportedRecordTable.sizeOfIncludingThis(MallocSizeOf);
W(" Once-reported table: %10s bytes (%s entries, %s used)\n",
Show(onceReportedSize, gBuf1, kBufLen),
Show(onceReportedRecordTable.capacity(), gBuf2, kBufLen),
Show(onceReportedRecordTable.count(), gBuf3, kBufLen));
size_t twiceReportedSize =
twiceReportedRecordTable.sizeOfIncludingThis(MallocSizeOf);
W(" Twice-reported table: %10s bytes (%s entries, %s used)\n",
Show(twiceReportedSize, gBuf1, kBufLen),
Show(twiceReportedRecordTable.capacity(), gBuf2, kBufLen),
Show(twiceReportedRecordTable.count(), gBuf3, kBufLen));
aAnalyzer->PrintStats(aWriter);
W(" Location service: %10s bytes\n",
Show(locService->SizeOfIncludingThis(), gBuf1, kBufLen));
W("\n Counts:\n");
W(" }\n");
W(" Counts {\n");
size_t hits = locService->NumCacheHits();
size_t misses = locService->NumCacheMisses();
@ -2130,16 +2242,29 @@ Dump(Writer aWriter)
"%4.1f%% hit rate, %.1f%% occupancy at end\n",
Percent(hits, requests), Percent(count, capacity));
W(" }\n");
W("}\n\n");
}
InfallibleAllocPolicy::delete_(locService);
ClearReportsInternal(); // Use internal version, we already have the lock.
StatusMsg("}\n");
}
MOZ_EXPORT void
AnalyzeReports(const Writer& aWriter)
{
ReportsAnalyzer aAnalyzer;
AnalyzeImpl(&aAnalyzer, aWriter);
}
MOZ_EXPORT void
AnalyzeHeap(const Writer& aWriter)
{
HeapAnalyzer analyzer;
AnalyzeImpl(&analyzer, aWriter);
}
//---------------------------------------------------------------------------
// Testing
//---------------------------------------------------------------------------
@ -2180,11 +2305,12 @@ RunTestMode(FILE* fp)
// The first part of this test requires sampling to be disabled.
gOptions->SetSampleBelowSize(1);
// Dump 1. Zero for everything.
Dump(writer);
// AnalyzeReports 1. Zero for everything.
AnalyzeReports(writer);
AnalyzeHeap(writer);
// Dump 2: 1 freed, 9 out of 10 unreported.
// Dump 3: still present and unreported.
// AnalyzeReports 2: 1 freed, 9 out of 10 unreported.
// AnalyzeReports 3: still present and unreported.
int i;
char* a;
for (i = 0; i < 10; i++) {
@ -2194,94 +2320,94 @@ RunTestMode(FILE* fp)
free(a);
// Min-sized block.
// Dump 2: reported.
// Dump 3: thrice-reported.
// AnalyzeReports 2: reported.
// AnalyzeReports 3: thrice-reported.
char* a2 = (char*) malloc(0);
Report(a2);
// Operator new[].
// Dump 2: reported.
// Dump 3: reportedness carries over, due to ReportOnAlloc.
// AnalyzeReports 2: reported.
// AnalyzeReports 3: reportedness carries over, due to ReportOnAlloc.
char* b = new char[10];
ReportOnAlloc(b);
// ReportOnAlloc, then freed.
// Dump 2: freed, irrelevant.
// Dump 3: freed, irrelevant.
// AnalyzeReports 2: freed, irrelevant.
// AnalyzeReports 3: freed, irrelevant.
char* b2 = new char;
ReportOnAlloc(b2);
free(b2);
// Dump 2: reported 4 times.
// Dump 3: freed, irrelevant.
// AnalyzeReports 2: reported 4 times.
// AnalyzeReports 3: freed, irrelevant.
char* c = (char*) calloc(10, 3);
Report(c);
for (int i = 0; i < 3; i++) {
Report(c);
}
// Dump 2: ignored.
// Dump 3: irrelevant.
// AnalyzeReports 2: ignored.
// AnalyzeReports 3: irrelevant.
Report((void*)(intptr_t)i);
// jemalloc rounds this up to 8192.
// Dump 2: reported.
// Dump 3: freed.
// AnalyzeReports 2: reported.
// AnalyzeReports 3: freed.
char* e = (char*) malloc(4096);
e = (char*) realloc(e, 4097);
Report(e);
// First realloc is like malloc; second realloc is shrinking.
// Dump 2: reported.
// Dump 3: re-reported.
// AnalyzeReports 2: reported.
// AnalyzeReports 3: re-reported.
char* e2 = (char*) realloc(nullptr, 1024);
e2 = (char*) realloc(e2, 512);
Report(e2);
// First realloc is like malloc; second realloc creates a min-sized block.
// XXX: on Windows, second realloc frees the block.
// Dump 2: reported.
// Dump 3: freed, irrelevant.
// AnalyzeReports 2: reported.
// AnalyzeReports 3: freed, irrelevant.
char* e3 = (char*) realloc(nullptr, 1023);
//e3 = (char*) realloc(e3, 0);
MOZ_ASSERT(e3);
Report(e3);
// Dump 2: freed, irrelevant.
// Dump 3: freed, irrelevant.
// AnalyzeReports 2: freed, irrelevant.
// AnalyzeReports 3: freed, irrelevant.
char* f = (char*) malloc(64);
free(f);
// Dump 2: ignored.
// Dump 3: irrelevant.
// AnalyzeReports 2: ignored.
// AnalyzeReports 3: irrelevant.
Report((void*)(intptr_t)0x0);
// Dump 2: mixture of reported and unreported.
// Dump 3: all unreported.
// AnalyzeReports 2: mixture of reported and unreported.
// AnalyzeReports 3: all unreported.
foo();
foo();
// Dump 2: twice-reported.
// Dump 3: twice-reported.
// AnalyzeReports 2: twice-reported.
// AnalyzeReports 3: twice-reported.
char* g1 = (char*) malloc(77);
ReportOnAlloc(g1);
ReportOnAlloc(g1);
// Dump 2: twice-reported.
// Dump 3: once-reported.
// AnalyzeReports 2: twice-reported.
// AnalyzeReports 3: once-reported.
char* g2 = (char*) malloc(78);
Report(g2);
ReportOnAlloc(g2);
// Dump 2: twice-reported.
// Dump 3: once-reported.
// AnalyzeReports 2: twice-reported.
// AnalyzeReports 3: once-reported.
char* g3 = (char*) malloc(79);
ReportOnAlloc(g3);
Report(g3);
// All the odd-ball ones.
// Dump 2: all unreported.
// Dump 3: all freed, irrelevant.
// AnalyzeReports 2: all unreported.
// AnalyzeReports 3: all freed, irrelevant.
// XXX: no memalign on Mac
//void* x = memalign(64, 65); // rounds up to 128
//UseItOrLoseIt(x);
@ -2294,8 +2420,9 @@ RunTestMode(FILE* fp)
//UseItOrLoseIt(z);
//aligned_alloc(64, 256); // XXX: C11 only
// Dump 2.
Dump(writer);
// AnalyzeReports 2.
AnalyzeReports(writer);
AnalyzeHeap(writer);
//---------
@ -2309,8 +2436,9 @@ RunTestMode(FILE* fp)
//free(y);
//free(z);
// Dump 3.
Dump(writer);
// AnalyzeReports 3.
AnalyzeReports(writer);
AnalyzeHeap(writer);
//---------
@ -2373,8 +2501,9 @@ RunTestMode(FILE* fp)
// At the end we're 64 bytes into the current sample so we report ~1,424
// bytes of allocation overall, which is 64 less than the real value 1,488.
// Dump 4.
Dump(writer);
// AnalyzeReports 4.
AnalyzeReports(writer);
AnalyzeHeap(writer);
}
//---------------------------------------------------------------------------
@ -2450,7 +2579,7 @@ RunStressMode(FILE* fp)
stress1(); stress1(); stress1(); stress1(); stress1();
stress1(); stress1(); stress1(); stress1(); stress1();
Dump(writer);
AnalyzeReports(writer);
}
} // namespace dmd

Просмотреть файл

@ -43,22 +43,28 @@ private:
// reporters. The following sequence should be used.
// - ClearReports()
// - run the memory reporters
// - Dump()
// - AnalyzeReports()
// This sequence avoids spurious twice-reported warnings.
MOZ_EXPORT void
ClearReports();
// Checks which heap blocks have been reported, and dumps a human-readable
// Determines which heap blocks have been reported, and dumps a human-readable
// summary (via |aWrite|). If |aWrite| is nullptr it will dump to stderr.
// Beware: this output may have very long lines.
// Beware: this output may have very long lines.
MOZ_EXPORT void
Dump(Writer aWriter);
AnalyzeReports(const Writer& aWriter);
// A useful |WriterFun|. If |fp| is a FILE* you want |Dump|'s output to be
// written to, call:
// Measures all heap blocks, and dumps a human-readable summary (via |aWrite|).
// If |aWrite| is nullptr it will dump to stderr. Beware: this output may
// have very long lines.
MOZ_EXPORT void
AnalyzeHeap(const Writer& aWriter);
// A useful |WriterFun|. For example, if |fp| is a FILE* you want
// |AnalyzeReports|'s output to be written to, call:
//
// dmd::Writer writer(FpWrite, fp);
// dmd::Dump(writer);
// dmd::AnalyzeReports(writer);
MOZ_EXPORT void
FpWrite(void* aFp, const char* aFmt, va_list aAp);

Просмотреть файл

@ -2,6 +2,7 @@
Invocation {
$DMD = '--mode=test'
Function = AnalyzeReports
Sample-below size = 1
}
@ -30,6 +31,25 @@ Summary {
Invocation {
$DMD = '--mode=test'
Function = AnalyzeHeap
Sample-below size = 1
}
#-----------------------------------------------------------------
# no live heap blocks
#-----------------------------------------------------------------
Summary {
Total: 0 bytes in 0 blocks
}
#-----------------------------------------------------------------
Invocation {
$DMD = '--mode=test'
Function = AnalyzeReports
Sample-below size = 1
}
@ -289,6 +309,131 @@ Summary {
Invocation {
$DMD = '--mode=test'
Function = AnalyzeHeap
Sample-below size = 1
}
#-----------------------------------------------------------------
Live {
1 block in heap block record 1 of 12
8,192 bytes (4,097 requested / 4,095 slop)
67.77% of the heap (67.77% cumulative)
Allocated at {
... DMD.cpp
}
}
Live {
1 block in heap block record 2 of 12
1,024 bytes (1,023 requested / 1 slop)
8.47% of the heap (76.24% cumulative)
Allocated at {
... DMD.cpp
}
}
Live {
9 blocks in heap block record 3 of 12
1,008 bytes (900 requested / 108 slop)
8.34% of the heap (84.58% cumulative)
Allocated at {
... DMD.cpp
}
}
Live {
6 blocks in heap block record 4 of 12
528 bytes (528 requested / 0 slop)
4.37% of the heap (88.95% cumulative)
Allocated at {
... DMD.cpp
}
}
Live {
6 blocks in heap block record 5 of 12
528 bytes (528 requested / 0 slop)
4.37% of the heap (93.32% cumulative)
Allocated at {
... DMD.cpp
}
}
Live {
1 block in heap block record 6 of 12
512 bytes (512 requested / 0 slop)
4.24% of the heap (97.55% cumulative)
Allocated at {
... DMD.cpp
}
}
Live {
1 block in heap block record 7 of 12
80 bytes (79 requested / 1 slop)
0.66% of the heap (98.21% cumulative)
Allocated at {
... DMD.cpp
}
}
Live {
1 block in heap block record 8 of 12
80 bytes (78 requested / 2 slop)
0.66% of the heap (98.87% cumulative)
Allocated at {
... DMD.cpp
}
}
Live {
1 block in heap block record 9 of 12
80 bytes (77 requested / 3 slop)
0.66% of the heap (99.54% cumulative)
Allocated at {
... DMD.cpp
}
}
Live {
1 block in heap block record 10 of 12
32 bytes (30 requested / 2 slop)
0.26% of the heap (99.80% cumulative)
Allocated at {
... DMD.cpp
}
}
Live {
1 block in heap block record 11 of 12
16 bytes (10 requested / 6 slop)
0.13% of the heap (99.93% cumulative)
Allocated at {
... DMD.cpp
}
}
Live {
1 block in heap block record 12 of 12
8 bytes (0 requested / 8 slop)
0.07% of the heap (100.00% cumulative)
Allocated at {
... DMD.cpp
}
}
#-----------------------------------------------------------------
Summary {
Total: 12,088 bytes in 30 blocks
}
#-----------------------------------------------------------------
Invocation {
$DMD = '--mode=test'
Function = AnalyzeReports
Sample-below size = 1
}
@ -425,6 +570,104 @@ Summary {
Invocation {
$DMD = '--mode=test'
Function = AnalyzeHeap
Sample-below size = 1
}
#-----------------------------------------------------------------
Live {
9 blocks in heap block record 1 of 9
1,008 bytes (900 requested / 108 slop)
35.49% of the heap (35.49% cumulative)
Allocated at {
... DMD.cpp
}
}
Live {
6 blocks in heap block record 2 of 9
528 bytes (528 requested / 0 slop)
18.59% of the heap (54.08% cumulative)
Allocated at {
... DMD.cpp
}
}
Live {
6 blocks in heap block record 3 of 9
528 bytes (528 requested / 0 slop)
18.59% of the heap (72.68% cumulative)
Allocated at {
... DMD.cpp
}
}
Live {
1 block in heap block record 4 of 9
512 bytes (512 requested / 0 slop)
18.03% of the heap (90.70% cumulative)
Allocated at {
... DMD.cpp
}
}
Live {
1 block in heap block record 5 of 9
80 bytes (79 requested / 1 slop)
2.82% of the heap (93.52% cumulative)
Allocated at {
... DMD.cpp
}
}
Live {
1 block in heap block record 6 of 9
80 bytes (78 requested / 2 slop)
2.82% of the heap (96.34% cumulative)
Allocated at {
... DMD.cpp
}
}
Live {
1 block in heap block record 7 of 9
80 bytes (77 requested / 3 slop)
2.82% of the heap (99.15% cumulative)
Allocated at {
... DMD.cpp
}
}
Live {
1 block in heap block record 8 of 9
16 bytes (10 requested / 6 slop)
0.56% of the heap (99.72% cumulative)
Allocated at {
... DMD.cpp
}
}
Live {
1 block in heap block record 9 of 9
8 bytes (0 requested / 8 slop)
0.28% of the heap (100.00% cumulative)
Allocated at {
... DMD.cpp
}
}
#-----------------------------------------------------------------
Summary {
Total: 2,840 bytes in 27 blocks
}
#-----------------------------------------------------------------
Invocation {
$DMD = '--mode=test'
Function = AnalyzeReports
Sample-below size = 128
}
@ -517,3 +760,82 @@ Summary {
Twice-reported: ~0 bytes ( 0.00%) in ~0 blocks ( 0.00%)
}
#-----------------------------------------------------------------
Invocation {
$DMD = '--mode=test'
Function = AnalyzeHeap
Sample-below size = 128
}
#-----------------------------------------------------------------
Live {
~4 blocks in heap block record 1 of 7
~512 bytes (~512 requested / ~0 slop)
35.96% of the heap (35.96% cumulative)
Allocated at {
... DMD.cpp
}
}
Live {
1 block in heap block record 2 of 7
256 bytes (256 requested / 0 slop)
17.98% of the heap (53.93% cumulative)
Allocated at {
... DMD.cpp
}
}
Live {
1 block in heap block record 3 of 7
144 bytes (144 requested / 0 slop)
10.11% of the heap (64.04% cumulative)
Allocated at {
... DMD.cpp
}
}
Live {
1 block in heap block record 4 of 7
128 bytes (128 requested / 0 slop)
8.99% of the heap (73.03% cumulative)
Allocated at {
... DMD.cpp
}
}
Live {
~1 block in heap block record 5 of 7
~128 bytes (~128 requested / ~0 slop)
8.99% of the heap (82.02% cumulative)
Allocated at {
... DMD.cpp
}
}
Live {
~1 block in heap block record 6 of 7
~128 bytes (~128 requested / ~0 slop)
8.99% of the heap (91.01% cumulative)
Allocated at {
... DMD.cpp
}
}
Live {
~1 block in heap block record 7 of 7
~128 bytes (~128 requested / ~0 slop)
8.99% of the heap (100.00% cumulative)
Allocated at {
... DMD.cpp
}
}
#-----------------------------------------------------------------
Summary {
Total: ~1,424 bytes in ~10 blocks
}

Просмотреть файл

@ -93,11 +93,20 @@ MemoryStats.dump = function (logger,
}, null, /* anonymize = */ false);
}
// This is the old, deprecated function.
if (dumpDMD && typeof(DMDReportAndDump) != undefined) {
var basename = "dmd-" + testNumber + "-deprecated.txt";
var dumpfile = MemoryStats.constructPathname(dumpOutputDirectory,
basename);
logger.info(testURL + " | DMD-DUMP-deprecated " + dumpfile);
DMDReportAndDump(dumpfile);
}
if (dumpDMD && typeof(DMDAnalyzeReports) != undefined) {
var basename = "dmd-" + testNumber + ".txt";
var dumpfile = MemoryStats.constructPathname(dumpOutputDirectory,
basename);
logger.info(testURL + " | DMD-DUMP " + dumpfile);
DMDReportAndDump(dumpfile);
DMDAnalyzeReports(dumpfile);
}
};

Просмотреть файл

@ -53,7 +53,8 @@ const gUnnamedProcessStr = "Main Process";
let gIsDiff = false;
const DMDFile = "out.dmd";
const gAnalyzeReportsFile = "reports.dmd";
const gAnalyzeHeapFile = "heap.dmd";
//---------------------------------------------------------------------------
@ -298,7 +299,10 @@ function onLoad()
"collection log.\n" +
"WARNING: These logs may be large (>1GB).";
const DMDEnabledDesc = "Run DMD analysis and save it to '" + DMDFile + "'.\n";
const AnalyzeReportsDesc = "Analyze memory reports coverage and save the "
"output to '" + gAnalyzeReportsFile + "'.\n";
const AnalyzeHeapDesc = "Analyze heap usage and save the output to '" +
gAnalyzeHeapFile + "'.\n";
const DMDDisabledDesc = "DMD is not running. Please re-start with $DMD and " +
"the other relevant environment variables set " +
"appropriately.";
@ -359,12 +363,20 @@ function onLoad()
if (gMgr.isDMDEnabled) {
let row5 = appendElement(ops, "div", "opsRow");
appendElementWithText(row5, "div", "opsRowLabel", "Save DMD output");
let enableButton = gMgr.isDMDRunning;
let dmdButton =
appendButton(row5, enableButton ? DMDEnabledDesc : DMDDisabledDesc,
doDMD, "Save", "dmdButton");
dmdButton.disabled = !enableButton;
appendElementWithText(row5, "div", "opsRowLabel", "DMD operations");
let enableButtons = gMgr.isDMDRunning;
let analyzeReportsButton =
appendButton(row5,
enableButtons ? AnalyzeReportsDesc : DMDDisabledDesc,
doAnalyzeReports, "Analyze reports");
analyzeReportsButton.disabled = !enableButtons;
let analyzeHeapButton =
appendButton(row5,
enableButtons ? AnalyzeHeapDesc : DMDDisabledDesc,
doAnalyzeHeap, "Analyze heap");
analyzeHeapButton.disabled = !enableButtons;
}
// Generate the main div, where content ("section" divs) will go. It's
@ -445,12 +457,24 @@ function saveGCLogAndVerboseCCLog()
dumpGCLogAndCCLog(true);
}
function doDMD()
function doAnalyzeReports()
{
updateMainAndFooter('Saving DMD output...', HIDE_FOOTER);
try {
let x = DMDReportAndDump('out.dmd');
updateMainAndFooter('Saved DMD output to ' + DMDFile, HIDE_FOOTER);
let x = DMDAnalyzeReports(gAnalyzeReportsFile);
updateMainAndFooter('Saved DMD output to ' + gAnalyzeReportsFile,
HIDE_FOOTER);
} catch (ex) {
updateMainAndFooter(ex.toString(), HIDE_FOOTER);
}
}
function doAnalyzeHeap()
{
updateMainAndFooter('Saving DMD output...', HIDE_FOOTER);
try {
let x = DMDAnalyzeHeap(gAnalyzeHeapFile);
updateMainAndFooter('Saved DMD output to ' + gAnalyzeHeapFile, HIDE_FOOTER);
} catch (ex) {
updateMainAndFooter(ex.toString(), HIDE_FOOTER);
}

Просмотреть файл

@ -721,10 +721,10 @@ nsMemoryInfoDumper::DumpDMDToFile(FILE* aFile)
return rv;
}
// Dump DMD output to the file.
// Dump DMD's memory reports analysis to the file.
DMDWriteState state(dmdWriter);
dmd::Writer w(DMDWrite, &state);
dmd::Dump(w);
dmd::AnalyzeReports(w);
rv = dmdWriter->Finish();
NS_WARN_IF(NS_FAILED(rv));