author | Ben Turner <bent.mozilla@gmail.com> |
Mon, 01 Aug 2011 21:06:17 -0700 | |
changeset 74428 | baa3a2a2cea43895296c1bc656056e6e2bda7b43 |
parent 74427 | b3a481c2e02aaa5b1ca5f07b0db9d6023086d613 |
child 74430 | 9134cf8f5f4aae07898266968bce6042cc3d5bf2 |
push id | 235 |
push user | bzbarsky@mozilla.com |
push date | Tue, 27 Sep 2011 17:13:04 +0000 |
treeherder | mozilla-beta@2d1e082d176a [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | jst |
bugs | 674721 |
milestone | 8.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/dom/workers/RuntimeService.cpp +++ b/dom/workers/RuntimeService.cpp @@ -39,41 +39,45 @@ #include "RuntimeService.h" #include "nsIDOMChromeWindow.h" #include "nsIDocument.h" #include "nsIEffectiveTLDService.h" #include "nsIObserverService.h" #include "nsIPrincipal.h" #include "nsIJSContextStack.h" +#include "nsIMemoryReporter.h" #include "nsIScriptSecurityManager.h" #include "nsISupportsPriority.h" #include "nsITimer.h" #include "nsPIDOMWindow.h" +#include "jsprf.h" #include "mozilla/Preferences.h" #include "nsContentUtils.h" #include "nsDOMJSUtils.h" #include "nsGlobalWindow.h" #include "nsNetUtil.h" #include "nsServiceManagerUtils.h" #include "nsThreadUtils.h" #include "nsXPCOM.h" #include "nsXPCOMPrivate.h" +#include "xpcpublic.h" #include "Events.h" #include "EventTarget.h" #include "Worker.h" #include "WorkerPrivate.h" USING_WORKERS_NAMESPACE using mozilla::MutexAutoLock; using mozilla::MutexAutoUnlock; using mozilla::Preferences; +using namespace mozilla::xpconnect::memory; // The size of the worker runtime heaps in bytes. #define WORKER_RUNTIME_HEAPSIZE 32 * 1024 * 1024 // The size of the C stack in bytes. #define WORKER_CONTEXT_NATIVE_STACK_LIMIT sizeof(size_t) * 32 * 1024 // The maximum number of threads to use for workers, overridable via pref. @@ -280,16 +284,80 @@ CreateJSContextForWorker(WorkerPrivate* if (aWorkerPrivate->IsChromeWorker()) { JS_SetVersion(workerCx, JSVERSION_LATEST); } return workerCx; } +class WorkerMemoryReporter : public nsIMemoryMultiReporter +{ + JSRuntime* mRuntime; + nsCString mPathPrefix; + +public: + NS_DECL_ISUPPORTS + + WorkerMemoryReporter(WorkerPrivate* aWorkerPrivate, JSRuntime* aRuntime) + : mRuntime(aRuntime) + { + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + nsCString escapedDomain(aWorkerPrivate->Domain()); + escapedDomain.ReplaceChar('/', '\\'); + + NS_ConvertUTF16toUTF8 escapedURL(aWorkerPrivate->ScriptURL()); + escapedURL.ReplaceChar('/', '\\'); + + // 64bit address plus '0x' plus null terminator. + char address[21]; + JSUint32 addressSize = + JS_snprintf(address, sizeof(address), "0x%llx", aWorkerPrivate); + if (addressSize == JSUint32(-1)) { + NS_WARNING("JS_snprintf failed!"); + address[0] = '\0'; + addressSize = 0; + } + + mPathPrefix = NS_LITERAL_CSTRING("explicit/dom/workers(") + + escapedDomain + NS_LITERAL_CSTRING(")/worker(") + + escapedURL + NS_LITERAL_CSTRING(", ") + + nsDependentCString(address, addressSize) + + NS_LITERAL_CSTRING(")/"); + } + + NS_IMETHOD + CollectReports(nsIMemoryMultiReporterCallback* aCallback, + nsISupports* aClosure) + { + AssertIsOnMainThread(); + + JS_TriggerAllOperationCallbacks(mRuntime); + + IterateData data; + if (!CollectCompartmentStatsForRuntime(mRuntime, &data)) { + return NS_ERROR_FAILURE; + } + + for (CompartmentStats *stats = data.compartmentStatsVector.begin(); + stats != data.compartmentStatsVector.end(); + ++stats) + { + ReportCompartmentStats(*stats, mPathPrefix, aCallback, aClosure); + } + + ReportJSStackSizeForRuntime(mRuntime, mPathPrefix, aCallback, aClosure); + + return NS_OK; + } +}; + +NS_IMPL_THREADSAFE_ISUPPORTS1(WorkerMemoryReporter, nsIMemoryMultiReporter) + class WorkerThreadRunnable : public nsRunnable { WorkerPrivate* mWorkerPrivate; public: WorkerThreadRunnable(WorkerPrivate* aWorkerPrivate) : mWorkerPrivate(aWorkerPrivate) { @@ -306,22 +374,33 @@ public: JSContext* cx = CreateJSContextForWorker(workerPrivate); if (!cx) { // XXX need to fire an error at parent. NS_ERROR("Failed to create runtime and context!"); return NS_ERROR_FAILURE; } - { - JSAutoRequest ar(cx); - workerPrivate->DoRunLoop(cx); + JSRuntime* rt = JS_GetRuntime(cx); + + nsRefPtr<WorkerMemoryReporter> reporter = + new WorkerMemoryReporter(workerPrivate, rt); + if (NS_FAILED(NS_RegisterMemoryMultiReporter(reporter))) { + NS_WARNING("Failed to register memory reporter!"); + reporter = nsnull; } - JSRuntime* rt = JS_GetRuntime(cx); + workerPrivate->DoRunLoop(cx); + + if (reporter) { + if (NS_FAILED(NS_UnregisterMemoryMultiReporter(reporter))) { + NS_WARNING("Failed to unregister memory reporter!"); + } + reporter = nsnull; + } // XXX Bug 666963 - CTypes can create another JSContext for use with // closures, and then it holds that context in a reserved slot on the CType // prototype object. We have to destroy that context before we can destroy // the runtime, and we also have to make sure that it isn't the last context // to be destroyed (otherwise it will assert). To accomplish this we create // an unused dummy context, destroy our real context, and then destroy the // dummy. Once this bug is resolved we can remove this nastiness and simply
--- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -2037,28 +2037,32 @@ WorkerPrivate::DoRunLoop(JSContext* aCx) while (!mControlQueue.Pop(event) && !mQueue.Pop(event)) { mCondVar.Wait(); } { MutexAutoUnlock unlock(mMutex); + JSAutoRequest ar(aCx); + #ifdef EXTRA_GC // Find GC bugs... JS_GC(aCx); #endif event->Run(); NS_RELEASE(event); } currentStatus = mStatus; } + JSAutoRequest ar(aCx); + #ifdef EXTRA_GC // Find GC bugs... JS_GC(aCx); #endif if (currentStatus != Running && !HasActiveFeatures()) { // If the close handler has finished and all features are done then we can // kill this thread. @@ -2088,16 +2092,18 @@ WorkerPrivate::DoRunLoop(JSContext* aCx) NS_NOTREACHED("Shouldn't get here!"); } bool WorkerPrivate::OperationCallback(JSContext* aCx) { AssertIsOnWorkerThread(); + JS_YieldRequest(aCx); + bool mayContinue = true; for (;;) { // Run all control events now. for (;;) { nsIRunnable* event; { MutexAutoLock lock(mMutex); @@ -2121,16 +2127,18 @@ WorkerPrivate::OperationCallback(JSConte JS_FlushCaches(aCx); JS_GC(aCx); while ((mayContinue = MayContinueRunning())) { MutexAutoLock lock(mMutex); if (!mControlQueue.IsEmpty()) { break; } + + JSAutoSuspendRequest asr(aCx); mCondVar.Wait(PR_MillisecondsToInterval(RemainingRunTimeMS())); } } if (!mayContinue) { // We want only uncatchable exceptions here. NS_ASSERTION(!JS_IsExceptionPending(aCx), "Should not have an exception set here!"); @@ -2475,16 +2483,17 @@ WorkerPrivate::RunSyncLoop(JSContext* aC SyncQueue* syncQueue = mSyncQueues[aSyncLoopKey].get(); for (;;) { nsIRunnable* event; { MutexAutoLock lock(mMutex); while (!mControlQueue.Pop(event) && !syncQueue->mQueue.Pop(event)) { + JSAutoSuspendRequest asr(aCx); mCondVar.Wait(); } } #ifdef EXTRA_GC // Find GC bugs... JS_GC(mJSContext); #endif
--- a/js/src/xpconnect/src/xpcjsruntime.cpp +++ b/js/src/xpconnect/src/xpcjsruntime.cpp @@ -37,31 +37,33 @@ * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* Per JSRuntime object */ #include "xpcprivate.h" +#include "xpcpublic.h" #include "WrapperFactory.h" #include "dom_quickstubs.h" #include "jsgcchunk.h" #include "jsscope.h" #include "nsIMemoryReporter.h" #include "nsPrintfCString.h" #include "mozilla/FunctionTimer.h" #include "prsystem.h" #ifdef MOZ_CRASHREPORTER #include "nsExceptionHandler.h" #endif using namespace mozilla; +using namespace mozilla::xpconnect::memory; /***************************************************************************/ const char* XPCJSRuntime::mStrings[] = { "constructor", // IDX_CONSTRUCTOR "toString", // IDX_TO_STRING "toSource", // IDX_TO_SOURCE "lastResult", // IDX_LAST_RESULT @@ -1222,16 +1224,215 @@ XPCJSRuntime::~XPCJSRuntime() #ifdef DEBUG_shaver_off fprintf(stderr, "nJRSI: destroyed runtime %p\n", (void *)mJSRuntime); #endif } XPCPerThreadData::ShutDown(); } +namespace { + +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; +} + +#ifdef JS_METHODJIT + +PRInt64 +GetCompartmentMjitCodeSize(JSCompartment *c) +{ + return c->getMjitCodeSize(); +} + +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; +} + +#endif // JS_METHODJIT + +#ifdef JS_TRACER + +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; +} + +PRInt64 +GetCompartmentTjitDataAllocatorsMainSize(JSCompartment *c) +{ + return c->hasTraceMonitor() + ? c->traceMonitor()->getVMAllocatorsMainSize() + : 0; +} + +PRInt64 +GetCompartmentTjitDataAllocatorsReserveSize(JSCompartment *c) +{ + return c->hasTraceMonitor() + ? c->traceMonitor()->getVMAllocatorsReserveSize() + : 0; +} + +#endif // JS_TRACER + +void +CompartmentCallback(JSContext *cx, void *vdata, JSCompartment *compartment) +{ + // Append a new CompartmentStats to the vector. + IterateData *data = static_cast<IterateData *>(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 +} + +void +ArenaCallback(JSContext *cx, void *vdata, js::gc::Arena *arena, + size_t traceKind, size_t thingSize) +{ + IterateData *data = static_cast<IterateData *>(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); +} + +void +CellCallback(JSContext *cx, void *vdata, void *thing, size_t traceKind, + size_t thingSize) +{ + IterateData *data = static_cast<IterateData *>(vdata); + CompartmentStats *curr = data->currCompartmentStats; + if(traceKind == JSTRACE_OBJECT) + { + curr->gcHeapObjects += thingSize; + JSObject *obj = static_cast<JSObject *>(thing); + if(obj->hasSlotsArray()) + curr->objectSlots += obj->numSlots() * sizeof(js::Value); + } + else if(traceKind == JSTRACE_STRING) + { + curr->gcHeapStrings += thingSize; + JSString *str = static_cast<JSString *>(thing); + curr->stringChars += str->charsHeapSize(); + } + else if(traceKind == JSTRACE_SHAPE) + { + curr->gcHeapShapes += thingSize; + js::Shape *shape = static_cast<js::Shape *>(thing); + if(shape->hasTable()) + curr->propertyTables += shape->getTable()->sizeOf(); + } + else + { + JS_ASSERT(traceKind == JSTRACE_XML); + curr->gcHeapXml += thingSize; + } + // Yes, this is a subtraction: see ArenaCallback() for details. + curr->gcHeapArenaUnused -= thingSize; +} + +template <int N> +inline void +ReportMemory(const nsACString &path, PRInt32 kind, PRInt32 units, + PRInt64 amount, const char (&desc)[N], + nsIMemoryMultiReporterCallback *callback, nsISupports *closure) +{ + callback->Callback(NS_LITERAL_CSTRING(""), path, kind, units, amount, + NS_LITERAL_CSTRING(desc), closure); +} + +template <int N> +inline void +ReportMemoryBytes(const nsACString &path, PRInt32 kind, PRInt64 amount, + const char (&desc)[N], + nsIMemoryMultiReporterCallback *callback, + nsISupports *closure) +{ + ReportMemory(path, kind, nsIMemoryReporter::UNITS_BYTES, amount, desc, + callback, closure); +} + +template <int N> +inline void +ReportMemoryBytes0(const nsCString &path, PRInt32 kind, PRInt64 amount, + const char (&desc)[N], + nsIMemoryMultiReporterCallback *callback, + nsISupports *closure) +{ + if(amount) + ReportMemoryBytes(path, kind, amount, desc, callback, closure); +} + +template <int N> +inline void +ReportMemoryPercentage(const nsACString &path, PRInt32 kind, PRInt64 amount, + const char (&desc)[N], + nsIMemoryMultiReporterCallback *callback, + nsISupports *closure) +{ + ReportMemory(path, kind, nsIMemoryReporter::UNITS_PERCENTAGE, amount, desc, + callback, closure); +} + +template <int N> +inline const nsCString +MakeMemoryReporterPath(const nsACString &pathPrefix, + const nsACString &compartmentName, + const char (&reporterName)[N]) +{ + return pathPrefix + NS_LITERAL_CSTRING("compartment(") + compartmentName + + NS_LITERAL_CSTRING(")/") + nsDependentCString(reporterName); +} + +} // anonymous namespace + class XPConnectGCChunkAllocator : public js::GCChunkAllocator { public: XPConnectGCChunkAllocator() {} PRInt64 GetGCChunkBytesInUse() { return mNumGCChunksInUse * js::GC_CHUNK_SIZE; @@ -1278,35 +1479,16 @@ static XPConnectGCChunkAllocator gXPCJSC NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSGCHeap, "js-gc-heap", KIND_OTHER, nsIMemoryReporter::UNITS_BYTES, gXPCJSChunkAllocator.GetGCChunkBytesInUse, "Memory used by the garbage-collected JavaScript heap.") static PRInt64 -GetJSStack() -{ - JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->GetJSRuntime(); - PRInt64 n = 0; - for (js::ThreadDataIter i(rt); !i.empty(); i.popFront()) - n += i.threadData()->stackSpace.committedSize(); - return n; -} - -NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSStack, - "explicit/js/stack", - KIND_NONHEAP, - 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 GetJSSystemCompartmentCount() { JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->GetJSRuntime(); size_t n = 0; for (size_t i = 0; i < rt->compartments.length(); i++) { if (rt->compartments[i]->isSystemCompartment) { n++; } @@ -1349,448 +1531,337 @@ NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJS KIND_OTHER, nsIMemoryReporter::UNITS_COUNT, GetJSUserCompartmentCount, "The number of JavaScript compartments for user code. The sum of this " "and 'js-compartments-system' might not match the number of " "compartments listed under 'js' if a garbage collection occurs at an " "inopportune time, but such cases should be rare.") -class XPConnectJSCompartmentsMultiReporter : public nsIMemoryMultiReporter +namespace mozilla { +namespace xpconnect { +namespace memory { + +CompartmentStats::CompartmentStats(JSContext *cx, JSCompartment *c) { -private: - struct CompartmentStats - { - CompartmentStats(JSContext *cx, JSCompartment *c) { - memset(this, 0, sizeof(*this)); + memset(this, 0, sizeof(*this)); - 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 = '\\'; - } - } - // If it's the system compartment, append the address. - // This means that multiple system compartments (and there - // can be many) can be distinguished. - if (c->isSystemCompartment) { - static const int maxLength = 31; // ample; 64-bit address max is 18 chars - nsPrintfCString address(maxLength, ", 0x%llx", PRUint64(c)); - name.Append(address); - } - } else { - name = NS_LITERAL_CSTRING("null-codebase"); - } - } else { - name = NS_LITERAL_CSTRING("null-principal"); + if(c == cx->runtime->atomsCompartment) + { + name.AssignLiteral("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); + name.ReplaceChar('/', '\\'); + + // If it's the system compartment, append the address. + // This means that multiple system compartments (and there + // can be many) can be distinguished. + if(c->isSystemCompartment) + { + // ample; 64-bit address max is 18 chars + static const int maxLength = 31; + nsPrintfCString address(maxLength, ", 0x%llx", PRUint64(c)); + name.Append(address); } } - - nsCString name; - PRInt64 gcHeapArenaHeaders; - PRInt64 gcHeapArenaPadding; - PRInt64 gcHeapArenaUnused; - - PRInt64 gcHeapObjects; - PRInt64 gcHeapStrings; - PRInt64 gcHeapShapes; - PRInt64 gcHeapXml; - - PRInt64 objectSlots; - PRInt64 stringChars; - PRInt64 propertyTables; + else + { + name.AssignLiteral("null-codebase"); + } + } + else + { + name.AssignLiteral("null-principal"); + } +} - PRInt64 scripts; -#ifdef JS_METHODJIT - PRInt64 mjitCode; - PRInt64 mjitData; -#endif -#ifdef JS_TRACER - PRInt64 tjitCode; - PRInt64 tjitDataAllocatorsMain; - PRInt64 tjitDataAllocatorsReserve; -#endif - }; - - struct IterateData +JSBool +CollectCompartmentStatsForRuntime(JSRuntime *rt, IterateData *data) +{ + JSContext *cx = JS_NewContext(rt, 0); + if(!cx) { - IterateData(JSRuntime *rt) - : compartmentStatsVector() - , currCompartmentStats(NULL) - { - compartmentStatsVector.reserve(rt->compartments.length()); - } - - js::Vector<CompartmentStats, 0, js::SystemAllocPolicy> 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; + NS_ERROR("couldn't create context for memory tracing"); + return false; } - #ifdef JS_METHODJIT - - static PRInt64 - GetCompartmentMjitCodeSize(JSCompartment *c) { - return c->getMjitCodeSize(); - } + JSAutoRequest ar(cx); - 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; + data->compartmentStatsVector.reserve(rt->compartments.length()); + js::IterateCompartmentsArenasCells(cx, data, CompartmentCallback, + ArenaCallback, CellCallback); } - #endif // JS_METHODJIT + JS_DestroyContextNoGC(cx); + return true; +} - #ifdef JS_TRACER +void +ReportCompartmentStats(const CompartmentStats &stats, + const nsACString &pathPrefix, + nsIMemoryMultiReporterCallback *callback, + nsISupports *closure) +{ + ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name, + "gc-heap/arena-headers"), + JS_GC_HEAP_KIND, stats.gcHeapArenaHeaders, + "Memory on the compartment's garbage-collected JavaScript heap, within " + "arenas, that is used to hold internal book-keeping information.", + callback, closure); + + ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name, + "gc-heap/arena-padding"), + JS_GC_HEAP_KIND, stats.gcHeapArenaPadding, + "Memory on the compartment's garbage-collected JavaScript heap, within " + "arenas, that is unused and present only so that other data is aligned. " + "This constitutes internal fragmentation.", + callback, closure); - 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; - } + ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name, + "gc-heap/arena-unused"), + JS_GC_HEAP_KIND, stats.gcHeapArenaUnused, + "Memory on the compartment's garbage-collected JavaScript heap, within " + "arenas, that could be holding useful data but currently isn't.", + callback, closure); + + ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name, + "gc-heap/objects"), + JS_GC_HEAP_KIND, stats.gcHeapObjects, + "Memory on the compartment's garbage-collected JavaScript heap that holds " + "objects.", + callback, closure); - static PRInt64 - GetCompartmentTjitDataAllocatorsMainSize(JSCompartment *c) - { - return c->hasTraceMonitor() - ? c->traceMonitor()->getVMAllocatorsMainSize() - : 0; - } + ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name, + "gc-heap/strings"), + JS_GC_HEAP_KIND, stats.gcHeapStrings, + "Memory on the compartment's garbage-collected JavaScript heap that holds " + "string headers. String headers contain various pieces of information " + "about a string, but do not contain (except in the case of very short " + "strings) the string characters; characters in longer strings are counted " + "under 'gc-heap/string-chars' instead.", + callback, closure); + + ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name, + "gc-heap/shapes"), + JS_GC_HEAP_KIND, stats.gcHeapShapes, + "Memory on the compartment's garbage-collected JavaScript heap that holds " + "shapes. A shape is an internal data structure that makes JavaScript " + "property accesses fast.", + callback, closure); - static PRInt64 - GetCompartmentTjitDataAllocatorsReserveSize(JSCompartment *c) - { - return c->hasTraceMonitor() - ? c->traceMonitor()->getVMAllocatorsReserveSize() - : 0; - } + ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name, + "gc-heap/xml"), + JS_GC_HEAP_KIND, stats.gcHeapXml, + "Memory on the compartment's garbage-collected JavaScript heap that holds " + "E4X XML objects.", + callback, closure); - #endif // JS_TRACER + ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name, + "object-slots"), + nsIMemoryReporter::KIND_HEAP, stats.objectSlots, + "Memory allocated for the compartment's 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 compartment's " + "JavaScript heap; those slots are not counted here, but in " + "'gc-heap/objects' instead.", + callback, closure); + + ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name, + "string-chars"), + nsIMemoryReporter::KIND_HEAP, stats.stringChars, + "Memory allocated to hold the compartment's string characters. Sometimes " + "more memory is allocated than necessary, to simplify string " + "concatenation. Each string also includes a header which is stored on the " + "compartment's JavaScript heap; that header is not counted here, but in " + "'gc-heap/strings' instead.", + callback, closure); - static void - CompartmentCallback(JSContext *cx, void *vdata, JSCompartment *compartment) - { - // Append a new CompartmentStats to the vector. - IterateData *data = static_cast<IterateData *>(vdata); - CompartmentStats compartmentStats(cx, compartment); - data->compartmentStatsVector.infallibleAppend(compartmentStats); - CompartmentStats *curr = data->compartmentStatsVector.end() - 1; - data->currCompartmentStats = curr; + ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name, + "property-tables"), + nsIMemoryReporter::KIND_HEAP, stats.propertyTables, + "Memory allocated for the compartment's property tables. A property " + "table is an internal data structure that makes JavaScript property " + "accesses fast.", + callback, closure); - // Get the compartment-level numbers. - curr->scripts = GetCompartmentScriptsSize(compartment); + ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name, + "scripts"), + nsIMemoryReporter::KIND_HEAP, stats.scripts, + "Memory allocated for the compartment's 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.", + callback, closure); + #ifdef JS_METHODJIT - curr->mjitCode = GetCompartmentMjitCodeSize(compartment); - curr->mjitData = GetCompartmentMjitDataSize(compartment); + ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name, + "mjit-code"), + nsIMemoryReporter::KIND_NONHEAP, stats.mjitCode, + "Memory used by the method JIT to hold the compartment's generated code.", + callback, closure); + + ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name, + "mjit-data"), + nsIMemoryReporter::KIND_HEAP, stats.mjitData, + "Memory used by the method JIT for the compartment's compilation data: " + "JITScripts, native maps, and inline cache structs.", + callback, closure); #endif #ifdef JS_TRACER - curr->tjitCode = GetCompartmentTjitCodeSize(compartment); - curr->tjitDataAllocatorsMain = GetCompartmentTjitDataAllocatorsMainSize(compartment); - curr->tjitDataAllocatorsReserve = GetCompartmentTjitDataAllocatorsReserveSize(compartment); -#endif - } + ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name, + "tjit-code"), + nsIMemoryReporter::KIND_NONHEAP, stats.tjitCode, + "Memory used by the trace JIT to hold the compartment's generated code.", + callback, closure); - static void - ArenaCallback(JSContext *cx, void *vdata, js::gc::Arena *arena, - size_t traceKind, size_t thingSize) - { - IterateData *data = static_cast<IterateData *>(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); - } + ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name, + "tjit-data/allocators-main"), + nsIMemoryReporter::KIND_HEAP, + stats.tjitDataAllocatorsMain, + "Memory used by the trace JIT to store the compartment's trace-related " + "data. This data is allocated via the compartment's VMAllocators.", + callback, closure); + + ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name, + "tjit-data/allocators-reserve"), + nsIMemoryReporter::KIND_HEAP, + stats.tjitDataAllocatorsReserve, + "Memory used by the trace JIT and held in reserve for the compartment's " + "VMAllocators in case of OOM.", + callback, closure); +#endif +} - static void - CellCallback(JSContext *cx, void *vdata, void *thing, size_t traceKind, - size_t thingSize) - { - IterateData *data = static_cast<IterateData *>(vdata); - CompartmentStats *curr = data->currCompartmentStats; - if (traceKind == JSTRACE_OBJECT) { - curr->gcHeapObjects += thingSize; - JSObject *obj = static_cast<JSObject *>(thing); - if (obj->hasSlotsArray()) { - curr->objectSlots += obj->numSlots() * sizeof(js::Value); - } - } else if (traceKind == JSTRACE_STRING) { - curr->gcHeapStrings += thingSize; - JSString *str = static_cast<JSString *>(thing); - curr->stringChars += str->charsHeapSize(); - } else if (traceKind == JSTRACE_SHAPE) { - curr->gcHeapShapes += thingSize; - js::Shape *shape = static_cast<js::Shape *>(thing); - if (shape->hasTable()) { - curr->propertyTables += shape->getTable()->sizeOf(); - } - } else { - JS_ASSERT(traceKind == JSTRACE_XML); - curr->gcHeapXml += thingSize; - } - // Yes, this is a subtraction: see ArenaCallback() for details. - curr->gcHeapArenaUnused -= thingSize; - } +void +ReportJSStackSizeForRuntime(JSRuntime *rt, const nsACString &pathPrefix, + nsIMemoryMultiReporterCallback *callback, + nsISupports *closure) +{ + PRInt64 stackSize = 0; + for(js::ThreadDataIter i(rt); !i.empty(); i.popFront()) + stackSize += i.threadData()->stackSpace.committedSize(); + ReportMemoryBytes(pathPrefix + NS_LITERAL_CSTRING("stack"), + nsIMemoryReporter::KIND_NONHEAP, stackSize, + "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.", + callback, closure); +} + +} // namespace memory +} // namespace xpconnect +} // namespace mozilla + +class XPConnectJSCompartmentsMultiReporter : public nsIMemoryMultiReporter +{ 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, ""); + IterateData data; + if(!CollectCompartmentStatsForRuntime(rt, &data)) + return NS_ERROR_FAILURE; PRInt64 gcHeapChunkTotal = gXPCJSChunkAllocator.GetGCChunkBytesInUse(); // This is initialized to gcHeapChunkTotal, and then we subtract used // space from it each time around the loop. PRInt64 gcHeapChunkUnused = gcHeapChunkTotal; PRInt64 gcHeapArenaUnused = 0; - #define BYTES(path, kind, amount, desc) \ - callback->Callback(p, path, kind, nsIMemoryReporter::UNITS_BYTES, \ - amount, NS_LITERAL_CSTRING(desc), closure) - - #define BYTES0(path, kind, amount, desc) \ - do { \ - if (amount != 0) \ - BYTES(path, kind, amount, desc); \ - } while (0) - - #define PERCENTAGE(path, kind, amount, desc) \ - callback->Callback(p, path, kind, nsIMemoryReporter::UNITS_PERCENTAGE, \ - amount, NS_LITERAL_CSTRING(desc), closure); + NS_NAMED_LITERAL_CSTRING(pathPrefix, "explicit/js/"); // This is the second step (see above). - for (CompartmentStats *stats = data.compartmentStatsVector.begin(); - stats != data.compartmentStatsVector.end(); - ++stats) + 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; gcHeapArenaUnused += stats->gcHeapArenaUnused; - BYTES0(mkPath(name, "gc-heap/arena-headers"), - JS_GC_HEAP_KIND, stats->gcHeapArenaHeaders, - "Memory on the compartment's garbage-collected JavaScript heap, within " - "arenas, that is used to hold internal book-keeping information."); - - BYTES0(mkPath(name, "gc-heap/arena-padding"), - JS_GC_HEAP_KIND, stats->gcHeapArenaPadding, - "Memory on the compartment's garbage-collected JavaScript heap, within " - "arenas, that is unused and present only so that other data is aligned. " - "This constitutes internal fragmentation."); - - BYTES0(mkPath(name, "gc-heap/arena-unused"), - JS_GC_HEAP_KIND, stats->gcHeapArenaUnused, - "Memory on the compartment's garbage-collected JavaScript heap, within " - "arenas, that could be holding useful data but currently isn't."); - - BYTES0(mkPath(name, "gc-heap/objects"), - JS_GC_HEAP_KIND, stats->gcHeapObjects, - "Memory on the compartment's garbage-collected JavaScript heap that holds " - "objects."); - - BYTES0(mkPath(name, "gc-heap/strings"), - JS_GC_HEAP_KIND, stats->gcHeapStrings, - "Memory on the compartment's garbage-collected JavaScript heap that holds " - "string headers. String headers contain various pieces of information " - "about a string, but do not contain (except in the case of very short " - "strings) the string characters; characters in longer strings are counted " - "under 'gc-heap/string-chars' instead."); - - BYTES0(mkPath(name, "gc-heap/shapes"), - JS_GC_HEAP_KIND, stats->gcHeapShapes, - "Memory on the compartment's garbage-collected JavaScript heap that holds " - "shapes. A shape is an internal data structure that makes JavaScript " - "property accesses fast."); - - BYTES0(mkPath(name, "gc-heap/xml"), - JS_GC_HEAP_KIND, stats->gcHeapXml, - "Memory on the compartment's garbage-collected JavaScript heap that holds " - "E4X XML objects."); - - BYTES0(mkPath(name, "object-slots"), - nsIMemoryReporter::KIND_HEAP, stats->objectSlots, - "Memory allocated for the compartment's 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 compartment's " - "JavaScript heap; those slots are not counted here, but in " - "'gc-heap/objects' instead."); - - BYTES0(mkPath(name, "string-chars"), - nsIMemoryReporter::KIND_HEAP, stats->stringChars, - "Memory allocated to hold the compartment's string characters. Sometimes " - "more memory is allocated than necessary, to simplify string " - "concatenation. Each string also includes a header which is stored on the " - "compartment's JavaScript heap; that header is not counted here, but in " - "'gc-heap/strings' instead."); - - BYTES0(mkPath(name, "property-tables"), - nsIMemoryReporter::KIND_HEAP, stats->propertyTables, - "Memory allocated for the compartment's property tables. A property " - "table is an internal data structure that makes JavaScript property " - "accesses fast."); - - BYTES0(mkPath(name, "scripts"), - nsIMemoryReporter::KIND_HEAP, stats->scripts, - "Memory allocated for the compartment's 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 - BYTES0(mkPath(name, "mjit-code"), - nsIMemoryReporter::KIND_NONHEAP, stats->mjitCode, - "Memory used by the method JIT to hold the compartment's generated code."); - - BYTES0(mkPath(name, "mjit-data"), - nsIMemoryReporter::KIND_HEAP, stats->mjitData, - "Memory used by the method JIT for the compartment's compilation data: " - "JITScripts, native maps, and inline cache structs."); -#endif -#ifdef JS_TRACER - BYTES0(mkPath(name, "tjit-code"), - nsIMemoryReporter::KIND_NONHEAP, stats->tjitCode, - "Memory used by the trace JIT to hold the compartment's generated code."); - - BYTES0(mkPath(name, "tjit-data/allocators-main"), - nsIMemoryReporter::KIND_HEAP, stats->tjitDataAllocatorsMain, - "Memory used by the trace JIT to store the compartment's trace-related " - "data. This data is allocated via the compartment's VMAllocators."); - - BYTES0(mkPath(name, "tjit-data/allocators-reserve"), - nsIMemoryReporter::KIND_HEAP, stats->tjitDataAllocatorsReserve, - "Memory used by the trace JIT and held in reserve for the compartment's " - "VMAllocators in case of OOM."); -#endif + ReportCompartmentStats(*stats, pathPrefix, callback, closure); } 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; // Why 10000x? 100x because it's a percentage, and another 100x // because nsIMemoryReporter requires that for percentage amounts so // they can be fractional. PRInt64 gcHeapUnusedPercentage = (gcHeapChunkUnused + gcHeapArenaUnused) * 10000 / gXPCJSChunkAllocator.GetGCChunkBytesInUse(); - BYTES(NS_LITERAL_CSTRING("explicit/js/gc-heap-chunk-unused"), - JS_GC_HEAP_KIND, gcHeapChunkUnused, + ReportMemoryBytes(pathPrefix + + NS_LITERAL_CSTRING("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."); + "could be holding useful data but currently isn't.", + callback, closure); - BYTES(NS_LITERAL_CSTRING("js-gc-heap-chunk-unused"), - nsIMemoryReporter::KIND_OTHER, gcHeapChunkUnused, + ReportMemoryBytes(NS_LITERAL_CSTRING("js-gc-heap-chunk-unused"), + nsIMemoryReporter::KIND_OTHER, gcHeapChunkUnused, "The same as 'explicit/js/gc-heap-chunk-unused'. Shown here for " - "easy comparison with 'js-gc-heap' and 'js-gc-heap-arena-unused'."); + "easy comparison with 'js-gc-heap' and 'js-gc-heap-arena-unused'.", + callback, closure); - BYTES(NS_LITERAL_CSTRING("explicit/js/gc-heap-chunk-admin"), - JS_GC_HEAP_KIND, gcHeapChunkAdmin, + ReportMemoryBytes(pathPrefix + + NS_LITERAL_CSTRING("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."); + "used to hold internal book-keeping information.", + callback, closure); - BYTES(NS_LITERAL_CSTRING("js-gc-heap-arena-unused"), - nsIMemoryReporter::KIND_OTHER, gcHeapArenaUnused, + ReportMemoryBytes(NS_LITERAL_CSTRING("js-gc-heap-arena-unused"), + nsIMemoryReporter::KIND_OTHER, gcHeapArenaUnused, "Memory on the garbage-collected JavaScript heap, within arenas, that " "could be holding useful data but currently isn't. This is the sum of " - "all compartments' 'gc-heap/arena-unused' numbers."); + "all compartments' 'gc-heap/arena-unused' numbers.", + callback, closure); - PERCENTAGE(NS_LITERAL_CSTRING("js-gc-heap-unused-fraction"), - nsIMemoryReporter::KIND_OTHER, gcHeapUnusedPercentage, + ReportMemoryPercentage(NS_LITERAL_CSTRING("js-gc-heap-unused-fraction"), + nsIMemoryReporter::KIND_OTHER, + gcHeapUnusedPercentage, "Fraction of the garbage-collected JavaScript heap that is unused. " "Computed as ('js-gc-heap-chunk-unused' + 'js-gc-heap-arena-unused') / " - "'js-gc-heap'."); + "'js-gc-heap'.", + callback, closure); + + ReportJSStackSizeForRuntime(rt, pathPrefix, callback, closure); return NS_OK; } }; + NS_IMPL_THREADSAFE_ISUPPORTS1( XPConnectJSCompartmentsMultiReporter , nsIMemoryMultiReporter ) #ifdef MOZ_CRASHREPORTER static JSBool DiagnosticMemoryCallback(void *ptr, size_t size) @@ -1863,17 +1934,16 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* if (!mWatchdogWakeup) NS_RUNTIMEABORT("JS_NEW_CONDVAR failed."); mJSRuntime->setActivityCallback(ActivityCallback, this); mJSRuntime->setCustomGCChunkAllocator(&gXPCJSChunkAllocator); NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSGCHeap)); - NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSStack)); NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSSystemCompartmentCount)); NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSUserCompartmentCount)); NS_RegisterMemoryMultiReporter(new XPConnectJSCompartmentsMultiReporter); } if(!JS_DHashTableInit(&mJSHolders, JS_DHashGetStubOps(), nsnull, sizeof(ObjectHolder), 512)) mJSHolders.ops = nsnull;
--- a/js/src/xpconnect/src/xpcpublic.h +++ b/js/src/xpconnect/src/xpcpublic.h @@ -38,20 +38,22 @@ * ***** END LICENSE BLOCK ***** */ #ifndef xpcpublic_h #define xpcpublic_h #include "jsapi.h" #include "jsobj.h" #include "jsgc.h" +#include "jspubtd.h" #include "nsISupports.h" #include "nsIPrincipal.h" #include "nsWrapperCache.h" +#include "nsStringGlue.h" class nsIPrincipal; static const uint32 XPC_GC_COLOR_BLACK = 0; static const uint32 XPC_GC_COLOR_GRAY = 1; nsresult xpc_CreateGlobalObject(JSContext *cx, JSClass *clasp, @@ -178,9 +180,72 @@ xpc_UnmarkGrayObject(JSObject *obj) inline JSObject* nsWrapperCache::GetWrapper() const { JSObject* obj = GetWrapperPreserveColor(); xpc_UnmarkGrayObject(obj); return obj; } +class nsIMemoryMultiReporterCallback; + +namespace mozilla { +namespace xpconnect { +namespace memory { + +struct CompartmentStats +{ + CompartmentStats(JSContext *cx, JSCompartment *c); + + nsCString name; + PRInt64 gcHeapArenaHeaders; + PRInt64 gcHeapArenaPadding; + PRInt64 gcHeapArenaUnused; + + PRInt64 gcHeapObjects; + PRInt64 gcHeapStrings; + PRInt64 gcHeapShapes; + PRInt64 gcHeapXml; + + PRInt64 objectSlots; + PRInt64 stringChars; + PRInt64 propertyTables; + + PRInt64 scripts; +#ifdef JS_METHODJIT + PRInt64 mjitCode; + PRInt64 mjitData; #endif +#ifdef JS_TRACER + PRInt64 tjitCode; + PRInt64 tjitDataAllocatorsMain; + PRInt64 tjitDataAllocatorsReserve; +#endif +}; + +struct IterateData +{ + IterateData() + : compartmentStatsVector(), currCompartmentStats(NULL) { } + + js::Vector<CompartmentStats, 0, js::SystemAllocPolicy> compartmentStatsVector; + CompartmentStats *currCompartmentStats; +}; + +JSBool +CollectCompartmentStatsForRuntime(JSRuntime *rt, IterateData *data); + +void +ReportCompartmentStats(const CompartmentStats &stats, + const nsACString &pathPrefix, + nsIMemoryMultiReporterCallback *callback, + nsISupports *closure); + +void +ReportJSStackSizeForRuntime(JSRuntime *rt, const nsACString &pathPrefix, + nsIMemoryMultiReporterCallback *callback, + nsISupports *closure); + +} // namespace memory +} // namespace xpconnect +} // namespace mozilla + +#endif