From 0c90379b3542c588c5dc49866595fb7a53a3d395 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 15 Apr 2014 19:43:18 -0700 Subject: [PATCH] Bug 1023719 - Report notable classes in the JS memory reporter. r=till. --HG-- extra : rebase_source : 3139b17f6a660f460adb820c390368dc8367069a --- js/public/MemoryMetrics.h | 141 ++++++++++++------ js/src/jscompartment.cpp | 10 +- js/src/jscompartment.h | 2 +- js/src/jsobj.cpp | 20 +-- js/src/jsobj.h | 4 +- js/src/vm/ArrayBufferObject.cpp | 11 +- js/src/vm/ArrayBufferObject.h | 2 +- js/src/vm/MemoryMetrics.cpp | 198 +++++++++++++++++++------ js/src/vm/Shape.h | 14 +- js/xpconnect/src/XPCJSRuntime.cpp | 237 ++++++++++++++++-------------- 10 files changed, 414 insertions(+), 225 deletions(-) diff --git a/js/public/MemoryMetrics.h b/js/public/MemoryMetrics.h index 31e456baba31..1e246fcaa2e8 100644 --- a/js/public/MemoryMetrics.h +++ b/js/public/MemoryMetrics.h @@ -115,31 +115,45 @@ enum { namespace JS { -// Data for tracking memory usage of things hanging off objects. -struct ObjectsExtraSizes +struct ClassInfo { #define FOR_EACH_SIZE(macro) \ - macro(Objects, NotLiveGCThing, mallocHeapSlots) \ - macro(Objects, NotLiveGCThing, mallocHeapElementsNonAsmJS) \ - macro(Objects, NotLiveGCThing, mallocHeapElementsAsmJS) \ - macro(Objects, NotLiveGCThing, nonHeapElementsAsmJS) \ - macro(Objects, NotLiveGCThing, nonHeapElementsMapped) \ - macro(Objects, NotLiveGCThing, nonHeapCodeAsmJS) \ - macro(Objects, NotLiveGCThing, mallocHeapAsmJSModuleData) \ - macro(Objects, NotLiveGCThing, mallocHeapArgumentsData) \ - macro(Objects, NotLiveGCThing, mallocHeapRegExpStatics) \ - macro(Objects, NotLiveGCThing, mallocHeapPropertyIteratorData) \ - macro(Objects, NotLiveGCThing, mallocHeapCtypesData) + macro(Objects, IsLiveGCThing, objectsGCHeap) \ + macro(Objects, NotLiveGCThing, objectsMallocHeapSlots) \ + macro(Objects, NotLiveGCThing, objectsMallocHeapElementsNonAsmJS) \ + macro(Objects, NotLiveGCThing, objectsMallocHeapElementsAsmJS) \ + macro(Objects, NotLiveGCThing, objectsNonHeapElementsAsmJS) \ + macro(Objects, NotLiveGCThing, objectsNonHeapElementsMapped) \ + macro(Objects, NotLiveGCThing, objectsNonHeapCodeAsmJS) \ + macro(Objects, NotLiveGCThing, objectsMallocHeapMisc) \ + \ + macro(Other, IsLiveGCThing, shapesGCHeapTree) \ + macro(Other, IsLiveGCThing, shapesGCHeapDict) \ + macro(Other, IsLiveGCThing, shapesGCHeapBase) \ + macro(Other, NotLiveGCThing, shapesMallocHeapTreeTables) \ + macro(Other, NotLiveGCThing, shapesMallocHeapDictTables) \ + macro(Other, NotLiveGCThing, shapesMallocHeapTreeKids) \ - ObjectsExtraSizes() + ClassInfo() : FOR_EACH_SIZE(ZERO_SIZE) dummy() {} - void add(const ObjectsExtraSizes &other) { + void add(const ClassInfo &other) { FOR_EACH_SIZE(ADD_OTHER_SIZE) } + void subtract(const ClassInfo &other) { + FOR_EACH_SIZE(SUB_OTHER_SIZE) + } + + 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) @@ -156,6 +170,29 @@ struct ObjectsExtraSizes #undef FOR_EACH_SIZE }; +// Holds data about a notable class (one whose combined object and shape +// instances use more than a certain amount of memory) so we can report it +// individually. +// +// The only difference between this class and ClassInfo is that this class +// holds a copy of the filename. +struct NotableClassInfo : public ClassInfo +{ + NotableClassInfo(); + NotableClassInfo(const char *className, const ClassInfo &info); + NotableClassInfo(NotableClassInfo &&info); + NotableClassInfo &operator=(NotableClassInfo &&info); + + ~NotableClassInfo() { + js_free(className_); + } + + char *className_; + + private: + NotableClassInfo(const NotableClassInfo& info) MOZ_DELETE; +}; + // Data for tracking JIT-code memory usage. struct CodeSizes { @@ -489,20 +526,7 @@ struct ZoneStats struct CompartmentStats { #define FOR_EACH_SIZE(macro) \ - macro(Objects, IsLiveGCThing, objectsGCHeapOrdinary) \ - macro(Objects, IsLiveGCThing, objectsGCHeapFunction) \ - macro(Objects, IsLiveGCThing, objectsGCHeapDenseArray) \ - macro(Objects, IsLiveGCThing, objectsGCHeapSlowArray) \ - macro(Objects, IsLiveGCThing, objectsGCHeapCrossCompartmentWrapper) \ macro(Private, NotLiveGCThing, objectsPrivate) \ - macro(Other, IsLiveGCThing, shapesGCHeapTreeGlobalParented) \ - macro(Other, IsLiveGCThing, shapesGCHeapTreeNonGlobalParented) \ - macro(Other, IsLiveGCThing, shapesGCHeapDict) \ - macro(Other, IsLiveGCThing, shapesGCHeapBase) \ - macro(Other, NotLiveGCThing, shapesMallocHeapTreeTables) \ - macro(Other, NotLiveGCThing, shapesMallocHeapDictTables) \ - macro(Other, NotLiveGCThing, shapesMallocHeapTreeShapeKids) \ - macro(Other, NotLiveGCThing, shapesMallocHeapCompartmentTables) \ macro(Other, IsLiveGCThing, scriptsGCHeap) \ macro(Other, NotLiveGCThing, scriptsMallocHeapData) \ macro(Other, NotLiveGCThing, baselineData) \ @@ -513,6 +537,7 @@ struct CompartmentStats macro(Other, NotLiveGCThing, typeInferenceArrayTypeTables) \ macro(Other, NotLiveGCThing, typeInferenceObjectTypeTables) \ macro(Other, NotLiveGCThing, compartmentObject) \ + macro(Other, NotLiveGCThing, compartmentTables) \ macro(Other, NotLiveGCThing, crossCompartmentWrappersTable) \ macro(Other, NotLiveGCThing, regexpCompartment) \ macro(Other, NotLiveGCThing, debuggeesSet) \ @@ -520,39 +545,69 @@ struct CompartmentStats CompartmentStats() : FOR_EACH_SIZE(ZERO_SIZE) - objectsExtra(), - extra() + classInfo(), + extra(), + allClasses(nullptr), + notableClasses(), + isTotals(true) {} - CompartmentStats(const CompartmentStats &other) + CompartmentStats(CompartmentStats &&other) : FOR_EACH_SIZE(COPY_OTHER_SIZE) - objectsExtra(other.objectsExtra), - extra(other.extra) - {} + classInfo(mozilla::Move(other.classInfo)), + extra(other.extra), + allClasses(other.allClasses), + notableClasses(mozilla::Move(other.notableClasses)), + isTotals(other.isTotals) + { + other.allClasses = nullptr; + MOZ_ASSERT(!other.isTotals); + } - void add(const CompartmentStats &other) { + ~CompartmentStats() { + // |allClasses| 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(allClasses); + } + + bool initClasses(JSRuntime *rt); + + void addSizes(const CompartmentStats &other) { + MOZ_ASSERT(isTotals); FOR_EACH_SIZE(ADD_OTHER_SIZE) - objectsExtra.add(other.objectsExtra); - // Do nothing with |extra|. + classInfo.add(other.classInfo); } size_t sizeOfLiveGCThings() const { + MOZ_ASSERT(isTotals); size_t n = 0; FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING) - n += objectsExtra.sizeOfLiveGCThings(); - // Do nothing with |extra|. + n += classInfo.sizeOfLiveGCThings(); return n; } void addToTabSizes(TabSizes *sizes) const { + MOZ_ASSERT(isTotals); FOR_EACH_SIZE(ADD_TO_TAB_SIZES); - objectsExtra.addToTabSizes(sizes); - // Do nothing with |extra|. + classInfo.addToTabSizes(sizes); } + // The class measurements in |classInfo| are initially for all classes. At + // the end, if the measurement granularity is FineGrained, we subtract the + // measurements of the notable classes and move them into |notableClasses|. FOR_EACH_SIZE(DECL_SIZE) - ObjectsExtraSizes objectsExtra; - void *extra; // This field can be used by embedders. + ClassInfo classInfo; + void *extra; // This field can be used by embedders. + + typedef js::HashMap ClassesHashMap; + + // These are similar to |allStrings| and |notableStrings| in ZoneStats. + ClassesHashMap *allClasses; + js::Vector notableClasses; + bool isTotals; #undef FOR_EACH_SIZE }; diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index 46ee7e5ad958..a1c0d850b5ad 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -982,7 +982,7 @@ JSCompartment::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, size_t *tiArrayTypeTables, size_t *tiObjectTypeTables, size_t *compartmentObject, - size_t *shapesCompartmentTables, + size_t *compartmentTables, size_t *crossCompartmentWrappersArg, size_t *regexpCompartment, size_t *debuggeesSet, @@ -991,10 +991,10 @@ JSCompartment::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, *compartmentObject += mallocSizeOf(this); types.addSizeOfExcludingThis(mallocSizeOf, tiAllocationSiteTables, tiArrayTypeTables, tiObjectTypeTables); - *shapesCompartmentTables += baseShapes.sizeOfExcludingThis(mallocSizeOf) - + initialShapes.sizeOfExcludingThis(mallocSizeOf) - + newTypeObjects.sizeOfExcludingThis(mallocSizeOf) - + lazyTypeObjects.sizeOfExcludingThis(mallocSizeOf); + *compartmentTables += baseShapes.sizeOfExcludingThis(mallocSizeOf) + + initialShapes.sizeOfExcludingThis(mallocSizeOf) + + newTypeObjects.sizeOfExcludingThis(mallocSizeOf) + + lazyTypeObjects.sizeOfExcludingThis(mallocSizeOf); *crossCompartmentWrappersArg += crossCompartmentWrappers.sizeOfExcludingThis(mallocSizeOf); *regexpCompartment += regExps.sizeOfExcludingThis(mallocSizeOf); *debuggeesSet += debuggees.sizeOfExcludingThis(mallocSizeOf); diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index a169e0b6d85a..ffaabceb3abc 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -231,7 +231,7 @@ struct JSCompartment size_t *tiArrayTypeTables, size_t *tiObjectTypeTables, size_t *compartmentObject, - size_t *shapesCompartmentTables, + size_t *compartmentTables, size_t *crossCompartmentWrappers, size_t *regexpCompartment, size_t *debuggeesSet, diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index c0acc68bc099..2e79c64e5ba6 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -6345,15 +6345,15 @@ js_DumpBacktrace(JSContext *cx) } void -JSObject::addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::ObjectsExtraSizes *sizes) +JSObject::addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::ClassInfo *info) { if (hasDynamicSlots()) - sizes->mallocHeapSlots += mallocSizeOf(slots); + info->objectsMallocHeapSlots += mallocSizeOf(slots); if (hasDynamicElements()) { js::ObjectElements *elements = getElementsHeader(); if (!elements->isCopyOnWrite() || elements->ownerObject() == this) - sizes->mallocHeapElementsNonAsmJS += mallocSizeOf(elements); + info->objectsMallocHeapElementsNonAsmJS += mallocSizeOf(elements); } // Other things may be measured in the future if DMD indicates it is worthwhile. @@ -6375,20 +6375,20 @@ JSObject::addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::Objects // - ( 1.0%, 96.4%): Proxy } else if (is()) { - sizes->mallocHeapArgumentsData += as().sizeOfMisc(mallocSizeOf); + info->objectsMallocHeapMisc += as().sizeOfMisc(mallocSizeOf); } else if (is()) { - sizes->mallocHeapRegExpStatics += as().sizeOfData(mallocSizeOf); + info->objectsMallocHeapMisc += as().sizeOfData(mallocSizeOf); } else if (is()) { - sizes->mallocHeapPropertyIteratorData += as().sizeOfMisc(mallocSizeOf); + info->objectsMallocHeapMisc += as().sizeOfMisc(mallocSizeOf); } else if (is() || is()) { - ArrayBufferObject::addSizeOfExcludingThis(this, mallocSizeOf, sizes); + ArrayBufferObject::addSizeOfExcludingThis(this, mallocSizeOf, info); } else if (is()) { - as().addSizeOfMisc(mallocSizeOf, &sizes->nonHeapCodeAsmJS, - &sizes->mallocHeapAsmJSModuleData); + as().addSizeOfMisc(mallocSizeOf, &info->objectsNonHeapCodeAsmJS, + &info->objectsMallocHeapMisc); #ifdef JS_HAS_CTYPES } else { // This must be the last case. - sizes->mallocHeapCtypesData += + info->objectsMallocHeapMisc += js::SizeOfDataIfCDataObject(mallocSizeOf, const_cast(this)); #endif } diff --git a/js/src/jsobj.h b/js/src/jsobj.h index 358d76ffb84a..3ccd293c5c74 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -27,7 +27,7 @@ #include "vm/Xdr.h" namespace JS { -struct ObjectsExtraSizes; +struct ClassInfo; } namespace js { @@ -353,7 +353,7 @@ class JSObject : public js::ObjectImpl return lastProperty()->hasTable(); } - void addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::ObjectsExtraSizes *sizes); + void addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::ClassInfo *info); bool hasIdempotentProtoChain() const; diff --git a/js/src/vm/ArrayBufferObject.cpp b/js/src/vm/ArrayBufferObject.cpp index 238ea1af4bcd..f2bff295b04b 100644 --- a/js/src/vm/ArrayBufferObject.cpp +++ b/js/src/vm/ArrayBufferObject.cpp @@ -788,7 +788,8 @@ ArrayBufferObject::stealContents(JSContext *cx, Handle buffe } /* static */ void -ArrayBufferObject::addSizeOfExcludingThis(JSObject *obj, mozilla::MallocSizeOf mallocSizeOf, JS::ObjectsExtraSizes *sizes) +ArrayBufferObject::addSizeOfExcludingThis(JSObject *obj, mozilla::MallocSizeOf mallocSizeOf, + JS::ClassInfo *info) { ArrayBufferObject &buffer = AsArrayBuffer(obj); @@ -799,13 +800,13 @@ ArrayBufferObject::addSizeOfExcludingThis(JSObject *obj, mozilla::MallocSizeOf m // On x64, ArrayBufferObject::prepareForAsmJS switches the // ArrayBufferObject to use mmap'd storage. if (buffer.bufferKind() & MAPPED_BUFFER) - sizes->nonHeapElementsAsmJS += buffer.byteLength(); + info->objectsNonHeapElementsAsmJS += buffer.byteLength(); else - sizes->mallocHeapElementsAsmJS += mallocSizeOf(buffer.dataPointer()); + info->objectsMallocHeapElementsAsmJS += mallocSizeOf(buffer.dataPointer()); } else if (MOZ_UNLIKELY(buffer.bufferKind() & MAPPED_BUFFER)) { - sizes->nonHeapElementsMapped += buffer.byteLength(); + info->objectsNonHeapElementsMapped += buffer.byteLength(); } else if (buffer.dataPointer()) { - sizes->mallocHeapElementsNonAsmJS += mallocSizeOf(buffer.dataPointer()); + info->objectsMallocHeapElementsNonAsmJS += mallocSizeOf(buffer.dataPointer()); } } diff --git a/js/src/vm/ArrayBufferObject.h b/js/src/vm/ArrayBufferObject.h index cea82ae6ca3d..b43999f7f521 100644 --- a/js/src/vm/ArrayBufferObject.h +++ b/js/src/vm/ArrayBufferObject.h @@ -187,7 +187,7 @@ class ArrayBufferObject : public JSObject } static void addSizeOfExcludingThis(JSObject *obj, mozilla::MallocSizeOf mallocSizeOf, - JS::ObjectsExtraSizes *sizes); + JS::ClassInfo *info); void addView(ArrayBufferViewObject *view); diff --git a/js/src/vm/MemoryMetrics.cpp b/js/src/vm/MemoryMetrics.cpp index 10bb3af5a410..93076c64880e 100644 --- a/js/src/vm/MemoryMetrics.cpp +++ b/js/src/vm/MemoryMetrics.cpp @@ -194,6 +194,37 @@ NotableStringInfo &NotableStringInfo::operator=(NotableStringInfo &&info) return *this; } +NotableClassInfo::NotableClassInfo() + : ClassInfo(), + className_(nullptr) +{ +} + +NotableClassInfo::NotableClassInfo(const char *className, const ClassInfo &info) + : ClassInfo(info) +{ + size_t bytes = strlen(className) + 1; + className_ = js_pod_malloc(bytes); + if (!className_) + MOZ_CRASH("oom"); + PodCopy(className_, className, bytes); +} + +NotableClassInfo::NotableClassInfo(NotableClassInfo &&info) + : ClassInfo(Move(info)) +{ + className_ = info.className_; + info.className_ = nullptr; +} + +NotableClassInfo &NotableClassInfo::operator=(NotableClassInfo &&info) +{ + MOZ_ASSERT(this != &info, "self-move assignment is prohibited"); + this->~NotableClassInfo(); + new (this) NotableClassInfo(Move(info)); + return *this; +} + NotableScriptSourceInfo::NotableScriptSourceInfo() : ScriptSourceInfo(), filename_(nullptr) @@ -292,6 +323,8 @@ StatsCompartmentCallback(JSRuntime *rt, void *data, JSCompartment *compartment) // CollectRuntimeStats reserves enough space. MOZ_ALWAYS_TRUE(rtStats->compartmentStatsVector.growBy(1)); CompartmentStats &cStats = rtStats->compartmentStatsVector.back(); + if (!cStats.initClasses(rt)) + MOZ_CRASH("oom"); rtStats->initExtraCompartmentStats(compartment, &cStats); compartment->compartmentStats = &cStats; @@ -302,7 +335,7 @@ StatsCompartmentCallback(JSRuntime *rt, void *data, JSCompartment *compartment) &cStats.typeInferenceArrayTypeTables, &cStats.typeInferenceObjectTypeTables, &cStats.compartmentObject, - &cStats.shapesMallocHeapCompartmentTables, + &cStats.compartmentTables, &cStats.crossCompartmentWrappersTable, &cStats.regexpCompartment, &cStats.debuggeesSet, @@ -342,6 +375,25 @@ enum Granularity { CoarseGrained }; +static void +AddClassInfo(Granularity granularity, CompartmentStats *cStats, const char *className, + JS::ClassInfo &info) +{ + if (granularity == FineGrained) { + if (!className) + className = ""; + CompartmentStats::ClassesHashMap::AddPtr p = + cStats->allClasses->lookupForAdd(className); + if (!p) { + // Ignore failure -- we just won't record the + // object/shape/base-shape as notable. + (void)cStats->allClasses->add(p, className, info); + } else { + p->value().add(info); + } + } +} + // The various kinds of hashing are expensive, and the results are unused when // doing coarse-grained measurements. Skipping them more than doubles the // profile speed for complex pages such as gmail.com. @@ -357,16 +409,15 @@ StatsCellCallback(JSRuntime *rt, void *data, void *thing, JSGCTraceKind traceKin case JSTRACE_OBJECT: { JSObject *obj = static_cast(thing); CompartmentStats *cStats = GetCompartmentStats(obj->compartment()); - if (obj->is()) - cStats->objectsGCHeapFunction += thingSize; - else if (obj->is()) - cStats->objectsGCHeapDenseArray += thingSize; - else if (obj->is()) - cStats->objectsGCHeapCrossCompartmentWrapper += thingSize; - else - cStats->objectsGCHeapOrdinary += thingSize; + JS::ClassInfo info; // This zeroes all the sizes. + info.objectsGCHeap += thingSize; + obj->addSizeOfExcludingThis(rtStats->mallocSizeOf_, &info); - obj->addSizeOfExcludingThis(rtStats->mallocSizeOf_, &cStats->objectsExtra); + cStats->classInfo.add(info); + + const Class *clasp = obj->getClass(); + const char *className = clasp->name; + AddClassInfo(granularity, cStats, className, info); if (ObjectPrivateVisitor *opv = closure->opv) { nsISupports *iface; @@ -413,30 +464,34 @@ StatsCellCallback(JSRuntime *rt, void *data, void *thing, JSGCTraceKind traceKin case JSTRACE_SHAPE: { Shape *shape = static_cast(thing); CompartmentStats *cStats = GetCompartmentStats(shape->compartment()); - if (shape->inDictionary()) { - cStats->shapesGCHeapDict += thingSize; + JS::ClassInfo info; // This zeroes all the sizes. + if (shape->inDictionary()) + info.shapesGCHeapDict += thingSize; + else + info.shapesGCHeapTree += thingSize; + shape->addSizeOfExcludingThis(rtStats->mallocSizeOf_, &info); + cStats->classInfo.add(info); - // nullptr because kidsSize shouldn't be incremented in this case. - shape->addSizeOfExcludingThis(rtStats->mallocSizeOf_, - &cStats->shapesMallocHeapDictTables, nullptr); - } else { - JSObject *parent = shape->base()->getObjectParent(); - if (parent && parent->is()) - cStats->shapesGCHeapTreeGlobalParented += thingSize; - else - cStats->shapesGCHeapTreeNonGlobalParented += thingSize; - - shape->addSizeOfExcludingThis(rtStats->mallocSizeOf_, - &cStats->shapesMallocHeapTreeTables, - &cStats->shapesMallocHeapTreeShapeKids); - } + const BaseShape *base = shape->base(); + const Class *clasp = base->clasp(); + const char *className = clasp->name; + AddClassInfo(granularity, cStats, className, info); break; } case JSTRACE_BASE_SHAPE: { BaseShape *base = static_cast(thing); CompartmentStats *cStats = GetCompartmentStats(base->compartment()); - cStats->shapesGCHeapBase += thingSize; + + JS::ClassInfo info; // This zeroes all the sizes. + info.shapesGCHeapBase += thingSize; + // No malloc-heap measurements. + + cStats->classInfo.add(info); + + const Class *clasp = base->clasp(); + const char *className = clasp->name; + AddClassInfo(granularity, cStats, className, info); break; } @@ -508,6 +563,32 @@ StatsCellCallback(JSRuntime *rt, void *data, void *thing, JSGCTraceKind traceKin zStats->unusedGCThings -= thingSize; } +bool +ZoneStats::initStrings(JSRuntime *rt) +{ + isTotals = false; + allStrings = rt->new_(); + if (!allStrings || !allStrings->init()) { + js_delete(allStrings); + allStrings = nullptr; + return false; + } + return true; +} + +bool +CompartmentStats::initClasses(JSRuntime *rt) +{ + isTotals = false; + allClasses = rt->new_(); + if (!allClasses || !allClasses->init()) { + js_delete(allClasses); + allClasses = nullptr; + return false; + } + return true; +} + static bool FindNotableStrings(ZoneStats &zStats) { @@ -540,18 +621,39 @@ FindNotableStrings(ZoneStats &zStats) return true; } -bool -ZoneStats::initStrings(JSRuntime *rt) +static bool +FindNotableClasses(CompartmentStats &cStats) { - isTotals = false; - allStrings = rt->new_(); - if (!allStrings) - return false; - if (!allStrings->init()) { - js_delete(allStrings); - allStrings = nullptr; - return false; + using namespace JS; + + // We should only run FindNotableClasses once per ZoneStats object. + MOZ_ASSERT(cStats.notableClasses.empty()); + + for (CompartmentStats::ClassesHashMap::Range r = cStats.allClasses->all(); + !r.empty(); + r.popFront()) + { + const char *className = r.front().key(); + ClassInfo &info = r.front().value(); + + // If this class isn't notable, or if we can't grow the notableStrings + // vector, skip this string. + if (!info.isNotable()) + continue; + + if (!cStats.notableClasses.growBy(1)) + return false; + + cStats.notableClasses.back() = NotableClassInfo(className, info); + + // We're moving this class from a non-notable to a notable bucket, so + // subtract it out of the non-notable tallies. + cStats.classInfo.subtract(info); } + // Delete |allClasses| now, rather than waiting for zStats's destruction, + // to reduce peak memory consumption during reporting. + js_delete(cStats.allClasses); + cStats.allClasses = nullptr; return true; } @@ -639,11 +741,21 @@ JS::CollectRuntimeStats(JSRuntime *rt, RuntimeStats *rtStats, ObjectPrivateVisit MOZ_ASSERT(!zTotals.allStrings); - for (size_t i = 0; i < rtStats->compartmentStatsVector.length(); i++) { - CompartmentStats &cStats = rtStats->compartmentStatsVector[i]; - rtStats->cTotals.add(cStats); + CompartmentStatsVector &cs = rtStats->compartmentStatsVector; + CompartmentStats &cTotals = rtStats->cTotals; + + // As with the zones, we sum all compartments first, and then get the + // notable classes within each zone. + for (size_t i = 0; i < cs.length(); i++) + cTotals.addSizes(cs[i]); + + for (size_t i = 0; i < cs.length(); i++) { + if (!FindNotableClasses(cs[i])) + return false; } + MOZ_ASSERT(!cTotals.allClasses); + rtStats->gcHeapGCThings = rtStats->zTotals.sizeOfLiveGCThings() + rtStats->cTotals.sizeOfLiveGCThings(); @@ -748,10 +860,8 @@ AddSizeOfTab(JSRuntime *rt, HandleObject obj, MallocSizeOf mallocSizeOf, ObjectP JS_ASSERT(rtStats.zoneStatsVector.length() == 1); rtStats.zTotals.addSizes(rtStats.zoneStatsVector[0]); - for (size_t i = 0; i < rtStats.compartmentStatsVector.length(); i++) { - CompartmentStats &cStats = rtStats.compartmentStatsVector[i]; - rtStats.cTotals.add(cStats); - } + for (size_t i = 0; i < rtStats.compartmentStatsVector.length(); i++) + rtStats.cTotals.addSizes(rtStats.compartmentStatsVector[i]); for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) comp->compartmentStats = nullptr; diff --git a/js/src/vm/Shape.h b/js/src/vm/Shape.h index 90f35873ef89..e87723fa47a7 100644 --- a/js/src/vm/Shape.h +++ b/js/src/vm/Shape.h @@ -26,6 +26,7 @@ #include "gc/Marking.h" #include "gc/Rooting.h" #include "js/HashTable.h" +#include "js/MemoryMetrics.h" #include "js/RootingAPI.h" #include "vm/PropDesc.h" @@ -738,12 +739,17 @@ class Shape : public gc::BarrieredCell ShapeTable &table() const { return base()->table(); } void addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, - size_t *propTableSize, size_t *kidsSize) const { - if (hasTable()) - *propTableSize += table().sizeOfIncludingThis(mallocSizeOf); + JS::ClassInfo *info) const + { + if (hasTable()) { + if (inDictionary()) + info->shapesMallocHeapDictTables += table().sizeOfIncludingThis(mallocSizeOf); + else + info->shapesMallocHeapTreeTables += table().sizeOfIncludingThis(mallocSizeOf); + } if (!inDictionary() && kids.isHash()) - *kidsSize += kids.toHash()->sizeOfIncludingThis(mallocSizeOf); + info->shapesMallocHeapTreeKids += kids.toHash()->sizeOfIncludingThis(mallocSizeOf); } bool isNative() const { diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp index 2f7173206033..b56db50dbba8 100644 --- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -2003,6 +2003,109 @@ ReportZoneStats(const JS::ZoneStats &zStats, # undef STRING_LENGTH } +static nsresult +ReportClassStats(const ClassInfo &classInfo, const nsACString &path, + nsIHandleReportCallback *cb, nsISupports *closure, + size_t &gcTotal) +{ + // We deliberately don't use ZCREPORT_BYTES, so that these per-class values + // don't go into sundries. + + if (classInfo.objectsGCHeap > 0) { + REPORT_GC_BYTES(path + NS_LITERAL_CSTRING("objects/gc-heap"), + classInfo.objectsGCHeap, + "Objects, including fixed slots."); + } + + if (classInfo.objectsMallocHeapSlots > 0) { + REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/malloc-heap/slots"), + KIND_HEAP, classInfo.objectsMallocHeapSlots, + "Non-fixed object slots."); + } + + if (classInfo.objectsMallocHeapElementsNonAsmJS > 0) { + REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/malloc-heap/elements/non-asm.js"), + KIND_HEAP, classInfo.objectsMallocHeapElementsNonAsmJS, + "Non-asm.js indexed elements."); + } + + // asm.js arrays are heap-allocated on some platforms and + // non-heap-allocated on others. We never put them under sundries, + // because (a) in practice they're almost always larger than the sundries + // threshold, and (b) we'd need a third category of sundries ("non-heap"), + // which would be a pain. + size_t mallocHeapElementsAsmJS = classInfo.objectsMallocHeapElementsAsmJS; + size_t nonHeapElementsAsmJS = classInfo.objectsNonHeapElementsAsmJS; + MOZ_ASSERT(mallocHeapElementsAsmJS == 0 || nonHeapElementsAsmJS == 0); + if (mallocHeapElementsAsmJS > 0) { + REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/malloc-heap/elements/asm.js"), + KIND_HEAP, mallocHeapElementsAsmJS, + "asm.js array buffer elements on the malloc heap."); + } + if (nonHeapElementsAsmJS > 0) { + REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/non-heap/elements/asm.js"), + KIND_NONHEAP, nonHeapElementsAsmJS, + "asm.js array buffer elements outside both the malloc heap and " + "the GC heap."); + } + + if (classInfo.objectsNonHeapElementsMapped > 0) { + REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/non-heap/elements/mapped"), + KIND_NONHEAP, classInfo.objectsNonHeapElementsMapped, + "Memory-mapped array buffer elements."); + } + + if (classInfo.objectsNonHeapCodeAsmJS > 0) { + REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/non-heap/code/asm.js"), + KIND_NONHEAP, classInfo.objectsNonHeapCodeAsmJS, + "AOT-compiled asm.js code."); + } + + if (classInfo.objectsMallocHeapMisc > 0) { + REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/malloc-heap/misc"), + KIND_HEAP, classInfo.objectsMallocHeapMisc, + "Miscellaneous object data."); + } + + if (classInfo.shapesGCHeapTree > 0) { + REPORT_GC_BYTES(path + NS_LITERAL_CSTRING("shapes/gc-heap/tree"), + classInfo.shapesGCHeapTree, + "Shapes in a property tree."); + } + + if (classInfo.shapesGCHeapDict > 0) { + REPORT_GC_BYTES(path + NS_LITERAL_CSTRING("shapes/gc-heap/dict"), + classInfo.shapesGCHeapDict, + "Shapes in dictionary mode."); + } + + if (classInfo.shapesGCHeapBase > 0) { + REPORT_GC_BYTES(path + NS_LITERAL_CSTRING("shapes/gc-heap/base"), + classInfo.shapesGCHeapBase, + "Base shapes, which collate data common to many shapes."); + } + + if (classInfo.shapesMallocHeapTreeTables > 0) { + REPORT_BYTES(path + NS_LITERAL_CSTRING("shapes/malloc-heap/tree-tables"), + KIND_HEAP, classInfo.shapesMallocHeapTreeTables, + "Property tables of shapes in a property tree."); + } + + if (classInfo.shapesMallocHeapDictTables > 0) { + REPORT_BYTES(path + NS_LITERAL_CSTRING("shapes/malloc-heap/dict-tables"), + KIND_HEAP, classInfo.shapesMallocHeapDictTables, + "Property tables of shapes in dictionary mode."); + } + + if (classInfo.shapesMallocHeapTreeKids > 0) { + REPORT_BYTES(path + NS_LITERAL_CSTRING("shapes/malloc-heap/tree-kids"), + KIND_HEAP, classInfo.shapesMallocHeapTreeKids, + "Kid hashes of shapes in a property tree."); + } + + return NS_OK; +} + static nsresult ReportCompartmentStats(const JS::CompartmentStats &cStats, const xpc::CompartmentStatsExtras &extras, @@ -2015,6 +2118,9 @@ ReportCompartmentStats(const JS::CompartmentStats &cStats, size_t gcTotal = 0, sundriesGCHeap = 0, sundriesMallocHeap = 0; nsAutoCString cJSPathPrefix = extras.jsPathPrefix; nsAutoCString cDOMPathPrefix = extras.domPathPrefix; + nsresult rv; + + MOZ_ASSERT(!gcTotalOut == cStats.isTotals); // Only attempt to prefix if we got a location and the path wasn't already // prefixed. @@ -2035,26 +2141,25 @@ ReportCompartmentStats(const JS::CompartmentStats &cStats, } } - ZCREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/gc-heap/ordinary"), - cStats.objectsGCHeapOrdinary, - "Ordinary objects, i.e. not otherwise distinguished by memory " - "reporters."); + nsCString nonNotablePath = cJSPathPrefix; + nonNotablePath += cStats.isTotals + ? NS_LITERAL_CSTRING("classes/") + : NS_LITERAL_CSTRING("classes/class()/"); - ZCREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/gc-heap/function"), - cStats.objectsGCHeapFunction, - "Function objects."); + rv = ReportClassStats(cStats.classInfo, nonNotablePath, cb, closure, + gcTotal); + NS_ENSURE_SUCCESS(rv, rv); - ZCREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/gc-heap/dense-array"), - cStats.objectsGCHeapDenseArray, - "Dense array objects."); + for (size_t i = 0; i < cStats.notableClasses.length(); i++) { + MOZ_ASSERT(!cStats.isTotals); + const JS::NotableClassInfo& classInfo = cStats.notableClasses[i]; - ZCREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/gc-heap/slow-array"), - cStats.objectsGCHeapSlowArray, - "Slow array objects."); + nsCString classPath = cJSPathPrefix + + nsPrintfCString("classes/class(%s)/", classInfo.className_); - ZCREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/gc-heap/cross-compartment-wrapper"), - cStats.objectsGCHeapCrossCompartmentWrapper, - "Cross-compartment wrapper objects."); + rv = ReportClassStats(classInfo, classPath, cb, closure, gcTotal); + NS_ENSURE_SUCCESS(rv, rv); + } // Note that we use cDOMPathPrefix here. This is because we measure orphan // DOM nodes in the JS reporter, but we want to report them in a "dom" @@ -2064,41 +2169,6 @@ ReportCompartmentStats(const JS::CompartmentStats &cStats, "Orphan DOM nodes, i.e. those that are only reachable from JavaScript " "objects."); - ZCREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("shapes/gc-heap/tree/global-parented"), - cStats.shapesGCHeapTreeGlobalParented, - "Shapes that (a) are in a property tree, and (b) represent an object " - "whose parent is the global object."); - - ZCREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("shapes/gc-heap/tree/non-global-parented"), - cStats.shapesGCHeapTreeNonGlobalParented, - "Shapes that (a) are in a property tree, and (b) represent an object " - "whose parent is not the global object."); - - ZCREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("shapes/gc-heap/dict"), - cStats.shapesGCHeapDict, - "Shapes that are in dictionary mode."); - - ZCREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("shapes/gc-heap/base"), - cStats.shapesGCHeapBase, - "Base shapes, which collate data common to many shapes."); - - ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("shapes/malloc-heap/tree-tables"), - cStats.shapesMallocHeapTreeTables, - "Property tables belonging to shapes that are in a property tree."); - - ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("shapes/malloc-heap/dict-tables"), - cStats.shapesMallocHeapDictTables, - "Property tables that belong to shapes that are in dictionary mode."); - - ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("shapes/malloc-heap/tree-shape-kids"), - cStats.shapesMallocHeapTreeShapeKids, - "Kid hashes that belong to shapes that are in a property tree."); - - ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("shapes/malloc-heap/compartment-tables"), - cStats.shapesMallocHeapCompartmentTables, - "Compartment-wide tables storing shape information used during object " - "construction."); - ZCREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("scripts/gc-heap"), cStats.scriptsGCHeap, "JSScript instances. There is one per user-defined function in a " @@ -2140,6 +2210,10 @@ ReportCompartmentStats(const JS::CompartmentStats &cStats, cStats.compartmentObject, "The JSCompartment object itself."); + ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("compartment-tables"), + cStats.compartmentTables, + "Compartment-wide tables storing shape and type object information."); + ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("cross-compartment-wrapper-table"), cStats.crossCompartmentWrappersTable, "The cross-compartment wrapper table."); @@ -2152,64 +2226,6 @@ ReportCompartmentStats(const JS::CompartmentStats &cStats, cStats.debuggeesSet, "The debuggees set."); - ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/malloc-heap/slots"), - cStats.objectsExtra.mallocHeapSlots, - "Non-fixed object slot arrays, which represent object properties."); - - ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/malloc-heap/elements/non-asm.js"), - cStats.objectsExtra.mallocHeapElementsNonAsmJS, - "Non-asm.js indexed elements."); - - // asm.js arrays are heap-allocated on some platforms and - // non-heap-allocated on others. We never put them under sundries, - // because (a) in practice they're almost always larger than the sundries - // threshold, and (b) we'd need a third category of sundries ("non-heap"), - // which would be a pain. - size_t mallocHeapElementsAsmJS = cStats.objectsExtra.mallocHeapElementsAsmJS; - size_t nonHeapElementsAsmJS = cStats.objectsExtra.nonHeapElementsAsmJS; - MOZ_ASSERT(mallocHeapElementsAsmJS == 0 || nonHeapElementsAsmJS == 0); - if (mallocHeapElementsAsmJS > 0) { - REPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/malloc-heap/elements/asm.js"), - KIND_HEAP, mallocHeapElementsAsmJS, - "asm.js array buffer elements on the malloc heap."); - } - if (nonHeapElementsAsmJS > 0) { - REPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/non-heap/elements/asm.js"), - KIND_NONHEAP, nonHeapElementsAsmJS, - "asm.js array buffer elements outside both the malloc heap and " - "the GC heap."); - } - - if (cStats.objectsExtra.nonHeapElementsMapped > 0) { - REPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/non-heap/elements/mapped"), - KIND_NONHEAP, cStats.objectsExtra.nonHeapElementsMapped, - "Memory-mapped array buffer elements."); - } - - REPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/non-heap/code/asm.js"), - KIND_NONHEAP, cStats.objectsExtra.nonHeapCodeAsmJS, - "AOT-compiled asm.js code."); - - ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/malloc-heap/asm.js-module-data"), - cStats.objectsExtra.mallocHeapAsmJSModuleData, - "asm.js module data."); - - ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/malloc-heap/arguments-data"), - cStats.objectsExtra.mallocHeapArgumentsData, - "Data belonging to Arguments objects."); - - ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/malloc-heap/regexp-statics"), - cStats.objectsExtra.mallocHeapRegExpStatics, - "Data belonging to the RegExpStatics object."); - - ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/malloc-heap/property-iterator-data"), - cStats.objectsExtra.mallocHeapPropertyIteratorData, - "Data belonging to property iterator objects."); - - ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/malloc-heap/ctypes-data"), - cStats.objectsExtra.mallocHeapCtypesData, - "Data belonging to ctypes objects."); - if (sundriesGCHeap > 0) { // We deliberately don't use ZCREPORT_GC_BYTES here. REPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("sundries/gc-heap"), @@ -2281,9 +2297,10 @@ ReportJSRuntimeExplicitTreeStats(const JS::RuntimeStats &rtStats, } for (size_t i = 0; i < rtStats.compartmentStatsVector.length(); i++) { - JS::CompartmentStats cStats = rtStats.compartmentStatsVector[i]; + const JS::CompartmentStats &cStats = rtStats.compartmentStatsVector[i]; const xpc::CompartmentStatsExtras *extras = static_cast(cStats.extra); + rv = ReportCompartmentStats(cStats, *extras, addonManager, cb, closure, &gcTotal); NS_ENSURE_SUCCESS(rv, rv);