Bug 827523 (part 3) - DMD: Add --max-frames and --max-records options. r=jlebar.

--HG--
extra : rebase_source : 9840bcfa2d8fc1127a6f367a94e1d8488ae41c6a
This commit is contained in:
Nicholas Nethercote 2013-01-07 18:32:38 -08:00
Родитель d5ea3d0aef
Коммит 8685a2fb2b
1 изменённых файлов: 196 добавлений и 141 удалений

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

@ -1,5 +1,5 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@ -73,13 +73,6 @@ static const malloc_table_t* gMallocTable = nullptr;
// This enables/disables DMD.
static bool gIsDMDRunning = false;
enum Mode {
Normal, // run normally
Test, // do some basic correctness tests
Stress // do some performance stress tests
};
static Mode gMode = Normal;
// This provides infallible allocations (they abort on OOM). We use it for all
// of DMD's own allocations, which fall into the following three cases.
// - Direct allocations (the easy case).
@ -276,11 +269,64 @@ static char gBuf4[kBufLen];
static const size_t kNoSize = size_t(-1);
//---------------------------------------------------------------------------
// Options (Part 1)
//---------------------------------------------------------------------------
class Options
{
template <typename T>
struct NumOption
{
const T mDefault;
const T mMax;
T mActual;
NumOption(T aDefault, T aMax)
: mDefault(aDefault), mMax(aMax), mActual(aDefault)
{}
};
enum Mode {
Normal, // run normally
Test, // do some basic correctness tests
Stress // do some performance stress tests
};
char* mDMDEnvVar; // a saved copy, for printing during Dump()
NumOption<size_t> mSampleBelowSize;
NumOption<uint32_t> mMaxFrames;
NumOption<uint32_t> mMaxRecords;
Mode mMode;
void BadArg(const char* aArg);
static const char* ValueIfMatch(const char* aArg, const char* aOptionName);
static bool GetLong(const char* aArg, const char* aOptionName,
long aMin, long aMax, long* aN);
public:
Options(const char* aDMDEnvVar);
const char* DMDEnvVar() const { return mDMDEnvVar; }
size_t SampleBelowSize() const { return mSampleBelowSize.mActual; }
size_t MaxFrames() const { return mMaxFrames.mActual; }
size_t MaxRecords() const { return mMaxRecords.mActual; }
void SetSampleBelowSize(size_t aN) { mSampleBelowSize.mActual = aN; }
bool IsTestMode() const { return mMode == Test; }
bool IsStressMode() const { return mMode == Stress; }
};
static Options *gOptions;
//---------------------------------------------------------------------------
// The global lock
//---------------------------------------------------------------------------
// MutexBase implements the platform-specific parts of a mutex.
#ifdef XP_WIN
class MutexBase
@ -703,10 +749,14 @@ public:
class StackTrace
{
public:
static const uint32_t MaxFrames = 24;
uint32_t mLength; // The number of PCs.
void* mPcs[MaxFrames]; // The PCs themselves.
private:
uint32_t mLength; // The number of PCs.
void* mPcs[MaxFrames]; // The PCs themselves. If --max-frames is less
// than 24, this array is bigger than necessary,
// but that case is unusual.
public:
StackTrace() : mLength(0) {}
@ -800,8 +850,8 @@ StackTrace::Get(Thread* aT)
{
AutoUnlockState unlock;
uint32_t skipFrames = 2;
rv = NS_StackWalk(StackWalkCallback, skipFrames, MaxFrames, &tmp, 0,
nullptr);
rv = NS_StackWalk(StackWalkCallback, skipFrames,
gOptions->MaxFrames(), &tmp, 0, nullptr);
}
if (rv == NS_OK) {
@ -1075,7 +1125,6 @@ GCStackTraces()
// malloc/free callbacks
//---------------------------------------------------------------------------
static size_t gSampleBelowSize = 0;
static size_t gSmallBlockActualSizeCounter = 0;
static void
@ -1091,17 +1140,18 @@ AllocCallback(void* aPtr, size_t aReqSize, Thread* aT)
AutoBlockIntercepts block(aT);
size_t actualSize = gMallocTable->malloc_usable_size(aPtr);
size_t sampleBelowSize = gOptions->SampleBelowSize();
if (actualSize < gSampleBelowSize) {
if (actualSize < sampleBelowSize) {
// If this allocation is smaller than the sample-below size, increment the
// cumulative counter. Then, if that counter now exceeds the sample size,
// blame this allocation for gSampleBelowSize bytes. This precludes the
// blame this allocation for |sampleBelowSize| bytes. This precludes the
// measurement of slop.
gSmallBlockActualSizeCounter += actualSize;
if (gSmallBlockActualSizeCounter >= gSampleBelowSize) {
gSmallBlockActualSizeCounter -= gSampleBelowSize;
if (gSmallBlockActualSizeCounter >= sampleBelowSize) {
gSmallBlockActualSizeCounter -= sampleBelowSize;
Block b(aPtr, gSampleBelowSize, StackTrace::Get(aT), /* sampled */ true);
Block b(aPtr, sampleBelowSize, StackTrace::Get(aT), /* sampled */ true);
(void)gBlockTable->putNew(aPtr, b);
}
} else {
@ -1555,19 +1605,14 @@ FrameRecord::Print(const Writer& aWriter, LocationService* aLocService,
}
//---------------------------------------------------------------------------
// DMD start-up
// Options (Part 2)
//---------------------------------------------------------------------------
static void RunTestMode(FILE* fp);
static void RunStressMode(FILE* fp);
static const char* gDMDEnvVar = nullptr;
// Given an |aOptionName| like "foo", succeed if |aArg| has the form "foo=blah"
// (where "blah" is non-empty) and return the pointer to "blah". |aArg| can
// have leading space chars (but not other whitespace).
static const char*
OptionValueIfMatch(const char* aArg, const char* aOptionName)
const char*
Options::ValueIfMatch(const char* aArg, const char* aOptionName)
{
MOZ_ASSERT(!isspace(*aArg)); // any leading whitespace should not remain
size_t optionLen = strlen(aOptionName);
@ -1580,11 +1625,11 @@ OptionValueIfMatch(const char* aArg, const char* aOptionName)
// Extracts a |long| value for an option from an argument. It must be within
// the range |aMin..aMax| (inclusive).
static bool
OptionLong(const char* aArg, const char* aOptionName, long aMin, long aMax,
long* aN)
bool
Options::GetLong(const char* aArg, const char* aOptionName,
long aMin, long aMax, long* aN)
{
if (const char* optionValue = OptionValueIfMatch(aArg, aOptionName)) {
if (const char* optionValue = ValueIfMatch(aArg, aOptionName)) {
char* endPtr;
*aN = strtol(optionValue, &endPtr, /* base */ 10);
if (!*endPtr && aMin <= *aN && *aN <= aMax &&
@ -1595,95 +1640,22 @@ OptionLong(const char* aArg, const char* aOptionName, long aMin, long aMax,
return false;
}
static const size_t gMaxSampleBelowSize = 100 * 1000 * 1000; // bytes
// Default to sampling with a sample-below size that's a prime number close to
// 4096.
// The sample-below default is a prime number close to 4096.
// - Why that size? Because it's *much* faster but only moderately less precise
// than a size of 1.
// - Why prime? Because it makes our sampling more random. If we used a size
// of 4096, for example, then our alloc counter would only take on even
// values, because jemalloc always rounds up requests sizes. In contrast, a
// prime size will explore all possible values of the alloc counter.
//
// Using a sample-below size ~= 4096 is much faster than using a sample-below
// size of 1, and it's not much less accurate in practice, so it's a reasonable
// default.
//
// Using a prime sample-below size makes our sampling more random. If we used
// instead a sample-below size of 4096, for example, then if all our allocation
// sizes were even (which they likely are, due to how jemalloc rounds up), our
// alloc counter would take on only even values.
//
// In contrast, using a prime sample-below size lets us explore all possible
// values of the alloc counter.
static const size_t gDefaultSampleBelowSize = 4093;
static void
BadArg(const char* aArg)
Options::Options(const char* aDMDEnvVar)
: mDMDEnvVar(InfallibleAllocPolicy::strdup_(aDMDEnvVar)),
mSampleBelowSize(4093, 100 * 100 * 1000),
mMaxFrames(StackTrace::MaxFrames, StackTrace::MaxFrames),
mMaxRecords(1000, 1000000),
mMode(Normal)
{
StatusMsg("\n");
StatusMsg("Bad entry in the $DMD environment variable: '%s'.\n", aArg);
StatusMsg("\n");
StatusMsg("Valid values of $DMD are:\n");
StatusMsg("- undefined or \"\" or \"0\", which disables DMD, or\n");
StatusMsg("- \"1\", which enables it with the default options, or\n");
StatusMsg("- a whitespace-separated list of |--option=val| entries, which\n");
StatusMsg(" enables it with non-default options.\n");
StatusMsg("\n");
StatusMsg("The following options are allowed; defaults are shown in [].\n");
StatusMsg(" --sample-below=<1..%d> Sample blocks smaller than this [%d]\n"
" (prime numbers recommended).\n",
int(gMaxSampleBelowSize), int(gDefaultSampleBelowSize));
StatusMsg(" --mode=<normal|test|stress> Which mode to run in? [normal]\n");
StatusMsg("\n");
exit(1);
}
#ifdef XP_MACOSX
static void
NopStackWalkCallback(void* aPc, void* aSp, void* aClosure)
{
}
#endif
// Note that fopen() can allocate.
static FILE*
OpenOutputFile(const char* aFilename)
{
FILE* fp = fopen(aFilename, "w");
if (!fp) {
StatusMsg("can't create %s file: %s\n", aFilename, strerror(errno));
exit(1);
}
return fp;
}
// WARNING: this function runs *very* early -- before all static initializers
// have run. For this reason, non-scalar globals such as gStateLock and
// gStackTraceTable are allocated dynamically (so we can guarantee their
// construction in this function) rather than statically.
static void
Init(const malloc_table_t* aMallocTable)
{
MOZ_ASSERT(!gIsDMDRunning);
gMallocTable = aMallocTable;
// Set defaults of things that can be affected by the $DMD env var.
gMode = Normal;
gSampleBelowSize = gDefaultSampleBelowSize;
// DMD is controlled by the |DMD| environment variable.
// - If it's unset or empty or "0", DMD doesn't run.
// - Otherwise, the contents dictate DMD's behaviour.
char* e = getenv("DMD");
StatusMsg("$DMD = '%s'\n", e);
if (!e || strcmp(e, "") == 0 || strcmp(e, "0") == 0) {
StatusMsg("DMD is not enabled\n");
return;
}
// Save it so we can print it in Dump().
gDMDEnvVar = e = InfallibleAllocPolicy::strdup_(e);
char* e = mDMDEnvVar;
if (strcmp(e, "1") != 0) {
bool isEnd = false;
while (!isEnd) {
@ -1706,15 +1678,21 @@ Init(const malloc_table_t* aMallocTable)
// Handle arg
long myLong;
if (OptionLong(arg, "--sample-below", 1, gMaxSampleBelowSize, &myLong)) {
gSampleBelowSize = myLong;
if (GetLong(arg, "--sample-below", 1, mSampleBelowSize.mMax, &myLong)) {
mSampleBelowSize.mActual = myLong;
} else if (GetLong(arg, "--max-frames", 1, mMaxFrames.mMax, &myLong)) {
mMaxFrames.mActual = myLong;
} else if (GetLong(arg, "--max-records", 1, mMaxRecords.mMax, &myLong)) {
mMaxRecords.mActual = myLong;
} else if (strcmp(arg, "--mode=normal") == 0) {
gMode = Normal;
mMode = Options::Normal;
} else if (strcmp(arg, "--mode=test") == 0) {
gMode = Test;
mMode = Options::Test;
} else if (strcmp(arg, "--mode=stress") == 0) {
gMode = Stress;
mMode = Options::Stress;
} else if (strcmp(arg, "") == 0) {
// This can only happen if there is trailing whitespace. Ignore.
@ -1728,8 +1706,87 @@ Init(const malloc_table_t* aMallocTable)
*e = replacedChar;
}
}
}
// Finished parsing $DMD.
void
Options::BadArg(const char* aArg)
{
StatusMsg("\n");
StatusMsg("Bad entry in the $DMD environment variable: '%s'.\n", aArg);
StatusMsg("\n");
StatusMsg("Valid values of $DMD are:\n");
StatusMsg("- undefined or \"\" or \"0\", which disables DMD, or\n");
StatusMsg("- \"1\", which enables it with the default options, or\n");
StatusMsg("- a whitespace-separated list of |--option=val| entries, which\n");
StatusMsg(" enables it with non-default options.\n");
StatusMsg("\n");
StatusMsg("The following options are allowed; defaults are shown in [].\n");
StatusMsg(" --sample-below=<1..%d> Sample blocks smaller than this [%d]\n",
int(mSampleBelowSize.mMax),
int(mSampleBelowSize.mDefault));
StatusMsg(" (prime numbers are recommended)\n");
StatusMsg(" --max-frames=<1..%d> Max. depth of stack traces [%d]\n",
int(mMaxFrames.mMax),
int(mMaxFrames.mDefault));
StatusMsg(" --max-records=<1..%u> Max. number of records printed [%u]\n",
mMaxRecords.mMax,
mMaxRecords.mDefault);
StatusMsg(" --mode=<normal|test|stress> Mode of operation [normal]\n");
StatusMsg("\n");
exit(1);
}
//---------------------------------------------------------------------------
// DMD start-up
//---------------------------------------------------------------------------
#ifdef XP_MACOSX
static void
NopStackWalkCallback(void* aPc, void* aSp, void* aClosure)
{
}
#endif
// Note that fopen() can allocate.
static FILE*
OpenOutputFile(const char* aFilename)
{
FILE* fp = fopen(aFilename, "w");
if (!fp) {
StatusMsg("can't create %s file: %s\n", aFilename, strerror(errno));
exit(1);
}
return fp;
}
static void RunTestMode(FILE* fp);
static void RunStressMode(FILE* fp);
// WARNING: this function runs *very* early -- before all static initializers
// have run. For this reason, non-scalar globals such as gStateLock and
// gStackTraceTable are allocated dynamically (so we can guarantee their
// construction in this function) rather than statically.
static void
Init(const malloc_table_t* aMallocTable)
{
MOZ_ASSERT(!gIsDMDRunning);
gMallocTable = aMallocTable;
// DMD is controlled by the |DMD| environment variable.
// - If it's unset or empty or "0", DMD doesn't run.
// - Otherwise, the contents dictate DMD's behaviour.
char* e = getenv("DMD");
StatusMsg("$DMD = '%s'\n", e);
if (!e || strcmp(e, "") == 0 || strcmp(e, "0") == 0) {
StatusMsg("DMD is not enabled\n");
return;
}
// Parse $DMD env var.
gOptions = InfallibleAllocPolicy::new_<Options>(e);
StatusMsg("DMD is enabled\n");
@ -1756,7 +1813,7 @@ Init(const malloc_table_t* aMallocTable)
gBlockTable = InfallibleAllocPolicy::new_<BlockTable>();
gBlockTable->init(8192);
if (gMode == Test) {
if (gOptions->IsTestMode()) {
// OpenOutputFile() can allocate. So do this before setting
// gIsDMDRunning so those allocations don't show up in our results. Once
// gIsDMDRunning is set we are intercepting malloc et al. in earnest.
@ -1770,7 +1827,7 @@ Init(const malloc_table_t* aMallocTable)
exit(0);
}
if (gMode == Stress) {
if (gOptions->IsStressMode()) {
FILE* fp = OpenOutputFile("stress.dmd");
gIsDMDRunning = true;
@ -1857,23 +1914,23 @@ PrintSortedRecords(const Writer& aWriter, LocationService* aLocService,
return;
}
StatusMsg(" printing %s stack %s record array...\n", astr, kind);
size_t cumulativeUsableSize = 0;
// Limit the number of records printed, because fix-linux-stack.pl is too
// damn slow. Note that we don't break out of this loop because we need to
// keep adding to |cumulativeUsableSize|.
static const uint32_t MaxRecords = 1000;
uint32_t numRecords = recordArray.length();
StatusMsg(" printing %s stack %s record array...\n", astr, kind);
size_t cumulativeUsableSize = 0;
uint32_t maxRecords = gOptions->MaxRecords();
for (uint32_t i = 0; i < numRecords; i++) {
const Record* r = recordArray[i];
cumulativeUsableSize += r->GetRecordSize().Usable();
if (i < MaxRecords) {
if (i < maxRecords) {
r->Print(aWriter, aLocService, i+1, numRecords, aStr, astr,
aCategoryUsableSize, cumulativeUsableSize, aTotalUsableSize);
} else if (i == MaxRecords) {
} else if (i == maxRecords) {
W("%s: stopping after %s stack %s records\n\n", aStr,
Show(MaxRecords, gBuf1, kBufLen), kind);
Show(maxRecords, gBuf1, kBufLen), kind);
}
}
@ -2060,8 +2117,9 @@ Dump(Writer aWriter)
unreportedNumBlocks + onceReportedNumBlocks + twiceReportedNumBlocks;
WriteTitle("Invocation\n");
W("$DMD = '%s'\n", gDMDEnvVar);
W("Sample-below size = %lld\n\n", (long long)(gSampleBelowSize));
W("$DMD = '%s'\n", gOptions->DMDEnvVar());
W("Sample-below size = %lld\n\n",
(long long)(gOptions->SampleBelowSize()));
// Allocate this on the heap instead of the stack because it's fairly large.
LocationService* locService = InfallibleAllocPolicy::new_<LocationService>();
@ -2110,7 +2168,7 @@ Dump(Writer aWriter)
W("\n");
// Stats are non-deterministic, so don't show them in test mode.
if (gMode != Test) {
if (!gOptions->IsTestMode()) {
Sizes sizes;
SizeOfInternal(&sizes);
@ -2221,7 +2279,7 @@ RunTestMode(FILE* fp)
Writer writer(FpWrite, fp);
// The first part of this test requires sampling to be disabled.
gSampleBelowSize = 1;
gOptions->SetSampleBelowSize(1);
// 0th Dump. Zero for everything.
Dump(writer);
@ -2359,10 +2417,7 @@ RunTestMode(FILE* fp)
// Clear all knowledge of existing blocks to give us a clean slate.
gBlockTable->clear();
// Reset the counter just in case |sample-size| was specified in $DMD.
// Otherwise the assertions fail.
gSmallBlockActualSizeCounter = 0;
gSampleBelowSize = 128;
gOptions->SetSampleBelowSize(128);
char* s;
@ -2480,7 +2535,7 @@ RunStressMode(FILE* fp)
Writer writer(FpWrite, fp);
// Disable sampling for maximum stress.
gSampleBelowSize = 1;
gOptions->SetSampleBelowSize(1);
stress1(); stress1(); stress1(); stress1(); stress1();
stress1(); stress1(); stress1(); stress1(); stress1();