blob: 1e9c53fa59897695ce4a623df85014ea2561bb7d [file] [log] [blame]
/* -*- 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/. */
#include "js/MemoryMetrics.h"
#include "mozilla/DebugOnly.h"
#include "jsapi.h"
#include "jscompartment.h"
#include "jsgc.h"
#include "jsobj.h"
#include "jsscript.h"
#include "jit/BaselineJIT.h"
#include "jit/Ion.h"
#include "vm/ArrayObject.h"
#include "vm/Runtime.h"
#include "vm/Shape.h"
#include "vm/String.h"
#include "vm/Symbol.h"
#include "vm/WrapperObject.h"
using mozilla::DebugOnly;
using mozilla::MallocSizeOf;
using mozilla::Move;
using mozilla::PodCopy;
using mozilla::PodEqual;
using namespace js;
using JS::RuntimeStats;
using JS::ObjectPrivateVisitor;
using JS::ZoneStats;
using JS::CompartmentStats;
namespace js {
JS_FRIEND_API(size_t)
MemoryReportingSundriesThreshold()
{
return 8 * 1024;
}
template <typename CharT>
static uint32_t
HashStringChars(JSString* s)
{
ScopedJSFreePtr<CharT> ownedChars;
const CharT* chars;
JS::AutoCheckCannotGC nogc;
if (s->isLinear()) {
chars = s->asLinear().chars<CharT>(nogc);
} else {
// Slowest hash function evar!
if (!s->asRope().copyChars<CharT>(/* tcx */ nullptr, ownedChars))
MOZ_CRASH("oom");
chars = ownedChars;
}
return mozilla::HashString(chars, s->length());
}
/* static */ HashNumber
InefficientNonFlatteningStringHashPolicy::hash(const Lookup& l)
{
return l->hasLatin1Chars()
? HashStringChars<Latin1Char>(l)
: HashStringChars<char16_t>(l);
}
template <typename Char1, typename Char2>
static bool
EqualStringsPure(JSString* s1, JSString* s2)
{
if (s1->length() != s2->length())
return false;
const Char1* c1;
ScopedJSFreePtr<Char1> ownedChars1;
JS::AutoCheckCannotGC nogc;
if (s1->isLinear()) {
c1 = s1->asLinear().chars<Char1>(nogc);
} else {
if (!s1->asRope().copyChars<Char1>(/* tcx */ nullptr, ownedChars1))
MOZ_CRASH("oom");
c1 = ownedChars1;
}
const Char2* c2;
ScopedJSFreePtr<Char2> ownedChars2;
if (s2->isLinear()) {
c2 = s2->asLinear().chars<Char2>(nogc);
} else {
if (!s2->asRope().copyChars<Char2>(/* tcx */ nullptr, ownedChars2))
MOZ_CRASH("oom");
c2 = ownedChars2;
}
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);
}
/* static */ HashNumber
CStringHashPolicy::hash(const Lookup& l)
{
return mozilla::HashString(l);
}
/* static */ bool
CStringHashPolicy::match(const char* const& k, const Lookup& l)
{
return strcmp(k, l) == 0;
}
} // namespace js
namespace JS {
NotableStringInfo::NotableStringInfo()
: StringInfo(),
buffer(0),
length(0)
{
}
template <typename CharT>
static void
StoreStringChars(char* buffer, size_t bufferSize, JSString* str)
{
const CharT* chars;
ScopedJSFreePtr<CharT> ownedChars;
JS::AutoCheckCannotGC nogc;
if (str->isLinear()) {
chars = str->asLinear().chars<CharT>(nogc);
} else {
if (!str->asRope().copyChars<CharT>(/* tcx */ nullptr, ownedChars))
MOZ_CRASH("oom");
chars = ownedChars;
}
// 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 = Min(str->length() + 1, size_t(MAX_SAVED_CHARS));
buffer = js_pod_malloc<char>(bufferSize);
if (!buffer) {
MOZ_CRASH("oom");
}
if (str->hasLatin1Chars())
StoreStringChars<Latin1Char>(buffer, bufferSize, str);
else
StoreStringChars<char16_t>(buffer, bufferSize, str);
}
NotableStringInfo::NotableStringInfo(NotableStringInfo&& info)
: StringInfo(Move(info)),
length(info.length)
{
buffer = info.buffer;
info.buffer = nullptr;
}
NotableStringInfo& NotableStringInfo::operator=(NotableStringInfo&& info)
{
MOZ_ASSERT(this != &info, "self-move assignment is prohibited");
this->~NotableStringInfo();
new (this) NotableStringInfo(Move(info));
return *this;
}
NotableClassInfo::NotableClassInfo()
: ClassInfo(),
className_(nullptr)
{
}
NotableClassInfo::NotableClassInfo(const char* className, const ClassInfo& info)
: ClassInfo(info)
{
size_t bytes = strlen(className) + 1;
className_ = js_pod_malloc<char>(bytes);
if (!className_)
MOZ_CRASH("oom");
PodCopy(className_, className, bytes);
}
NotableClassInfo::NotableClassInfo(NotableClassInfo&& info)
: ClassInfo(Move(info))
{
className_ = info.className_;
info.className_ = nullptr;
}
NotableClassInfo& NotableClassInfo::operator=(NotableClassInfo&& info)
{
MOZ_ASSERT(this != &info, "self-move assignment is prohibited");
this->~NotableClassInfo();
new (this) NotableClassInfo(Move(info));
return *this;
}
NotableScriptSourceInfo::NotableScriptSourceInfo()
: ScriptSourceInfo(),
filename_(nullptr)
{
}
NotableScriptSourceInfo::NotableScriptSourceInfo(const char* filename, const ScriptSourceInfo& info)
: ScriptSourceInfo(info)
{
size_t bytes = strlen(filename) + 1;
filename_ = js_pod_malloc<char>(bytes);
if (!filename_)
MOZ_CRASH("oom");
PodCopy(filename_, filename, bytes);
}
NotableScriptSourceInfo::NotableScriptSourceInfo(NotableScriptSourceInfo&& info)
: ScriptSourceInfo(Move(info))
{
filename_ = info.filename_;
info.filename_ = nullptr;
}
NotableScriptSourceInfo& NotableScriptSourceInfo::operator=(NotableScriptSourceInfo&& info)
{
MOZ_ASSERT(this != &info, "self-move assignment is prohibited");
this->~NotableScriptSourceInfo();
new (this) NotableScriptSourceInfo(Move(info));
return *this;
}
} // namespace JS
typedef HashSet<ScriptSource*, DefaultHasher<ScriptSource*>, SystemAllocPolicy> SourceSet;
struct StatsClosure
{
RuntimeStats* rtStats;
ObjectPrivateVisitor* opv;
SourceSet seenSources;
bool anonymize;
StatsClosure(RuntimeStats* rt, ObjectPrivateVisitor* v, bool anon)
: rtStats(rt),
opv(v),
anonymize(anon)
{}
bool init() {
return seenSources.init();
}
};
static void
DecommittedArenasChunkCallback(JSRuntime* rt, void* data, gc::Chunk* chunk)
{
// This case is common and fast to check. Do it first.
if (chunk->decommittedArenas.isAllClear())
return;
size_t n = 0;
for (size_t i = 0; i < gc::ArenasPerChunk; i++) {
if (chunk->decommittedArenas.get(i))
n += gc::ArenaSize;
}
MOZ_ASSERT(n > 0);
*static_cast<size_t*>(data) += n;
}
static void
StatsZoneCallback(JSRuntime* rt, void* data, Zone* zone)
{
// Append a new CompartmentStats 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();
if (!zStats.initStrings(rt))
MOZ_CRASH("oom");
rtStats->initExtraZoneStats(zone, &zStats);
rtStats->currZoneStats = &zStats;
zone->addSizeOfIncludingThis(rtStats->mallocSizeOf_,
&zStats.typePool,
&zStats.baselineStubsOptimized,
&zStats.uniqueIdMap);
}
static void
StatsCompartmentCallback(JSRuntime* rt, void* data, JSCompartment* compartment)
{
// Append a new CompartmentStats to the vector.
RuntimeStats* rtStats = static_cast<StatsClosure*>(data)->rtStats;
// CollectRuntimeStats reserves enough space.
MOZ_ALWAYS_TRUE(rtStats->compartmentStatsVector.growBy(1));
CompartmentStats& cStats = rtStats->compartmentStatsVector.back();
if (!cStats.initClasses(rt))
MOZ_CRASH("oom");
rtStats->initExtraCompartmentStats(compartment, &cStats);
compartment->compartmentStats = &cStats;
// Measure the compartment object itself, and things hanging off it.
compartment->addSizeOfIncludingThis(rtStats->mallocSizeOf_,
&cStats.typeInferenceAllocationSiteTables,
&cStats.typeInferenceArrayTypeTables,
&cStats.typeInferenceObjectTypeTables,
&cStats.compartmentObject,
&cStats.compartmentTables,
&cStats.innerViewsTable,
&cStats.lazyArrayBuffersTable,
&cStats.objectMetadataTable,
&cStats.crossCompartmentWrappersTable,
&cStats.regexpCompartment,
&cStats.savedStacksSet,
&cStats.nonSyntacticLexicalScopesTable);
}
static void
StatsArenaCallback(JSRuntime* rt, void* data, gc::Arena* arena,
JS::TraceKind traceKind, size_t thingSize)
{
RuntimeStats* rtStats = static_cast<StatsClosure*>(data)->rtStats;
// The admin space includes (a) the header and (b) the padding between the
// end of the header and the start of the first GC thing.
size_t allocationSpace = arena->thingsSpan(thingSize);
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);
}
static CompartmentStats*
GetCompartmentStats(JSCompartment* comp)
{
return static_cast<CompartmentStats*>(comp->compartmentStats);
}
// 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, CompartmentStats* cStats, const char* className,
JS::ClassInfo& info)
{
if (granularity == FineGrained) {
if (!className)
className = "<no class name>";
CompartmentStats::ClassesHashMap::AddPtr p =
cStats->allClasses->lookupForAdd(className);
if (!p) {
// Ignore failure -- we just won't record the
// object/shape/base-shape as notable.
(void)cStats->allClasses->add(p, className, info);
} else {
p->value().add(info);
}
}
}
// The various kinds of hashing are expensive, and the results are unused when
// doing coarse-grained measurements. Skipping them more than doubles the
// profile speed for complex pages such as gmail.com.
template <Granularity granularity>
static void
StatsCellCallback(JSRuntime* rt, void* data, void* thing, JS::TraceKind traceKind,
size_t thingSize)
{
StatsClosure* closure = static_cast<StatsClosure*>(data);
RuntimeStats* rtStats = closure->rtStats;
ZoneStats* zStats = rtStats->currZoneStats;
switch (traceKind) {
case JS::TraceKind::Object: {
JSObject* obj = static_cast<JSObject*>(thing);
CompartmentStats* cStats = GetCompartmentStats(obj->compartment());
JS::ClassInfo info; // This zeroes all the sizes.
info.objectsGCHeap += thingSize;
obj->addSizeOfExcludingThis(rtStats->mallocSizeOf_, &info);
cStats->classInfo.add(info);
const Class* clasp = obj->getClass();
const char* className = clasp->name;
AddClassInfo(granularity, cStats, className, info);
if (ObjectPrivateVisitor* opv = closure->opv) {
nsISupports* iface;
if (opv->getISupports_(obj, &iface) && iface)
cStats->objectsPrivate += opv->sizeOfIncludingThis(iface);
}
break;
}
case JS::TraceKind::Script: {
JSScript* script = static_cast<JSScript*>(thing);
CompartmentStats* cStats = GetCompartmentStats(script->compartment());
cStats->scriptsGCHeap += thingSize;
cStats->scriptsMallocHeapData += script->sizeOfData(rtStats->mallocSizeOf_);
cStats->typeInferenceTypeScripts += script->sizeOfTypeScript(rtStats->mallocSizeOf_);
jit::AddSizeOfBaselineData(script, rtStats->mallocSizeOf_, &cStats->baselineData,
&cStats->baselineStubsFallback);
cStats->ionData += jit::SizeOfIonData(script, rtStats->mallocSizeOf_);
ScriptSource* ss = script->scriptSource();
SourceSet::AddPtr entry = closure->seenSources.lookupForAdd(ss);
if (!entry) {
(void)closure->seenSources.add(entry, ss); // Not much to be done on failure.
JS::ScriptSourceInfo info; // This zeroes all the sizes.
ss->addSizeOfIncludingThis(rtStats->mallocSizeOf_, &info);
MOZ_ASSERT(info.compressed == 0 || info.uncompressed == 0);
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) {
// Ignore failure -- we just won't record the script source as notable.
(void)rtStats->runtime.allScriptSources->add(p, filename, info);
} else {
p->value().add(info);
}
}
}
break;
}
case JS::TraceKind::String: {
JSString* str = static_cast<JSString*>(thing);
JS::StringInfo info;
if (str->hasLatin1Chars()) {
info.gcHeapLatin1 = thingSize;
info.mallocHeapLatin1 = str->sizeOfExcludingThis(rtStats->mallocSizeOf_);
} else {
info.gcHeapTwoByte = thingSize;
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) {
// Ignore failure -- we just won't record the string as notable.
(void)zStats->allStrings->add(p, str, info);
} else {
p->value().add(info);
}
}
break;
}
case JS::TraceKind::Symbol:
zStats->symbolsGCHeap += thingSize;
break;
case JS::TraceKind::BaseShape: {
BaseShape* base = static_cast<BaseShape*>(thing);
CompartmentStats* cStats = GetCompartmentStats(base->compartment());
JS::ClassInfo info; // This zeroes all the sizes.
info.shapesGCHeapBase += thingSize;
// No malloc-heap measurements.
cStats->classInfo.add(info);
const Class* clasp = base->clasp();
const char* className = clasp->name;
AddClassInfo(granularity, cStats, className, info);
break;
}
case JS::TraceKind::JitCode: {
zStats->jitCodesGCHeap += thingSize;
// The code for a script is counted in ExecutableAllocator::sizeOfCode().
break;
}
case JS::TraceKind::LazyScript: {
LazyScript* lazy = static_cast<LazyScript*>(thing);
zStats->lazyScriptsGCHeap += thingSize;
zStats->lazyScriptsMallocHeap += lazy->sizeOfExcludingThis(rtStats->mallocSizeOf_);
break;
}
case JS::TraceKind::Shape: {
Shape* shape = static_cast<Shape*>(thing);
CompartmentStats* cStats = GetCompartmentStats(shape->compartment());
JS::ClassInfo info; // This zeroes all the sizes.
if (shape->inDictionary())
info.shapesGCHeapDict += thingSize;
else
info.shapesGCHeapTree += thingSize;
shape->addSizeOfExcludingThis(rtStats->mallocSizeOf_, &info);
cStats->classInfo.add(info);
const BaseShape* base = shape->base();
const Class* clasp = base->clasp();
const char* className = clasp->name;
AddClassInfo(granularity, cStats, className, info);
break;
}
case JS::TraceKind::ObjectGroup: {
ObjectGroup* group = static_cast<ObjectGroup*>(thing);
zStats->objectGroupsGCHeap += thingSize;
zStats->objectGroupsMallocHeap += group->sizeOfExcludingThis(rtStats->mallocSizeOf_);
break;
}
default:
MOZ_CRASH("invalid traceKind in StatsCellCallback");
}
// Yes, this is a subtraction: see StatsArenaCallback() for details.
zStats->unusedGCThings.addToKind(traceKind, -thingSize);
}
bool
ZoneStats::initStrings(JSRuntime* rt)
{
isTotals = false;
allStrings = rt->new_<StringsHashMap>();
if (!allStrings || !allStrings->init()) {
js_delete(allStrings);
allStrings = nullptr;
return false;
}
return true;
}
bool
CompartmentStats::initClasses(JSRuntime* rt)
{
isTotals = false;
allClasses = rt->new_<ClassesHashMap>();
if (!allClasses || !allClasses->init()) {
js_delete(allClasses);
allClasses = nullptr;
return false;
}
return true;
}
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.growBy(1))
return false;
zStats.notableStrings.back() = NotableStringInfo(str, info);
// 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);
}
// Delete |allStrings| now, rather than waiting for zStats's destruction,
// to reduce peak memory consumption during reporting.
js_delete(zStats.allStrings);
zStats.allStrings = nullptr;
return true;
}
static bool
FindNotableClasses(CompartmentStats& cStats)
{
using namespace JS;
// We should only run FindNotableClasses once per ZoneStats object.
MOZ_ASSERT(cStats.notableClasses.empty());
for (CompartmentStats::ClassesHashMap::Range r = cStats.allClasses->all();
!r.empty();
r.popFront())
{
const char* className = r.front().key();
ClassInfo& info = r.front().value();
// If this class isn't notable, or if we can't grow the notableStrings
// vector, skip this string.
if (!info.isNotable())
continue;
if (!cStats.notableClasses.growBy(1))
return false;
cStats.notableClasses.back() = NotableClassInfo(className, info);
// We're moving this class from a non-notable to a notable bucket, so
// subtract it out of the non-notable tallies.
cStats.classInfo.subtract(info);
}
// Delete |allClasses| now, rather than waiting for zStats's destruction,
// to reduce peak memory consumption during reporting.
js_delete(cStats.allClasses);
cStats.allClasses = nullptr;
return true;
}
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.growBy(1))
return false;
runtime.notableScriptSources.back() = NotableScriptSourceInfo(filename, info);
// 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);
}
// Delete |allScriptSources| now, rather than waiting for zStats's
// destruction, to reduce peak memory consumption during reporting.
js_delete(runtime.allScriptSources);
runtime.allScriptSources = nullptr;
return true;
}
static bool
CollectRuntimeStatsHelper(JSRuntime* rt, RuntimeStats* rtStats, ObjectPrivateVisitor* opv,
bool anonymize, IterateCellCallback statsCellCallback)
{
if (!rtStats->compartmentStatsVector.reserve(rt->numCompartments))
return false;
if (!rtStats->zoneStatsVector.reserve(rt->gc.zones.length()))
return false;
rtStats->gcHeapChunkTotal =
size_t(JS_GetGCParameter(rt, JSGC_TOTAL_CHUNKS)) * gc::ChunkSize;
rtStats->gcHeapUnusedChunks =
size_t(JS_GetGCParameter(rt, JSGC_UNUSED_CHUNKS)) * gc::ChunkSize;
IterateChunks(rt, &rtStats->gcHeapDecommittedArenas,
DecommittedArenasChunkCallback);
// Take the per-compartment measurements.
StatsClosure closure(rtStats, opv, anonymize);
if (!closure.init())
return false;
IterateZonesCompartmentsArenasCells(rt, &closure,
StatsZoneCallback,
StatsCompartmentCallback,
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::CompartmentStatsVector& cs = rtStats->compartmentStatsVector;
CompartmentStats& cTotals = rtStats->cTotals;
// As with the zones, we sum all compartments first, and then get the
// notable classes within each zone.
for (size_t i = 0; i < cs.length(); i++)
cTotals.addSizes(cs[i]);
for (size_t i = 0; i < cs.length(); i++) {
if (!FindNotableClasses(cs[i]))
return false;
}
MOZ_ASSERT(!cTotals.allClasses);
rtStats->gcHeapGCThings = rtStats->zTotals.sizeOfLiveGCThings() +
rtStats->cTotals.sizeOfLiveGCThings();
#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 (CompartmentsIter comp(rt, WithAtoms); !comp.done(); comp.next())
comp->compartmentStats = nullptr;
size_t numDirtyChunks =
(rtStats->gcHeapChunkTotal - rtStats->gcHeapUnusedChunks) / gc::ChunkSize;
size_t perChunkAdmin =
sizeof(gc::Chunk) - (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->gcHeapDecommittedArenas -
rtStats->gcHeapUnusedChunks -
rtStats->zTotals.unusedGCThings.totalSize() -
rtStats->gcHeapChunkAdmin -
rtStats->zTotals.gcHeapArenaAdmin -
rtStats->gcHeapGCThings;
return true;
}
JS_PUBLIC_API(bool)
JS::CollectRuntimeStats(JSRuntime *rt, RuntimeStats *rtStats, ObjectPrivateVisitor *opv,
bool anonymize)
{
return CollectRuntimeStatsHelper(rt, rtStats, opv, anonymize, StatsCellCallback<FineGrained>);
}
JS_PUBLIC_API(size_t)
JS::SystemCompartmentCount(JSRuntime* rt)
{
size_t n = 0;
for (CompartmentsIter comp(rt, WithAtoms); !comp.done(); comp.next()) {
if (comp->isSystem())
++n;
}
return n;
}
JS_PUBLIC_API(size_t)
JS::UserCompartmentCount(JSRuntime* rt)
{
size_t n = 0;
for (CompartmentsIter comp(rt, WithAtoms); !comp.done(); comp.next()) {
if (!comp->isSystem())
++n;
}
return n;
}
JS_PUBLIC_API(size_t)
JS::PeakSizeOfTemporary(const JSRuntime* rt)
{
return rt->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)
override
{}
virtual void initExtraCompartmentStats(
JSCompartment* c, JS::CompartmentStats* cStats) override
{}
};
JS_PUBLIC_API(bool)
AddSizeOfTab(JSRuntime* rt, HandleObject obj, MallocSizeOf mallocSizeOf, ObjectPrivateVisitor* opv,
TabSizes* sizes)
{
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. No need to anonymize because
// these measurements will be aggregated.
StatsClosure closure(&rtStats, opv, /* anonymize = */ false);
if (!closure.init())
return false;
IterateZoneCompartmentsArenasCells(rt, zone, &closure,
StatsZoneCallback,
StatsCompartmentCallback,
StatsArenaCallback,
StatsCellCallback<CoarseGrained>);
MOZ_ASSERT(rtStats.zoneStatsVector.length() == 1);
rtStats.zTotals.addSizes(rtStats.zoneStatsVector[0]);
for (size_t i = 0; i < rtStats.compartmentStatsVector.length(); i++)
rtStats.cTotals.addSizes(rtStats.compartmentStatsVector[i]);
for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next())
comp->compartmentStats = nullptr;
rtStats.zTotals.addToTabSizes(sizes);
rtStats.cTotals.addToTabSizes(sizes);
return true;
}
JS_PUBLIC_API(bool)
AddServoSizeOf(JSRuntime *rt, MallocSizeOf mallocSizeOf, ObjectPrivateVisitor *opv,
ServoSizes *sizes)
{
SimpleJSRuntimeStats rtStats(mallocSizeOf);
// No need to anonymize because the results will be aggregated.
if (!CollectRuntimeStatsHelper(rt, &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.cTotals.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