| /* -*- 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/PodOperations.h" |
| #include "mozilla/TypeTraits.h" |
| |
| #include <string.h> |
| |
| #include "jsalloc.h" |
| #include "jspubtd.h" |
| |
| #include "js/HashTable.h" |
| #include "js/TracingAPI.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; |
| }; |
| |
| /** These are the measurements used by Servo. */ |
| struct ServoSizes |
| { |
| enum Kind { |
| GCHeapUsed, |
| GCHeapUnused, |
| GCHeapAdmin, |
| GCHeapDecommitted, |
| MallocHeap, |
| NonHeap, |
| Ignore |
| }; |
| |
| ServoSizes() { mozilla::PodZero(this); } |
| |
| void add(Kind kind, size_t n) { |
| switch (kind) { |
| case GCHeapUsed: gcHeapUsed += n; break; |
| case GCHeapUnused: gcHeapUnused += n; break; |
| case GCHeapAdmin: gcHeapAdmin += n; break; |
| case GCHeapDecommitted: gcHeapDecommitted += n; break; |
| case MallocHeap: mallocHeap += n; break; |
| case NonHeap: nonHeap += n; break; |
| case Ignore: /* do nothing */ break; |
| default: MOZ_CRASH("bad ServoSizes kind"); |
| } |
| } |
| |
| size_t gcHeapUsed; |
| size_t gcHeapUnused; |
| size_t gcHeapAdmin; |
| size_t gcHeapDecommitted; |
| size_t mallocHeap; |
| size_t nonHeap; |
| }; |
| |
| } // 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. |
| // |
| // - The |tabKind| value is used when measuring TabSizes. |
| // |
| // - The |servoKind| value is used when measuring ServoSizes and also for |
| // the various sizeOfLiveGCThings() methods. |
| // |
| // In some classes, one or more of the macro arguments aren't used. We use '_' |
| // for those. |
| // |
| #define DECL_SIZE(tabKind, servoKind, mSize) size_t mSize; |
| #define ZERO_SIZE(tabKind, servoKind, mSize) mSize(0), |
| #define COPY_OTHER_SIZE(tabKind, servoKind, mSize) mSize(other.mSize), |
| #define ADD_OTHER_SIZE(tabKind, servoKind, mSize) mSize += other.mSize; |
| #define SUB_OTHER_SIZE(tabKind, servoKind, mSize) \ |
| MOZ_ASSERT(mSize >= other.mSize); \ |
| mSize -= other.mSize; |
| #define ADD_SIZE_TO_N(tabKind, servoKind, mSize) n += mSize; |
| #define ADD_SIZE_TO_N_IF_LIVE_GC_THING(tabKind, servoKind, mSize) \ |
| /* Avoid self-comparison warnings by comparing enums indirectly. */ \ |
| n += (mozilla::IsSame<int[ServoSizes::servoKind], int[ServoSizes::GCHeapUsed]>::value) \ |
| ? mSize \ |
| : 0; |
| #define ADD_TO_TAB_SIZES(tabKind, servoKind, mSize) sizes->add(JS::TabSizes::tabKind, mSize); |
| #define ADD_TO_SERVO_SIZES(tabKind, servoKind, mSize) sizes->add(JS::ServoSizes::servoKind, mSize); |
| |
| } // namespace js |
| |
| namespace JS { |
| |
| struct ClassInfo |
| { |
| #define FOR_EACH_SIZE(macro) \ |
| macro(Objects, GCHeapUsed, objectsGCHeap) \ |
| macro(Objects, MallocHeap, objectsMallocHeapSlots) \ |
| macro(Objects, MallocHeap, objectsMallocHeapElementsNonAsmJS) \ |
| macro(Objects, MallocHeap, objectsMallocHeapElementsAsmJS) \ |
| macro(Objects, NonHeap, objectsNonHeapElementsAsmJS) \ |
| macro(Objects, NonHeap, objectsNonHeapElementsMapped) \ |
| macro(Objects, NonHeap, objectsNonHeapCodeAsmJS) \ |
| macro(Objects, MallocHeap, objectsMallocHeapMisc) \ |
| \ |
| macro(Other, GCHeapUsed, shapesGCHeapTree) \ |
| macro(Other, GCHeapUsed, shapesGCHeapDict) \ |
| macro(Other, GCHeapUsed, shapesGCHeapBase) \ |
| macro(Other, MallocHeap, shapesMallocHeapTreeTables) \ |
| macro(Other, MallocHeap, shapesMallocHeapDictTables) \ |
| macro(Other, MallocHeap, shapesMallocHeapTreeKids) |
| |
| ClassInfo() |
| : FOR_EACH_SIZE(ZERO_SIZE) |
| dummy() |
| {} |
| |
| void add(const ClassInfo& other) { |
| FOR_EACH_SIZE(ADD_OTHER_SIZE) |
| } |
| |
| void subtract(const ClassInfo& other) { |
| FOR_EACH_SIZE(SUB_OTHER_SIZE) |
| } |
| |
| size_t sizeOfAllThings() const { |
| size_t n = 0; |
| FOR_EACH_SIZE(ADD_SIZE_TO_N) |
| return n; |
| } |
| |
| bool isNotable() const { |
| static const size_t NotabilityThreshold = 16 * 1024; |
| return sizeOfAllThings() >= 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) |
| } |
| |
| void addToServoSizes(ServoSizes *sizes) const { |
| FOR_EACH_SIZE(ADD_TO_SERVO_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 |
| }; |
| |
| /** |
| * 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) = delete; |
| }; |
| |
| /** Data for tracking JIT-code memory usage. */ |
| struct CodeSizes |
| { |
| #define FOR_EACH_SIZE(macro) \ |
| macro(_, NonHeap, ion) \ |
| macro(_, NonHeap, baseline) \ |
| macro(_, NonHeap, regexp) \ |
| macro(_, NonHeap, other) \ |
| macro(_, NonHeap, unused) |
| |
| CodeSizes() |
| : FOR_EACH_SIZE(ZERO_SIZE) |
| dummy() |
| {} |
| |
| void addToServoSizes(ServoSizes *sizes) const { |
| FOR_EACH_SIZE(ADD_TO_SERVO_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 GC memory usage. */ |
| struct GCSizes |
| { |
| // |nurseryDecommitted| is marked as NonHeap rather than GCHeapDecommitted |
| // because we don't consider the nursery to be part of the GC heap. |
| #define FOR_EACH_SIZE(macro) \ |
| macro(_, MallocHeap, marker) \ |
| macro(_, NonHeap, nurseryCommitted) \ |
| macro(_, NonHeap, nurseryDecommitted) \ |
| macro(_, MallocHeap, nurseryMallocedBuffers) \ |
| macro(_, MallocHeap, storeBufferVals) \ |
| macro(_, MallocHeap, storeBufferCells) \ |
| macro(_, MallocHeap, storeBufferSlots) \ |
| macro(_, MallocHeap, storeBufferWholeCells) \ |
| macro(_, MallocHeap, storeBufferGenerics) |
| |
| GCSizes() |
| : FOR_EACH_SIZE(ZERO_SIZE) |
| dummy() |
| {} |
| |
| void addToServoSizes(ServoSizes *sizes) const { |
| FOR_EACH_SIZE(ADD_TO_SERVO_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 |
| }; |
| |
| /** |
| * 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, GCHeapUsed, gcHeapLatin1) \ |
| macro(Strings, GCHeapUsed, gcHeapTwoByte) \ |
| macro(Strings, MallocHeap, mallocHeapLatin1) \ |
| macro(Strings, MallocHeap, mallocHeapTwoByte) |
| |
| 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) |
| } |
| |
| void addToServoSizes(ServoSizes *sizes) const { |
| FOR_EACH_SIZE(ADD_TO_SERVO_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) = 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(_, MallocHeap, compressed) \ |
| macro(_, MallocHeap, uncompressed) \ |
| macro(_, MallocHeap, 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--; |
| } |
| |
| void addToServoSizes(ServoSizes *sizes) const { |
| FOR_EACH_SIZE(ADD_TO_SERVO_SIZES) |
| } |
| |
| 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) = delete; |
| }; |
| |
| /** |
| * These measurements relate directly to the JSRuntime, and not to zones and |
| * compartments within it. |
| */ |
| struct RuntimeSizes |
| { |
| #define FOR_EACH_SIZE(macro) \ |
| macro(_, MallocHeap, object) \ |
| macro(_, MallocHeap, atomsTable) \ |
| macro(_, MallocHeap, contexts) \ |
| macro(_, MallocHeap, dtoa) \ |
| macro(_, MallocHeap, temporary) \ |
| macro(_, MallocHeap, interpreterStack) \ |
| macro(_, MallocHeap, mathCache) \ |
| macro(_, MallocHeap, uncompressedSourceCache) \ |
| macro(_, MallocHeap, compressedSourceSet) \ |
| macro(_, MallocHeap, scriptData) |
| |
| RuntimeSizes() |
| : FOR_EACH_SIZE(ZERO_SIZE) |
| scriptSourceInfo(), |
| code(), |
| gc(), |
| notableScriptSources() |
| { |
| allScriptSources = js_new<ScriptSourcesHashMap>(); |
| 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); |
| } |
| |
| void addToServoSizes(ServoSizes *sizes) const { |
| FOR_EACH_SIZE(ADD_TO_SERVO_SIZES) |
| scriptSourceInfo.addToServoSizes(sizes); |
| code.addToServoSizes(sizes); |
| gc.addToServoSizes(sizes); |
| } |
| |
| // 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<const char*, ScriptSourceInfo, |
| js::CStringHashPolicy, |
| js::SystemAllocPolicy> 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<NotableScriptSourceInfo, 0, js::SystemAllocPolicy> notableScriptSources; |
| |
| #undef FOR_EACH_SIZE |
| }; |
| |
| struct UnusedGCThingSizes |
| { |
| #define FOR_EACH_SIZE(macro) \ |
| macro(Other, GCHeapUnused, object) \ |
| macro(Other, GCHeapUnused, script) \ |
| macro(Other, GCHeapUnused, lazyScript) \ |
| macro(Other, GCHeapUnused, shape) \ |
| macro(Other, GCHeapUnused, baseShape) \ |
| macro(Other, GCHeapUnused, objectGroup) \ |
| macro(Other, GCHeapUnused, string) \ |
| macro(Other, GCHeapUnused, symbol) \ |
| macro(Other, GCHeapUnused, jitcode) \ |
| |
| UnusedGCThingSizes() |
| : FOR_EACH_SIZE(ZERO_SIZE) |
| dummy() |
| {} |
| |
| UnusedGCThingSizes(UnusedGCThingSizes&& other) |
| : FOR_EACH_SIZE(COPY_OTHER_SIZE) |
| dummy() |
| {} |
| |
| void addToKind(JS::TraceKind kind, intptr_t n) { |
| switch (kind) { |
| case JS::TraceKind::Object: object += n; break; |
| case JS::TraceKind::String: string += n; break; |
| case JS::TraceKind::Symbol: symbol += n; break; |
| case JS::TraceKind::Script: script += n; break; |
| case JS::TraceKind::Shape: shape += n; break; |
| case JS::TraceKind::BaseShape: baseShape += n; break; |
| case JS::TraceKind::JitCode: jitcode += n; break; |
| case JS::TraceKind::LazyScript: lazyScript += n; break; |
| case JS::TraceKind::ObjectGroup: objectGroup += n; break; |
| default: |
| MOZ_CRASH("Bad trace kind for UnusedGCThingSizes"); |
| } |
| } |
| |
| void addSizes(const UnusedGCThingSizes& other) { |
| FOR_EACH_SIZE(ADD_OTHER_SIZE) |
| } |
| |
| size_t totalSize() const { |
| size_t n = 0; |
| FOR_EACH_SIZE(ADD_SIZE_TO_N) |
| return n; |
| } |
| |
| void addToTabSizes(JS::TabSizes *sizes) const { |
| FOR_EACH_SIZE(ADD_TO_TAB_SIZES) |
| } |
| |
| void addToServoSizes(JS::ServoSizes *sizes) const { |
| FOR_EACH_SIZE(ADD_TO_SERVO_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 |
| }; |
| |
| struct ZoneStats |
| { |
| #define FOR_EACH_SIZE(macro) \ |
| macro(Other, GCHeapUsed, symbolsGCHeap) \ |
| macro(Other, GCHeapAdmin, gcHeapArenaAdmin) \ |
| macro(Other, GCHeapUsed, lazyScriptsGCHeap) \ |
| macro(Other, MallocHeap, lazyScriptsMallocHeap) \ |
| macro(Other, GCHeapUsed, jitCodesGCHeap) \ |
| macro(Other, GCHeapUsed, objectGroupsGCHeap) \ |
| macro(Other, MallocHeap, objectGroupsMallocHeap) \ |
| macro(Other, MallocHeap, typePool) \ |
| macro(Other, MallocHeap, baselineStubsOptimized) \ |
| macro(Other, MallocHeap, uniqueIdMap) |
| |
| ZoneStats() |
| : FOR_EACH_SIZE(ZERO_SIZE) |
| unusedGCThings(), |
| stringInfo(), |
| extra(), |
| allStrings(nullptr), |
| notableStrings(), |
| isTotals(true) |
| {} |
| |
| ZoneStats(ZoneStats&& other) |
| : FOR_EACH_SIZE(COPY_OTHER_SIZE) |
| unusedGCThings(mozilla::Move(other.unusedGCThings)), |
| 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) |
| unusedGCThings.addSizes(other.unusedGCThings); |
| 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) |
| unusedGCThings.addToTabSizes(sizes); |
| stringInfo.addToTabSizes(sizes); |
| } |
| |
| void addToServoSizes(JS::ServoSizes *sizes) const { |
| MOZ_ASSERT(isTotals); |
| FOR_EACH_SIZE(ADD_TO_SERVO_SIZES) |
| unusedGCThings.addToServoSizes(sizes); |
| stringInfo.addToServoSizes(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) |
| UnusedGCThingSizes unusedGCThings; |
| StringInfo stringInfo; |
| void* extra; // This field can be used by embedders. |
| |
| typedef js::HashMap<JSString*, StringInfo, |
| js::InefficientNonFlatteningStringHashPolicy, |
| js::SystemAllocPolicy> 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<NotableStringInfo, 0, js::SystemAllocPolicy> notableStrings; |
| bool isTotals; |
| |
| #undef FOR_EACH_SIZE |
| }; |
| |
| struct CompartmentStats |
| { |
| // We assume that |objectsPrivate| is on the malloc heap, but it's not |
| // actually guaranteed. But for Servo, at least, it's a moot point because |
| // it doesn't provide an ObjectPrivateVisitor so the value will always be |
| // zero. |
| #define FOR_EACH_SIZE(macro) \ |
| macro(Private, MallocHeap, objectsPrivate) \ |
| macro(Other, GCHeapUsed, scriptsGCHeap) \ |
| macro(Other, MallocHeap, scriptsMallocHeapData) \ |
| macro(Other, MallocHeap, baselineData) \ |
| macro(Other, MallocHeap, baselineStubsFallback) \ |
| macro(Other, MallocHeap, ionData) \ |
| macro(Other, MallocHeap, typeInferenceTypeScripts) \ |
| macro(Other, MallocHeap, typeInferenceAllocationSiteTables) \ |
| macro(Other, MallocHeap, typeInferenceArrayTypeTables) \ |
| macro(Other, MallocHeap, typeInferenceObjectTypeTables) \ |
| macro(Other, MallocHeap, compartmentObject) \ |
| macro(Other, MallocHeap, compartmentTables) \ |
| macro(Other, MallocHeap, innerViewsTable) \ |
| macro(Other, MallocHeap, lazyArrayBuffersTable) \ |
| macro(Other, MallocHeap, objectMetadataTable) \ |
| macro(Other, MallocHeap, crossCompartmentWrappersTable) \ |
| macro(Other, MallocHeap, regexpCompartment) \ |
| macro(Other, MallocHeap, savedStacksSet) \ |
| macro(Other, MallocHeap, nonSyntacticLexicalScopesTable) |
| |
| CompartmentStats() |
| : FOR_EACH_SIZE(ZERO_SIZE) |
| classInfo(), |
| extra(), |
| allClasses(nullptr), |
| notableClasses(), |
| isTotals(true) |
| {} |
| |
| CompartmentStats(CompartmentStats&& other) |
| : FOR_EACH_SIZE(COPY_OTHER_SIZE) |
| 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); |
| } |
| |
| ~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) |
| 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 += classInfo.sizeOfLiveGCThings(); |
| return n; |
| } |
| |
| void addToTabSizes(TabSizes* sizes) const { |
| MOZ_ASSERT(isTotals); |
| FOR_EACH_SIZE(ADD_TO_TAB_SIZES); |
| classInfo.addToTabSizes(sizes); |
| } |
| |
| void addToServoSizes(ServoSizes *sizes) const { |
| MOZ_ASSERT(isTotals); |
| FOR_EACH_SIZE(ADD_TO_SERVO_SIZES); |
| classInfo.addToServoSizes(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) |
| ClassInfo classInfo; |
| void* extra; // This field can be used by embedders. |
| |
| typedef js::HashMap<const char*, ClassInfo, |
| js::CStringHashPolicy, |
| js::SystemAllocPolicy> ClassesHashMap; |
| |
| // These are similar to |allStrings| and |notableStrings| in ZoneStats. |
| ClassesHashMap* allClasses; |
| js::Vector<NotableClassInfo, 0, js::SystemAllocPolicy> notableClasses; |
| bool isTotals; |
| |
| #undef FOR_EACH_SIZE |
| }; |
| |
| typedef js::Vector<CompartmentStats, 0, js::SystemAllocPolicy> CompartmentStatsVector; |
| typedef js::Vector<ZoneStats, 0, js::SystemAllocPolicy> ZoneStatsVector; |
| |
| struct RuntimeStats |
| { |
| // |gcHeapChunkTotal| is ignored because it's the sum of all the other |
| // values. |gcHeapGCThings| is ignored because it's the sum of some of the |
| // values from the zones and compartments. Both of those values are not |
| // reported directly, but are just present for sanity-checking other |
| // values. |
| #define FOR_EACH_SIZE(macro) \ |
| macro(_, Ignore, gcHeapChunkTotal) \ |
| macro(_, GCHeapDecommitted, gcHeapDecommittedArenas) \ |
| macro(_, GCHeapUnused, gcHeapUnusedChunks) \ |
| macro(_, GCHeapUnused, gcHeapUnusedArenas) \ |
| macro(_, GCHeapAdmin, gcHeapChunkAdmin) \ |
| macro(_, Ignore, gcHeapGCThings) |
| |
| explicit 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.totalSize() (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. |
| |
| void addToServoSizes(ServoSizes *sizes) const { |
| FOR_EACH_SIZE(ADD_TO_SERVO_SIZES) |
| runtime.addToServoSizes(sizes); |
| } |
| |
| 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_; |
| |
| explicit ObjectPrivateVisitor(GetISupportsFun getISupports) |
| : getISupports_(getISupports) |
| {} |
| }; |
| |
| extern JS_PUBLIC_API(bool) |
| CollectRuntimeStats(JSRuntime* rt, RuntimeStats* rtStats, ObjectPrivateVisitor* opv, bool anonymize); |
| |
| 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); |
| |
| extern JS_PUBLIC_API(bool) |
| AddServoSizeOf(JSRuntime *rt, mozilla::MallocSizeOf mallocSizeOf, |
| ObjectPrivateVisitor *opv, ServoSizes *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 */ |