diff -r 000000000000 -r 6474c204b198 js/public/MemoryMetrics.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/public/MemoryMetrics.h Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,665 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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/. */ + +#ifndef js_MemoryMetrics_h +#define js_MemoryMetrics_h + +// These declarations are highly likely to change in the future. Depend on them +// at your own risk. + +#include "mozilla/MemoryReporting.h" +#include "mozilla/NullPtr.h" +#include "mozilla/PodOperations.h" + +#include + +#include "jsalloc.h" +#include "jspubtd.h" + +#include "js/HashTable.h" +#include "js/Utility.h" +#include "js/Vector.h" + +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 +// small to be worth reporting individually. Under some circumstances, a memory +// reporter gets tossed into the sundries bucket if it's smaller than +// MemoryReportingSundriesThreshold() bytes. +// +// We need to define this value here, rather than in the code which actually +// generates the memory reports, because NotableStringInfo uses this value. +JS_FRIEND_API(size_t) MemoryReportingSundriesThreshold(); + +// This hash policy avoids flattening ropes (which perturbs the site being +// measured and requires a JSContext) at the expense of doing a FULL ROPE COPY +// on every hash and match! Beware. +struct InefficientNonFlatteningStringHashPolicy +{ + typedef JSString *Lookup; + static HashNumber hash(const Lookup &l); + static bool match(const JSString *const &k, const Lookup &l); +}; + +struct CStringHashPolicy +{ + typedef const char *Lookup; + static HashNumber hash(const Lookup &l); + static bool match(const char *const &k, const Lookup &l); +}; + +// This file features many classes with numerous size_t fields, and each such +// class has one or more methods that need to operate on all of these fields. +// Writing these individually is error-prone -- it's easy to add a new field +// without updating all the required methods. So we define a single macro list +// in each class to name the fields (and notable characteristics of them), and +// then use the following macros to transform those lists into the required +// methods. +// +// In some classes, one or more of the macro arguments aren't used. We use '_' +// for those. +// +#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 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); + +// Used to annotate which size_t fields measure live GC things and which don't. +enum { + NotLiveGCThing = false, + IsLiveGCThing = true +}; + +} // namespace js + +namespace JS { + +// Data for tracking memory usage of things hanging off objects. +struct ObjectsExtraSizes +{ +#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) + + ObjectsExtraSizes() + : FOR_EACH_SIZE(ZERO_SIZE) + dummy() + {} + + void add(const ObjectsExtraSizes &other) { + FOR_EACH_SIZE(ADD_OTHER_SIZE) + } + + 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) + int dummy; // present just to absorb the trailing comma from FOR_EACH_SIZE(ZERO_SIZE) + +#undef FOR_EACH_SIZE +}; + +// Data for tracking JIT-code memory usage. +struct CodeSizes +{ +#define FOR_EACH_SIZE(macro) \ + macro(_, _, ion) \ + macro(_, _, baseline) \ + macro(_, _, regexp) \ + macro(_, _, other) \ + macro(_, _, unused) + + CodeSizes() + : FOR_EACH_SIZE(ZERO_SIZE) + dummy() + {} + + FOR_EACH_SIZE(DECL_SIZE) + int dummy; // present just to absorb the trailing comma from FOR_EACH_SIZE(ZERO_SIZE) + +#undef FOR_EACH_SIZE +}; + +// Data for tracking GC memory usage. +struct GCSizes +{ +#define FOR_EACH_SIZE(macro) \ + macro(_, _, marker) \ + macro(_, _, nurseryCommitted) \ + macro(_, _, nurseryDecommitted) \ + macro(_, _, nurseryHugeSlots) \ + macro(_, _, storeBufferVals) \ + macro(_, _, storeBufferCells) \ + macro(_, _, storeBufferSlots) \ + macro(_, _, storeBufferWholeCells) \ + macro(_, _, storeBufferRelocVals) \ + macro(_, _, storeBufferRelocCells) \ + macro(_, _, storeBufferGenerics) + + GCSizes() + : FOR_EACH_SIZE(ZERO_SIZE) + dummy() + {} + + FOR_EACH_SIZE(DECL_SIZE) + int dummy; // present just to absorb the trailing comma from FOR_EACH_SIZE(ZERO_SIZE) + +#undef FOR_EACH_SIZE +}; + +// This class holds information about the memory taken up by identical copies of +// a particular string. Multiple JSStrings may have their sizes aggregated +// together into one StringInfo object. Note that two strings with identical +// chars will not be aggregated together if one is a short string and the other +// is not. +struct StringInfo +{ +#define FOR_EACH_SIZE(macro) \ + macro(Strings, IsLiveGCThing, gcHeap) \ + macro(Strings, NotLiveGCThing, mallocHeap) \ + + StringInfo() + : FOR_EACH_SIZE(ZERO_SIZE) + numCopies(0) + {} + + void add(const StringInfo &other) { + FOR_EACH_SIZE(ADD_OTHER_SIZE); + numCopies++; + } + + 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? + +#undef FOR_EACH_SIZE +}; + +// 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. +// +// 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); + NotableStringInfo &operator=(NotableStringInfo &&info); + + ~NotableStringInfo() { + js_free(buffer); + } + + char *buffer; + size_t length; + + private: + NotableStringInfo(const NotableStringInfo& info) MOZ_DELETE; +}; + +// This class holds information about the memory taken up by script sources +// from a particular file. +struct ScriptSourceInfo +{ +#define FOR_EACH_SIZE(macro) \ + macro(_, _, compressed) \ + macro(_, _, uncompressed) \ + macro(_, _, misc) + + ScriptSourceInfo() + : FOR_EACH_SIZE(ZERO_SIZE) + numScripts(0) + {} + + void add(const ScriptSourceInfo &other) { + FOR_EACH_SIZE(ADD_OTHER_SIZE) + numScripts++; + } + + void subtract(const ScriptSourceInfo &other) { + FOR_EACH_SIZE(SUB_OTHER_SIZE) + numScripts--; + } + + bool isNotable() const { + static const size_t NotabilityThreshold = 16 * 1024; + size_t n = 0; + FOR_EACH_SIZE(ADD_SIZE_TO_N) + return n >= NotabilityThreshold; + } + + FOR_EACH_SIZE(DECL_SIZE) + uint32_t numScripts; // How many ScriptSources come from this file? (It + // can be more than one in XML files that have + // multiple scripts in CDATA sections.) +#undef FOR_EACH_SIZE +}; + +// Holds data about a notable script source file (one whose combined +// script sources use more than a certain amount of memory) so we can report it +// individually. +// +// The only difference between this class and ScriptSourceInfo is that this +// class holds a copy of the filename. +struct NotableScriptSourceInfo : public ScriptSourceInfo +{ + NotableScriptSourceInfo(); + NotableScriptSourceInfo(const char *filename, const ScriptSourceInfo &info); + NotableScriptSourceInfo(NotableScriptSourceInfo &&info); + NotableScriptSourceInfo &operator=(NotableScriptSourceInfo &&info); + + ~NotableScriptSourceInfo() { + js_free(filename_); + } + + char *filename_; + + private: + NotableScriptSourceInfo(const NotableScriptSourceInfo& info) MOZ_DELETE; +}; + +// These measurements relate directly to the JSRuntime, and not to zones and +// compartments within it. +struct RuntimeSizes +{ +#define FOR_EACH_SIZE(macro) \ + macro(_, _, object) \ + macro(_, _, atomsTable) \ + macro(_, _, contexts) \ + macro(_, _, dtoa) \ + macro(_, _, temporary) \ + macro(_, _, regexpData) \ + macro(_, _, interpreterStack) \ + macro(_, _, mathCache) \ + macro(_, _, sourceDataCache) \ + macro(_, _, scriptData) \ + + RuntimeSizes() + : FOR_EACH_SIZE(ZERO_SIZE) + scriptSourceInfo(), + code(), + gc(), + notableScriptSources() + { + allScriptSources = js_new(); + if (!allScriptSources || !allScriptSources->init()) + MOZ_CRASH("oom"); + } + + ~RuntimeSizes() { + // |allScriptSources| 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(allScriptSources); + } + + // The script source measurements in |scriptSourceInfo| are initially for + // all script sources. At the end, if the measurement granularity is + // FineGrained, we subtract the measurements of the notable script sources + // and move them into |notableScriptSources|. + FOR_EACH_SIZE(DECL_SIZE) + ScriptSourceInfo scriptSourceInfo; + CodeSizes code; + GCSizes gc; + + typedef js::HashMap ScriptSourcesHashMap; + + // |allScriptSources| is only used transiently. During the reporting phase + // it is filled with info about every script source in the runtime. It's + // then used to fill in |notableScriptSources| (which actually gets + // reported), and immediately discarded afterwards. + ScriptSourcesHashMap *allScriptSources; + js::Vector notableScriptSources; + +#undef FOR_EACH_SIZE +}; + +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) \ + macro(Other, NotLiveGCThing, baselineStubsOptimized) \ + + ZoneStats() + : FOR_EACH_SIZE(ZERO_SIZE) + stringInfo(), + extra(), + allStrings(nullptr), + notableStrings(), + isTotals(true) + {} + + ZoneStats(ZoneStats &&other) + : 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.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); + + void addSizes(const ZoneStats &other) { + MOZ_ASSERT(isTotals); + FOR_EACH_SIZE(ADD_OTHER_SIZE) + stringInfo.add(other.stringInfo); + } + + size_t sizeOfLiveGCThings() const { + MOZ_ASSERT(isTotals); + size_t n = 0; + FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING) + n += stringInfo.sizeOfLiveGCThings(); + return n; + } + + 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 StringsHashMap; + + // |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 *allStrings; + js::Vector notableStrings; + bool isTotals; + +#undef FOR_EACH_SIZE +}; + +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) \ + macro(Other, NotLiveGCThing, baselineStubsFallback) \ + macro(Other, NotLiveGCThing, ionData) \ + macro(Other, NotLiveGCThing, typeInferenceTypeScripts) \ + 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) \ + macro(Other, NotLiveGCThing, savedStacksSet) + + CompartmentStats() + : FOR_EACH_SIZE(ZERO_SIZE) + objectsExtra(), + extra() + {} + + CompartmentStats(const CompartmentStats &other) + : FOR_EACH_SIZE(COPY_OTHER_SIZE) + objectsExtra(other.objectsExtra), + extra(other.extra) + {} + + void add(const CompartmentStats &other) { + FOR_EACH_SIZE(ADD_OTHER_SIZE) + objectsExtra.add(other.objectsExtra); + // Do nothing with |extra|. + } + + size_t sizeOfLiveGCThings() const { + size_t n = 0; + FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING) + n += objectsExtra.sizeOfLiveGCThings(); + // Do nothing with |extra|. + 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. + +#undef FOR_EACH_SIZE +}; + +typedef js::Vector CompartmentStatsVector; +typedef js::Vector ZoneStatsVector; + +struct RuntimeStats +{ +#define FOR_EACH_SIZE(macro) \ + macro(_, _, gcHeapChunkTotal) \ + macro(_, _, gcHeapDecommittedArenas) \ + macro(_, _, gcHeapUnusedChunks) \ + macro(_, _, gcHeapUnusedArenas) \ + macro(_, _, gcHeapChunkAdmin) \ + macro(_, _, gcHeapGCThings) \ + + RuntimeStats(mozilla::MallocSizeOf mallocSizeOf) + : FOR_EACH_SIZE(ZERO_SIZE) + runtime(), + cTotals(), + zTotals(), + compartmentStatsVector(), + zoneStatsVector(), + currZoneStats(nullptr), + mallocSizeOf_(mallocSizeOf) + {} + + // Here's a useful breakdown of the GC heap. + // + // - rtStats.gcHeapChunkTotal + // - decommitted bytes + // - rtStats.gcHeapDecommittedArenas (decommitted arenas in non-empty chunks) + // - unused bytes + // - rtStats.gcHeapUnusedChunks (empty chunks) + // - rtStats.gcHeapUnusedArenas (empty arenas within non-empty chunks) + // - rtStats.zTotals.unusedGCThings (empty GC thing slots within non-empty arenas) + // - used bytes + // - rtStats.gcHeapChunkAdmin + // - rtStats.zTotals.gcHeapArenaAdmin + // - rtStats.gcHeapGCThings (in-use GC things) + // == rtStats.zTotals.sizeOfLiveGCThings() + rtStats.cTotals.sizeOfLiveGCThings() + // + // It's possible that some arenas in empty chunks may be decommitted, but + // we don't count those under rtStats.gcHeapDecommittedArenas because (a) + // it's rare, and (b) this means that rtStats.gcHeapUnusedChunks is a + // multiple of the chunk size, which is good. + + FOR_EACH_SIZE(DECL_SIZE) + + RuntimeSizes runtime; + + CompartmentStats cTotals; // The sum of this runtime's compartments' measurements. + ZoneStats zTotals; // The sum of this runtime's zones' measurements. + + CompartmentStatsVector compartmentStatsVector; + ZoneStatsVector zoneStatsVector; + + ZoneStats *currZoneStats; + + mozilla::MallocSizeOf mallocSizeOf_; + + virtual void initExtraCompartmentStats(JSCompartment *c, CompartmentStats *cstats) = 0; + virtual void initExtraZoneStats(JS::Zone *zone, ZoneStats *zstats) = 0; + +#undef FOR_EACH_SIZE +}; + +class ObjectPrivateVisitor +{ + public: + // Within CollectRuntimeStats, this method is called for each JS object + // that has an nsISupports pointer. + virtual size_t sizeOfIncludingThis(nsISupports *aSupports) = 0; + + // A callback that gets a JSObject's nsISupports pointer, if it has one. + // Note: this function does *not* addref |iface|. + typedef bool(*GetISupportsFun)(JSObject *obj, nsISupports **iface); + GetISupportsFun getISupports_; + + ObjectPrivateVisitor(GetISupportsFun getISupports) + : getISupports_(getISupports) + {} +}; + +extern JS_PUBLIC_API(bool) +CollectRuntimeStats(JSRuntime *rt, RuntimeStats *rtStats, ObjectPrivateVisitor *opv); + +extern JS_PUBLIC_API(size_t) +SystemCompartmentCount(JSRuntime *rt); + +extern JS_PUBLIC_API(size_t) +UserCompartmentCount(JSRuntime *rt); + +extern JS_PUBLIC_API(size_t) +PeakSizeOfTemporary(const JSRuntime *rt); + +extern JS_PUBLIC_API(bool) +AddSizeOfTab(JSRuntime *rt, JS::HandleObject 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 SUB_OTHER_SIZE +#undef ADD_SIZE_TO_N +#undef ADD_SIZE_TO_N_IF_LIVE_GC_THING +#undef ADD_TO_TAB_SIZES + +#endif /* js_MemoryMetrics_h */