Bug 972712 (part 3) - Rework notable string reporting. r=till.

--HG--
extra : rebase_source : 274c2ee9beafca5e464234f37e894967d20abb25
This commit is contained in:
Nicholas Nethercote 2014-02-26 18:11:01 -08:00
parent 3cd29dbeac
commit fdb45bb355
3 changed files with 181 additions and 194 deletions

View File

@ -92,6 +92,9 @@ struct InefficientNonFlatteningStringHashPolicy
#define ZERO_SIZE(kind, gc, mSize) mSize(0),
#define COPY_OTHER_SIZE(kind, gc, mSize) mSize(other.mSize),
#define ADD_OTHER_SIZE(kind, gc, mSize) mSize += other.mSize;
#define SUB_OTHER_SIZE(kind, gc, mSize) MOZ_ASSERT(mSize >= other.mSize); \
mSize -= other.mSize;
#define ADD_SIZE_TO_N(kind, gc, mSize) n += mSize;
#define ADD_SIZE_TO_N_IF_LIVE_GC_THING(kind, gc, mSize) n += (js::gc) ? mSize : 0;
#define ADD_TO_TAB_SIZES(kind, gc, mSize) sizes->add(JS::TabSizes::kind, mSize);
@ -101,48 +104,6 @@ enum {
IsLiveGCThing = true
};
struct ZoneStatsPod
{
#define FOR_EACH_SIZE(macro) \
macro(Other, NotLiveGCThing, gcHeapArenaAdmin) \
macro(Other, NotLiveGCThing, unusedGCThings) \
macro(Other, IsLiveGCThing, lazyScriptsGCHeap) \
macro(Other, NotLiveGCThing, lazyScriptsMallocHeap) \
macro(Other, IsLiveGCThing, jitCodesGCHeap) \
macro(Other, IsLiveGCThing, typeObjectsGCHeap) \
macro(Other, NotLiveGCThing, typeObjectsMallocHeap) \
macro(Other, NotLiveGCThing, typePool) \
macro(Strings, IsLiveGCThing, stringsGCHeap) \
macro(Strings, NotLiveGCThing, stringsMallocHeap)
ZoneStatsPod()
: FOR_EACH_SIZE(ZERO_SIZE)
extra()
{}
void add(const ZoneStatsPod &other) {
FOR_EACH_SIZE(ADD_OTHER_SIZE)
// Do nothing with |extra|.
}
size_t sizeOfLiveGCThings() const {
size_t n = 0;
FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING)
// Do nothing with |extra|.
return n;
}
void addToTabSizes(JS::TabSizes *sizes) const {
FOR_EACH_SIZE(ADD_TO_TAB_SIZES)
// Do nothing with |extra|.
}
FOR_EACH_SIZE(DECL_SIZE)
void *extra; // This field can be used by embedders.
#undef FOR_EACH_SIZE
};
} // namespace js
namespace JS {
@ -240,45 +201,57 @@ struct GCSizes
// is not.
struct StringInfo
{
#define FOR_EACH_SIZE(macro) \
macro(Strings, IsLiveGCThing, gcHeap) \
macro(Strings, NotLiveGCThing, mallocHeap) \
StringInfo()
: numCopies(0),
gcHeap(0),
mallocHeap(0)
: FOR_EACH_SIZE(ZERO_SIZE)
numCopies(0)
{}
StringInfo(size_t gcSize, size_t mallocSize)
: numCopies(1),
gcHeap(gcSize),
mallocHeap(mallocSize)
{}
void add(size_t gcSize, size_t mallocSize) {
void add(const StringInfo &other) {
FOR_EACH_SIZE(ADD_OTHER_SIZE);
numCopies++;
gcHeap += gcSize;
mallocHeap += mallocSize;
}
void add(const StringInfo& info) {
numCopies += info.numCopies;
gcHeap += info.gcHeap;
mallocHeap += info.mallocHeap;
void subtract(const StringInfo &other) {
FOR_EACH_SIZE(SUB_OTHER_SIZE);
numCopies--;
}
bool isNotable() const {
static const size_t NotabilityThreshold = 16 * 1024;
size_t n = 0;
FOR_EACH_SIZE(ADD_SIZE_TO_N)
return n >= NotabilityThreshold;
}
size_t sizeOfLiveGCThings() const {
size_t n = 0;
FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING)
return n;
}
void addToTabSizes(TabSizes *sizes) const {
FOR_EACH_SIZE(ADD_TO_TAB_SIZES)
}
FOR_EACH_SIZE(DECL_SIZE)
uint32_t numCopies; // How many copies of the string have we seen?
// These are all totals across all copies of the string we've seen.
size_t gcHeap;
size_t mallocHeap;
#undef FOR_EACH_SIZE
};
// Holds data about a notable string (one which uses more than
// NotableStringInfo::notableSize() bytes of memory), so we can report it
// individually.
// Holds data about a notable string (one which, counting all duplicates, uses
// more than a certain amount of memory) so we can report it individually.
//
// Essentially the only difference between this class and StringInfo is that
// NotableStringInfo holds a copy of the string's chars.
// The only difference between this class and StringInfo is that
// NotableStringInfo holds a copy of some or all of the string's chars.
struct NotableStringInfo : public StringInfo
{
static const size_t MAX_SAVED_CHARS = 1024;
NotableStringInfo();
NotableStringInfo(JSString *str, const StringInfo &info);
NotableStringInfo(NotableStringInfo &&info);
@ -288,12 +261,6 @@ struct NotableStringInfo : public StringInfo
js_free(buffer);
}
// A string needs to take up this many bytes of storage before we consider
// it to be "notable".
static size_t notableSize() {
return js::MemoryReportingSundriesThreshold();
}
char *buffer;
size_t length;
@ -331,63 +298,89 @@ struct RuntimeSizes
#undef FOR_EACH_SIZE
};
struct ZoneStats : js::ZoneStatsPod
struct ZoneStats
{
#define FOR_EACH_SIZE(macro) \
macro(Other, NotLiveGCThing, gcHeapArenaAdmin) \
macro(Other, NotLiveGCThing, unusedGCThings) \
macro(Other, IsLiveGCThing, lazyScriptsGCHeap) \
macro(Other, NotLiveGCThing, lazyScriptsMallocHeap) \
macro(Other, IsLiveGCThing, jitCodesGCHeap) \
macro(Other, IsLiveGCThing, typeObjectsGCHeap) \
macro(Other, NotLiveGCThing, typeObjectsMallocHeap) \
macro(Other, NotLiveGCThing, typePool) \
ZoneStats()
: strings(nullptr)
: FOR_EACH_SIZE(ZERO_SIZE)
stringInfo(),
extra(),
allStrings(nullptr),
notableStrings(),
isTotals(true)
{}
ZoneStats(ZoneStats &&other)
: ZoneStatsPod(mozilla::Move(other)),
strings(other.strings),
notableStrings(mozilla::Move(other.notableStrings))
: FOR_EACH_SIZE(COPY_OTHER_SIZE)
stringInfo(mozilla::Move(other.stringInfo)),
extra(other.extra),
allStrings(other.allStrings),
notableStrings(mozilla::Move(other.notableStrings)),
isTotals(other.isTotals)
{
other.strings = nullptr;
other.allStrings = nullptr;
MOZ_ASSERT(!other.isTotals);
}
~ZoneStats() {
// |allStrings| is usually deleted and set to nullptr before this
// destructor runs. But there are failure cases due to OOMs that may
// prevent that, so it doesn't hurt to try again here.
js_delete(allStrings);
}
bool initStrings(JSRuntime *rt);
// Add |other|'s numbers to this object's numbers. The strings data isn't
// touched.
void addIgnoringStrings(const ZoneStats &other) {
ZoneStatsPod::add(other);
}
// Add |other|'s strings data to this object's strings data. (We don't do
// anything with notableStrings.)
void addStrings(const ZoneStats &other) {
for (StringsHashMap::Range r = other.strings->all(); !r.empty(); r.popFront()) {
StringsHashMap::AddPtr p = strings->lookupForAdd(r.front().key());
if (p) {
// We've seen this string before; add its size to our tally.
p->value().add(r.front().value());
} else {
// We haven't seen this string before; add it to the hashtable.
strings->add(p, r.front().key(), r.front().value());
}
}
void addSizes(const ZoneStats &other) {
MOZ_ASSERT(isTotals);
FOR_EACH_SIZE(ADD_OTHER_SIZE)
stringInfo.add(other.stringInfo);
}
size_t sizeOfLiveGCThings() const {
size_t n = ZoneStatsPod::sizeOfLiveGCThings();
for (size_t i = 0; i < notableStrings.length(); i++) {
const JS::NotableStringInfo& info = notableStrings[i];
n += info.gcHeap;
}
MOZ_ASSERT(isTotals);
size_t n = 0;
FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING)
n += stringInfo.sizeOfLiveGCThings();
return n;
}
typedef js::HashMap<JSString*,
StringInfo,
void addToTabSizes(JS::TabSizes *sizes) const {
MOZ_ASSERT(isTotals);
FOR_EACH_SIZE(ADD_TO_TAB_SIZES)
stringInfo.addToTabSizes(sizes);
}
// These string measurements are initially for all strings. At the end,
// if the measurement granularity is FineGrained, we subtract the
// measurements of the notable script sources and move them into
// |notableStrings|.
FOR_EACH_SIZE(DECL_SIZE)
StringInfo stringInfo;
void *extra; // This field can be used by embedders.
typedef js::HashMap<JSString*, StringInfo,
js::InefficientNonFlatteningStringHashPolicy,
js::SystemAllocPolicy> StringsHashMap;
// |strings| is only used transiently. During the zone traversal it is
// |allStrings| is only used transiently. During the zone traversal it is
// filled with info about every string in the zone. It's then used to fill
// in |notableStrings| (which actually gets reported), and immediately
// discarded afterwards.
StringsHashMap *strings;
StringsHashMap *allStrings;
js::Vector<NotableStringInfo, 0, js::SystemAllocPolicy> notableStrings;
bool isTotals;
#undef FOR_EACH_SIZE
};
struct CompartmentStats
@ -564,6 +557,8 @@ AddSizeOfTab(JSRuntime *rt, JS::HandleObject obj, mozilla::MallocSizeOf mallocSi
#undef ZERO_SIZE
#undef COPY_OTHER_SIZE
#undef ADD_OTHER_SIZE
#undef SUB_OTHER_SIZE
#undef ADD_SIZE_TO_N
#undef ADD_SIZE_TO_N_IF_LIVE_GC_THING
#undef ADD_TO_TAB_SIZES

View File

@ -94,7 +94,8 @@ InefficientNonFlatteningStringHashPolicy::match(const JSString *const &k, const
namespace JS {
NotableStringInfo::NotableStringInfo()
: buffer(0),
: StringInfo(),
buffer(0),
length(0)
{
}
@ -103,7 +104,7 @@ NotableStringInfo::NotableStringInfo(JSString *str, const StringInfo &info)
: StringInfo(info),
length(str->length())
{
size_t bufferSize = Min(str->length() + 1, size_t(4096));
size_t bufferSize = Min(str->length() + 1, size_t(MAX_SAVED_CHARS));
buffer = js_pod_malloc<char>(bufferSize);
if (!buffer) {
MOZ_CRASH("oom");
@ -119,7 +120,7 @@ NotableStringInfo::NotableStringInfo(JSString *str, const StringInfo &info)
chars = ownedChars;
}
// We might truncate |str| even if it's much shorter than 4096 chars, if
// We might truncate |str| even if it's much shorter than 1024 chars, if
// |str| contains unicode chars. Since this is just for a memory reporter,
// we don't care.
PutEscapedString(buffer, bufferSize, chars, str->length(), /* quote */ 0);
@ -181,7 +182,8 @@ StatsZoneCallback(JSRuntime *rt, void *data, Zone *zone)
// CollectRuntimeStats reserves enough space.
MOZ_ALWAYS_TRUE(rtStats->zoneStatsVector.growBy(1));
ZoneStats &zStats = rtStats->zoneStatsVector.back();
zStats.initStrings(rt);
if (!zStats.initStrings(rt))
MOZ_CRASH("oom");
rtStats->initExtraZoneStats(zone, &zStats);
rtStats->currZoneStats = &zStats;
@ -280,21 +282,22 @@ StatsCellCallback(JSRuntime *rt, void *data, void *thing, JSGCTraceKind traceKin
case JSTRACE_STRING: {
JSString *str = static_cast<JSString *>(thing);
size_t strCharsSize = str->sizeOfExcludingThis(rtStats->mallocSizeOf_);
JS::StringInfo info;
info.gcHeap = thingSize;
info.mallocHeap = str->sizeOfExcludingThis(rtStats->mallocSizeOf_);
info.numCopies = 1;
zStats->stringsGCHeap += thingSize;
zStats->stringsMallocHeap += strCharsSize;
zStats->stringInfo.add(info);
if (granularity == FineGrained) {
ZoneStats::StringsHashMap::AddPtr p = zStats->strings->lookupForAdd(str);
ZoneStats::StringsHashMap::AddPtr p = zStats->allStrings->lookupForAdd(str);
if (!p) {
JS::StringInfo info(thingSize, strCharsSize);
zStats->strings->add(p, str, info);
// Ignore failure -- we just won't record the string as notable.
(void)zStats->allStrings->add(p, str, info);
} else {
p->value().add(thingSize, strCharsSize);
p->value().add(info);
}
}
break;
}
@ -379,47 +382,48 @@ StatsCellCallback(JSRuntime *rt, void *data, void *thing, JSGCTraceKind traceKin
zStats->unusedGCThings -= thingSize;
}
static void
static bool
FindNotableStrings(ZoneStats &zStats)
{
using namespace JS;
// You should only run FindNotableStrings once per ZoneStats object
// (although it's not going to break anything if you run it more than once,
// unless you add to |strings| in the meantime).
// We should only run FindNotableStrings once per ZoneStats object.
MOZ_ASSERT(zStats.notableStrings.empty());
for (ZoneStats::StringsHashMap::Range r = zStats.strings->all(); !r.empty(); r.popFront()) {
for (ZoneStats::StringsHashMap::Range r = zStats.allStrings->all(); !r.empty(); r.popFront()) {
JSString *str = r.front().key();
StringInfo &info = r.front().value();
// If this string is too small, or if we can't grow the notableStrings
// vector, skip this string.
if (info.gcHeap + info.mallocHeap < NotableStringInfo::notableSize() ||
!zStats.notableStrings.growBy(1))
if (!info.isNotable())
continue;
if (!zStats.notableStrings.growBy(1))
return false;
zStats.notableStrings.back() = NotableStringInfo(str, info);
// We're moving this string from a non-notable to a notable bucket, so
// subtract it out of the non-notable tallies.
MOZ_ASSERT(zStats.stringsGCHeap >= info.gcHeap);
MOZ_ASSERT(zStats.stringsMallocHeap >= info.mallocHeap);
zStats.stringsGCHeap -= info.gcHeap;
zStats.stringsMallocHeap -= info.mallocHeap;
zStats.stringInfo.subtract(info);
}
// Delete |allStrings| now, rather than waiting for zStats's destruction,
// to reduce peak memory consumption during reporting.
js_delete(zStats.allStrings);
zStats.allStrings = nullptr;
return true;
}
bool
ZoneStats::initStrings(JSRuntime *rt)
{
strings = rt->new_<StringsHashMap>();
if (!strings)
isTotals = false;
allStrings = rt->new_<StringsHashMap>();
if (!allStrings)
return false;
if (!strings->init()) {
js_delete(strings);
strings = nullptr;
if (!allStrings->init()) {
js_delete(allStrings);
allStrings = nullptr;
return false;
}
return true;
@ -456,41 +460,17 @@ JS::CollectRuntimeStats(JSRuntime *rt, RuntimeStats *rtStats, ObjectPrivateVisit
ZoneStatsVector &zs = rtStats->zoneStatsVector;
ZoneStats &zTotals = rtStats->zTotals;
// For each zone:
// - sum everything except its strings data into zTotals, and
// - find its notable strings.
// Also, record which zone had the biggest |strings| hashtable -- to save
// time and memory, we will re-use that hashtable to find the notable
// strings for zTotals.
size_t iMax = 0;
for (size_t i = 0; i < zs.length(); i++) {
zTotals.addIgnoringStrings(zs[i]);
FindNotableStrings(zs[i]);
if (zs[i].strings->count() > zs[iMax].strings->count())
iMax = i;
}
// We don't look for notable strings for zTotals. So we first sum all the
// zones' measurements to get the totals. Then we find the notable strings
// within each zone.
for (size_t i = 0; i < zs.length(); i++)
zTotals.addSizes(zs[i]);
// Transfer the biggest strings table to zTotals. We can do this because:
// (a) we've found the notable strings for zs[IMax], and so don't need it
// any more for zs, and
// (b) zs[iMax].strings contains a subset of the values that will end up in
// zTotals.strings.
MOZ_ASSERT(!zTotals.strings);
zTotals.strings = zs[iMax].strings;
zs[iMax].strings = nullptr;
for (size_t i = 0; i < zs.length(); i++)
if (!FindNotableStrings(zs[i]))
return false;
// Add the remaining strings hashtables to zTotals, and then get the
// notable strings for zTotals.
for (size_t i = 0; i < zs.length(); i++) {
if (i != iMax) {
zTotals.addStrings(zs[i]);
js_delete(zs[i].strings);
zs[i].strings = nullptr;
}
}
FindNotableStrings(zTotals);
js_delete(zTotals.strings);
zTotals.strings = nullptr;
MOZ_ASSERT(!zTotals.allStrings);
for (size_t i = 0; i < rtStats->compartmentStatsVector.length(); i++) {
CompartmentStats &cStats = rtStats->compartmentStatsVector[i];
@ -598,7 +578,7 @@ AddSizeOfTab(JSRuntime *rt, HandleObject obj, MallocSizeOf mallocSizeOf, ObjectP
StatsCellCallback<CoarseGrained>);
JS_ASSERT(rtStats.zoneStatsVector.length() == 1);
rtStats.zTotals.add(rtStats.zoneStatsVector[0]);
rtStats.zTotals.addSizes(rtStats.zoneStatsVector[0]);
for (size_t i = 0; i < rtStats.compartmentStatsVector.length(); i++) {
CompartmentStats &cStats = rtStats.compartmentStatsVector[i];

View File

@ -1759,6 +1759,8 @@ ReportZoneStats(const JS::ZoneStats &zStats,
const nsAutoCString& pathPrefix = extras.pathPrefix;
size_t gcTotal = 0, sundriesGCHeap = 0, sundriesMallocHeap = 0;
MOZ_ASSERT(!gcTotalOut == zStats.isTotals);
ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap-arena-admin"),
zStats.gcHeapArenaAdmin,
"Bookkeeping information and alignment padding within GC arenas.");
@ -1802,6 +1804,8 @@ ReportZoneStats(const JS::ZoneStats &zStats,
for (size_t i = 0; i < zStats.notableStrings.length(); i++) {
const JS::NotableStringInfo& info = zStats.notableStrings[i];
MOZ_ASSERT(!zStats.isTotals);
nsDependentCString notableString(info.buffer);
// Viewing about:memory generates many notable strings which contain
@ -1810,8 +1814,7 @@ ReportZoneStats(const JS::ZoneStats &zStats,
// there's a GC in the meantime), and so on ad infinitum.
//
// To avoid cluttering up about:memory like this, we stick notable
// strings which contain "strings/notable/string(length=" into their own
// bucket.
// strings which contain "string(length=" into their own bucket.
# define STRING_LENGTH "string(length="
if (FindInReadable(NS_LITERAL_CSTRING(STRING_LENGTH), notableString)) {
stringsNotableAboutMemoryGCHeap += info.gcHeap;
@ -1828,48 +1831,57 @@ ReportZoneStats(const JS::ZoneStats &zStats,
bool truncated = notableString.Length() < info.length;
nsCString path = pathPrefix +
nsPrintfCString("strings/notable/" STRING_LENGTH "%d, copies=%d, \"%s\"%s)/",
nsPrintfCString("strings/" STRING_LENGTH "%d, copies=%d, \"%s\"%s)/",
info.length, info.numCopies, escapedString.get(),
truncated ? " (truncated)" : "");
REPORT_BYTES(path + NS_LITERAL_CSTRING("gc-heap"),
KIND_NONHEAP, info.gcHeap,
"A notable string, i.e. one whose copies together use a lot of "
"GC heap and malloc heap memory. " MAYBE_INLINE);
gcTotal += info.gcHeap;
if (info.gcHeap > 0) {
REPORT_GC_BYTES(path + NS_LITERAL_CSTRING("gc-heap"),
info.gcHeap,
"Strings. " MAYBE_INLINE);
}
if (info.mallocHeap > 0) {
REPORT_BYTES(path + NS_LITERAL_CSTRING("malloc-heap"),
KIND_HEAP, info.mallocHeap,
"Non-inline string characters of a notable string. "
MAYBE_OVERALLOCATED);
"Non-inline string characters. " MAYBE_OVERALLOCATED);
}
}
ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("strings/non-notable/gc-heap"),
zStats.stringsGCHeap,
"Non-notable strings. " MAYBE_INLINE);
nsCString nonNotablePath = pathPrefix;
nonNotablePath += zStats.isTotals
? NS_LITERAL_CSTRING("strings/")
: NS_LITERAL_CSTRING("strings/string(<non-notable strings>)/");
ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("strings/non-notable/malloc-heap"),
zStats.stringsMallocHeap,
"Non-inline string characters of non-notable strings. "
MAYBE_OVERALLOCATED);
if (zStats.stringInfo.gcHeap > 0) {
REPORT_GC_BYTES(nonNotablePath + NS_LITERAL_CSTRING("gc-heap"),
zStats.stringInfo.gcHeap,
"Strings. " MAYBE_INLINE);
}
if (zStats.stringInfo.mallocHeap > 0) {
REPORT_BYTES(nonNotablePath + NS_LITERAL_CSTRING("malloc-heap"),
KIND_HEAP, zStats.stringInfo.mallocHeap,
"Non-inline string characters. " MAYBE_OVERALLOCATED);
}
if (stringsNotableAboutMemoryGCHeap > 0) {
ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("strings/notable/about-memory/gc-heap"),
MOZ_ASSERT(!zStats.isTotals);
REPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("strings/string(<about-memory>)/gc-heap"),
stringsNotableAboutMemoryGCHeap,
"Notable strings that contain the characters '" STRING_LENGTH "', "
"which are probably from about:memory itself." MAYBE_INLINE
"Strings that contain the characters '" STRING_LENGTH "', which "
"are probably from about:memory itself." MAYBE_INLINE
" We filter them out rather than display them, because displaying "
"them would create even more such strings every time about:memory "
"is refreshed.");
}
if (stringsNotableAboutMemoryMallocHeap > 0) {
ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("strings/notable/about-memory/malloc-heap"),
stringsNotableAboutMemoryMallocHeap,
"Non-inline string characters of notable strings that contain "
"the characters '" STRING_LENGTH "', which are probably from "
MOZ_ASSERT(!zStats.isTotals);
REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("strings/string(<about-memory>)/malloc-heap"),
KIND_HEAP, stringsNotableAboutMemoryMallocHeap,
"Non-inline string characters of strings that contain the "
"characters '" STRING_LENGTH "', which are probably from "
"about:memory itself. " MAYBE_OVERALLOCATED
" We filter them out rather than display them, because displaying "
"them would create even more such strings every time about:memory "