Bug 674721 - 'Add memory reporting for web worker data'. r=jst+njn.

--HG--
extra : transplant_source : 9%D0%8D%BF%CE%B1Z%CC%85%EF%F5e%28%0B%A9%D1%18%99%21%11
This commit is contained in:
Ben Turner 2011-08-01 21:06:17 -07:00
Родитель b760b58bf8
Коммит 893fdf3927
4 изменённых файлов: 615 добавлений и 392 удалений

Просмотреть файл

@ -44,11 +44,13 @@
#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"
@ -58,6 +60,7 @@
#include "nsThreadUtils.h"
#include "nsXPCOM.h"
#include "nsXPCOMPrivate.h"
#include "xpcpublic.h"
#include "Events.h"
#include "EventTarget.h"
@ -69,6 +72,7 @@ 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
@ -285,6 +289,70 @@ CreateJSContextForWorker(WorkerPrivate* aWorkerPrivate)
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;
@ -311,12 +379,23 @@ public:
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

Просмотреть файл

@ -2042,6 +2042,8 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
{
MutexAutoUnlock unlock(mMutex);
JSAutoRequest ar(aCx);
#ifdef EXTRA_GC
// Find GC bugs...
JS_GC(aCx);
@ -2054,6 +2056,8 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
currentStatus = mStatus;
}
JSAutoRequest ar(aCx);
#ifdef EXTRA_GC
// Find GC bugs...
JS_GC(aCx);
@ -2093,6 +2097,8 @@ WorkerPrivate::OperationCallback(JSContext* aCx)
{
AssertIsOnWorkerThread();
JS_YieldRequest(aCx);
bool mayContinue = true;
for (;;) {
@ -2126,6 +2132,8 @@ WorkerPrivate::OperationCallback(JSContext* aCx)
if (!mControlQueue.IsEmpty()) {
break;
}
JSAutoSuspendRequest asr(aCx);
mCondVar.Wait(PR_MillisecondsToInterval(RemainingRunTimeMS()));
}
}
@ -2480,6 +2488,7 @@ WorkerPrivate::RunSyncLoop(JSContext* aCx, PRUint32 aSyncLoopKey)
MutexAutoLock lock(mMutex);
while (!mControlQueue.Pop(event) && !syncQueue->mQueue.Pop(event)) {
JSAutoSuspendRequest asr(aCx);
mCondVar.Wait();
}
}

Просмотреть файл

@ -42,6 +42,7 @@
/* Per JSRuntime object */
#include "xpcprivate.h"
#include "xpcpublic.h"
#include "WrapperFactory.h"
#include "dom_quickstubs.h"
@ -57,6 +58,7 @@
#endif
using namespace mozilla;
using namespace mozilla::xpconnect::memory;
/***************************************************************************/
@ -1227,6 +1229,205 @@ XPCJSRuntime::~XPCJSRuntime()
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
{
@ -1282,25 +1483,6 @@ NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSGCHeap,
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()
{
@ -1354,262 +1536,247 @@ NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSUserCompartmentCount,
"compartments listed under 'js' if a garbage collection occurs at an "
"inopportune time, but such cases should be rare.")
namespace mozilla {
namespace xpconnect {
namespace memory {
CompartmentStats::CompartmentStats(JSContext *cx, JSCompartment *c)
{
memset(this, 0, sizeof(*this));
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);
}
}
else
{
name.AssignLiteral("null-codebase");
}
}
else
{
name.AssignLiteral("null-principal");
}
}
JSBool
CollectCompartmentStatsForRuntime(JSRuntime *rt, IterateData *data)
{
JSContext *cx = JS_NewContext(rt, 0);
if(!cx)
{
NS_ERROR("couldn't create context for memory tracing");
return false;
}
{
JSAutoRequest ar(cx);
data->compartmentStatsVector.reserve(rt->compartments.length());
js::IterateCompartmentsArenasCells(cx, data, CompartmentCallback,
ArenaCallback, CellCallback);
}
JS_DestroyContextNoGC(cx);
return true;
}
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);
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);
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);
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);
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);
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);
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
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
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);
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
}
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
{
private:
struct CompartmentStats
{
CompartmentStats(JSContext *cx, JSCompartment *c) {
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");
}
}
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(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;
}
#ifdef JS_METHODJIT
static PRInt64
GetCompartmentMjitCodeSize(JSCompartment *c)
{
return c->getMjitCodeSize();
}
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;
}
#endif // JS_METHODJIT
#ifdef JS_TRACER
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;
}
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<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
}
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);
}
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;
}
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
@ -1617,27 +1784,13 @@ public:
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 +
@ -1646,100 +1799,7 @@ public:
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);
@ -1756,36 +1816,47 @@ public:
(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
@ -1868,7 +1939,6 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect)
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);

Просмотреть файл

@ -43,10 +43,12 @@
#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;
@ -183,4 +185,67 @@ nsWrapperCache::GetWrapper() const
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