summaryrefslogtreecommitdiffstats
path: root/js/src/vm/MemoryMetrics.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/MemoryMetrics.cpp')
-rw-r--r--js/src/vm/MemoryMetrics.cpp889
1 files changed, 889 insertions, 0 deletions
diff --git a/js/src/vm/MemoryMetrics.cpp b/js/src/vm/MemoryMetrics.cpp
new file mode 100644
index 0000000000..7c7d72394c
--- /dev/null
+++ b/js/src/vm/MemoryMetrics.cpp
@@ -0,0 +1,889 @@
+/* -*- 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/. */
+
+#include "js/MemoryMetrics.h"
+
+#include "mozilla/MathAlgorithms.h"
+
+#include <algorithm>
+
+#include "gc/GC.h"
+#include "gc/Memory.h"
+#include "gc/Nursery.h"
+#include "gc/PublicIterators.h"
+#include "jit/BaselineJIT.h"
+#include "jit/Ion.h"
+#include "js/HeapAPI.h"
+#include "util/Text.h"
+#include "vm/BigIntType.h"
+#include "vm/HelperThreadState.h"
+#include "vm/JSObject.h"
+#include "vm/JSScript.h"
+#include "vm/PropMap.h"
+#include "vm/Realm.h"
+#include "vm/Runtime.h"
+#include "vm/Shape.h"
+#include "vm/StringType.h"
+#include "wasm/WasmInstance.h"
+#include "wasm/WasmJS.h"
+#include "wasm/WasmModule.h"
+
+#include "wasm/WasmInstance-inl.h"
+
+using mozilla::MallocSizeOf;
+using mozilla::PodCopy;
+
+using namespace js;
+
+using JS::ObjectPrivateVisitor;
+using JS::RealmStats;
+using JS::RuntimeStats;
+using JS::ZoneStats;
+
+namespace js {
+
+JS_PUBLIC_API size_t MemoryReportingSundriesThreshold() { return 8 * 1024; }
+
+/* static */
+HashNumber InefficientNonFlatteningStringHashPolicy::hash(const Lookup& l) {
+ if (l->isLinear()) {
+ return HashStringChars(&l->asLinear());
+ }
+
+ // Use rope's non-copying hash function.
+ uint32_t hash = 0;
+ if (!l->asRope().hash(&hash)) {
+ MOZ_CRASH("oom");
+ }
+ return hash;
+}
+
+template <typename Char1, typename Char2>
+static bool EqualStringsPure(JSString* s1, JSString* s2) {
+ if (s1->length() != s2->length()) {
+ return false;
+ }
+
+ const Char1* c1;
+ UniquePtr<Char1[], JS::FreePolicy> ownedChars1;
+ JS::AutoCheckCannotGC nogc;
+ if (s1->isLinear()) {
+ c1 = s1->asLinear().chars<Char1>(nogc);
+ } else {
+ ownedChars1 =
+ s1->asRope().copyChars<Char1>(/* tcx */ nullptr, js::MallocArena);
+ if (!ownedChars1) {
+ MOZ_CRASH("oom");
+ }
+ c1 = ownedChars1.get();
+ }
+
+ const Char2* c2;
+ UniquePtr<Char2[], JS::FreePolicy> ownedChars2;
+ if (s2->isLinear()) {
+ c2 = s2->asLinear().chars<Char2>(nogc);
+ } else {
+ ownedChars2 =
+ s2->asRope().copyChars<Char2>(/* tcx */ nullptr, js::MallocArena);
+ if (!ownedChars2) {
+ MOZ_CRASH("oom");
+ }
+ c2 = ownedChars2.get();
+ }
+
+ return EqualChars(c1, c2, s1->length());
+}
+
+/* static */
+bool InefficientNonFlatteningStringHashPolicy::match(const JSString* const& k,
+ const Lookup& l) {
+ // We can't use js::EqualStrings, because that flattens our strings.
+ JSString* s1 = const_cast<JSString*>(k);
+ if (k->hasLatin1Chars()) {
+ return l->hasLatin1Chars() ? EqualStringsPure<Latin1Char, Latin1Char>(s1, l)
+ : EqualStringsPure<Latin1Char, char16_t>(s1, l);
+ }
+
+ return l->hasLatin1Chars() ? EqualStringsPure<char16_t, Latin1Char>(s1, l)
+ : EqualStringsPure<char16_t, char16_t>(s1, l);
+}
+
+} // namespace js
+
+namespace JS {
+
+template <typename CharT>
+static void StoreStringChars(char* buffer, size_t bufferSize, JSString* str) {
+ const CharT* chars;
+ UniquePtr<CharT[], JS::FreePolicy> ownedChars;
+ JS::AutoCheckCannotGC nogc;
+ if (str->isLinear()) {
+ chars = str->asLinear().chars<CharT>(nogc);
+ } else {
+ ownedChars =
+ str->asRope().copyChars<CharT>(/* tcx */ nullptr, js::MallocArena);
+ if (!ownedChars) {
+ MOZ_CRASH("oom");
+ }
+ chars = ownedChars.get();
+ }
+
+ // We might truncate |str| even if it's much shorter than 1024 chars, if
+ // |str| contains unicode chars. Since this is just for a memory reporter,
+ // we don't care.
+ PutEscapedString(buffer, bufferSize, chars, str->length(), /* quote */ 0);
+}
+
+NotableStringInfo::NotableStringInfo(JSString* str, const StringInfo& info)
+ : StringInfo(info), length(str->length()) {
+ size_t bufferSize = std::min(str->length() + 1, size_t(MAX_SAVED_CHARS));
+ buffer.reset(js_pod_malloc<char>(bufferSize));
+ if (!buffer) {
+ MOZ_CRASH("oom");
+ }
+
+ if (str->hasLatin1Chars()) {
+ StoreStringChars<Latin1Char>(buffer.get(), bufferSize, str);
+ } else {
+ StoreStringChars<char16_t>(buffer.get(), bufferSize, str);
+ }
+}
+
+NotableClassInfo::NotableClassInfo(const char* className, const ClassInfo& info)
+ : ClassInfo(info) {
+ className_ = DuplicateString(className);
+ if (!className_) {
+ MOZ_CRASH("oom");
+ }
+}
+
+NotableScriptSourceInfo::NotableScriptSourceInfo(const char* filename,
+ const ScriptSourceInfo& info)
+ : ScriptSourceInfo(info) {
+ filename_ = DuplicateString(filename);
+ if (!filename_) {
+ MOZ_CRASH("oom");
+ }
+}
+
+} // namespace JS
+
+typedef HashSet<ScriptSource*, DefaultHasher<ScriptSource*>, SystemAllocPolicy>
+ SourceSet;
+
+struct StatsClosure {
+ RuntimeStats* rtStats;
+ ObjectPrivateVisitor* opv;
+ SourceSet seenSources;
+ wasm::Metadata::SeenSet wasmSeenMetadata;
+ wasm::Code::SeenSet wasmSeenCode;
+ wasm::Table::SeenSet wasmSeenTables;
+ bool anonymize;
+
+ StatsClosure(RuntimeStats* rt, ObjectPrivateVisitor* v, bool anon)
+ : rtStats(rt), opv(v), anonymize(anon) {}
+};
+
+static void DecommittedPagesChunkCallback(JSRuntime* rt, void* data,
+ gc::TenuredChunk* chunk,
+ const JS::AutoRequireNoGC& nogc) {
+ size_t n = 0;
+ for (uint32_t word : chunk->decommittedPages.Storage()) {
+ n += mozilla::CountPopulation32(word);
+ }
+
+ *static_cast<size_t*>(data) += n * gc::PageSize;
+}
+
+static void StatsZoneCallback(JSRuntime* rt, void* data, Zone* zone,
+ const JS::AutoRequireNoGC& nogc) {
+ // Append a new RealmStats to the vector.
+ RuntimeStats* rtStats = static_cast<StatsClosure*>(data)->rtStats;
+
+ // CollectRuntimeStats reserves enough space.
+ MOZ_ALWAYS_TRUE(rtStats->zoneStatsVector.growBy(1));
+ ZoneStats& zStats = rtStats->zoneStatsVector.back();
+ zStats.initStrings();
+ rtStats->initExtraZoneStats(zone, &zStats, nogc);
+ rtStats->currZoneStats = &zStats;
+
+ zone->addSizeOfIncludingThis(
+ rtStats->mallocSizeOf_, &zStats.code, &zStats.regexpZone, &zStats.jitZone,
+ &zStats.baselineStubsOptimized, &zStats.uniqueIdMap,
+ &zStats.initialPropMapTable, &zStats.shapeTables,
+ &rtStats->runtime.atomsMarkBitmaps, &zStats.compartmentObjects,
+ &zStats.crossCompartmentWrappersTables, &zStats.compartmentsPrivateData,
+ &zStats.scriptCountsMap);
+}
+
+static void StatsRealmCallback(JSContext* cx, void* data, Realm* realm,
+ const JS::AutoRequireNoGC& nogc) {
+ // Append a new RealmStats to the vector.
+ RuntimeStats* rtStats = static_cast<StatsClosure*>(data)->rtStats;
+
+ // CollectRuntimeStats reserves enough space.
+ MOZ_ALWAYS_TRUE(rtStats->realmStatsVector.growBy(1));
+ RealmStats& realmStats = rtStats->realmStatsVector.back();
+ realmStats.initClasses();
+ rtStats->initExtraRealmStats(realm, &realmStats, nogc);
+
+ realm->setRealmStats(&realmStats);
+
+ // Measure the realm object itself, and things hanging off it.
+ realm->addSizeOfIncludingThis(
+ rtStats->mallocSizeOf_, &realmStats.realmObject, &realmStats.realmTables,
+ &realmStats.innerViewsTable, &realmStats.objectMetadataTable,
+ &realmStats.savedStacksSet, &realmStats.nonSyntacticLexicalScopesTable,
+ &realmStats.jitRealm);
+}
+
+static void StatsArenaCallback(JSRuntime* rt, void* data, gc::Arena* arena,
+ JS::TraceKind traceKind, size_t thingSize,
+ const JS::AutoRequireNoGC& nogc) {
+ RuntimeStats* rtStats = static_cast<StatsClosure*>(data)->rtStats;
+
+ // The admin space includes (a) the header fields and (b) the padding
+ // between the end of the header fields and the first GC thing.
+ size_t allocationSpace = gc::Arena::thingsSpan(arena->getAllocKind());
+ rtStats->currZoneStats->gcHeapArenaAdmin += gc::ArenaSize - allocationSpace;
+
+ // We don't call the callback on unused things. So we compute the
+ // unused space like this: arenaUnused = maxArenaUnused - arenaUsed.
+ // We do this by setting arenaUnused to maxArenaUnused here, and then
+ // subtracting thingSize for every used cell, in StatsCellCallback().
+ rtStats->currZoneStats->unusedGCThings.addToKind(traceKind, allocationSpace);
+}
+
+// FineGrained is used for normal memory reporting. CoarseGrained is used by
+// AddSizeOfTab(), which aggregates all the measurements into a handful of
+// high-level numbers, which means that fine-grained reporting would be a waste
+// of effort.
+enum Granularity { FineGrained, CoarseGrained };
+
+static void AddClassInfo(Granularity granularity, RealmStats& realmStats,
+ const char* className, JS::ClassInfo& info) {
+ if (granularity == FineGrained) {
+ if (!className) {
+ className = "<no class name>";
+ }
+ RealmStats::ClassesHashMap::AddPtr p =
+ realmStats.allClasses->lookupForAdd(className);
+ if (!p) {
+ bool ok = realmStats.allClasses->add(p, className, info);
+ // Ignore failure -- we just won't record the
+ // object/shape/base-shape as notable.
+ (void)ok;
+ } else {
+ p->value().add(info);
+ }
+ }
+}
+
+template <Granularity granularity>
+static void CollectScriptSourceStats(StatsClosure* closure, ScriptSource* ss) {
+ RuntimeStats* rtStats = closure->rtStats;
+
+ SourceSet::AddPtr entry = closure->seenSources.lookupForAdd(ss);
+ if (entry) {
+ return;
+ }
+
+ bool ok = closure->seenSources.add(entry, ss);
+ (void)ok; // Not much to be done on failure.
+
+ JS::ScriptSourceInfo info; // This zeroes all the sizes.
+ ss->addSizeOfIncludingThis(rtStats->mallocSizeOf_, &info);
+
+ rtStats->runtime.scriptSourceInfo.add(info);
+
+ if (granularity == FineGrained) {
+ const char* filename = ss->filename();
+ if (!filename) {
+ filename = "<no filename>";
+ }
+
+ JS::RuntimeSizes::ScriptSourcesHashMap::AddPtr p =
+ rtStats->runtime.allScriptSources->lookupForAdd(filename);
+ if (!p) {
+ bool ok = rtStats->runtime.allScriptSources->add(p, filename, info);
+ // Ignore failure -- we just won't record the script source as notable.
+ (void)ok;
+ } 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.
+template <Granularity granularity>
+static void StatsCellCallback(JSRuntime* rt, void* data, JS::GCCellPtr cellptr,
+ size_t thingSize,
+ const JS::AutoRequireNoGC& nogc) {
+ StatsClosure* closure = static_cast<StatsClosure*>(data);
+ RuntimeStats* rtStats = closure->rtStats;
+ ZoneStats* zStats = rtStats->currZoneStats;
+ JS::TraceKind kind = cellptr.kind();
+ switch (kind) {
+ case JS::TraceKind::Object: {
+ JSObject* obj = &cellptr.as<JSObject>();
+ RealmStats& realmStats = obj->maybeCCWRealm()->realmStats();
+ JS::ClassInfo info; // This zeroes all the sizes.
+ info.objectsGCHeap += thingSize;
+
+ if (!obj->isTenured()) {
+ info.objectsGCHeap += Nursery::nurseryCellHeaderSize();
+ }
+
+ obj->addSizeOfExcludingThis(rtStats->mallocSizeOf_, &info,
+ &rtStats->runtime);
+
+ // These classes require special handling due to shared resources which
+ // we must be careful not to report twice.
+ if (obj->is<WasmModuleObject>()) {
+ const wasm::Module& module = obj->as<WasmModuleObject>().module();
+ if (ScriptSource* ss = module.metadata().maybeScriptSource()) {
+ CollectScriptSourceStats<granularity>(closure, ss);
+ }
+ module.addSizeOfMisc(rtStats->mallocSizeOf_, &closure->wasmSeenMetadata,
+ &closure->wasmSeenCode,
+ &info.objectsNonHeapCodeWasm,
+ &info.objectsMallocHeapMisc);
+ } else if (obj->is<WasmInstanceObject>()) {
+ wasm::Instance& instance = obj->as<WasmInstanceObject>().instance();
+ if (ScriptSource* ss = instance.metadata().maybeScriptSource()) {
+ CollectScriptSourceStats<granularity>(closure, ss);
+ }
+ instance.addSizeOfMisc(
+ rtStats->mallocSizeOf_, &closure->wasmSeenMetadata,
+ &closure->wasmSeenCode, &closure->wasmSeenTables,
+ &info.objectsNonHeapCodeWasm, &info.objectsMallocHeapMisc);
+ }
+
+ realmStats.classInfo.add(info);
+
+ const JSClass* clasp = obj->getClass();
+ const char* className = clasp->name;
+ AddClassInfo(granularity, realmStats, className, info);
+
+ if (ObjectPrivateVisitor* opv = closure->opv) {
+ nsISupports* iface;
+ if (opv->getISupports_(obj, &iface) && iface) {
+ realmStats.objectsPrivate += opv->sizeOfIncludingThis(iface);
+ }
+ }
+ break;
+ }
+
+ case JS::TraceKind::Script: {
+ BaseScript* base = &cellptr.as<BaseScript>();
+ RealmStats& realmStats = base->realm()->realmStats();
+ realmStats.scriptsGCHeap += thingSize;
+ realmStats.scriptsMallocHeapData +=
+ base->sizeOfExcludingThis(rtStats->mallocSizeOf_);
+ if (base->hasJitScript()) {
+ JSScript* script = static_cast<JSScript*>(base);
+ script->addSizeOfJitScript(rtStats->mallocSizeOf_,
+ &realmStats.jitScripts,
+ &realmStats.baselineStubsFallback);
+ jit::AddSizeOfBaselineData(script, rtStats->mallocSizeOf_,
+ &realmStats.baselineData);
+ realmStats.ionData +=
+ jit::SizeOfIonData(script, rtStats->mallocSizeOf_);
+ }
+ CollectScriptSourceStats<granularity>(closure, base->scriptSource());
+ break;
+ }
+
+ case JS::TraceKind::String: {
+ JSString* str = &cellptr.as<JSString>();
+ size_t size = thingSize;
+ if (!str->isTenured()) {
+ size += Nursery::nurseryCellHeaderSize();
+ }
+
+ JS::StringInfo info;
+ if (str->hasLatin1Chars()) {
+ info.gcHeapLatin1 = size;
+ info.mallocHeapLatin1 =
+ str->sizeOfExcludingThis(rtStats->mallocSizeOf_);
+ } else {
+ info.gcHeapTwoByte = size;
+ info.mallocHeapTwoByte =
+ str->sizeOfExcludingThis(rtStats->mallocSizeOf_);
+ }
+ info.numCopies = 1;
+
+ zStats->stringInfo.add(info);
+
+ // The primary use case for anonymization is automated crash submission
+ // (to help detect OOM crashes). In that case, we don't want to pay the
+ // memory cost required to do notable string detection.
+ if (granularity == FineGrained && !closure->anonymize) {
+ ZoneStats::StringsHashMap::AddPtr p =
+ zStats->allStrings->lookupForAdd(str);
+ if (!p) {
+ bool ok = zStats->allStrings->add(p, str, info);
+ // Ignore failure -- we just won't record the string as notable.
+ (void)ok;
+ } else {
+ p->value().add(info);
+ }
+ }
+ break;
+ }
+
+ case JS::TraceKind::Symbol:
+ zStats->symbolsGCHeap += thingSize;
+ break;
+
+ case JS::TraceKind::BigInt: {
+ JS::BigInt* bi = &cellptr.as<BigInt>();
+ size_t size = thingSize;
+ if (!bi->isTenured()) {
+ size += Nursery::nurseryCellHeaderSize();
+ }
+ zStats->bigIntsGCHeap += size;
+ zStats->bigIntsMallocHeap +=
+ bi->sizeOfExcludingThis(rtStats->mallocSizeOf_);
+ break;
+ }
+
+ case JS::TraceKind::BaseShape: {
+ JS::ShapeInfo info; // This zeroes all the sizes.
+ info.shapesGCHeapBase += thingSize;
+ // No malloc-heap measurements.
+
+ zStats->shapeInfo.add(info);
+ break;
+ }
+
+ case JS::TraceKind::GetterSetter: {
+ zStats->getterSettersGCHeap += thingSize;
+ break;
+ }
+
+ case JS::TraceKind::PropMap: {
+ PropMap* map = &cellptr.as<PropMap>();
+ if (map->isDictionary()) {
+ zStats->dictPropMapsGCHeap += thingSize;
+ } else if (map->isCompact()) {
+ zStats->compactPropMapsGCHeap += thingSize;
+ } else {
+ MOZ_ASSERT(map->isNormal());
+ zStats->normalPropMapsGCHeap += thingSize;
+ }
+ map->addSizeOfExcludingThis(rtStats->mallocSizeOf_,
+ &zStats->propMapChildren,
+ &zStats->propMapTables);
+ break;
+ }
+
+ case JS::TraceKind::JitCode: {
+ zStats->jitCodesGCHeap += thingSize;
+ // The code for a script is counted in ExecutableAllocator::sizeOfCode().
+ break;
+ }
+
+ case JS::TraceKind::Shape: {
+ Shape* shape = &cellptr.as<Shape>();
+
+ JS::ShapeInfo info; // This zeroes all the sizes.
+ if (shape->isDictionary()) {
+ info.shapesGCHeapDict += thingSize;
+ } else {
+ info.shapesGCHeapShared += thingSize;
+ }
+ shape->addSizeOfExcludingThis(rtStats->mallocSizeOf_, &info);
+ zStats->shapeInfo.add(info);
+ break;
+ }
+
+ case JS::TraceKind::Scope: {
+ Scope* scope = &cellptr.as<Scope>();
+ zStats->scopesGCHeap += thingSize;
+ zStats->scopesMallocHeap +=
+ scope->sizeOfExcludingThis(rtStats->mallocSizeOf_);
+ break;
+ }
+
+ case JS::TraceKind::RegExpShared: {
+ auto regexp = &cellptr.as<RegExpShared>();
+ zStats->regExpSharedsGCHeap += thingSize;
+ zStats->regExpSharedsMallocHeap +=
+ regexp->sizeOfExcludingThis(rtStats->mallocSizeOf_);
+ break;
+ }
+
+ default:
+ MOZ_CRASH("invalid traceKind in StatsCellCallback");
+ }
+
+ // Yes, this is a subtraction: see StatsArenaCallback() for details.
+ zStats->unusedGCThings.addToKind(kind, -thingSize);
+}
+
+void ZoneStats::initStrings() {
+ isTotals = false;
+ allStrings.emplace();
+}
+
+void RealmStats::initClasses() {
+ isTotals = false;
+ allClasses.emplace();
+}
+
+static bool FindNotableStrings(ZoneStats& zStats) {
+ using namespace JS;
+
+ // We should only run FindNotableStrings once per ZoneStats object.
+ MOZ_ASSERT(zStats.notableStrings.empty());
+
+ for (ZoneStats::StringsHashMap::Range r = zStats.allStrings->all();
+ !r.empty(); r.popFront()) {
+ JSString* str = r.front().key();
+ StringInfo& info = r.front().value();
+
+ if (!info.isNotable()) {
+ continue;
+ }
+
+ if (!zStats.notableStrings.emplaceBack(str, info)) {
+ return false;
+ }
+
+ // We're moving this string from a non-notable to a notable bucket, so
+ // subtract it out of the non-notable tallies.
+ zStats.stringInfo.subtract(info);
+ }
+ // Release |allStrings| now, rather than waiting for zStats's destruction, to
+ // reduce peak memory consumption during reporting.
+ zStats.allStrings.reset();
+ return true;
+}
+
+static bool FindNotableClasses(RealmStats& realmStats) {
+ using namespace JS;
+
+ // We should only run FindNotableClasses once per ZoneStats object.
+ MOZ_ASSERT(realmStats.notableClasses.empty());
+
+ for (RealmStats::ClassesHashMap::Range r = realmStats.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 (!realmStats.notableClasses.emplaceBack(className, info)) {
+ return false;
+ }
+
+ // We're moving this class from a non-notable to a notable bucket, so
+ // subtract it out of the non-notable tallies.
+ realmStats.classInfo.subtract(info);
+ }
+ // Release |allClasses| now, rather than waiting for zStats's destruction, to
+ // reduce peak memory consumption during reporting.
+ realmStats.allClasses.reset();
+ return true;
+}
+
+static bool FindNotableScriptSources(JS::RuntimeSizes& runtime) {
+ using namespace JS;
+
+ // We should only run FindNotableScriptSources once per RuntimeSizes.
+ MOZ_ASSERT(runtime.notableScriptSources.empty());
+
+ for (RuntimeSizes::ScriptSourcesHashMap::Range r =
+ runtime.allScriptSources->all();
+ !r.empty(); r.popFront()) {
+ const char* filename = r.front().key();
+ ScriptSourceInfo& info = r.front().value();
+
+ if (!info.isNotable()) {
+ continue;
+ }
+
+ if (!runtime.notableScriptSources.emplaceBack(filename, info)) {
+ return false;
+ }
+
+ // We're moving this script source from a non-notable to a notable
+ // bucket, so subtract its sizes from the non-notable tallies.
+ runtime.scriptSourceInfo.subtract(info);
+ }
+ // Release |allScriptSources| now, rather than waiting for zStats's
+ // destruction, to reduce peak memory consumption during reporting.
+ runtime.allScriptSources.reset();
+ return true;
+}
+
+static bool CollectRuntimeStatsHelper(JSContext* cx, RuntimeStats* rtStats,
+ ObjectPrivateVisitor* opv, bool anonymize,
+ IterateCellCallback statsCellCallback) {
+ // Finish any ongoing incremental GC that may change the data we're gathering
+ // and ensure that we don't do anything that could start another one.
+ gc::FinishGC(cx);
+ JS::AutoAssertNoGC nogc(cx);
+
+ // Wait for any background tasks to finish.
+ WaitForAllHelperThreads();
+
+ JSRuntime* rt = cx->runtime();
+ if (!rtStats->realmStatsVector.reserve(rt->numRealms)) {
+ return false;
+ }
+
+ size_t totalZones = rt->gc.zones().length();
+ if (!rtStats->zoneStatsVector.reserve(totalZones)) {
+ return false;
+ }
+
+ rtStats->gcHeapChunkTotal =
+ size_t(JS_GetGCParameter(cx, JSGC_TOTAL_CHUNKS)) * gc::ChunkSize;
+
+ rtStats->gcHeapUnusedChunks =
+ size_t(JS_GetGCParameter(cx, JSGC_UNUSED_CHUNKS)) * gc::ChunkSize;
+
+ if (js::gc::DecommitEnabled()) {
+ IterateChunks(cx, &rtStats->gcHeapDecommittedPages,
+ DecommittedPagesChunkCallback);
+ }
+
+ // Take the per-compartment measurements.
+ StatsClosure closure(rtStats, opv, anonymize);
+ IterateHeapUnbarriered(cx, &closure, StatsZoneCallback, StatsRealmCallback,
+ StatsArenaCallback, statsCellCallback);
+
+ // Take the "explicit/js/runtime/" measurements.
+ rt->addSizeOfIncludingThis(rtStats->mallocSizeOf_, &rtStats->runtime);
+
+ if (!FindNotableScriptSources(rtStats->runtime)) {
+ return false;
+ }
+
+ JS::ZoneStatsVector& zs = rtStats->zoneStatsVector;
+ ZoneStats& zTotals = rtStats->zTotals;
+
+ // We don't look for notable strings for zTotals. So we first sum all the
+ // zones' measurements to get the totals. Then we find the notable strings
+ // within each zone.
+ for (size_t i = 0; i < zs.length(); i++) {
+ zTotals.addSizes(zs[i]);
+ }
+
+ for (size_t i = 0; i < zs.length(); i++) {
+ if (!FindNotableStrings(zs[i])) {
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(!zTotals.allStrings);
+
+ JS::RealmStatsVector& realmStats = rtStats->realmStatsVector;
+ RealmStats& realmTotals = rtStats->realmTotals;
+
+ // As with the zones, we sum all realms first, and then get the
+ // notable classes within each zone.
+ for (size_t i = 0; i < realmStats.length(); i++) {
+ realmTotals.addSizes(realmStats[i]);
+ }
+
+ for (size_t i = 0; i < realmStats.length(); i++) {
+ if (!FindNotableClasses(realmStats[i])) {
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(!realmTotals.allClasses);
+
+ rtStats->gcHeapGCThings = rtStats->zTotals.sizeOfLiveGCThings() +
+ rtStats->realmTotals.sizeOfLiveGCThings();
+
+#ifdef DEBUG
+ // Check that the in-arena measurements look ok.
+ size_t totalArenaSize = rtStats->zTotals.gcHeapArenaAdmin +
+ rtStats->zTotals.unusedGCThings.totalSize() +
+ rtStats->gcHeapGCThings;
+ MOZ_ASSERT(totalArenaSize % gc::ArenaSize == 0);
+#endif
+
+ for (RealmsIter realm(rt); !realm.done(); realm.next()) {
+ realm->nullRealmStats();
+ }
+
+ size_t numDirtyChunks =
+ (rtStats->gcHeapChunkTotal - rtStats->gcHeapUnusedChunks) / gc::ChunkSize;
+ size_t perChunkAdmin =
+ sizeof(gc::TenuredChunk) - (sizeof(gc::Arena) * gc::ArenasPerChunk);
+ rtStats->gcHeapChunkAdmin = numDirtyChunks * perChunkAdmin;
+
+ // |gcHeapUnusedArenas| is the only thing left. Compute it in terms of
+ // all the others. See the comment in RuntimeStats for explanation.
+ rtStats->gcHeapUnusedArenas =
+ rtStats->gcHeapChunkTotal - rtStats->gcHeapDecommittedPages -
+ rtStats->gcHeapUnusedChunks -
+ rtStats->zTotals.unusedGCThings.totalSize() - rtStats->gcHeapChunkAdmin -
+ rtStats->zTotals.gcHeapArenaAdmin - rtStats->gcHeapGCThings;
+ return true;
+}
+
+JS_PUBLIC_API bool JS::CollectGlobalStats(GlobalStats* gStats) {
+ AutoLockHelperThreadState lock;
+
+ // HelperThreadState holds data that is not part of a Runtime. This does
+ // not include data is is currently being processed by a HelperThread.
+ if (IsHelperThreadStateInitialized()) {
+ HelperThreadState().addSizeOfIncludingThis(gStats, lock);
+ }
+
+ return true;
+}
+
+JS_PUBLIC_API bool JS::CollectRuntimeStats(JSContext* cx, RuntimeStats* rtStats,
+ ObjectPrivateVisitor* opv,
+ bool anonymize) {
+ return CollectRuntimeStatsHelper(cx, rtStats, opv, anonymize,
+ StatsCellCallback<FineGrained>);
+}
+
+JS_PUBLIC_API size_t JS::SystemCompartmentCount(JSContext* cx) {
+ size_t n = 0;
+ for (CompartmentsIter comp(cx->runtime()); !comp.done(); comp.next()) {
+ if (IsSystemCompartment(comp)) {
+ ++n;
+ }
+ }
+ return n;
+}
+
+JS_PUBLIC_API size_t JS::UserCompartmentCount(JSContext* cx) {
+ size_t n = 0;
+ for (CompartmentsIter comp(cx->runtime()); !comp.done(); comp.next()) {
+ if (!IsSystemCompartment(comp)) {
+ ++n;
+ }
+ }
+ return n;
+}
+
+JS_PUBLIC_API size_t JS::SystemRealmCount(JSContext* cx) {
+ size_t n = 0;
+ for (RealmsIter realm(cx->runtime()); !realm.done(); realm.next()) {
+ if (realm->isSystem()) {
+ ++n;
+ }
+ }
+ return n;
+}
+
+JS_PUBLIC_API size_t JS::UserRealmCount(JSContext* cx) {
+ size_t n = 0;
+ for (RealmsIter realm(cx->runtime()); !realm.done(); realm.next()) {
+ if (!realm->isSystem()) {
+ ++n;
+ }
+ }
+ return n;
+}
+
+JS_PUBLIC_API size_t JS::PeakSizeOfTemporary(const JSContext* cx) {
+ return cx->tempLifoAlloc().peakSizeOfExcludingThis();
+}
+
+namespace JS {
+
+class SimpleJSRuntimeStats : public JS::RuntimeStats {
+ public:
+ explicit SimpleJSRuntimeStats(MallocSizeOf mallocSizeOf)
+ : JS::RuntimeStats(mallocSizeOf) {}
+
+ virtual void initExtraZoneStats(JS::Zone* zone, JS::ZoneStats* zStats,
+ const JS::AutoRequireNoGC& nogc) override {}
+
+ virtual void initExtraRealmStats(Realm* realm, JS::RealmStats* realmStats,
+ const JS::AutoRequireNoGC& nogc) override {}
+};
+
+JS_PUBLIC_API bool AddSizeOfTab(JSContext* cx, HandleObject obj,
+ MallocSizeOf mallocSizeOf,
+ ObjectPrivateVisitor* opv, TabSizes* sizes) {
+ SimpleJSRuntimeStats rtStats(mallocSizeOf);
+
+ JS::Zone* zone = GetObjectZone(obj);
+
+ size_t numRealms = 0;
+ for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) {
+ numRealms += comp->realms().length();
+ }
+
+ if (!rtStats.realmStatsVector.reserve(numRealms)) {
+ return false;
+ }
+
+ if (!rtStats.zoneStatsVector.reserve(1)) {
+ return false;
+ }
+
+ // Take the per-compartment measurements. No need to anonymize because
+ // these measurements will be aggregated.
+ StatsClosure closure(&rtStats, opv, /* anonymize = */ false);
+ IterateHeapUnbarrieredForZone(cx, zone, &closure, StatsZoneCallback,
+ StatsRealmCallback, StatsArenaCallback,
+ StatsCellCallback<CoarseGrained>);
+
+ MOZ_ASSERT(rtStats.zoneStatsVector.length() == 1);
+ rtStats.zTotals.addSizes(rtStats.zoneStatsVector[0]);
+
+ for (size_t i = 0; i < rtStats.realmStatsVector.length(); i++) {
+ rtStats.realmTotals.addSizes(rtStats.realmStatsVector[i]);
+ }
+
+ for (RealmsInZoneIter realm(zone); !realm.done(); realm.next()) {
+ realm->nullRealmStats();
+ }
+
+ rtStats.zTotals.addToTabSizes(sizes);
+ rtStats.realmTotals.addToTabSizes(sizes);
+
+ return true;
+}
+
+JS_PUBLIC_API bool AddServoSizeOf(JSContext* cx, MallocSizeOf mallocSizeOf,
+ ObjectPrivateVisitor* opv,
+ ServoSizes* sizes) {
+ SimpleJSRuntimeStats rtStats(mallocSizeOf);
+
+ // No need to anonymize because the results will be aggregated.
+ if (!CollectRuntimeStatsHelper(cx, &rtStats, opv, /* anonymize = */ false,
+ StatsCellCallback<CoarseGrained>))
+ return false;
+
+#ifdef DEBUG
+ size_t gcHeapTotalOriginal = sizes->gcHeapUsed + sizes->gcHeapUnused +
+ sizes->gcHeapAdmin + sizes->gcHeapDecommitted;
+#endif
+
+ rtStats.addToServoSizes(sizes);
+ rtStats.zTotals.addToServoSizes(sizes);
+ rtStats.realmTotals.addToServoSizes(sizes);
+
+#ifdef DEBUG
+ size_t gcHeapTotal = sizes->gcHeapUsed + sizes->gcHeapUnused +
+ sizes->gcHeapAdmin + sizes->gcHeapDecommitted;
+ MOZ_ASSERT(rtStats.gcHeapChunkTotal == gcHeapTotal - gcHeapTotalOriginal);
+#endif
+
+ return true;
+}
+
+} // namespace JS