diff --git a/dom/base/nsWindowMemoryReporter.cpp b/dom/base/nsWindowMemoryReporter.cpp index 41f863fd941..b675149a613 100644 --- a/dom/base/nsWindowMemoryReporter.cpp +++ b/dom/base/nsWindowMemoryReporter.cpp @@ -8,6 +8,7 @@ #include "nsWindowMemoryReporter.h" #include "nsGlobalWindow.h" #include "nsIDocument.h" +#include "nsIDOMWindowCollection.h" #include "nsIEffectiveTLDService.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/Preferences.h" @@ -31,14 +32,70 @@ nsWindowMemoryReporter::nsWindowMemoryReporter() NS_IMPL_ISUPPORTS3(nsWindowMemoryReporter, nsIMemoryReporter, nsIObserver, nsSupportsWeakReference) -/* static */ -void +static nsresult +AddNonJSSizeOfWindowAndItsDescendents(nsGlobalWindow* aWindow, + nsTabSizes* aSizes) +{ + // Measure the window. + nsWindowSizes windowSizes(moz_malloc_size_of); + aWindow->AddSizeOfIncludingThis(&windowSizes); + windowSizes.addToTabSizes(aSizes); + + // Measure the inner window, if there is one. + nsWindowSizes innerWindowSizes(moz_malloc_size_of); + nsGlobalWindow* inner = aWindow->GetCurrentInnerWindowInternal(); + if (inner) { + inner->AddSizeOfIncludingThis(&innerWindowSizes); + innerWindowSizes.addToTabSizes(aSizes); + } + + nsCOMPtr frames; + nsresult rv = aWindow->GetFrames(getter_AddRefs(frames)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t length; + rv = frames->GetLength(&length); + NS_ENSURE_SUCCESS(rv, rv); + + // Measure this window's descendents. + for (uint32_t i = 0; i < length; i++) { + nsCOMPtr child; + rv = frames->Item(i, getter_AddRefs(child)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_STATE(child); + + nsGlobalWindow* childWin = + static_cast(static_cast(child.get())); + + rv = AddNonJSSizeOfWindowAndItsDescendents(childWin, aSizes); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + +static nsresult +NonJSSizeOfTab(nsPIDOMWindow* aWindow, size_t* aDomSize, size_t* aStyleSize, size_t* aOtherSize) +{ + nsGlobalWindow* window = static_cast(aWindow); + + nsTabSizes sizes; + nsresult rv = AddNonJSSizeOfWindowAndItsDescendents(window, &sizes); + NS_ENSURE_SUCCESS(rv, rv); + + *aDomSize = sizes.mDom; + *aStyleSize = sizes.mStyle; + *aOtherSize = sizes.mOther; + return NS_OK; +} + +/* static */ void nsWindowMemoryReporter::Init() { MOZ_ASSERT(!sWindowReporter); sWindowReporter = new nsWindowMemoryReporter(); ClearOnShutdown(&sWindowReporter); NS_RegisterMemoryReporter(sWindowReporter); + RegisterNonJSSizeOfTab(NonJSSizeOfTab); nsCOMPtr os = services::GetObserverService(); if (os) { diff --git a/dom/base/nsWindowMemoryReporter.h b/dom/base/nsWindowMemoryReporter.h index 1e30eeb1e7d..e2ec07cd5b6 100644 --- a/dom/base/nsWindowMemoryReporter.h +++ b/dom/base/nsWindowMemoryReporter.h @@ -12,10 +12,12 @@ #include "nsDataHashtable.h" #include "nsWeakReference.h" #include "nsAutoPtr.h" +#include "mozilla/Attributes.h" +#include "mozilla/Assertions.h" #include "mozilla/MemoryReporting.h" +#include "mozilla/PodOperations.h" #include "mozilla/TimeStamp.h" #include "nsArenaMemoryStats.h" -#include "mozilla/Attributes.h" // This should be used for any nsINode sub-class that has fields of its own // that it needs to measure; any sub-class that doesn't use it will inherit @@ -25,25 +27,44 @@ virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; class nsWindowSizes { +#define FOR_EACH_SIZE(macro) \ + macro(DOM, mDOMElementNodes) \ + macro(DOM, mDOMTextNodes) \ + macro(DOM, mDOMCDATANodes) \ + macro(DOM, mDOMCommentNodes) \ + macro(DOM, mDOMEventTargets) \ + macro(DOM, mDOMOther) \ + macro(Style, mStyleSheets) \ + macro(Other, mLayoutPresShell) \ + macro(Style, mLayoutStyleSets) \ + macro(Other, mLayoutTextRuns) \ + macro(Other, mLayoutPresContext) \ + macro(Other, mPropertyTables) \ + public: - nsWindowSizes(mozilla::MallocSizeOf aMallocSizeOf) { - memset(this, 0, sizeof(nsWindowSizes)); - mMallocSizeOf = aMallocSizeOf; + nsWindowSizes(mozilla::MallocSizeOf aMallocSizeOf) + : + #define ZERO_SIZE(kind, mSize) mSize(0), + FOR_EACH_SIZE(ZERO_SIZE) + #undef ZERO_SIZE + mArenaStats(), + mMallocSizeOf(aMallocSizeOf) + {} + + void addToTabSizes(nsTabSizes *sizes) const { + #define ADD_TO_TAB_SIZES(kind, mSize) sizes->add(nsTabSizes::kind, mSize); + FOR_EACH_SIZE(ADD_TO_TAB_SIZES) + #undef ADD_TO_TAB_SIZES + mArenaStats.addToTabSizes(sizes); } - mozilla::MallocSizeOf mMallocSizeOf; + + #define DECL_SIZE(kind, mSize) size_t mSize; + FOR_EACH_SIZE(DECL_SIZE); + #undef DECL_SIZE nsArenaMemoryStats mArenaStats; - size_t mDOMElementNodes; - size_t mDOMTextNodes; - size_t mDOMCDATANodes; - size_t mDOMCommentNodes; - size_t mDOMEventTargets; - size_t mDOMOther; - size_t mStyleSheets; - size_t mLayoutPresShell; - size_t mLayoutStyleSets; - size_t mLayoutTextRuns; - size_t mLayoutPresContext; - size_t mPropertyTables; + mozilla::MallocSizeOf mMallocSizeOf; + +#undef FOR_EACH_SIZE }; /** diff --git a/js/public/MemoryMetrics.h b/js/public/MemoryMetrics.h index b33aa21ce95..d86b553f402 100644 --- a/js/public/MemoryMetrics.h +++ b/js/public/MemoryMetrics.h @@ -25,6 +25,37 @@ class nsISupports; // Needed for ObjectPrivateVisitor. +namespace JS { + +struct TabSizes +{ + enum Kind { + Objects, + Strings, + Private, + Other + }; + + TabSizes() { mozilla::PodZero(this); } + + void add(Kind kind, size_t n) { + switch (kind) { + case Objects: objects += n; break; + case Strings: strings += n; break; + case Private: private_ += n; break; + case Other: other += n; break; + default: MOZ_CRASH("bad TabSizes kind"); + } + } + + size_t objects; + size_t strings; + size_t private_; + size_t other; +}; + +} // namespace JS + namespace js { // In memory reporting, we have concept of "sundries", line items which are too @@ -57,11 +88,12 @@ struct InefficientNonFlatteningStringHashPolicy // In some classes, one or more of the macro arguments aren't used. We use '_' // for those. // -#define DECL_SIZE(gc, mSize) size_t mSize; -#define ZERO_SIZE(gc, mSize) mSize(0), -#define COPY_OTHER_SIZE(gc, mSize) mSize(other.mSize), -#define ADD_OTHER_SIZE(gc, mSize) mSize += other.mSize; -#define ADD_SIZE_TO_N_IF_LIVE_GC_THING(gc, mSize) n += (gc) ? mSize : 0; +#define DECL_SIZE(kind, gc, mSize) size_t mSize; +#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 ADD_SIZE_TO_N_IF_LIVE_GC_THING(kind, gc, mSize) n += (js::gc == js::IsLiveGCThing) ? mSize : 0; +#define ADD_TO_TAB_SIZES(kind, gc, mSize) sizes->add(JS::TabSizes::kind, mSize); // Used to annotate which size_t fields measure live GC things and which don't. enum { @@ -72,17 +104,17 @@ enum { struct ZoneStatsPod { #define FOR_EACH_SIZE(macro) \ - macro(NotLiveGCThing, gcHeapArenaAdmin) \ - macro(NotLiveGCThing, unusedGCThings) \ - macro(IsLiveGCThing, lazyScriptsGCHeap) \ - macro(NotLiveGCThing, lazyScriptsMallocHeap) \ - macro(IsLiveGCThing, ionCodesGCHeap) \ - macro(IsLiveGCThing, typeObjectsGCHeap) \ - macro(NotLiveGCThing, typeObjectsMallocHeap) \ - macro(NotLiveGCThing, typePool) \ - macro(IsLiveGCThing, stringsShortGCHeap) \ - macro(IsLiveGCThing, stringsNormalGCHeap) \ - macro(NotLiveGCThing, stringsNormalMallocHeap) + macro(Other, NotLiveGCThing, gcHeapArenaAdmin) \ + macro(Other, NotLiveGCThing, unusedGCThings) \ + macro(Other, IsLiveGCThing, lazyScriptsGCHeap) \ + macro(Other, NotLiveGCThing, lazyScriptsMallocHeap) \ + macro(Other, IsLiveGCThing, ionCodesGCHeap) \ + macro(Other, IsLiveGCThing, typeObjectsGCHeap) \ + macro(Other, NotLiveGCThing, typeObjectsMallocHeap) \ + macro(Other, NotLiveGCThing, typePool) \ + macro(Strings, IsLiveGCThing, stringsShortGCHeap) \ + macro(Strings, IsLiveGCThing, stringsNormalGCHeap) \ + macro(Strings, NotLiveGCThing, stringsNormalMallocHeap) ZoneStatsPod() : FOR_EACH_SIZE(ZERO_SIZE) @@ -101,6 +133,11 @@ struct ZoneStatsPod 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. @@ -115,16 +152,16 @@ namespace JS { struct ObjectsExtraSizes { #define FOR_EACH_SIZE(macro) \ - macro(js::NotLiveGCThing, mallocHeapSlots) \ - macro(js::NotLiveGCThing, mallocHeapElementsNonAsmJS) \ - macro(js::NotLiveGCThing, mallocHeapElementsAsmJS) \ - macro(js::NotLiveGCThing, nonHeapElementsAsmJS) \ - macro(js::NotLiveGCThing, nonHeapCodeAsmJS) \ - macro(js::NotLiveGCThing, mallocHeapAsmJSModuleData) \ - macro(js::NotLiveGCThing, mallocHeapArgumentsData) \ - macro(js::NotLiveGCThing, mallocHeapRegExpStatics) \ - macro(js::NotLiveGCThing, mallocHeapPropertyIteratorData) \ - macro(js::NotLiveGCThing, mallocHeapCtypesData) + macro(Objects, NotLiveGCThing, mallocHeapSlots) \ + macro(Objects, NotLiveGCThing, mallocHeapElementsNonAsmJS) \ + macro(Objects, NotLiveGCThing, mallocHeapElementsAsmJS) \ + macro(Objects, NotLiveGCThing, nonHeapElementsAsmJS) \ + macro(Objects, NotLiveGCThing, nonHeapCodeAsmJS) \ + macro(Objects, NotLiveGCThing, mallocHeapAsmJSModuleData) \ + macro(Objects, NotLiveGCThing, mallocHeapArgumentsData) \ + macro(Objects, NotLiveGCThing, mallocHeapRegExpStatics) \ + macro(Objects, NotLiveGCThing, mallocHeapPropertyIteratorData) \ + macro(Objects, NotLiveGCThing, mallocHeapCtypesData) ObjectsExtraSizes() : FOR_EACH_SIZE(ZERO_SIZE) @@ -141,6 +178,10 @@ struct ObjectsExtraSizes return n; } + void addToTabSizes(TabSizes *sizes) const { + FOR_EACH_SIZE(ADD_TO_TAB_SIZES) + } + FOR_EACH_SIZE(DECL_SIZE) int dummy; // present just to absorb the trailing comma from FOR_EACH_SIZE(ZERO_SIZE) @@ -151,11 +192,11 @@ struct ObjectsExtraSizes struct CodeSizes { #define FOR_EACH_SIZE(macro) \ - macro(_, ion) \ - macro(_, baseline) \ - macro(_, regexp) \ - macro(_, other) \ - macro(_, unused) + macro(_, _, ion) \ + macro(_, _, baseline) \ + macro(_, _, regexp) \ + macro(_, _, other) \ + macro(_, _, unused) CodeSizes() : FOR_EACH_SIZE(ZERO_SIZE) @@ -256,17 +297,17 @@ struct NotableStringInfo : public StringInfo struct RuntimeSizes { #define FOR_EACH_SIZE(macro) \ - macro(_, object) \ - macro(_, atomsTable) \ - macro(_, contexts) \ - macro(_, dtoa) \ - macro(_, temporary) \ - macro(_, regexpData) \ - macro(_, interpreterStack) \ - macro(_, gcMarker) \ - macro(_, mathCache) \ - macro(_, scriptData) \ - macro(_, scriptSources) + macro(_, _, object) \ + macro(_, _, atomsTable) \ + macro(_, _, contexts) \ + macro(_, _, dtoa) \ + macro(_, _, temporary) \ + macro(_, _, regexpData) \ + macro(_, _, interpreterStack) \ + macro(_, _, gcMarker) \ + macro(_, _, mathCache) \ + macro(_, _, scriptData) \ + macro(_, _, scriptSources) RuntimeSizes() : FOR_EACH_SIZE(ZERO_SIZE) @@ -335,35 +376,35 @@ struct ZoneStats : js::ZoneStatsPod struct CompartmentStats { #define FOR_EACH_SIZE(macro) \ - macro(js::IsLiveGCThing, objectsGCHeapOrdinary) \ - macro(js::IsLiveGCThing, objectsGCHeapFunction) \ - macro(js::IsLiveGCThing, objectsGCHeapDenseArray) \ - macro(js::IsLiveGCThing, objectsGCHeapSlowArray) \ - macro(js::IsLiveGCThing, objectsGCHeapCrossCompartmentWrapper) \ - macro(js::NotLiveGCThing, objectsPrivate) \ - macro(js::IsLiveGCThing, shapesGCHeapTreeGlobalParented) \ - macro(js::IsLiveGCThing, shapesGCHeapTreeNonGlobalParented) \ - macro(js::IsLiveGCThing, shapesGCHeapDict) \ - macro(js::IsLiveGCThing, shapesGCHeapBase) \ - macro(js::NotLiveGCThing, shapesMallocHeapTreeTables) \ - macro(js::NotLiveGCThing, shapesMallocHeapDictTables) \ - macro(js::NotLiveGCThing, shapesMallocHeapTreeShapeKids) \ - macro(js::NotLiveGCThing, shapesMallocHeapCompartmentTables) \ - macro(js::IsLiveGCThing, scriptsGCHeap) \ - macro(js::NotLiveGCThing, scriptsMallocHeapData) \ - macro(js::NotLiveGCThing, baselineData) \ - macro(js::NotLiveGCThing, baselineStubsFallback) \ - macro(js::NotLiveGCThing, baselineStubsOptimized) \ - macro(js::NotLiveGCThing, ionData) \ - macro(js::NotLiveGCThing, typeInferenceTypeScripts) \ - macro(js::NotLiveGCThing, typeInferencePendingArrays) \ - macro(js::NotLiveGCThing, typeInferenceAllocationSiteTables) \ - macro(js::NotLiveGCThing, typeInferenceArrayTypeTables) \ - macro(js::NotLiveGCThing, typeInferenceObjectTypeTables) \ - macro(js::NotLiveGCThing, compartmentObject) \ - macro(js::NotLiveGCThing, crossCompartmentWrappersTable) \ - macro(js::NotLiveGCThing, regexpCompartment) \ - macro(js::NotLiveGCThing, debuggeesSet) + 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) \ + macro(Other, NotLiveGCThing, baselineStubsFallback) \ + macro(Other, NotLiveGCThing, baselineStubsOptimized) \ + macro(Other, NotLiveGCThing, ionData) \ + macro(Other, NotLiveGCThing, typeInferenceTypeScripts) \ + macro(Other, NotLiveGCThing, typeInferencePendingArrays) \ + macro(Other, NotLiveGCThing, typeInferenceAllocationSiteTables) \ + macro(Other, NotLiveGCThing, typeInferenceArrayTypeTables) \ + macro(Other, NotLiveGCThing, typeInferenceObjectTypeTables) \ + macro(Other, NotLiveGCThing, compartmentObject) \ + macro(Other, NotLiveGCThing, crossCompartmentWrappersTable) \ + macro(Other, NotLiveGCThing, regexpCompartment) \ + macro(Other, NotLiveGCThing, debuggeesSet) CompartmentStats() : FOR_EACH_SIZE(ZERO_SIZE) @@ -391,6 +432,12 @@ struct CompartmentStats return n; } + void addToTabSizes(TabSizes *sizes) const { + FOR_EACH_SIZE(ADD_TO_TAB_SIZES); + objectsExtra.addToTabSizes(sizes); + // Do nothing with |extra|. + } + FOR_EACH_SIZE(DECL_SIZE) ObjectsExtraSizes objectsExtra; void *extra; // This field can be used by embedders. @@ -401,12 +448,12 @@ struct CompartmentStats struct RuntimeStats { #define FOR_EACH_SIZE(macro) \ - macro(_, gcHeapChunkTotal) \ - macro(_, gcHeapDecommittedArenas) \ - macro(_, gcHeapUnusedChunks) \ - macro(_, gcHeapUnusedArenas) \ - macro(_, gcHeapChunkAdmin) \ - macro(_, gcHeapGCThings) \ + macro(_, _, gcHeapChunkTotal) \ + macro(_, _, gcHeapDecommittedArenas) \ + macro(_, _, gcHeapUnusedChunks) \ + macro(_, _, gcHeapUnusedArenas) \ + macro(_, _, gcHeapChunkAdmin) \ + macro(_, _, gcHeapGCThings) \ RuntimeStats(mozilla::MallocSizeOf mallocSizeOf) : FOR_EACH_SIZE(ZERO_SIZE) @@ -488,6 +535,17 @@ UserCompartmentCount(JSRuntime *rt); extern JS_PUBLIC_API(size_t) PeakSizeOfTemporary(const JSRuntime *rt); +extern JS_PUBLIC_API(bool) +AddSizeOfTab(JSRuntime *rt, JSObject *obj, mozilla::MallocSizeOf mallocSizeOf, + ObjectPrivateVisitor *opv, TabSizes *sizes); + } // namespace JS +#undef DECL_SIZE +#undef ZERO_SIZE +#undef COPY_OTHER_SIZE +#undef ADD_OTHER_SIZE +#undef ADD_SIZE_TO_N_IF_LIVE_GC_THING +#undef ADD_TO_TAB_SIZES + #endif /* js_MemoryMetrics_h */ diff --git a/js/src/gc/Iteration.cpp b/js/src/gc/Iteration.cpp index 34c402d48b2..ae081e51df3 100644 --- a/js/src/gc/Iteration.cpp +++ b/js/src/gc/Iteration.cpp @@ -26,6 +26,28 @@ js::TraceRuntime(JSTracer *trc) MarkRuntime(trc); } +static void +IterateCompartmentsArenasCells(JSRuntime *rt, Zone *zone, void *data, + JSIterateCompartmentCallback compartmentCallback, + IterateArenaCallback arenaCallback, + IterateCellCallback cellCallback) +{ + for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) + (*compartmentCallback)(rt, data, comp); + + for (size_t thingKind = 0; thingKind != FINALIZE_LIMIT; thingKind++) { + JSGCTraceKind traceKind = MapAllocToTraceKind(AllocKind(thingKind)); + size_t thingSize = Arena::thingSize(AllocKind(thingKind)); + + for (ArenaIter aiter(zone, AllocKind(thingKind)); !aiter.done(); aiter.next()) { + ArenaHeader *aheader = aiter.get(); + (*arenaCallback)(rt, data, aheader->getArena(), traceKind, thingSize); + for (CellIterUnderGC iter(aheader); !iter.done(); iter.next()) + (*cellCallback)(rt, data, iter.getCell(), traceKind, thingSize); + } + } +} + void js::IterateZonesCompartmentsArenasCells(JSRuntime *rt, void *data, IterateZoneCallback zoneCallback, @@ -37,24 +59,25 @@ js::IterateZonesCompartmentsArenasCells(JSRuntime *rt, void *data, for (ZonesIter zone(rt); !zone.done(); zone.next()) { (*zoneCallback)(rt, data, zone); - - for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) - (*compartmentCallback)(rt, data, comp); - - for (size_t thingKind = 0; thingKind != FINALIZE_LIMIT; thingKind++) { - JSGCTraceKind traceKind = MapAllocToTraceKind(AllocKind(thingKind)); - size_t thingSize = Arena::thingSize(AllocKind(thingKind)); - - for (ArenaIter aiter(zone, AllocKind(thingKind)); !aiter.done(); aiter.next()) { - ArenaHeader *aheader = aiter.get(); - (*arenaCallback)(rt, data, aheader->getArena(), traceKind, thingSize); - for (CellIterUnderGC iter(aheader); !iter.done(); iter.next()) - (*cellCallback)(rt, data, iter.getCell(), traceKind, thingSize); - } - } + IterateCompartmentsArenasCells(rt, zone, data, + compartmentCallback, arenaCallback, cellCallback); } } +void +js::IterateZoneCompartmentsArenasCells(JSRuntime *rt, Zone *zone, void *data, + IterateZoneCallback zoneCallback, + JSIterateCompartmentCallback compartmentCallback, + IterateArenaCallback arenaCallback, + IterateCellCallback cellCallback) +{ + AutoPrepareForTracing prop(rt); + + (*zoneCallback)(rt, data, zone); + IterateCompartmentsArenasCells(rt, zone, data, + compartmentCallback, arenaCallback, cellCallback); +} + void js::IterateChunks(JSRuntime *rt, void *data, IterateChunkCallback chunkCallback) { diff --git a/js/src/jsgc.h b/js/src/jsgc.h index 70931772398..01152d5528c 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -1274,9 +1274,9 @@ typedef void (*IterateCellCallback)(JSRuntime *rt, void *data, void *thing, JSGCTraceKind traceKind, size_t thingSize); /* - * This function calls |compartmentCallback| on every compartment, - * |arenaCallback| on every in-use arena, and |cellCallback| on every in-use - * cell in the GC heap. + * This function calls |zoneCallback| on every zone, |compartmentCallback| on + * every compartment, |arenaCallback| on every in-use arena, and |cellCallback| + * on every in-use cell in the GC heap. */ extern void IterateZonesCompartmentsArenasCells(JSRuntime *rt, void *data, @@ -1285,6 +1285,17 @@ IterateZonesCompartmentsArenasCells(JSRuntime *rt, void *data, IterateArenaCallback arenaCallback, IterateCellCallback cellCallback); +/* + * This function is like IterateZonesCompartmentsArenasCells, but does it for a + * single zone. + */ +extern void +IterateZoneCompartmentsArenasCells(JSRuntime *rt, Zone *zone, void *data, + IterateZoneCallback zoneCallback, + JSIterateCompartmentCallback compartmentCallback, + IterateArenaCallback arenaCallback, + IterateCellCallback cellCallback); + /* * Invoke chunkCallback on every in-use chunk. */ diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 5ac786025c6..29788411d24 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -43,6 +43,7 @@ #include "js/OldDebugAPI.h" #include "vm/ArgumentsObject.h" #include "vm/Interpreter.h" +#include "vm/ProxyObject.h" #include "vm/RegExpStaticsObject.h" #include "vm/Shape.h" @@ -5680,8 +5681,24 @@ JSObject::addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::Objects } // Other things may be measured in the future if DMD indicates it is worthwhile. - // Note that sizes->private_ is measured elsewhere. - if (is()) { + if (is() || + is() || + is() || + is() || + is() || + is()) + { + // Do nothing. But this function is hot, and we win by getting the + // common cases out of the way early. Some stats on the most common + // classes, as measured during a vanilla browser session: + // - (53.7%, 53.7%): Function + // - (18.0%, 71.7%): Object + // - (16.9%, 88.6%): Array + // - ( 3.9%, 92.5%): Call + // - ( 2.8%, 95.3%): RegExp + // - ( 1.0%, 96.4%): Proxy + + } else if (is()) { sizes->mallocHeapArgumentsData += as().sizeOfMisc(mallocSizeOf); } else if (is()) { sizes->mallocHeapRegExpStatics += as().sizeOfData(mallocSizeOf); diff --git a/js/src/jsobj.h b/js/src/jsobj.h index ab521f23e36..03f216f3358 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -378,7 +378,7 @@ class JSObject : public js::ObjectImpl static void shrinkSlots(js::ThreadSafeContext *cx, js::HandleObject obj, uint32_t oldCount, uint32_t newCount); - bool hasDynamicSlots() const { return slots != nullptr; } + bool hasDynamicSlots() const { return !!slots; } protected: static inline bool updateSlotsForSpan(js::ThreadSafeContext *cx, diff --git a/js/src/vm/MemoryMetrics.cpp b/js/src/vm/MemoryMetrics.cpp index 9ec372e9f01..31268049342 100644 --- a/js/src/vm/MemoryMetrics.cpp +++ b/js/src/vm/MemoryMetrics.cpp @@ -23,8 +23,9 @@ #include "vm/WrapperObject.h" using mozilla::DebugOnly; -using mozilla::OldMove; +using mozilla::MallocSizeOf; using mozilla::MoveRef; +using mozilla::OldMove; using mozilla::PodEqual; using namespace js; @@ -268,13 +269,10 @@ StatsCellCallback(JSRuntime *rt, void *data, void *thing, JSGCTraceKind traceKin obj->addSizeOfExcludingThis(rtStats->mallocSizeOf_, &cStats->objectsExtra); - // JSObject::sizeOfExcludingThis() doesn't measure objectsPrivate, - // so we do it here. if (ObjectPrivateVisitor *opv = closure->opv) { nsISupports *iface; - if (opv->getISupports_(obj, &iface) && iface) { + if (opv->getISupports_(obj, &iface) && iface) cStats->objectsPrivate += opv->sizeOfIncludingThis(iface); - } } break; } @@ -338,7 +336,6 @@ StatsCellCallback(JSRuntime *rt, void *data, void *thing, JSGCTraceKind traceKin JSScript *script = static_cast(thing); CompartmentStats *cStats = GetCompartmentStats(script->compartment()); cStats->scriptsGCHeap += thingSize; - cStats->scriptsMallocHeapData += script->sizeOfData(rtStats->mallocSizeOf_); cStats->typeInferenceTypeScripts += script->sizeOfTypeScript(rtStats->mallocSizeOf_); #ifdef JS_ION @@ -446,14 +443,12 @@ JS::CollectRuntimeStats(JSRuntime *rt, RuntimeStats *rtStats, ObjectPrivateVisit StatsClosure closure(rtStats, opv); if (!closure.init()) return false; - rtStats->runtime.scriptSources = 0; IterateZonesCompartmentsArenasCells(rt, &closure, StatsZoneCallback, StatsCompartmentCallback, StatsArenaCallback, StatsCellCallback); // Take the "explicit/js/runtime/" measurements. rt->addSizeOfIncludingThis(rtStats->mallocSizeOf_, &rtStats->runtime); - rtStats->gcHeapGCThings = 0; for (size_t i = 0; i < rtStats->zoneStatsVector.length(); i++) { ZoneStats &zStats = rtStats->zoneStatsVector[i]; @@ -531,3 +526,62 @@ JS::PeakSizeOfTemporary(const JSRuntime *rt) return rt->tempLifoAlloc.peakSizeOfExcludingThis(); } +namespace JS { + +JS_PUBLIC_API(bool) +AddSizeOfTab(JSRuntime *rt, JSObject *obj, MallocSizeOf mallocSizeOf, ObjectPrivateVisitor *opv, + TabSizes *sizes) +{ + class SimpleJSRuntimeStats : public JS::RuntimeStats + { + public: + SimpleJSRuntimeStats(MallocSizeOf mallocSizeOf) + : JS::RuntimeStats(mallocSizeOf) + {} + + virtual void initExtraZoneStats(JS::Zone *zone, JS::ZoneStats *zStats) + MOZ_OVERRIDE + {} + + virtual void initExtraCompartmentStats( + JSCompartment *c, JS::CompartmentStats *cStats) MOZ_OVERRIDE + {} + }; + + SimpleJSRuntimeStats rtStats(mallocSizeOf); + + JS::Zone *zone = GetObjectZone(obj); + + if (!rtStats.compartmentStatsVector.reserve(zone->compartments.length())) + return false; + + if (!rtStats.zoneStatsVector.reserve(1)) + return false; + + // Take the per-compartment measurements. + StatsClosure closure(&rtStats, opv); + if (!closure.init()) + return false; + IterateZoneCompartmentsArenasCells(rt, zone, &closure, StatsZoneCallback, + StatsCompartmentCallback, StatsArenaCallback, + StatsCellCallback); + + JS_ASSERT(rtStats.zoneStatsVector.length() == 1); + rtStats.zTotals.add(rtStats.zoneStatsVector[0]); + + for (size_t i = 0; i < rtStats.compartmentStatsVector.length(); i++) { + CompartmentStats &cStats = rtStats.compartmentStatsVector[i]; + rtStats.cTotals.add(cStats); + } + + for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) + comp->compartmentStats = NULL; + + rtStats.zTotals.addToTabSizes(sizes); + rtStats.cTotals.addToTabSizes(sizes); + + return true; +} + +} // namespace JS + diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp index 025ee766a33..10d77cd306b 100644 --- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -2416,7 +2416,7 @@ class OrphanReporter : public JS::ObjectPrivateVisitor { } - virtual size_t sizeOfIncludingThis(nsISupports *aSupports) { + virtual size_t sizeOfIncludingThis(nsISupports *aSupports) MOZ_OVERRIDE { size_t n = 0; nsCOMPtr node = do_QueryInterface(aSupports); // https://bugzilla.mozilla.org/show_bug.cgi?id=773533#c11 explains @@ -2674,6 +2674,25 @@ JSReporter::CollectReports(WindowPaths *windowPaths, return NS_OK; } +static nsresult +JSSizeOfTab(JSObject *obj, size_t *jsObjectsSize, size_t *jsStringsSize, + size_t *jsPrivateSize, size_t *jsOtherSize) +{ + JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->Runtime(); + + TabSizes sizes; + OrphanReporter orphanReporter(XPCConvert::GetISupportsFromJSObject); + NS_ENSURE_TRUE(JS::AddSizeOfTab(rt, obj, moz_malloc_size_of, + &orphanReporter, &sizes), + NS_ERROR_OUT_OF_MEMORY); + + *jsObjectsSize = sizes.objects; + *jsStringsSize = sizes.strings; + *jsPrivateSize = sizes.private_; + *jsOtherSize = sizes.other; + return NS_OK; +} + } // namespace xpc #ifdef MOZ_CRASHREPORTER @@ -3040,6 +3059,7 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect) RegisterJSMainRuntimeTemporaryPeakDistinguishedAmount(JSMainRuntimeTemporaryPeakDistinguishedAmount); RegisterJSMainRuntimeCompartmentsSystemDistinguishedAmount(JSMainRuntimeCompartmentsSystemDistinguishedAmount); RegisterJSMainRuntimeCompartmentsUserDistinguishedAmount(JSMainRuntimeCompartmentsUserDistinguishedAmount); + mozilla::RegisterJSSizeOfTab(JSSizeOfTab); // Install a JavaScript 'debugger' keyword handler in debug builds only #ifdef DEBUG diff --git a/layout/base/nsArenaMemoryStats.h b/layout/base/nsArenaMemoryStats.h index e6f85b7a170..80e03a0e905 100644 --- a/layout/base/nsArenaMemoryStats.h +++ b/layout/base/nsArenaMemoryStats.h @@ -1,3 +1,5 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ @@ -5,16 +7,75 @@ #ifndef nsArenaMemoryStats_h #define nsArenaMemoryStats_h -#define FRAME_ID_STAT_FIELD(classname) mArena##classname +#include "mozilla/Assertions.h" +#include "mozilla/PodOperations.h" -struct nsArenaMemoryStats { -#define FRAME_ID(classname) size_t FRAME_ID_STAT_FIELD(classname); -#include "nsFrameIdList.h" -#undef FRAME_ID - size_t mLineBoxes; - size_t mRuleNodes; - size_t mStyleContexts; +class nsTabSizes { +public: + enum Kind { + DOM, // DOM stuff. + Style, // Style stuff. + Other // Everything else. + }; + + nsTabSizes() { mozilla::PodZero(this); } + + void add(Kind kind, size_t n) + { + switch (kind) { + case DOM: mDom += n; break; + case Style: mStyle += n; break; + case Other: mOther += n; break; + default: MOZ_CRASH("bad nsTabSizes kind"); + } + } + + size_t mDom; + size_t mStyle; size_t mOther; }; +#define FRAME_ID_STAT_FIELD(classname) mArena##classname + +struct nsArenaMemoryStats { +#define FOR_EACH_SIZE(macro) \ + macro(Other, mLineBoxes) \ + macro(Style, mRuleNodes) \ + macro(Style, mStyleContexts) \ + macro(Other, mOther) + + nsArenaMemoryStats() + : + #define ZERO_SIZE(kind, mSize) mSize(0), + FOR_EACH_SIZE(ZERO_SIZE) + #undef ZERO_SIZE + #define FRAME_ID(classname) FRAME_ID_STAT_FIELD(classname)(), + #include "nsFrameIdList.h" + #undef FRAME_ID + dummy() + {} + + void addToTabSizes(nsTabSizes *sizes) const + { + #define ADD_TO_TAB_SIZES(kind, mSize) sizes->add(nsTabSizes::kind, mSize); + FOR_EACH_SIZE(ADD_TO_TAB_SIZES) + #undef ADD_TO_TAB_SIZES + #define FRAME_ID(classname) \ + sizes->add(nsTabSizes::Other, FRAME_ID_STAT_FIELD(classname)); + #include "nsFrameIdList.h" + #undef FRAME_ID + } + + #define DECL_SIZE(kind, mSize) size_t mSize; + FOR_EACH_SIZE(DECL_SIZE) + #undef DECL_SIZE + #define FRAME_ID(classname) size_t FRAME_ID_STAT_FIELD(classname); + #include "nsFrameIdList.h" + #undef FRAME_ID + int dummy; // present just to absorb the trailing comma from FRAME_ID in the + // constructor + +#undef FOR_EACH_SIZE +}; + #endif // nsArenaMemoryStats_h diff --git a/toolkit/components/aboutmemory/tests/test_memoryReporters.xul b/toolkit/components/aboutmemory/tests/test_memoryReporters.xul index a00856b7653..2573f28a8bd 100644 --- a/toolkit/components/aboutmemory/tests/test_memoryReporters.xul +++ b/toolkit/components/aboutmemory/tests/test_memoryReporters.xul @@ -155,6 +155,21 @@ } } + // Run sizeOfTab() to make sure it doesn't crash. We can't check the result + // values because they're non-deterministic. + let jsObjectsSize = {}; + let jsStringsSize = {}; + let jsOtherSize = {}; + let domSize = {}; + let styleSize = {}; + let otherSize = {}; + let totalSize = {}; + let jsMilliseconds = {}; + let nonJSMilliseconds = {}; + mgr.sizeOfTab(window, jsObjectsSize, jsStringsSize, jsOtherSize, + domSize, styleSize, otherSize, totalSize, + jsMilliseconds, nonJSMilliseconds); + let e = mgr.enumerateReporters(); while (e.hasMoreElements()) { let r = e.getNext().QueryInterface(Ci.nsIMemoryReporter); diff --git a/xpcom/base/nsIMemoryReporter.idl b/xpcom/base/nsIMemoryReporter.idl index e413b4750de..fff600bd7d2 100644 --- a/xpcom/base/nsIMemoryReporter.idl +++ b/xpcom/base/nsIMemoryReporter.idl @@ -6,12 +6,13 @@ #include "nsISupports.idl" -interface nsISimpleEnumerator; -interface nsIRunnable; interface nsICancelableRunnable; +interface nsIDOMWindow; +interface nsIRunnable; +interface nsISimpleEnumerator; /* - * Memory reporters measure Firefox's memory usage. They are mainly used to + * Memory reporters measure Firefox's memory usage. They are primarily used to * generate the about:memory page. You should read * https://wiki.mozilla.org/Memory_Reporting before writing a memory * reporter. @@ -308,12 +309,32 @@ interface nsIMemoryReporterManager : nsISupports * null. Returns a reference to the runnable used for carrying out the task. */ nsICancelableRunnable minimizeMemoryUsage(in nsIRunnable callback); + + /* + * Measure the memory that is known to be owned by this tab, split up into + * several broad categories. Note that this will be an underestimate of the + * true number, due to imperfect memory reporter coverage (corresponding to + * about:memory's "heap-unclassified"), and due to some memory shared between + * tabs not being counted. + * + * The time taken for the measurement (split into JS and non-JS parts) is + * also returned. + */ + void sizeOfTab(in nsIDOMWindow window, + out int64_t jsObjectsSize, out int64_t jsStringsSize, + out int64_t jsOtherSize, out int64_t domSize, + out int64_t styleSize, out int64_t otherSize, + out int64_t totalSize, + out double jsMilliseconds, out double nonJSMilliseconds); }; %{C++ +#include "js/TypeDecls.h" #include "nsStringGlue.h" +class nsPIDOMWindow; + // Note that the memory reporters are held in an nsCOMArray, which means // that individual reporters should be referenced with |nsIMemoryReporter *| // instead of nsCOMPtr. @@ -355,6 +376,21 @@ DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, GhostWindows) #undef DECL_REGISTER_DISTINGUISHED_AMOUNT #undef DECL_UNREGISTER_DISTINGUISHED_AMOUNT +// Likewise for per-tab measurement. + +typedef nsresult (*JSSizeOfTabFn)(JSObject* aObj, + size_t* aJsObjectsSize, + size_t* aJsStringSize, + size_t* aJsPrivateSize, + size_t* aJsOtherSize); +typedef nsresult (*NonJSSizeOfTabFn)(nsPIDOMWindow* aWindow, + size_t* aDomSize, + size_t* aStyleSize, + size_t* aOtherSize); + +nsresult RegisterJSSizeOfTab(JSSizeOfTabFn aSizeOfTabFn); +nsresult RegisterNonJSSizeOfTab(NonJSSizeOfTabFn aSizeOfTabFn); + } #if defined(MOZ_DMD) diff --git a/xpcom/base/nsMemoryReporterManager.cpp b/xpcom/base/nsMemoryReporterManager.cpp index d89edb91d76..07c441cb8b9 100644 --- a/xpcom/base/nsMemoryReporterManager.cpp +++ b/xpcom/base/nsMemoryReporterManager.cpp @@ -12,7 +12,10 @@ #include "nsMemoryReporterManager.h" #include "nsISimpleEnumerator.h" #include "nsThreadUtils.h" +#include "nsIDOMWindow.h" +#include "nsPIDOMWindow.h" #include "nsIObserverService.h" +#include "nsIGlobalObject.h" #if defined(XP_LINUX) #include "nsMemoryInfoDumper.h" #endif @@ -845,6 +848,7 @@ nsMemoryReporterManager::nsMemoryReporterManager() mIsRegistrationBlocked(false) { PodZero(&mAmountFns); + PodZero(&mSizeOfTabFns); } nsMemoryReporterManager::~nsMemoryReporterManager() @@ -1267,6 +1271,56 @@ nsMemoryReporterManager::MinimizeMemoryUsage(nsIRunnable* aCallback, return NS_DispatchToMainThread(runnable); } +NS_IMETHODIMP +nsMemoryReporterManager::SizeOfTab(nsIDOMWindow* aTopWindow, + int64_t* aJSObjectsSize, + int64_t* aJSStringsSize, + int64_t* aJSOtherSize, + int64_t* aDomSize, + int64_t* aStyleSize, + int64_t* aOtherSize, + int64_t* aTotalSize, + double* aJSMilliseconds, + double* aNonJSMilliseconds) +{ + nsCOMPtr global = do_QueryInterface(aTopWindow); + nsCOMPtr piWindow = do_QueryInterface(aTopWindow); + NS_ENSURE_TRUE(!!global && !!piWindow, NS_ERROR_FAILURE); + + TimeStamp t1 = TimeStamp::Now(); + + // Measure JS memory consumption (and possibly some non-JS consumption, via + // |jsPrivateSize|). + size_t jsObjectsSize, jsStringsSize, jsPrivateSize, jsOtherSize; + nsresult rv = mSizeOfTabFns.mJS(global->GetGlobalJSObject(), + &jsObjectsSize, &jsStringsSize, + &jsPrivateSize, &jsOtherSize); + NS_ENSURE_SUCCESS(rv, rv); + + TimeStamp t2 = TimeStamp::Now(); + + // Measure non-JS memory consumption. + size_t domSize, styleSize, otherSize; + mSizeOfTabFns.mNonJS(piWindow, &domSize, &styleSize, &otherSize); + + TimeStamp t3 = TimeStamp::Now(); + + *aTotalSize = 0; + #define DO(aN, n) { *aN = (n); *aTotalSize += (n); } + DO(aJSObjectsSize, jsObjectsSize); + DO(aJSStringsSize, jsStringsSize); + DO(aJSOtherSize, jsOtherSize); + DO(aDomSize, jsPrivateSize + domSize); + DO(aStyleSize, styleSize); + DO(aOtherSize, otherSize); + #undef DO + + *aJSMilliseconds = (t2 - t1).ToMilliseconds(); + *aNonJSMilliseconds = (t3 - t2).ToMilliseconds(); + + return NS_OK; +} + // Most memory reporters don't need thread safety, but some do. Make them all // thread-safe just to be safe. Memory reporters are created and destroyed // infrequently enough that the performance cost should be negligible. @@ -1294,19 +1348,22 @@ NS_UnregisterMemoryReporter(nsIMemoryReporter* aReporter) namespace mozilla { +#define GET_MEMORY_REPORTER_MANAGER(mgr) \ + nsCOMPtr imgr = \ + do_GetService("@mozilla.org/memory-reporter-manager;1"); \ + nsRefPtr mgr = \ + static_cast(imgr.get()); \ + if (!mgr) { \ + return NS_ERROR_FAILURE; \ + } + // Macro for generating functions that register distinguished amount functions // with the memory reporter manager. #define DEFINE_REGISTER_DISTINGUISHED_AMOUNT(kind, name) \ nsresult \ Register##name##DistinguishedAmount(kind##AmountFn aAmountFn) \ { \ - nsCOMPtr imgr = \ - do_GetService("@mozilla.org/memory-reporter-manager;1"); \ - nsRefPtr mgr = \ - static_cast(imgr.get()); \ - if (!mgr) { \ - return NS_ERROR_FAILURE; \ - } \ + GET_MEMORY_REPORTER_MANAGER(mgr) \ mgr->mAmountFns.m##name = aAmountFn; \ return NS_OK; \ } @@ -1315,13 +1372,7 @@ namespace mozilla { nsresult \ Unregister##name##DistinguishedAmount() \ { \ - nsCOMPtr imgr = \ - do_GetService("@mozilla.org/memory-reporter-manager;1"); \ - nsRefPtr mgr = \ - static_cast(imgr.get()); \ - if (!mgr) { \ - return NS_ERROR_FAILURE; \ - } \ + GET_MEMORY_REPORTER_MANAGER(mgr) \ mgr->mAmountFns.m##name = nullptr; \ return NS_OK; \ } @@ -1344,6 +1395,22 @@ DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, GhostWindows) #undef DEFINE_REGISTER_DISTINGUISHED_AMOUNT #undef DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT +#define DEFINE_REGISTER_SIZE_OF_TAB(name) \ + nsresult \ + Register##name##SizeOfTab(name##SizeOfTabFn aSizeOfTabFn) \ + { \ + GET_MEMORY_REPORTER_MANAGER(mgr) \ + mgr->mSizeOfTabFns.m##name = aSizeOfTabFn; \ + return NS_OK; \ + } + +DEFINE_REGISTER_SIZE_OF_TAB(JS); +DEFINE_REGISTER_SIZE_OF_TAB(NonJS); + +#undef DEFINE_REGISTER_SIZE_OF_TAB + +#undef GET_MEMORY_REPORTER_MANAGER + } #if defined(MOZ_DMD) diff --git a/xpcom/base/nsMemoryReporterManager.h b/xpcom/base/nsMemoryReporterManager.h index b757bffa42a..af46957a43a 100644 --- a/xpcom/base/nsMemoryReporterManager.h +++ b/xpcom/base/nsMemoryReporterManager.h @@ -39,6 +39,13 @@ public: }; AmountFns mAmountFns; + // Functions that measure per-tab memory consumption. + struct SizeOfTabFns { + mozilla::JSSizeOfTabFn mJS; + mozilla::NonJSSizeOfTabFn mNonJS; + }; + SizeOfTabFns mSizeOfTabFns; + private: nsresult RegisterReporterHelper(nsIMemoryReporter *aReporter, bool aForce);