| /* -*- 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 "jscntxt.h" |
| #include "jscompartment.h" |
| #include "jsgc.h" |
| #include "jsobj.h" |
| #include "jsscript.h" |
| |
| #include "jit/BaselineJIT.h" |
| #include "jit/Ion.h" |
| #include "vm/Shape.h" |
| |
| #include "jsobjinlines.h" |
| |
| using mozilla::DebugOnly; |
| |
| using namespace js; |
| |
| using JS::RuntimeStats; |
| using JS::ObjectPrivateVisitor; |
| using JS::ZoneStats; |
| using JS::CompartmentStats; |
| |
| JS_FRIEND_API(size_t) |
| js::MemoryReportingSundriesThreshold() |
| { |
| return 8 * 1024; |
| } |
| |
| typedef HashSet<ScriptSource *, DefaultHasher<ScriptSource *>, SystemAllocPolicy> SourceSet; |
| |
| struct IteratorClosure |
| { |
| RuntimeStats *rtStats; |
| ObjectPrivateVisitor *opv; |
| SourceSet seenSources; |
| IteratorClosure(RuntimeStats *rt, ObjectPrivateVisitor *v) : rtStats(rt), opv(v) {} |
| bool init() { |
| return seenSources.init(); |
| } |
| }; |
| |
| size_t |
| ZoneStats::GCHeapThingsSize() |
| { |
| // These are just the GC-thing measurements. |
| size_t n = 0; |
| n += gcHeapStringsNormal; |
| n += gcHeapStringsShort; |
| n += gcHeapLazyScripts; |
| n += gcHeapTypeObjects; |
| n += gcHeapIonCodes; |
| |
| return n; |
| } |
| |
| size_t |
| CompartmentStats::GCHeapThingsSize() |
| { |
| // These are just the GC-thing measurements. |
| size_t n = 0; |
| n += gcHeapObjectsOrdinary; |
| n += gcHeapObjectsFunction; |
| n += gcHeapObjectsDenseArray; |
| n += gcHeapObjectsSlowArray; |
| n += gcHeapObjectsCrossCompartmentWrapper; |
| n += gcHeapShapesTreeGlobalParented; |
| n += gcHeapShapesTreeNonGlobalParented; |
| n += gcHeapShapesDict; |
| n += gcHeapShapesBase; |
| n += gcHeapScripts; |
| |
| return n; |
| } |
| |
| 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; |
| } |
| JS_ASSERT(n > 0); |
| *static_cast<size_t *>(data) += n; |
| } |
| |
| static void |
| StatsCompartmentCallback(JSRuntime *rt, void *data, JSCompartment *compartment) |
| { |
| // Append a new CompartmentStats to the vector. |
| RuntimeStats *rtStats = static_cast<IteratorClosure *>(data)->rtStats; |
| |
| // CollectRuntimeStats reserves enough space. |
| MOZ_ALWAYS_TRUE(rtStats->compartmentStatsVector.growBy(1)); |
| CompartmentStats &cStats = rtStats->compartmentStatsVector.back(); |
| rtStats->initExtraCompartmentStats(compartment, &cStats); |
| |
| compartment->compartmentStats = &cStats; |
| |
| // Measure the compartment object itself, and things hanging off it. |
| compartment->sizeOfIncludingThis(rtStats->mallocSizeOf_, |
| &cStats.compartmentObject, |
| &cStats.typeInference, |
| &cStats.shapesCompartmentTables, |
| &cStats.crossCompartmentWrappersTable, |
| &cStats.regexpCompartment, |
| &cStats.debuggeesSet, |
| &cStats.baselineStubsOptimized); |
| } |
| |
| static void |
| StatsZoneCallback(JSRuntime *rt, void *data, Zone *zone) |
| { |
| // Append a new CompartmentStats to the vector. |
| RuntimeStats *rtStats = static_cast<IteratorClosure *>(data)->rtStats; |
| |
| // CollectRuntimeStats reserves enough space. |
| MOZ_ALWAYS_TRUE(rtStats->zoneStatsVector.growBy(1)); |
| ZoneStats &zStats = rtStats->zoneStatsVector.back(); |
| rtStats->initExtraZoneStats(zone, &zStats); |
| rtStats->currZoneStats = &zStats; |
| |
| zone->sizeOfIncludingThis(rtStats->mallocSizeOf_, |
| &zStats.typePool); |
| } |
| |
| static void |
| StatsArenaCallback(JSRuntime *rt, void *data, gc::Arena *arena, |
| JSGCTraceKind traceKind, size_t thingSize) |
| { |
| RuntimeStats *rtStats = static_cast<IteratorClosure *>(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->gcHeapUnusedGcThings += allocationSpace; |
| } |
| |
| static CompartmentStats * |
| GetCompartmentStats(JSCompartment *comp) |
| { |
| return static_cast<CompartmentStats *>(comp->compartmentStats); |
| } |
| |
| static void |
| StatsCellCallback(JSRuntime *rt, void *data, void *thing, JSGCTraceKind traceKind, |
| size_t thingSize) |
| { |
| IteratorClosure *closure = static_cast<IteratorClosure *>(data); |
| RuntimeStats *rtStats = closure->rtStats; |
| ZoneStats *zStats = rtStats->currZoneStats; |
| switch (traceKind) { |
| case JSTRACE_OBJECT: { |
| JSObject *obj = static_cast<JSObject *>(thing); |
| CompartmentStats *cStats = GetCompartmentStats(obj->compartment()); |
| if (obj->is<JSFunction>()) |
| cStats->gcHeapObjectsFunction += thingSize; |
| else if (obj->isArray()) |
| cStats->gcHeapObjectsDenseArray += thingSize; |
| else if (obj->isCrossCompartmentWrapper()) |
| cStats->gcHeapObjectsCrossCompartmentWrapper += thingSize; |
| else |
| cStats->gcHeapObjectsOrdinary += thingSize; |
| |
| JS::ObjectsExtraSizes objectsExtra; |
| obj->sizeOfExcludingThis(rtStats->mallocSizeOf_, &objectsExtra); |
| cStats->objectsExtra.add(objectsExtra); |
| |
| // JSObject::sizeOfExcludingThis() doesn't measure objectsExtraPrivate, |
| // so we do it here. |
| if (ObjectPrivateVisitor *opv = closure->opv) { |
| nsISupports *iface; |
| if (opv->getISupports_(obj, &iface) && iface) { |
| cStats->objectsExtra.private_ += opv->sizeOfIncludingThis(iface); |
| } |
| } |
| break; |
| } |
| |
| case JSTRACE_STRING: { |
| JSString *str = static_cast<JSString *>(thing); |
| |
| size_t strSize = str->sizeOfExcludingThis(rtStats->mallocSizeOf_); |
| |
| // If we can't grow hugeStrings, let's just call this string non-huge. |
| // We're probably about to OOM anyway. |
| if (strSize >= JS::HugeStringInfo::MinSize() && zStats->hugeStrings.growBy(1)) { |
| zStats->gcHeapStringsNormal += thingSize; |
| JS::HugeStringInfo &info = zStats->hugeStrings.back(); |
| info.length = str->length(); |
| info.size = strSize; |
| PutEscapedString(info.buffer, sizeof(info.buffer), &str->asLinear(), 0); |
| } else if (str->isShort()) { |
| MOZ_ASSERT(strSize == 0); |
| zStats->gcHeapStringsShort += thingSize; |
| } else { |
| zStats->gcHeapStringsNormal += thingSize; |
| zStats->stringCharsNonHuge += strSize; |
| } |
| break; |
| } |
| |
| case JSTRACE_SHAPE: { |
| Shape *shape = static_cast<Shape *>(thing); |
| CompartmentStats *cStats = GetCompartmentStats(shape->compartment()); |
| size_t propTableSize, kidsSize; |
| shape->sizeOfExcludingThis(rtStats->mallocSizeOf_, &propTableSize, &kidsSize); |
| if (shape->inDictionary()) { |
| cStats->gcHeapShapesDict += thingSize; |
| cStats->shapesExtraDictTables += propTableSize; |
| JS_ASSERT(kidsSize == 0); |
| } else { |
| if (shape->base()->getObjectParent() == shape->compartment()->maybeGlobal()) { |
| cStats->gcHeapShapesTreeGlobalParented += thingSize; |
| } else { |
| cStats->gcHeapShapesTreeNonGlobalParented += thingSize; |
| } |
| cStats->shapesExtraTreeTables += propTableSize; |
| cStats->shapesExtraTreeShapeKids += kidsSize; |
| } |
| break; |
| } |
| |
| case JSTRACE_BASE_SHAPE: { |
| BaseShape *base = static_cast<BaseShape *>(thing); |
| CompartmentStats *cStats = GetCompartmentStats(base->compartment()); |
| cStats->gcHeapShapesBase += thingSize; |
| break; |
| } |
| |
| case JSTRACE_SCRIPT: { |
| JSScript *script = static_cast<JSScript *>(thing); |
| CompartmentStats *cStats = GetCompartmentStats(script->compartment()); |
| cStats->gcHeapScripts += thingSize; |
| cStats->scriptData += script->sizeOfData(rtStats->mallocSizeOf_); |
| #ifdef JS_ION |
| size_t baselineData = 0, baselineStubsFallback = 0; |
| jit::SizeOfBaselineData(script, rtStats->mallocSizeOf_, &baselineData, |
| &baselineStubsFallback); |
| cStats->baselineData += baselineData; |
| cStats->baselineStubsFallback += baselineStubsFallback; |
| cStats->ionData += jit::SizeOfIonData(script, rtStats->mallocSizeOf_); |
| #endif |
| |
| ScriptSource *ss = script->scriptSource(); |
| SourceSet::AddPtr entry = closure->seenSources.lookupForAdd(ss); |
| if (!entry) { |
| closure->seenSources.add(entry, ss); // Not much to be done on failure. |
| rtStats->runtime.scriptSources += ss->sizeOfIncludingThis(rtStats->mallocSizeOf_); |
| } |
| break; |
| } |
| |
| case JSTRACE_LAZY_SCRIPT: { |
| LazyScript *lazy = static_cast<LazyScript *>(thing); |
| zStats->gcHeapLazyScripts += thingSize; |
| zStats->lazyScripts += lazy->sizeOfExcludingThis(rtStats->mallocSizeOf_); |
| break; |
| } |
| |
| case JSTRACE_IONCODE: { |
| #ifdef JS_ION |
| zStats->gcHeapIonCodes += thingSize; |
| // The code for a script is counted in ExecutableAllocator::sizeOfCode(). |
| #endif |
| break; |
| } |
| |
| case JSTRACE_TYPE_OBJECT: { |
| types::TypeObject *obj = static_cast<types::TypeObject *>(thing); |
| zStats->gcHeapTypeObjects += thingSize; |
| zStats->typeObjects += obj->sizeOfExcludingThis(rtStats->mallocSizeOf_); |
| break; |
| } |
| |
| } |
| |
| // Yes, this is a subtraction: see StatsArenaCallback() for details. |
| zStats->gcHeapUnusedGcThings -= thingSize; |
| } |
| |
| JS_PUBLIC_API(bool) |
| JS::CollectRuntimeStats(JSRuntime *rt, RuntimeStats *rtStats, ObjectPrivateVisitor *opv) |
| { |
| if (!rtStats->compartmentStatsVector.reserve(rt->numCompartments)) |
| return false; |
| |
| if (!rtStats->zoneStatsVector.reserve(rt->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. |
| IteratorClosure closure(rtStats, opv); |
| if (!closure.init()) |
| return false; |
| rtStats->runtime.scriptSources = 0; |
| IterateZonesCompartmentsArenasCells(rt, &closure, StatsZoneCallback, StatsCompartmentCallback, |
| StatsArenaCallback, StatsCellCallback); |
| |
| // Take the "explicit/js/runtime/" measurements. |
| rt->sizeOfIncludingThis(rtStats->mallocSizeOf_, &rtStats->runtime); |
| |
| DebugOnly<size_t> totalArenaSize = 0; |
| |
| rtStats->gcHeapGcThings = 0; |
| for (size_t i = 0; i < rtStats->zoneStatsVector.length(); i++) { |
| ZoneStats &zStats = rtStats->zoneStatsVector[i]; |
| |
| rtStats->zTotals.add(zStats); |
| rtStats->gcHeapGcThings += zStats.GCHeapThingsSize(); |
| #ifdef DEBUG |
| totalArenaSize += zStats.gcHeapArenaAdmin + zStats.gcHeapUnusedGcThings; |
| #endif |
| } |
| |
| for (size_t i = 0; i < rtStats->compartmentStatsVector.length(); i++) { |
| CompartmentStats &cStats = rtStats->compartmentStatsVector[i]; |
| |
| rtStats->cTotals.add(cStats); |
| rtStats->gcHeapGcThings += cStats.GCHeapThingsSize(); |
| } |
| |
| #ifdef DEBUG |
| totalArenaSize += rtStats->gcHeapGcThings; |
| JS_ASSERT(totalArenaSize % gc::ArenaSize == 0); |
| #endif |
| |
| for (CompartmentsIter comp(rt); !comp.done(); comp.next()) |
| comp->compartmentStats = NULL; |
| |
| size_t numDirtyChunks = |
| (rtStats->gcHeapChunkTotal - rtStats->gcHeapUnusedChunks) / gc::ChunkSize; |
| size_t perChunkAdmin = |
| sizeof(gc::Chunk) - (sizeof(gc::Arena) * gc::ArenasPerChunk); |
| rtStats->gcHeapChunkAdmin = numDirtyChunks * perChunkAdmin; |
| rtStats->gcHeapUnusedArenas -= rtStats->gcHeapChunkAdmin; |
| |
| // |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.gcHeapUnusedGcThings - |
| rtStats->gcHeapChunkAdmin - |
| rtStats->zTotals.gcHeapArenaAdmin - |
| rtStats->gcHeapGcThings; |
| return true; |
| } |
| |
| JS_PUBLIC_API(size_t) |
| JS::SystemCompartmentCount(JSRuntime *rt) |
| { |
| size_t n = 0; |
| for (CompartmentsIter comp(rt); !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); !comp.done(); comp.next()) { |
| if (!comp->isSystem) |
| ++n; |
| } |
| return n; |
| } |
| |
| JS_PUBLIC_API(size_t) |
| JS::PeakSizeOfTemporary(const JSRuntime *rt) |
| { |
| return rt->tempLifoAlloc.peakSizeOfExcludingThis(); |
| } |
| |