diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index f8c3249d1e03..13f0cbbb43a5 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -617,11 +617,6 @@ struct JSRuntime { JSWrapObjectCallback wrapObjectCallback; JSPreWrapCallback preWrapObjectCallback; -#ifdef JS_METHODJIT - /* This measures the size of JITScripts, native maps and IC structs. */ - size_t mjitDataSize; -#endif - /* * To ensure that cx->malloc does not cause a GC, we set this flag during * OOM reporting (in js_ReportOutOfMemory). If a GC is requested while diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index ae28f4e9c3a7..6f3f8e44cfa4 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -2729,40 +2729,14 @@ TraceRuntime(JSTracer *trc) MarkRuntime(trc); } -static void -IterateCompartmentCells(JSContext *cx, JSCompartment *comp, uint64 traceKindMask, - void *data, IterateCallback callback) -{ - for (unsigned thingKind = 0; thingKind < FINALIZE_LIMIT; thingKind++) { - size_t traceKind = GetFinalizableTraceKind(thingKind); - if (traceKindMask && !TraceKindInMask(traceKind, traceKindMask)) - continue; - - size_t thingSize = GCThingSizeMap[thingKind]; - ArenaHeader *aheader = comp->arenas[thingKind].getHead(); - for (; aheader; aheader = aheader->next) { - Arena *a = aheader->getArena(); - FreeSpan firstSpan(aheader->getFirstFreeSpan()); - FreeSpan *span = &firstSpan; - for (uintptr_t thing = a->thingsStart(thingSize);; thing += thingSize) { - JS_ASSERT(thing <= a->thingsEnd()); - if (thing == span->start) { - if (!span->hasNext()) - break; - thing = span->end; - span = span->nextSpan(); - } else { - (*callback)(cx, data, traceKind, reinterpret_cast(thing)); - } - } - } - } -} - void -IterateCells(JSContext *cx, JSCompartment *comp, uint64 traceKindMask, - void *data, IterateCallback callback) +IterateCompartmentsArenasCells(JSContext *cx, void *data, + IterateCompartmentCallback compartmentCallback, + IterateArenaCallback arenaCallback, + IterateCellCallback cellCallback) { + CHECK_REQUEST(cx); + LeaveTrace(cx); JSRuntime *rt = cx->runtime; @@ -2776,11 +2750,35 @@ IterateCells(JSContext *cx, JSCompartment *comp, uint64 traceKindMask, AutoUnlockGC unlock(rt); AutoCopyFreeListToArenas copy(rt); - if (comp) { - IterateCompartmentCells(cx, comp, traceKindMask, data, callback); - } else { - for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c) - IterateCompartmentCells(cx, *c, traceKindMask, data, callback); + for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c) { + JSCompartment *compartment = *c; + (*compartmentCallback)(cx, data, compartment); + + for (unsigned thingKind = 0; thingKind < FINALIZE_LIMIT; thingKind++) { + size_t traceKind = GetFinalizableTraceKind(thingKind); + size_t thingSize = GCThingSizeMap[thingKind]; + ArenaHeader *aheader = compartment->arenas[thingKind].getHead(); + + for (; aheader; aheader = aheader->next) { + Arena *arena = aheader->getArena(); + (*arenaCallback)(cx, data, arena, traceKind, thingSize); + FreeSpan firstSpan(aheader->getFirstFreeSpan()); + FreeSpan *span = &firstSpan; + + for (uintptr_t thing = arena->thingsStart(thingSize); ; thing += thingSize) { + JS_ASSERT(thing <= arena->thingsEnd()); + if (thing == span->start) { + if (!span->hasNext()) + break; + thing = span->end; + span = span->nextSpan(); + } else { + (*cellCallback)(cx, data, reinterpret_cast(thing), traceKind, + thingSize); + } + } + } + } } } diff --git a/js/src/jsgc.h b/js/src/jsgc.h index f1efd2c312d0..3f1e304e473e 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -1332,30 +1332,22 @@ struct GCMarker : public JSTracer { void MarkStackRangeConservatively(JSTracer *trc, Value *begin, Value *end); -static inline uint64 -TraceKindMask(unsigned kind) -{ - return uint64(1) << kind; -} - -static inline bool -TraceKindInMask(unsigned kind, uint64 mask) -{ - return !!(mask & TraceKindMask(kind)); -} - -typedef void (*IterateCallback)(JSContext *cx, void *data, size_t traceKind, void *obj); +typedef void (*IterateCompartmentCallback)(JSContext *cx, void *data, JSCompartment *compartment); +typedef void (*IterateArenaCallback)(JSContext *cx, void *data, gc::Arena *arena, size_t traceKind, + size_t thingSize); +typedef void (*IterateCellCallback)(JSContext *cx, void *data, void *thing, size_t traceKind, + size_t thingSize); /* - * This function calls |callback| on every cell in the GC heap. If |comp| is - * non-null, then it only selects cells in that compartment. If |traceKindMask| - * is non-zero, then only cells whose traceKind belongs to the mask will be - * selected. The mask should be constructed by ORing |TraceKindMask(...)| - * results. + * This function calls |compartmentCallback| on every compartment, + * |arenaCallback| on every in-use arena, and |cellCallback| on every in-use + * cell in the GC heap. */ extern JS_FRIEND_API(void) -IterateCells(JSContext *cx, JSCompartment *comp, uint64 traceKindMask, - void *data, IterateCallback callback); +IterateCompartmentsArenasCells(JSContext *cx, void *data, + IterateCompartmentCallback compartmentCallback, + IterateArenaCallback arenaCallback, + IterateCellCallback cellCallback); } /* namespace js */ diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 1ad6dd8ab104..aaa9d6ce53b2 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -575,6 +575,9 @@ struct JSScript { return JITScript_Invalid; return JITScript_Valid; } + + // This method is implemented in MethodJIT.h. + JS_FRIEND_API(size_t) jitDataSize();/* Size of the JITScript and all sections */ #endif JS_FRIEND_API(size_t) totalSize(); /* Size of the JSScript and all sections */ diff --git a/js/src/methodjit/Compiler.cpp b/js/src/methodjit/Compiler.cpp index 012ce3b0ce13..4821c514d32c 100644 --- a/js/src/methodjit/Compiler.cpp +++ b/js/src/methodjit/Compiler.cpp @@ -816,9 +816,6 @@ mjit::Compiler::finishThisUp(JITScript **jitp) *jitp = jit; - /* We tolerate a race in the stats. */ - cx->runtime->mjitDataSize += dataSize; - return Compile_Okay; } diff --git a/js/src/methodjit/MethodJIT.cpp b/js/src/methodjit/MethodJIT.cpp index 728ddeebe137..04701c46ebbc 100644 --- a/js/src/methodjit/MethodJIT.cpp +++ b/js/src/methodjit/MethodJIT.cpp @@ -875,6 +875,17 @@ mjit::JITScript::~JITScript() #endif } +size_t +JSScript::jitDataSize() +{ + size_t n = 0; + if (jitNormal) + n += jitNormal->scriptDataSize(); + if (jitCtor) + n += jitCtor->scriptDataSize(); + return n; +} + /* Please keep in sync with Compiler::finishThisUp! */ size_t mjit::JITScript::scriptDataSize() @@ -905,8 +916,6 @@ mjit::ReleaseScriptCode(JSContext *cx, JSScript *script) JITScript *jscr; if ((jscr = script->jitNormal)) { - cx->runtime->mjitDataSize -= jscr->scriptDataSize(); - jscr->~JITScript(); cx->free_(jscr); script->jitNormal = NULL; @@ -914,8 +923,6 @@ mjit::ReleaseScriptCode(JSContext *cx, JSScript *script) } if ((jscr = script->jitCtor)) { - cx->runtime->mjitDataSize -= jscr->scriptDataSize(); - jscr->~JITScript(); cx->free_(jscr); script->jitCtor = NULL; diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index dd5fb1f68a26..e488a21fb91c 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -4715,7 +4715,18 @@ JSBool MJitDataStats(JSContext *cx, uintN argc, jsval *vp) { #ifdef JS_METHODJIT - JS_SET_RVAL(cx, vp, INT_TO_JSVAL(cx->runtime->mjitDataSize)); + JSRuntime *rt = cx->runtime; + AutoLockGC lock(rt); + size_t n = 0; + for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c) { + for (JSScript *script = (JSScript *)(*c)->scripts.next; + &script->links != &(*c)->scripts; + script = (JSScript *)script->links.next) + { + n += script->jitDataSize(); + } + } + JS_SET_RVAL(cx, vp, INT_TO_JSVAL(n)); #else JS_SET_RVAL(cx, vp, JSVAL_VOID); #endif diff --git a/js/src/xpconnect/src/xpcjsruntime.cpp b/js/src/xpconnect/src/xpcjsruntime.cpp index b54a9dd51063..ababefc06d34 100644 --- a/js/src/xpconnect/src/xpcjsruntime.cpp +++ b/js/src/xpconnect/src/xpcjsruntime.cpp @@ -1259,29 +1259,21 @@ protected: static XPConnectGCChunkAllocator gXPCJSChunkAllocator; #ifdef MOZ_MEMORY -#define JS_GC_HEAP_KIND KIND_HEAP +#define JS_GC_HEAP_KIND nsIMemoryReporter::KIND_HEAP #else -#define JS_GC_HEAP_KIND KIND_MAPPED +#define JS_GC_HEAP_KIND nsIMemoryReporter::KIND_MAPPED #endif +// We have per-compartment GC heap totals, so we can't put the total GC heap +// size in the explicit allocations tree. But it's a useful figure, so put it +// in the "others" list. NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSGCHeap, - "explicit/js/gc-heap", - JS_GC_HEAP_KIND, - UNITS_BYTES, + "js-gc-heap", + KIND_OTHER, + nsIMemoryReporter::UNITS_BYTES, gXPCJSChunkAllocator.GetGCChunkBytesInUse, "Memory used by the garbage-collected JavaScript heap.") -static PRInt64 -GetPerCompartmentSize(PRInt64 (*f)(JSCompartment *c)) -{ - JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->GetJSRuntime(); - js::AutoLockGC lock(rt); - PRInt64 n = 0; - for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c) - n += f(*c); - return n; -} - static PRInt64 GetJSStack() { @@ -1295,246 +1287,384 @@ GetJSStack() NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSStack, "explicit/js/stack", KIND_MAPPED, - UNITS_BYTES, + nsIMemoryReporter::UNITS_BYTES, GetJSStack, "Memory used for the JavaScript stack. This is the committed portion " "of the stack; any uncommitted portion is not measured because it " "hardly costs anything.") -static PRInt64 -GetCompartmentScriptsSize(JSCompartment *c) +class XPConnectJSCompartmentsMultiReporter : public nsIMemoryMultiReporter { - PRInt64 n = 0; - for (JSScript *script = (JSScript *)c->scripts.next; - &script->links != &c->scripts; - script = (JSScript *)script->links.next) +private: + struct CompartmentStats { - n += script->totalSize(); - } - return n; -} + CompartmentStats(JSContext *cx, JSCompartment *c) { + memset(this, 0, sizeof(*this)); -static PRInt64 -GetJSScripts() -{ - return GetPerCompartmentSize(GetCompartmentScriptsSize); -} + if (c == cx->runtime->atomsCompartment) { + name = NS_LITERAL_CSTRING("atoms"); + } else if (c->principals) { + if (c->principals->codebase) { + // A hack: replace forward slashes with '\\' so they aren't + // treated as path separators. Users of the reporters + // (such as about:memory) have to undo this change. + name.Assign(c->principals->codebase); + char* cur = name.BeginWriting(); + char* end = name.EndWriting(); + for (; cur < end; ++cur) { + if ('/' == *cur) { + *cur = '\\'; + } + } + } else { + name = NS_LITERAL_CSTRING("null-codebase"); + } + } else { + name = NS_LITERAL_CSTRING("null-principal"); + } + } -struct PRInt64Data { - PRInt64Data() : n(0) { } - PRInt64 n; -}; + nsCString name; + PRInt64 gcHeapArenaHeaders; + PRInt64 gcHeapArenaPadding; + PRInt64 gcHeapArenaUnused; -// This function is miscompiled with MSVC 2005 when PGO is on. See bug 664647. -#ifdef _MSC_VER -#pragma optimize("", off) + PRInt64 gcHeapObjects; + PRInt64 gcHeapStrings; + PRInt64 gcHeapShapes; + PRInt64 gcHeapXml; + + PRInt64 objectSlots; + PRInt64 stringChars; + + PRInt64 scripts; +#ifdef JS_METHODJIT + PRInt64 mjitCode; + PRInt64 mjitData; #endif -void -GetJSObjectSlotsCallback(JSContext *cx, void *v, size_t traceKind, void *thing) -{ - JS_ASSERT(traceKind == JSTRACE_OBJECT); - JSObject *obj = (JSObject *)thing; - if (obj->hasSlotsArray()) { - PRInt64Data *data = (PRInt64Data *) v; - data->n += obj->numSlots() * sizeof(js::Value); - } -} -#ifdef _MSC_VER -#pragma optimize("", on) +#ifdef JS_TRACER + PRInt64 tjitCode; + PRInt64 tjitDataAllocatorsMain; + PRInt64 tjitDataAllocatorsReserve; #endif + }; -static PRInt64 -GetJSObjectSlots() -{ - JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->GetJSRuntime(); - JSContext *cx = JS_NewContext(rt, 0); - if (!cx) { - NS_ERROR("couldn't create context for memory tracing"); - return (PRInt64) -1; + struct IterateData + { + IterateData(JSRuntime *rt) + : compartmentStatsVector() + , currCompartmentStats(NULL) + { + compartmentStatsVector.reserve(rt->compartments.length()); + } + + js::Vector compartmentStatsVector; + CompartmentStats *currCompartmentStats; + }; + + static PRInt64 + GetCompartmentScriptsSize(JSCompartment *c) + { + PRInt64 n = 0; + for (JSScript *script = (JSScript *)c->scripts.next; + &script->links != &c->scripts; + script = (JSScript *)script->links.next) + { + n += script->totalSize(); + } + return n; } - PRInt64Data data; - js::IterateCells(cx, NULL, js::TraceKindMask(JSTRACE_OBJECT), &data, - *GetJSObjectSlotsCallback); + #ifdef JS_METHODJIT - JS_DestroyContextNoGC(cx); - - return data.n; -} - -// This function is miscompiled with MSVC 2005 when PGO is on. See bug 664647. -#ifdef _MSC_VER -#pragma optimize("", off) -#endif -void -GetJSStringCharsCallback(JSContext *cx, void *v, size_t traceKind, void *thing) -{ - JS_ASSERT(traceKind == JSTRACE_STRING); - JSString *str = (JSString *)thing; - PRInt64Data *data = (PRInt64Data *) v; - data->n += str->charsHeapSize(); -} -#ifdef _MSC_VER -#pragma optimize("", on) -#endif - -static PRInt64 -GetJSStringChars() -{ - JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->GetJSRuntime(); - JSContext *cx = JS_NewContext(rt, 0); - if (!cx) { - NS_ERROR("couldn't create context for memory tracing"); - return (PRInt64) -1; + static PRInt64 + GetCompartmentMjitCodeSize(JSCompartment *c) + { + return c->getMjitCodeSize(); } - PRInt64Data data; - js::IterateCells(cx, NULL, js::TraceKindMask(JSTRACE_STRING), &data, - *GetJSStringCharsCallback); + static PRInt64 + GetCompartmentMjitDataSize(JSCompartment *c) + { + PRInt64 n = 0; + for (JSScript *script = (JSScript *)c->scripts.next; + &script->links != &c->scripts; + script = (JSScript *)script->links.next) + { + n += script->jitDataSize(); + } + return n; + } - JS_DestroyContextNoGC(cx); + #endif // JS_METHODJIT - return data.n; -} + #ifdef JS_TRACER -NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSScripts, - "explicit/js/scripts", - KIND_HEAP, - UNITS_BYTES, - GetJSScripts, - "Memory allocated for JSScripts. A JSScript is created for each " - "user-defined function in a script. One is also created for " - "the top-level code in a script. Each JSScript includes byte-code and " - "various other things.") + static PRInt64 + GetCompartmentTjitCodeSize(JSCompartment *c) + { + if (c->hasTraceMonitor()) { + size_t total, frag_size, free_size; + c->traceMonitor()->getCodeAllocStats(total, frag_size, free_size); + return total; + } + return 0; + } -NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSObjectSlots, - "explicit/js/object-slots", - KIND_HEAP, - UNITS_BYTES, - GetJSObjectSlots, + static PRInt64 + GetCompartmentTjitDataAllocatorsMainSize(JSCompartment *c) + { + return c->hasTraceMonitor() + ? c->traceMonitor()->getVMAllocatorsMainSize() + : 0; + } + + static PRInt64 + GetCompartmentTjitDataAllocatorsReserveSize(JSCompartment *c) + { + return c->hasTraceMonitor() + ? c->traceMonitor()->getVMAllocatorsReserveSize() + : 0; + } + + #endif // JS_TRACER + + static void + CompartmentCallback(JSContext *cx, void *vdata, JSCompartment *compartment) + { + // Append a new CompartmentStats to the vector. + IterateData *data = static_cast(vdata); + CompartmentStats compartmentStats(cx, compartment); + data->compartmentStatsVector.infallibleAppend(compartmentStats); + CompartmentStats *curr = data->compartmentStatsVector.end() - 1; + data->currCompartmentStats = curr; + + // Get the compartment-level numbers. + curr->scripts = GetCompartmentScriptsSize(compartment); +#ifdef JS_METHODJIT + curr->mjitCode = GetCompartmentMjitCodeSize(compartment); + curr->mjitData = GetCompartmentMjitDataSize(compartment); +#endif +#ifdef JS_TRACER + curr->tjitCode = GetCompartmentTjitCodeSize(compartment); + curr->tjitDataAllocatorsMain = GetCompartmentTjitDataAllocatorsMainSize(compartment); + curr->tjitDataAllocatorsReserve = GetCompartmentTjitDataAllocatorsReserveSize(compartment); +#endif + } + + static void + ArenaCallback(JSContext *cx, void *vdata, js::gc::Arena *arena, + size_t traceKind, size_t thingSize) + { + IterateData *data = static_cast(vdata); + data->currCompartmentStats->gcHeapArenaHeaders += + sizeof(js::gc::ArenaHeader); + data->currCompartmentStats->gcHeapArenaPadding += + arena->thingsStartOffset(thingSize) - sizeof(js::gc::ArenaHeader); + // 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 CellCallback(). + data->currCompartmentStats->gcHeapArenaUnused += arena->thingsSpan(thingSize); + } + + static void + CellCallback(JSContext *cx, void *vdata, void *thing, size_t traceKind, + size_t thingSize) + { + IterateData *data = static_cast(vdata); + CompartmentStats *curr = data->currCompartmentStats; + if (traceKind == JSTRACE_OBJECT) { + JSObject *obj = static_cast(thing); + curr->gcHeapObjects += thingSize; + if (obj->hasSlotsArray()) { + curr->objectSlots += obj->numSlots() * sizeof(js::Value); + } + } else if (traceKind == JSTRACE_STRING) { + JSString *str = static_cast(thing); + curr->gcHeapStrings += thingSize; + curr->stringChars += str->charsHeapSize(); + } else if (traceKind == JSTRACE_SHAPE) { + curr->gcHeapShapes += thingSize; + } else { + JS_ASSERT(traceKind == JSTRACE_XML); + curr->gcHeapXml += thingSize; + } + // Yes, this is a subtraction: see ArenaCallback() for details. + curr->gcHeapArenaUnused -= thingSize; + } + +public: + NS_DECL_ISUPPORTS + + XPConnectJSCompartmentsMultiReporter() + { + } + + nsCString mkPath(const nsACString &compartmentName, + const char* reporterName) + { + nsCString path(NS_LITERAL_CSTRING("explicit/js/compartment(")); + path += compartmentName; + path += NS_LITERAL_CSTRING(")/"); + path += nsDependentCString(reporterName); + return path; + } + + NS_IMETHOD CollectReports(nsIMemoryMultiReporterCallback *callback, + nsISupports *closure) + { + JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->GetJSRuntime(); + IterateData data(rt); + + // In the first step we get all the stats and stash them in a local + // data structure. In the second step we pass all the stashed stats to + // the callback. Separating these steps is important because the + // callback may be a JS function, and executing JS while getting these + // stats seems like a bad idea. + { + JSContext *cx = JS_NewContext(rt, 0); + if (!cx) { + NS_ERROR("couldn't create context for memory tracing"); + return NS_ERROR_FAILURE; + } + JS_BeginRequest(cx); + js::IterateCompartmentsArenasCells(cx, &data, CompartmentCallback, ArenaCallback, + CellCallback); + JS_EndRequest(cx); + JS_DestroyContextNoGC(cx); + } + + NS_NAMED_LITERAL_CSTRING(p, ""); + + PRInt64 gcHeapChunkTotal = gXPCJSChunkAllocator.GetGCChunkBytesInUse(); + // This is initialized to gcHeapChunkUnused, and then we subtract used + // space from it each time around the loop. + PRInt64 gcHeapChunkUnused = gcHeapChunkTotal; + + #define DO(path, kind, amount, desc) \ + callback->Callback(p, path, kind, nsIMemoryReporter::UNITS_BYTES, \ + amount, NS_LITERAL_CSTRING(desc), closure); + + // This is the second step (see above). + for (CompartmentStats *stats = data.compartmentStatsVector.begin(); + stats != data.compartmentStatsVector.end(); + ++stats) + { + nsCString &name = stats->name; + + gcHeapChunkUnused -= + stats->gcHeapArenaHeaders + stats->gcHeapArenaPadding + + stats->gcHeapArenaUnused + + stats->gcHeapObjects + stats->gcHeapStrings + + stats->gcHeapShapes + stats->gcHeapXml; + + DO(mkPath(name, "gc-heap/arena-headers"), + JS_GC_HEAP_KIND, stats->gcHeapArenaHeaders, + "Memory on the garbage-collected JavaScript heap, within arenas, that is " + "used to hold internal book-keeping information."); + + DO(mkPath(name, "gc-heap/arena-padding"), + JS_GC_HEAP_KIND, stats->gcHeapArenaPadding, + "Memory on the garbage-collected JavaScript heap, within arenas, that is " + "unused and present only so that other data is aligned."); + + DO(mkPath(name, "gc-heap/arena-unused"), + JS_GC_HEAP_KIND, stats->gcHeapArenaUnused, + "Memory on the garbage-collected JavaScript heap, within arenas, that " + "could be holding useful data but currently isn't."); + + DO(mkPath(name, "gc-heap/objects"), + JS_GC_HEAP_KIND, stats->gcHeapObjects, + "Memory on the garbage-collected JavaScript heap that holds objects."); + + DO(mkPath(name, "gc-heap/strings"), + JS_GC_HEAP_KIND, stats->gcHeapStrings, + "Memory on the garbage-collected JavaScript heap that holds string " + "headers."); + + DO(mkPath(name, "gc-heap/shapes"), + JS_GC_HEAP_KIND, stats->gcHeapShapes, + "Memory on the garbage-collected JavaScript heap that holds shapes. " + "A shape is an internal data structure that makes property accesses " + "fast."); + + DO(mkPath(name, "gc-heap/xml"), + JS_GC_HEAP_KIND, stats->gcHeapXml, + "Memory on the garbage-collected JavaScript heap that holds E4X XML " + "objects."); + + DO(mkPath(name, "object-slots"), + nsIMemoryReporter::KIND_HEAP, stats->objectSlots, "Memory allocated for non-fixed object slot arrays, which are used " "to represent object properties. Some objects also contain a fixed " "number of slots which are stored on the JavaScript heap; those slots " - "are not counted here.") + "are not counted here, but in 'gc-heap/objects'."); -NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSStringChars, - "explicit/js/string-chars", - KIND_HEAP, - UNITS_BYTES, - GetJSStringChars, + DO(mkPath(name, "string-chars"), + nsIMemoryReporter::KIND_HEAP, stats->stringChars, "Memory allocated to hold string characters. Not all of this allocated " "memory is necessarily used to hold characters. Each string also " "includes a header which is stored on the JavaScript heap; that header " - "is not counted here.") + "is not counted here, but in 'gc-heap/strings'."); + + DO(mkPath(name, "scripts"), + nsIMemoryReporter::KIND_HEAP, stats->scripts, + "Memory allocated for JSScripts. A JSScript is created for each " + "user-defined function in a script. One is also created for " + "the top-level code in a script. Each JSScript includes byte-code and " + "various other things."); #ifdef JS_METHODJIT + DO(mkPath(name, "mjit-code"), + nsIMemoryReporter::KIND_MAPPED, stats->mjitCode, + "Memory used by the method JIT to hold generated code."); -static PRInt64 -GetCompartmentMjitCodeSize(JSCompartment *c) -{ - return c->getMjitCodeSize(); -} - -static PRInt64 -GetJSMjitCode() -{ - return GetPerCompartmentSize(GetCompartmentMjitCodeSize); -} - -static PRInt64 -GetJSMJitData() -{ - JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->GetJSRuntime(); - return rt->mjitDataSize; -} - -NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSMjitCode, - "explicit/js/mjit-code", - KIND_MAPPED, - UNITS_BYTES, - GetJSMjitCode, - "Memory used by the method JIT to hold generated code.") - -NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSMjitData, - "explicit/js/mjit-data", - KIND_HEAP, - UNITS_BYTES, - GetJSMJitData, + DO(mkPath(name, "mjit-data"), + nsIMemoryReporter::KIND_HEAP, stats->mjitData, "Memory used by the method JIT for the following data: " - "JITScripts, native maps, and inline cache structs.") - -#endif // JS_METHODJIT - + "JITScripts, native maps, and inline cache structs."); +#endif #ifdef JS_TRACER + DO(mkPath(name, "tjit-code"), + nsIMemoryReporter::KIND_MAPPED, stats->tjitCode, + "Memory used by the trace JIT to hold generated code."); -static PRInt64 -GetCompartmentTjitCode(JSCompartment *c) -{ - if (c->hasTraceMonitor()) { - size_t total, frag_size, free_size; - c->traceMonitor()->getCodeAllocStats(total, frag_size, free_size); - return total; - } - return 0; -} + DO(mkPath(name, "tjit-data/allocators-main"), + nsIMemoryReporter::KIND_HEAP, stats->tjitDataAllocatorsMain, + "Memory used by the trace JIT's VMAllocators."); -static PRInt64 -GetCompartmentTjitDataAllocatorsMain(JSCompartment *c) -{ - return c->hasTraceMonitor() - ? c->traceMonitor()->getVMAllocatorsMainSize() - : 0; -} - -static PRInt64 -GetCompartmentTjitDataAllocatorsReserve(JSCompartment *c) -{ - return c->hasTraceMonitor() - ? c->traceMonitor()->getVMAllocatorsReserveSize() - : 0; -} - -static PRInt64 -GetJSTjitCode() -{ - return GetPerCompartmentSize(GetCompartmentTjitCode); -} - -static PRInt64 -GetJSTjitDataAllocatorsMain() -{ - return GetPerCompartmentSize(GetCompartmentTjitDataAllocatorsMain); -} - -static PRInt64 -GetJSTjitDataAllocatorsReserve() -{ - return GetPerCompartmentSize(GetCompartmentTjitDataAllocatorsReserve); -} - -NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSTjitCode, - "explicit/js/tjit-code", - KIND_MAPPED, - UNITS_BYTES, - GetJSTjitCode, - "Memory used by the trace JIT to hold generated code.") - -NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSTjitDataAllocatorsMain, - "explicit/js/tjit-data/allocators-main", - KIND_HEAP, - UNITS_BYTES, - GetJSTjitDataAllocatorsMain, - "Memory used by the trace JIT's VMAllocators.") - -NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSTjitDataAllocatorsReserve, - "explicit/js/tjit-data/allocators-reserve", - KIND_HEAP, - UNITS_BYTES, - GetJSTjitDataAllocatorsReserve, + DO(mkPath(name, "tjit-data/allocators-reserve"), + nsIMemoryReporter::KIND_HEAP, stats->tjitDataAllocatorsReserve, "Memory used by the trace JIT and held in reserve for VMAllocators " - "in case of OOM.") + "in case of OOM."); +#endif + } -#endif // JS_TRACER + JS_ASSERT(gcHeapChunkTotal % js::GC_CHUNK_SIZE == 0); + size_t numChunks = gcHeapChunkTotal / js::GC_CHUNK_SIZE; + PRInt64 perChunkAdmin = + sizeof(js::gc::Chunk) - (sizeof(js::gc::Arena) * js::gc::ArenasPerChunk); + PRInt64 gcHeapChunkAdmin = numChunks * perChunkAdmin; + gcHeapChunkUnused -= gcHeapChunkAdmin; + + DO(NS_LITERAL_CSTRING("explicit/js/gc-heap-chunk-unused"), + JS_GC_HEAP_KIND, gcHeapChunkUnused, + "Memory on the garbage-collected JavaScript heap, within chunks, that " + "could be holding useful data but currently isn't."); + + DO(NS_LITERAL_CSTRING("explicit/js/gc-heap-chunk-admin"), + JS_GC_HEAP_KIND, gcHeapChunkAdmin, + "Memory on the garbage-collected JavaScript heap, within chunks, that is " + "used to hold internal book-keeping information."); + + return NS_OK; + } +}; +NS_IMPL_THREADSAFE_ISUPPORTS1( + XPConnectJSCompartmentsMultiReporter +, nsIMemoryMultiReporter +) XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect) : mXPConnect(aXPConnect), @@ -1603,18 +1733,7 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect) NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSGCHeap)); NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSStack)); - NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSScripts)); - NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSObjectSlots)); - NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSStringChars)); -#ifdef JS_METHODJIT - NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSMjitCode)); - NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSMjitData)); -#endif -#ifdef JS_TRACER - NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSTjitCode)); - NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSTjitDataAllocatorsMain)); - NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSTjitDataAllocatorsReserve)); -#endif + NS_RegisterMemoryMultiReporter(new XPConnectJSCompartmentsMultiReporter); } if(!JS_DHashTableInit(&mJSHolders, JS_DHashGetStubOps(), nsnull, diff --git a/toolkit/components/aboutmemory/content/aboutMemory.js b/toolkit/components/aboutmemory/content/aboutMemory.js index 35f319808736..1d5cd36e69cf 100644 --- a/toolkit/components/aboutmemory/content/aboutMemory.js +++ b/toolkit/components/aboutmemory/content/aboutMemory.js @@ -243,7 +243,6 @@ function update() "reporter to see a detailed description of what it measures." ""; - var div = document.createElement("div"); div.innerHTML = text; content.appendChild(div); @@ -252,7 +251,7 @@ function update() // Compare two memory reporter nodes. We want to group together measurements // with the same units, so sort first by the nodes' _units field, then sort by // the amount if the units are equal. -function cmp_amount(a, b) +function cmpAmount(a, b) { if (a._units != b._units) return a._units - b._units; // use the enum order from nsIMemoryReporter @@ -451,7 +450,7 @@ function genProcessText(aProcess, aReporters) */ function filterTree(aT) { - aT._kids.sort(cmp_amount); + aT._kids.sort(cmpAmount); for (var i = 0; i < aT._kids.length; i++) { if (shouldOmit(aT._kids[i]._amount)) { @@ -460,21 +459,26 @@ function genProcessText(aProcess, aReporters) // replace them with a single aggregate node. var i0 = i; var aggBytes = 0; - var aggNames = []; for ( ; i < aT._kids.length; i++) { aggBytes += aT._kids[i]._amount; - aggNames.push(aT._kids[i]._name); } aT._kids.splice(i0); var n = i - i0; var rSub = { _name: "(" + n + " omitted)", _kind: KIND_OTHER, - _description: "Omitted sub-trees: " + aggNames.join(", ") + ".", _amount: aggBytes, + _description: n + " sub-trees that were below the " + + omitThresholdPerc + "% significance threshold. " + + "Click 'More verbose' at the bottom of this page " + + "to see them.", _kids: [] }; + // Add the "omitted" sub-tree at the end and then resort, because the + // sum of the omitted sub-trees may be larger than some of the + // shown sub-trees. aT._kids[i0] = rSub; + aT._kids.sort(cmpAmount); break; } filterTree(aT._kids[i]); @@ -648,7 +652,41 @@ function kindToString(aKind) function escapeQuotes(aStr) { - return aStr.replace(/'/g, '''); + return aStr.replace(/\&/g, '&').replace(/'/g, '''); +} + +// For user-controlled strings. +function escapeAll(aStr) +{ + return aStr.replace(/\&/g, '&').replace(/'/g, '''). + replace(/\/g, '>'). + replace(/\"/g, '"'); +} + +// Compartment reporter names are URLs and so can include forward slashes. But +// forward slash is the memory reporter path separator. So the memory +// reporters change them to backslashes. Undo that here. +function flipBackslashes(aStr) +{ + return aStr.replace(/\\/g, '/'); +} + +// Truncate the URL in a compartment name if not in verbose mode. +function truncateCompartmentName(aStr) +{ + return (gVerbose) + ? aStr + : aStr.replace(/compartment\((.{40}).*\)/, 'compartment($1...)'); +} + +function prepName(aStr) +{ + return escapeAll(flipBackslashes(truncateCompartmentName(aStr))); +} + +function prepDesc(aStr) +{ + return escapeQuotes(flipBackslashes(aStr)); } function genMrNameText(aKind, aDesc, aName, aHasProblem) @@ -658,8 +696,8 @@ function genMrNameText(aKind, aDesc, aName, aHasProblem) "The reported value is the sum of all entries below '" + aName + "', " + "which is probably less than the true value."; var text = "-- " + aName + ""; + kindToString(aKind) + prepDesc(aDesc) + + "'>" + prepName(aName) + ""; text += aHasProblem ? " [*]\n" : "\n"; @@ -802,7 +840,7 @@ function genOtherText(aReporters) _path: r._path, _kind: r._kind, _units: r._units, - _amount: hasProblem ? 0 : r._amount, + _amount: hasProblem ? 0 : r._amount, _description: r._description, _hasProblem: hasProblem }; @@ -813,7 +851,7 @@ function genOtherText(aReporters) } } } - rArray.sort(cmp_amount); + rArray.sort(cmpAmount); // Generate text for the not-yet-printed values. var text = ""; diff --git a/toolkit/components/aboutmemory/tests/chrome/test_aboutmemory.xul b/toolkit/components/aboutmemory/tests/chrome/test_aboutmemory.xul index c5ffaa33e0f6..03528c69cd89 100644 --- a/toolkit/components/aboutmemory/tests/chrome/test_aboutmemory.xul +++ b/toolkit/components/aboutmemory/tests/chrome/test_aboutmemory.xul @@ -84,8 +84,14 @@ f("2nd", "heap-unused", OTHER, 100 * MB), f("2nd", "explicit/a/b/c", HEAP, 498 * MB), f("2nd", "explicit/a/b/c", HEAP, 1 * MB), // dup: merge - f("2nd", "explicit/b", HEAP, 400 * MB), - f("2nd", "other1", OTHER, 777 * MB), + f("2nd", "explicit/flip\\the\\backslashes", + HEAP, 200 * MB), + f("2nd", "explicit/compartment(this-will-be-truncated-in-non-verbose-mode-abcdefghijklmnopqrstuvwxyz)", + HEAP, 200 * MB), + // The escaping of compartment names must prevent this script from running. + f("2nd", "danger", + OTHER, 666 * MB), + f("2nd", "other1", OTHER, 111 * MB), // kUnknown should be handled gracefully for "heap-used", non-leaf // reporters, leaf-reporters, and "other" reporters. @@ -146,12 +152,14 @@ Explicit Allocations\n\ ├────499.00 MB (49.90%) -- a\n\ │ └──499.00 MB (49.90%) -- b\n\ │ └──499.00 MB (49.90%) -- c\n\ -├────400.00 MB (40.00%) -- b\n\ +├────200.00 MB (20.00%) -- flip/the/backslashes\n\ +├────200.00 MB (20.00%) -- compartment(this-will-be-truncated-in-non-verbose-mo...)\n\ └────101.00 MB (10.10%) -- heap-unclassified\n\ \n\ Other Measurements\n\ 1,000.00 MB -- heap-used\n\ - 777.00 MB -- other1\n\ + 666.00 MB -- danger\n\ + 111.00 MB -- other1\n\ 100.00 MB -- heap-unused\n\ \n\ 3rd Process\n\ @@ -209,12 +217,14 @@ Explicit Allocations\n\ ├────523,239,424 B (49.90%) -- a\n\ │ └──523,239,424 B (49.90%) -- b\n\ │ └──523,239,424 B (49.90%) -- c\n\ -├────419,430,400 B (40.00%) -- b\n\ +├────209,715,200 B (20.00%) -- flip/the/backslashes\n\ +├────209,715,200 B (20.00%) -- compartment(this-will-be-truncated-in-non-verbose-mode-abcdefghijklmnopqrstuvwxyz)\n\ └────105,906,176 B (10.10%) -- heap-unclassified\n\ \n\ Other Measurements\n\ 1,048,576,000 B -- heap-used\n\ - 814,743,552 B -- other1\n\ + 698,351,616 B -- danger\n\ + 116,391,936 B -- other1\n\ 104,857,600 B -- heap-unused\n\ \n\ 3rd Process\n\ diff --git a/toolkit/components/telemetry/TelemetryPing.js b/toolkit/components/telemetry/TelemetryPing.js index 80714074ff57..32ad3890fc41 100644 --- a/toolkit/components/telemetry/TelemetryPing.js +++ b/toolkit/components/telemetry/TelemetryPing.js @@ -54,7 +54,7 @@ const TELEMETRY_INTERVAL = 60; const TELEMETRY_DELAY = 60000; // about:memory values to turn into histograms const MEM_HISTOGRAMS = { - "explicit/js/gc-heap": "MEMORY_JS_GC_HEAP", + "js-gc-heap": "MEMORY_JS_GC_HEAP", "resident": "MEMORY_RESIDENT", "explicit/layout/all": "MEMORY_LAYOUT_ALL", "hard-page-faults": "HARD_PAGE_FAULTS"