Bug 633653 - revamp about:memory. r=vlad,sdwilsh,dvander,gavin,ehsan,edwsmith; sr=benjamin.

This commit is contained in:
Nicholas Nethercote 2011-05-03 17:12:58 -07:00
Родитель 2a37b38b71
Коммит 020b76968f
38 изменённых файлов: 1602 добавлений и 515 удалений

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

@ -173,8 +173,9 @@ static PRInt64 GetCanvasMemoryUsed(void *) {
}
NS_MEMORY_REPORTER_IMPLEMENT(CanvasMemory,
"content/canvas/2d_pixel_bytes",
"Total memory used by 2D canvas (width * height * 4)",
"heap-used/content/canvas/2d-pixel-bytes",
"Memory used by 2D canvases. Each canvas "
"requires (width * height * 4) bytes.",
GetCanvasMemoryUsed,
NULL)

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

@ -319,7 +319,9 @@ ContentChild::RecvPMemoryReportRequestConstructor(PMemoryReportRequestChild* chi
report->GetDescription(getter_Copies(desc));
report->GetMemoryUsed(&memoryUsed);
MemoryReport memreport(nsPrintfCString("Content Process - %d - ", getpid()),
static const int maxLength = 31; // big enough; pid is only a few chars
MemoryReport memreport(nsPrintfCString(maxLength, "Content (%d)",
getpid()),
path,
desc,
memoryUsed);

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

@ -521,30 +521,30 @@ gfxASurface::MovePixels(const nsIntRect& aSourceRect,
/** Memory reporting **/
static const char *sSurfaceNamesForSurfaceType[] = {
"gfx/surface/image",
"gfx/surface/pdf",
"gfx/surface/ps",
"gfx/surface/xlib",
"gfx/surface/xcb",
"gfx/surface/glitz",
"gfx/surface/quartz",
"gfx/surface/win32",
"gfx/surface/beos",
"gfx/surface/directfb",
"gfx/surface/svg",
"gfx/surface/os2",
"gfx/surface/win32printing",
"gfx/surface/quartzimage",
"gfx/surface/script",
"gfx/surface/qpainter",
"gfx/surface/recording",
"gfx/surface/vg",
"gfx/surface/gl",
"gfx/surface/drm",
"gfx/surface/tee",
"gfx/surface/xml",
"gfx/surface/skia",
"gfx/surface/d2d"
"heap-used/gfx/surface/image",
"heap-used/gfx/surface/pdf",
"heap-used/gfx/surface/ps",
"heap-used/gfx/surface/xlib",
"heap-used/gfx/surface/xcb",
"heap-used/gfx/surface/glitz",
"heap-used/gfx/surface/quartz",
"heap-used/gfx/surface/win32",
"heap-used/gfx/surface/beos",
"heap-used/gfx/surface/directfb",
"heap-used/gfx/surface/svg",
"heap-used/gfx/surface/os2",
"heap-used/gfx/surface/win32printing",
"heap-used/gfx/surface/quartzimage",
"heap-used/gfx/surface/script",
"heap-used/gfx/surface/qpainter",
"heap-used/gfx/surface/recording",
"heap-used/gfx/surface/vg",
"heap-used/gfx/surface/gl",
"heap-used/gfx/surface/drm",
"heap-used/gfx/surface/tee",
"heap-used/gfx/surface/xml",
"heap-used/gfx/surface/skia",
"heap-used/gfx/surface/d2d"
};
PR_STATIC_ASSERT(NS_ARRAY_LENGTH(sSurfaceNamesForSurfaceType) == gfxASurface::SurfaceTypeMax);
@ -558,7 +558,7 @@ SurfaceMemoryReporterPathForType(gfxASurface::gfxSurfaceType aType)
{
if (aType < 0 ||
aType >= gfxASurface::SurfaceTypeMax)
return "gfx/surface/unknown";
return "heap-used/gfx/surface/unknown";
return sSurfaceNamesForSurfaceType[aType];
}

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

@ -96,12 +96,12 @@ public:
NS_DECL_ISUPPORTS
NS_IMETHOD GetPath(char **memoryPath) {
*memoryPath = strdup("gfx/d2d/surfacecache");
*memoryPath = strdup("gfx-d2d-surfacecache");
return NS_OK;
}
NS_IMETHOD GetDescription(char **desc) {
*desc = strdup("Memory used by Direct2D internal surface cache.");
*desc = strdup("Memory used by the Direct2D internal surface cache.");
return NS_OK;
}
@ -123,7 +123,7 @@ public:
NS_DECL_ISUPPORTS
NS_IMETHOD GetPath(char **memoryPath) {
*memoryPath = strdup("gfx/d2d/surfacevram");
*memoryPath = strdup("gfx-d2d-surfacevram");
return NS_OK;
}

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

@ -52,13 +52,15 @@ static PRInt64 GetShmemAllocated(void*) { return gShmemAllocated; }
static PRInt64 GetShmemMapped(void*) { return gShmemMapped; }
NS_MEMORY_REPORTER_IMPLEMENT(ShmemAllocated,
"shmem/allocated",
"Shmem bytes accessible (not necessarily mapped)",
"shmem-allocated",
"Memory shared with other processes that is "
"accessible (but not necessarily mapped).",
GetShmemAllocated,
nsnull)
NS_MEMORY_REPORTER_IMPLEMENT(ShmemMapped,
"shmem/mapped",
"Shmem bytes mapped into address space",
"shmem-mapped",
"Memory shared with other processes that is "
"mapped into the address space.",
GetShmemMapped,
nsnull)

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

@ -32,6 +32,22 @@ namespace JSC {
size_t ExecutableAllocator::pageSize = 0;
size_t ExecutableAllocator::largeAllocSize = 0;
ExecutablePool::~ExecutablePool()
{
m_allocator->releasePoolPages(this);
}
size_t
ExecutableAllocator::getCodeSize() const
{
size_t n = 0;
for (ExecPoolHashSet::Range r = m_pools.all(); !r.empty(); r.popFront()) {
ExecutablePool* pool = r.front();
n += pool->m_allocation.size;
}
return n;
}
}
#endif // HAVE(ASSEMBLER)

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

@ -31,6 +31,7 @@
#include "assembler/wtf/Assertions.h"
#include "jsapi.h"
#include "jshashtable.h"
#include "jsprvtd.h"
#include "jsvector.h"
#include "jslock.h"
@ -78,6 +79,8 @@ extern "C" void sync_instruction_memory(caddr_t v, u_int len);
namespace JSC {
class ExecutableAllocator;
// These are reference-counted. A new one starts with a count of 1.
class ExecutablePool {
@ -92,6 +95,7 @@ private:
#endif
};
ExecutableAllocator* m_allocator;
char* m_freePtr;
char* m_end;
Allocation m_allocation;
@ -126,17 +130,12 @@ private:
++m_refCount;
}
private:
ExecutablePool(Allocation a)
: m_freePtr(a.pages), m_end(m_freePtr + a.size), m_allocation(a), m_refCount(1),
m_destroy(false), m_gcNumber(0)
ExecutablePool(ExecutableAllocator* allocator, Allocation a)
: m_allocator(allocator), m_freePtr(a.pages), m_end(m_freePtr + a.size), m_allocation(a),
m_refCount(1), m_destroy(false), m_gcNumber(0)
{ }
~ExecutablePool()
{
if (m_allocation.pages)
ExecutablePool::systemRelease(m_allocation);
}
~ExecutablePool();
void* alloc(size_t n)
{
@ -150,14 +149,10 @@ private:
JS_ASSERT(m_end >= m_freePtr);
return m_end - m_freePtr;
}
// On OOM, this will return an Allocation where pages is NULL.
static Allocation systemAlloc(size_t n);
static void systemRelease(const Allocation& alloc);
};
class ExecutableAllocator {
enum ProtectionSeting { Writable, Executable };
enum ProtectionSetting { Writable, Executable };
public:
ExecutableAllocator()
@ -175,13 +170,14 @@ public:
largeAllocSize = pageSize * 16;
}
JS_ASSERT(m_smallAllocationPools.empty());
JS_ASSERT(m_smallPools.empty());
}
~ExecutableAllocator()
{
for (size_t i = 0; i < m_smallAllocationPools.length(); i++)
m_smallAllocationPools[i]->release(/* willDestroy = */true);
for (size_t i = 0; i < m_smallPools.length(); i++)
m_smallPools[i]->release(/* willDestroy = */true);
JS_ASSERT(m_pools.empty()); // if this asserts we have a pool leak
}
// alloc() returns a pointer to some memory, and also (by reference) a
@ -209,6 +205,14 @@ public:
return result;
}
void releasePoolPages(ExecutablePool *pool) {
JS_ASSERT(pool->m_allocation.pages);
systemRelease(pool->m_allocation);
m_pools.remove(m_pools.lookup(pool)); // this asserts if |pool| is not in m_pools
}
size_t getCodeSize() const;
private:
static size_t pageSize;
static size_t largeAllocSize;
@ -233,20 +237,34 @@ private:
return size;
}
// On OOM, this will return an Allocation where pages is NULL.
static ExecutablePool::Allocation systemAlloc(size_t n);
static void systemRelease(const ExecutablePool::Allocation& alloc);
ExecutablePool* createPool(size_t n)
{
size_t allocSize = roundUpAllocationSize(n, pageSize);
if (allocSize == OVERSIZE_ALLOCATION)
return NULL;
if (!m_pools.initialized() && !m_pools.init())
return NULL;
#ifdef DEBUG_STRESS_JSC_ALLOCATOR
ExecutablePool::Allocation a = ExecutablePool::systemAlloc(size_t(4294967291));
ExecutablePool::Allocation a = systemAlloc(size_t(4294967291));
#else
ExecutablePool::Allocation a = ExecutablePool::systemAlloc(allocSize);
ExecutablePool::Allocation a = systemAlloc(allocSize);
#endif
if (!a.pages)
return NULL;
return js::OffTheBooks::new_<ExecutablePool>(a);
ExecutablePool *pool = js::OffTheBooks::new_<ExecutablePool>(this, a);
if (!pool) {
systemRelease(a);
return NULL;
}
m_pools.put(pool);
return pool;
}
ExecutablePool* poolForSize(size_t n)
@ -258,8 +276,8 @@ private:
// allocation fitting in a small pool, and (b) it minimizes the
// potential waste when a small pool is next abandoned.
ExecutablePool *minPool = NULL;
for (size_t i = 0; i < m_smallAllocationPools.length(); i++) {
ExecutablePool *pool = m_smallAllocationPools[i];
for (size_t i = 0; i < m_smallPools.length(); i++) {
ExecutablePool *pool = m_smallPools[i];
if (n <= pool->available() && (!minPool || pool->available() < minPool->available()))
minPool = pool;
}
@ -279,26 +297,26 @@ private:
return NULL;
// At this point, local |pool| is the owner.
if (m_smallAllocationPools.length() < maxSmallPools) {
if (m_smallPools.length() < maxSmallPools) {
// We haven't hit the maximum number of live pools; add the new pool.
m_smallAllocationPools.append(pool);
m_smallPools.append(pool);
pool->addRef();
} else {
// Find the pool with the least space.
int iMin = 0;
for (size_t i = 1; i < m_smallAllocationPools.length(); i++)
if (m_smallAllocationPools[i]->available() <
m_smallAllocationPools[iMin]->available())
for (size_t i = 1; i < m_smallPools.length(); i++)
if (m_smallPools[i]->available() <
m_smallPools[iMin]->available())
{
iMin = i;
}
// If the new allocator will result in more free space than the small
// pool with the least space, then we will use it instead
ExecutablePool *minPool = m_smallAllocationPools[iMin];
ExecutablePool *minPool = m_smallPools[iMin];
if ((pool->available() - n) > minPool->available()) {
minPool->release();
m_smallAllocationPools[iMin] = pool;
m_smallPools[iMin] = pool;
pool->addRef();
}
}
@ -411,12 +429,21 @@ public:
private:
#if ENABLE_ASSEMBLER_WX_EXCLUSIVE
static void reprotectRegion(void*, size_t, ProtectionSeting);
static void reprotectRegion(void*, size_t, ProtectionSetting);
#endif
// These are strong references; they keep pools alive.
static const size_t maxSmallPools = 4;
typedef js::Vector<ExecutablePool *, maxSmallPools, js::SystemAllocPolicy > SmallExecPoolVector;
SmallExecPoolVector m_smallAllocationPools;
typedef js::Vector<ExecutablePool *, maxSmallPools, js::SystemAllocPolicy> SmallExecPoolVector;
SmallExecPoolVector m_smallPools;
// All live pools are recorded here, just for stats purposes. These are
// weak references; they don't keep pools alive. When a pool is destroyed
// its reference is removed from m_pools.
typedef js::HashSet<ExecutablePool *, js::DefaultHasher<ExecutablePool *>, js::SystemAllocPolicy>
ExecPoolHashSet;
ExecPoolHashSet m_pools; // All pools, just for stats purposes.
static size_t determinePageSize();
};

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

@ -38,7 +38,7 @@ size_t ExecutableAllocator::determinePageSize()
return 4096u;
}
ExecutablePool::Allocation ExecutablePool::systemAlloc(size_t n)
ExecutablePool::Allocation ExecutableAllocator::systemAlloc(size_t n)
{
void* allocation = NULL;
if (DosAllocMem(&allocation, n, OBJ_ANY|PAG_COMMIT|PAG_READ|PAG_WRITE) &&
@ -48,7 +48,7 @@ ExecutablePool::Allocation ExecutablePool::systemAlloc(size_t n)
return alloc;
}
void ExecutablePool::systemRelease(const ExecutablePool::Allocation& alloc)
void ExecutableAllocator::systemRelease(const ExecutablePool::Allocation& alloc)
{
DosFreeMem(alloc.pages);
}

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

@ -38,7 +38,7 @@ size_t ExecutableAllocator::determinePageSize()
return getpagesize();
}
ExecutablePool::Allocation ExecutablePool::systemAlloc(size_t n)
ExecutablePool::Allocation ExecutableAllocator::systemAlloc(size_t n)
{
void* allocation = mmap(NULL, n, INITIAL_PROTECTION_FLAGS, MAP_PRIVATE | MAP_ANON, VM_TAG_FOR_EXECUTABLEALLOCATOR_MEMORY, 0);
if (allocation == MAP_FAILED)
@ -47,14 +47,14 @@ ExecutablePool::Allocation ExecutablePool::systemAlloc(size_t n)
return alloc;
}
void ExecutablePool::systemRelease(const ExecutablePool::Allocation& alloc)
void ExecutableAllocator::systemRelease(const ExecutablePool::Allocation& alloc)
{
int result = munmap(alloc.pages, alloc.size);
ASSERT_UNUSED(result, !result);
}
#if WTF_ENABLE_ASSEMBLER_WX_EXCLUSIVE
void ExecutableAllocator::reprotectRegion(void* start, size_t size, ProtectionSeting setting)
void ExecutableAllocator::reprotectRegion(void* start, size_t size, ProtectionSetting setting)
{
if (!pageSize)
intializePageSize();

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

@ -47,7 +47,7 @@ size_t ExecutableAllocator::determinePageSize()
#endif
}
ExecutablePool::Allocation ExecutablePool::systemAlloc(size_t n)
ExecutablePool::Allocation ExecutableAllocator::systemAlloc(size_t n)
{
RChunk* codeChunk = new RChunk();
@ -58,7 +58,7 @@ ExecutablePool::Allocation ExecutablePool::systemAlloc(size_t n)
return alloc;
}
void ExecutablePool::systemRelease(const ExecutablePool::Allocation& alloc)
void ExecutableAllocator::systemRelease(const ExecutablePool::Allocation& alloc)
{
alloc.chunk->Close();
delete alloc.chunk;

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

@ -39,14 +39,14 @@ size_t ExecutableAllocator::determinePageSize()
return system_info.dwPageSize;
}
ExecutablePool::Allocation ExecutablePool::systemAlloc(size_t n)
ExecutablePool::Allocation ExecutableAllocator::systemAlloc(size_t n)
{
void *allocation = VirtualAlloc(0, n, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
ExecutablePool::Allocation alloc = {reinterpret_cast<char*>(allocation), n};
return alloc;
}
void ExecutablePool::systemRelease(const ExecutablePool::Allocation& alloc)
void ExecutableAllocator::systemRelease(const ExecutablePool::Allocation& alloc)
{
VirtualFree(alloc.pages, 0, MEM_RELEASE);
}

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

@ -702,9 +702,10 @@ struct JSRuntime {
JSPreWrapCallback preWrapObjectCallback;
#ifdef JS_METHODJIT
uint32 mjitMemoryUsed;
/* This measures the size of JITScripts, native maps and IC structs. */
size_t mjitDataSize;
#endif
uint32 stringMemoryUsed;
size_t stringMemoryUsed;
JSRuntime();
~JSRuntime();

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

@ -165,6 +165,14 @@ JSCompartment::init()
#endif
}
#ifdef JS_METHODJIT
size_t
JSCompartment::getMjitCodeSize() const
{
return jaegerCompartment->execAlloc()->getCodeSize();
}
#endif
bool
JSCompartment::arenaListsAreEmpty()
{

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

@ -285,6 +285,10 @@ struct TraceMonitor {
void mark(JSTracer *trc);
bool outOfMemory() const;
JS_FRIEND_API(void) getCodeAllocStats(size_t &total, size_t &frag_size, size_t &free_size) const;
JS_FRIEND_API(size_t) getVMAllocatorsMainSize() const;
JS_FRIEND_API(size_t) getVMAllocatorsReserveSize() const;
};
namespace mjit {
@ -409,6 +413,13 @@ struct JS_FRIEND_API(JSCompartment) {
#ifdef JS_METHODJIT
js::mjit::JaegerCompartment *jaegerCompartment;
/*
* This function is here so that xpconnect/src/xpcjsruntime.cpp doesn't
* need to see the declaration of JaegerCompartment, which would require
* #including MethodJIT.h into xpconnect/src/xpcjsruntime.cpp, which is
* difficult due to reasons explained in bug 483677.
*/
size_t getMjitCodeSize() const;
#endif
/*

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

@ -2461,6 +2461,34 @@ TraceMonitor::outOfMemory() const
traceAlloc->outOfMemory();
}
void
TraceMonitor::getCodeAllocStats(size_t &total, size_t &frag_size, size_t &free_size) const
{
if (codeAlloc) {
codeAlloc->getStats(total, frag_size, free_size);
} else {
total = 0;
frag_size = 0;
free_size = 0;
}
}
size_t
TraceMonitor::getVMAllocatorsMainSize() const
{
return dataAlloc->getBytesAllocated() +
traceAlloc->getBytesAllocated() +
tempAlloc->getBytesAllocated();
}
size_t
TraceMonitor::getVMAllocatorsReserveSize() const
{
return dataAlloc->mReserveSize +
traceAlloc->mReserveSize +
tempAlloc->mReserveSize;
}
/*
* This function destroys the recorder after a successful recording, possibly
* starting a suspended outer recorder.

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

@ -409,6 +409,7 @@ public:
: mOutOfMemory(false)
, mSize(0)
, mReserve(reserve)
, mReserveSize(reserveSize)
, mReserveCurr(uintptr_t(reserve))
, mReserveLimit(uintptr_t(reserve + reserveSize))
, mRt(rt)
@ -470,6 +471,7 @@ public:
/* See nanojit::Allocator::allocChunk() for details on these. */
char* mReserve;
size_t mReserveSize;
uintptr_t mReserveCurr;
uintptr_t mReserveLimit;

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

@ -404,24 +404,24 @@ mjit::Compiler::finishThisUp(JITScript **jitp)
#endif
JaegerSpew(JSpew_Insns, "## Fast code (masm) size = %u, Slow code (stubcc) size = %u.\n", masm.size(), stubcc.size());
size_t totalSize = masm.size() +
stubcc.size() +
doubleList.length() * sizeof(double) +
jumpTableOffsets.length() * sizeof(void *);
size_t codeSize = masm.size() +
stubcc.size() +
doubleList.length() * sizeof(double) +
jumpTableOffsets.length() * sizeof(void *);
JSC::ExecutablePool *execPool;
uint8 *result =
(uint8 *)script->compartment->jaegerCompartment->execAlloc()->alloc(totalSize, &execPool);
(uint8 *)script->compartment->jaegerCompartment->execAlloc()->alloc(codeSize, &execPool);
if (!result) {
js_ReportOutOfMemory(cx);
return Compile_Error;
}
JS_ASSERT(execPool);
JSC::ExecutableAllocator::makeWritable(result, totalSize);
JSC::ExecutableAllocator::makeWritable(result, codeSize);
masm.executableCopy(result);
stubcc.masm.executableCopy(result + masm.size());
JSC::LinkBuffer fullCode(result, totalSize);
JSC::LinkBuffer fullCode(result, codeSize);
JSC::LinkBuffer stubCode(result + masm.size(), stubcc.size());
size_t nNmapLive = 0;
@ -432,23 +432,23 @@ mjit::Compiler::finishThisUp(JITScript **jitp)
}
/* Please keep in sync with JITScript::scriptDataSize! */
size_t totalBytes = sizeof(JITScript) +
sizeof(NativeMapEntry) * nNmapLive +
size_t dataSize = sizeof(JITScript) +
sizeof(NativeMapEntry) * nNmapLive +
#if defined JS_MONOIC
sizeof(ic::GetGlobalNameIC) * getGlobalNames.length() +
sizeof(ic::SetGlobalNameIC) * setGlobalNames.length() +
sizeof(ic::CallICInfo) * callICs.length() +
sizeof(ic::EqualityICInfo) * equalityICs.length() +
sizeof(ic::TraceICInfo) * traceICs.length() +
sizeof(ic::GetGlobalNameIC) * getGlobalNames.length() +
sizeof(ic::SetGlobalNameIC) * setGlobalNames.length() +
sizeof(ic::CallICInfo) * callICs.length() +
sizeof(ic::EqualityICInfo) * equalityICs.length() +
sizeof(ic::TraceICInfo) * traceICs.length() +
#endif
#if defined JS_POLYIC
sizeof(ic::PICInfo) * pics.length() +
sizeof(ic::GetElementIC) * getElemICs.length() +
sizeof(ic::SetElementIC) * setElemICs.length() +
sizeof(ic::PICInfo) * pics.length() +
sizeof(ic::GetElementIC) * getElemICs.length() +
sizeof(ic::SetElementIC) * setElemICs.length() +
#endif
sizeof(CallSite) * callSites.length();
sizeof(CallSite) * callSites.length();
uint8 *cursor = (uint8 *)cx->calloc_(totalBytes);
uint8 *cursor = (uint8 *)cx->calloc_(dataSize);
if (!cursor) {
execPool->release();
js_ReportOutOfMemory(cx);
@ -808,12 +808,12 @@ mjit::Compiler::finishThisUp(JITScript **jitp)
to.initialize(codeOffset, from.pc - script->code, from.id);
}
JS_ASSERT(size_t(cursor - (uint8*)jit) == totalBytes);
JS_ASSERT(size_t(cursor - (uint8*)jit) == dataSize);
*jitp = jit;
/* We tolerate a race in the stats. */
cx->runtime->mjitMemoryUsed += totalSize + totalBytes;
cx->runtime->mjitDataSize += dataSize;
return Compile_Okay;
}

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

@ -909,7 +909,7 @@ mjit::ReleaseScriptCode(JSContext *cx, JSScript *script)
JITScript *jscr;
if ((jscr = script->jitNormal)) {
cx->runtime->mjitMemoryUsed -= jscr->scriptDataSize() + jscr->mainCodeSize();
cx->runtime->mjitDataSize -= jscr->scriptDataSize();
jscr->~JITScript();
cx->free_(jscr);
@ -918,7 +918,7 @@ mjit::ReleaseScriptCode(JSContext *cx, JSScript *script)
}
if ((jscr = script->jitCtor)) {
cx->runtime->mjitMemoryUsed -= jscr->scriptDataSize() + jscr->mainCodeSize();
cx->runtime->mjitDataSize -= jscr->scriptDataSize();
jscr->~JITScript();
cx->free_(jscr);

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

@ -415,8 +415,6 @@ struct JITScript {
size_t scriptDataSize();
size_t mainCodeSize() { return code.m_size; } /* doesn't account for fragmentation */
jsbytecode *nativeToPC(void *returnAddress) const;
private:

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

@ -89,6 +89,7 @@ namespace nanojit
if (mem) {
Chunk* chunk = (Chunk*) mem;
chunk->prev = current_chunk;
chunk->size = chunkbytes;
current_chunk = chunk;
current_top = (char*)chunk->data;
current_limit = (char*)mem + chunkbytes;
@ -98,6 +99,17 @@ namespace nanojit
return false;
}
}
size_t Allocator::getBytesAllocated()
{
size_t n = 0;
Chunk *c = current_chunk;
while (c) {
n += c->size;
c = c->prev;
}
return n;
}
}
#endif // FEATURE_NANOJIT

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

@ -90,6 +90,8 @@ namespace nanojit
return p;
}
size_t getBytesAllocated();
protected:
void* allocSlow(size_t nbytes, bool fallible = false);
bool fill(size_t minbytes, bool fallible);
@ -97,6 +99,7 @@ namespace nanojit
class Chunk {
public:
Chunk* prev;
size_t size;
int64_t data[1]; // int64_t forces 8-byte alignment.
};

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

@ -98,10 +98,10 @@ namespace nanojit
return (int)((x + 512) >> 10);
}
void CodeAlloc::logStats() {
size_t total = 0;
size_t frag_size = 0;
size_t free_size = 0;
void CodeAlloc::getStats(size_t& total, size_t& frag_size, size_t& free_size) {
total = 0;
frag_size = 0;
free_size = 0;
int free_count = 0;
for (CodeList* hb = heapblocks; hb != 0; hb = hb->next) {
total += bytesPerAlloc;
@ -114,6 +114,11 @@ namespace nanojit
}
}
}
}
void CodeAlloc::logStats() {
size_t total, frag_size, free_size;
getStats(total, frag_size, free_size);
avmplus::AvmLog("code-heap: %dk free %dk fragmented %d\n",
round(total), round(free_size), frag_size);
}

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

@ -217,6 +217,9 @@ namespace nanojit
/** return the total number of bytes held by this CodeAlloc. */
size_t size();
/** get stats about heap usage */
void getStats(size_t& total, size_t& frag_size, size_t& free_size);
/** print out stats about heap usage */
void logStats();

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

@ -4570,10 +4570,26 @@ Deserialize(JSContext *cx, uintN argc, jsval *vp)
}
JSBool
MJitStats(JSContext *cx, uintN argc, jsval *vp)
MJitCodeStats(JSContext *cx, uintN argc, jsval *vp)
{
#ifdef JS_METHODJIT
JS_SET_RVAL(cx, vp, INT_TO_JSVAL(cx->runtime->mjitMemoryUsed));
JSRuntime *rt = cx->runtime;
AutoLockGC lock(rt);
size_t n = 0;
for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c)
n += (*c)->getMjitCodeSize();
JS_SET_RVAL(cx, vp, INT_TO_JSVAL(n));
#else
JS_SET_RVAL(cx, vp, JSVAL_VOID);
#endif
return true;
}
JSBool
MJitDataStats(JSContext *cx, uintN argc, jsval *vp)
{
#ifdef JS_METHODJIT
JS_SET_RVAL(cx, vp, INT_TO_JSVAL(cx->runtime->mjitDataSize));
#else
JS_SET_RVAL(cx, vp, JSVAL_VOID);
#endif
@ -4733,7 +4749,8 @@ static JSFunctionSpec shell_functions[] = {
JS_FN("serialize", Serialize, 1,0),
JS_FN("deserialize", Deserialize, 1,0),
#ifdef JS_METHODJIT
JS_FN("mjitstats", MJitStats, 0,0),
JS_FN("mjitcodestats", MJitCodeStats, 0,0),
JS_FN("mjitdatastats", MJitDataStats, 0,0),
#endif
JS_FN("stringstats", StringStats, 0,0),
JS_FN("newGlobal", NewGlobal, 1,0),
@ -4867,7 +4884,8 @@ static const char *const shell_help_messages[] = {
"serialize(sd) Serialize sd using JS_WriteStructuredClone. Returns a TypedArray.",
"deserialize(a) Deserialize data generated by serialize.",
#ifdef JS_METHODJIT
"mjitstats() Return stats on mjit memory usage.",
"mjitcodestats() Return stats on mjit code memory usage.",
"mjitdatastats() Return stats on mjit data memory usage.",
#endif
"stringstats() Return stats on string memory usage.",
"newGlobal(kind) Return a new global object, in the current\n"

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

@ -1278,43 +1278,140 @@ protected:
static XPConnectGCChunkAllocator gXPCJSChunkAllocator;
NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSRuntimeGCChunks,
"js/gc-heap",
"Main JS GC heap",
NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSGCHeap,
"heap-used/js/gc-heap",
"Memory used by the garbage-collected JavaScript "
"heap.",
XPConnectGCChunkAllocator::GetGCChunkBytesInUse,
&gXPCJSChunkAllocator)
/* FIXME: use API provided by bug 623271 */
#include "jscntxt.h"
static PRInt64
GetJSMethodJitCodeMemoryInUse(void *data)
GetPerCompartmentSize(PRInt64 (*f)(JSCompartment *c))
{
JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->GetJSRuntime();
#ifdef JS_METHODJIT
return rt->mjitMemoryUsed;
#else
return 0;
#endif
js::AutoLockGC lock(rt);
PRInt64 n = 0;
for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c)
n += f(*c);
return n;
}
NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSMethodJitCode,
"js/mjit-code",
"Memory in use by method-JIT for compiled code",
GetJSMethodJitCodeMemoryInUse,
NULL)
#ifdef JS_METHODJIT
static PRInt64
GetJSStringMemoryInUse(void *data)
GetCompartmentMjitCodeSize(JSCompartment *c)
{
return c->getMjitCodeSize();
}
static PRInt64
GetJSMjitCode(void *data)
{
return GetPerCompartmentSize(GetCompartmentMjitCodeSize);
}
static PRInt64
GetJSMJitData(void *data)
{
JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->GetJSRuntime();
return rt->mjitDataSize;
}
NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSMjitCode,
"mapped/js/mjit-code",
"Memory mapped by the method JIT to hold "
"generated code.",
GetJSMjitCode,
NULL)
NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSMjitData,
"heap-used/js/mjit-data",
"Memory allocated by the method JIT for the "
"following data: JITScripts, native maps, and "
"inline cache structs.",
GetJSMJitData,
NULL)
#endif // JS_METHODJIT
#ifdef JS_TRACER
static PRInt64
GetCompartmentTjitCode(JSCompartment *c)
{
js::TraceMonitor &tm = c->traceMonitor;
if (tm.codeAlloc) {
size_t total, frag_size, free_size;
tm.getCodeAllocStats(total, frag_size, free_size);
return total;
}
return 0;
}
static PRInt64
GetCompartmentTjitDataAllocatorsMain(JSCompartment *c)
{
js::TraceMonitor &tm = c->traceMonitor;
return tm.dataAlloc ? tm.getVMAllocatorsMainSize() : 0;
}
static PRInt64
GetCompartmentTjitDataAllocatorsReserve(JSCompartment *c)
{
js::TraceMonitor &tm = c->traceMonitor;
return tm.dataAlloc ? tm.getVMAllocatorsReserveSize() : 0;
}
static PRInt64
GetJSTjitCode(void *data)
{
return GetPerCompartmentSize(GetCompartmentTjitCode);
}
static PRInt64
GetJSTjitDataAllocatorsMain(void *data)
{
return GetPerCompartmentSize(GetCompartmentTjitDataAllocatorsMain);
}
static PRInt64
GetJSTjitDataAllocatorsReserve(void *data)
{
return GetPerCompartmentSize(GetCompartmentTjitDataAllocatorsReserve);
}
NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSTjitCode,
"mapped/js/tjit-code",
"Memory mapped by the trace JIT to hold "
"generated code.",
GetJSTjitCode,
NULL)
NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSTjitDataAllocatorsMain,
"heap-used/js/tjit-data/allocators/main",
"Memory allocated by the trace JIT's "
"VMAllocators.",
GetJSTjitDataAllocatorsMain,
NULL)
NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSTjitDataAllocatorsReserve,
"heap-used/js/tjit-data/allocators/reserve",
"Memory allocated by the trace JIT and held in "
"reserve for VMAllocators in case of OOM.",
GetJSTjitDataAllocatorsReserve,
NULL)
#endif // JS_TRACER
static PRInt64
GetJSStringData(void *data)
{
JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->GetJSRuntime();
return rt->stringMemoryUsed;
}
NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSStringMemory,
"js/string-data",
"Memory in use for string data",
GetJSStringMemoryInUse,
NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSStringData,
"heap-used/js/string-data",
"Memory allocated for JavaScript string data.",
GetJSStringData,
NULL)
XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect)
@ -1382,9 +1479,13 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect)
mJSRuntime->setCustomGCChunkAllocator(&gXPCJSChunkAllocator);
NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSRuntimeGCChunks));
NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSStringMemory));
NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSMethodJitCode));
NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSGCHeap));
NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSStringData));
NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSMjitCode));
NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSMjitData));
NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSTjitCode));
NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSTjitDataAllocatorsMain));
NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSTjitDataAllocatorsReserve));
}
if(!JS_DHashTableInit(&mJSHolders, JS_DHashGetStubOps(), nsnull,

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

@ -1646,14 +1646,15 @@ NS_NewPresShell(nsIPresShell** aInstancePtrResult)
nsTHashtable<PresShell::PresShellPtrKey> *nsIPresShell::sLiveShells = 0;
NS_MEMORY_REPORTER_IMPLEMENT(LayoutPresShell,
"layout/all",
"Memory in use by layout PresShell, PresContext, and other related areas.",
"heap-used/layout/all",
"Memory used by layout PresShell, PresContext, "
"and other related areas.",
PresShell::SizeOfLayoutMemoryReporter,
nsnull)
NS_MEMORY_REPORTER_IMPLEMENT(LayoutBidi,
"layout/bidi",
"Memory in use by layout Bidi processor.",
"heap-used/layout/bidi",
"Memory used by layout Bidi processor.",
PresShell::SizeOfBidiMemoryReporter,
nsnull)

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

@ -160,21 +160,21 @@ public:
NS_IMETHOD GetPath(char **memoryPath)
{
if (mType == ChromeUsedRaw) {
*memoryPath = strdup("images/chrome/used/raw");
*memoryPath = strdup("heap-used/images/chrome/used/raw");
} else if (mType == ChromeUsedUncompressed) {
*memoryPath = strdup("images/chrome/used/uncompressed");
*memoryPath = strdup("heap-used/images/chrome/used/uncompressed");
} else if (mType == ChromeUnusedRaw) {
*memoryPath = strdup("images/chrome/unused/raw");
*memoryPath = strdup("heap-used/images/chrome/unused/raw");
} else if (mType == ChromeUnusedUncompressed) {
*memoryPath = strdup("images/chrome/unused/uncompressed");
*memoryPath = strdup("heap-used/images/chrome/unused/uncompressed");
} else if (mType == ContentUsedRaw) {
*memoryPath = strdup("images/content/used/raw");
*memoryPath = strdup("heap-used/images/content/used/raw");
} else if (mType == ContentUsedUncompressed) {
*memoryPath = strdup("images/content/used/uncompressed");
*memoryPath = strdup("heap-used/images/content/used/uncompressed");
} else if (mType == ContentUnusedRaw) {
*memoryPath = strdup("images/content/unused/raw");
*memoryPath = strdup("heap-used/images/content/unused/raw");
} else if (mType == ContentUnusedUncompressed) {
*memoryPath = strdup("images/content/unused/uncompressed");
*memoryPath = strdup("heap-used/images/content/unused/uncompressed");
}
return NS_OK;
}
@ -182,21 +182,21 @@ public:
NS_IMETHOD GetDescription(char **desc)
{
if (mType == ChromeUsedRaw) {
*desc = strdup("Memory used by in-use chrome images, compressed data");
*desc = strdup("Memory used by in-use chrome images (compressed data).");
} else if (mType == ChromeUsedUncompressed) {
*desc = strdup("Memory used by in-use chrome images, uncompressed data");
*desc = strdup("Memory used by in-use chrome images (uncompressed data).");
} else if (mType == ChromeUnusedRaw) {
*desc = strdup("Memory used by not in-use chrome images, compressed data");
*desc = strdup("Memory used by not in-use chrome images (compressed data).");
} else if (mType == ChromeUnusedUncompressed) {
*desc = strdup("Memory used by not in-use chrome images, uncompressed data");
*desc = strdup("Memory used by not in-use chrome images (uncompressed data).");
} else if (mType == ContentUsedRaw) {
*desc = strdup("Memory used by in-use content images, compressed data");
*desc = strdup("Memory used by in-use content images (compressed data).");
} else if (mType == ContentUsedUncompressed) {
*desc = strdup("Memory used by in-use content images, uncompressed data");
*desc = strdup("Memory used by in-use content images (uncompressed data).");
} else if (mType == ContentUnusedRaw) {
*desc = strdup("Memory used by not in-use content images, compressed data");
*desc = strdup("Memory used by not in-use content images (compressed data).");
} else if (mType == ContentUnusedUncompressed) {
*desc = strdup("Memory used by not in-use content images, uncompressed data");
*desc = strdup("Memory used by not in-use content images (uncompressed data).");
}
return NS_OK;
}

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

@ -353,20 +353,20 @@ public:
{
nsCString path;
path.AppendLiteral("storage/");
path.AppendLiteral("heap-used/storage/");
path.Append(mDBConn.getFilename());
if (mType == LookAside_Used) {
path.AppendLiteral("/LookAside_Used");
path.AppendLiteral("/lookaside-used");
}
else if (mType == Cache_Used) {
path.AppendLiteral("/Cache_Used");
path.AppendLiteral("/cache-used");
}
else if (mType == Schema_Used) {
path.AppendLiteral("/Schema_Used");
path.AppendLiteral("/schema-used");
}
else if (mType == Stmt_Used) {
path.AppendLiteral("/Stmt_Used");
path.AppendLiteral("/stmt-used");
}
*memoryPath = ::ToNewCString(path);
@ -376,16 +376,16 @@ public:
NS_IMETHOD GetDescription(char **desc)
{
if (mType == LookAside_Used) {
*desc = ::strdup("Number of lookaside memory slots currently checked out");
*desc = ::strdup("Number of lookaside memory slots currently checked out.");
}
else if (mType == Cache_Used) {
*desc = ::strdup("Approximate number of bytes of heap memory used by all pager caches");
*desc = ::strdup("Memory (approximate) used by all pager caches.");
}
else if (mType == Schema_Used) {
*desc = ::strdup("Approximate number of bytes of heap memory used to store the schema for all databases associated with the connection");
*desc = ::strdup("Memory (approximate) used to store the schema for all databases associated with the connection");
}
else if (mType == Stmt_Used) {
*desc = ::strdup("Approximate number of bytes of heap and lookaside memory used by all prepared statements");
*desc = ::strdup("Memory (approximate) used by all prepared statements");
}
return NS_OK;
}
@ -576,10 +576,16 @@ Connection::initialize(nsIFile *aDatabaseFile,
}
nsRefPtr<nsIMemoryReporter> reporter;
#if 0
// FIXME: Bug 649867 explains why this is disabled.
reporter =
new StorageMemoryReporter(*this, StorageMemoryReporter::LookAside_Used);
mMemoryReporters.AppendElement(reporter);
#endif
// FIXME: These reporters overlap with storage/sqlite/pagecache and
// storage/sqlite/other, and therefore double-count some memory. See bug
// 653630 for details.
reporter =
new StorageMemoryReporter(*this, StorageMemoryReporter::Cache_Used);
mMemoryReporters.AppendElement(reporter);
@ -588,7 +594,8 @@ Connection::initialize(nsIFile *aDatabaseFile,
new StorageMemoryReporter(*this, StorageMemoryReporter::Schema_Used);
mMemoryReporters.AppendElement(reporter);
reporter = new StorageMemoryReporter(*this, StorageMemoryReporter::Stmt_Used);
reporter =
new StorageMemoryReporter(*this, StorageMemoryReporter::Stmt_Used);
mMemoryReporters.AppendElement(reporter);
for (PRUint32 i = 0; i < mMemoryReporters.Length(); i++) {

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

@ -151,14 +151,20 @@ GetStorageSQLiteOtherMemoryUsed(void *)
}
NS_MEMORY_REPORTER_IMPLEMENT(StorageSQLitePageCacheMemoryUsed,
"storage/sqlite/pagecache",
"Memory in use by SQLite for the page cache",
"heap-used/storage/sqlite/pagecache",
"Memory used by SQLite for the page cache. "
"This overlaps with the per-connection cache-used "
"figure, thus over-counting some bytes. Bug "
"653630 has the details.",
GetStorageSQLitePageCacheMemoryUsed,
nsnull)
NS_MEMORY_REPORTER_IMPLEMENT(StorageSQLiteOtherMemoryUsed,
"storage/sqlite/other",
"Memory in use by SQLite for other various reasons",
"heap-used/storage/sqlite/other",
"Memory used by SQLite for other various reasons."
"This overlaps with the per-connection stmt-used "
"and schema-used figures, thus over-counting some "
"bytes. Bug 653630 has the details.",
GetStorageSQLiteOtherMemoryUsed,
nsnull)

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

@ -43,4 +43,8 @@ VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
ifdef ENABLE_TESTS
DIRS += tests
endif
include $(topsrcdir)/config/rules.mk

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

@ -13,13 +13,13 @@
*
* The Original Code is about:memory.
*
* The Initial Developer of the Original Code is
* Mozilla Corporation
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Vladimir Vukicevic <vladimir@pobox.com>
* Nicholas Nethercote <nnethercote@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -35,46 +35,24 @@
*
* ***** END LICENSE BLOCK ***** */
html {
background: -moz-Dialog;
color: -moz-DialogText;
.mrValue {
font-weight: bold;
color: #400;
}
body {
margin: 0;
padding: 0 1em;
font: message-box;
.mrPerc {
}
h1 {
font-weight: bold;
font-size: x-large;
.mrName {
font-style: italic;
color: #004;
}
label {
-moz-user-select: text;
.treeLine {
color: #888;
}
h2 {
margin-top: 0;
font-weight: bold;
font-size: large;
}
.memBox {
max-width: 30em;
background: -moz-Field;
color: -moz-FieldText;
margin-top: 25px;
padding: 5px;
border: 1px solid -moz-FieldText;
border-radius: 10px 10px;
}
.memValue {
text-align: right;
}
.memOverview table {
font-size: 120%;
.option {
font-size: 80%;
-moz-user-select: none; /* no need to include this when cutting+pasting */
}

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

@ -14,13 +14,13 @@
*
* The Original Code is about:memory
*
* The Initial Developer of the Original Code is
* the Mozilla Foundation.
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Vladimir Vukicevic <vladimir@pobox.com>
* Nicholas Nethercote <nnethercote@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -36,180 +36,564 @@
*
* ***** END LICENSE BLOCK ***** */
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
var gMemReporters = { };
// Must use .href here instead of .search because "about:memory" is a
// non-standard URL.
var gVerbose = (location.href.split(/[\?,]/).indexOf("verbose") !== -1);
function $(n) {
return document.getElementById(n);
function onLoad()
{
var os = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
os.notifyObservers(null, "child-memory-reporter-request", null);
os.addObserver(ChildMemoryListener, "child-memory-reporter-update", false);
update();
}
function makeTableCell(content, c) {
var td = document.createElement("td");
if (typeof content == "string")
content = document.createTextNode(content);
td.appendChild(content);
if (c)
td.setAttribute("class", c);
return td;
function onUnload()
{
var os = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
os.removeObserver(ChildMemoryListener, "child-memory-reporter-update");
}
function makeAbbrNode(str, title) {
var abbr = document.createElement("abbr");
var text = document.createTextNode(str);
abbr.appendChild(text);
abbr.setAttribute("title", title);
return abbr;
function ChildMemoryListener(aSubject, aTopic, aData)
{
update();
}
function makeTableRow() {
var row = document.createElement("tr");
for (var i = 0; i < arguments.length; ++i) {
var arg = arguments[i];
if (typeof(arg) == "string") {
row.appendChild(makeTableCell(arg));
} else if (arg.__proto__ == Array.prototype) {
row.appendChild(makeTableCell(makeAbbrNode(arg[0], arg[1])));
} else {
row.appendChild(arg);
}
}
return row;
}
function setTextContent(node, s) {
while (node.lastChild)
node.removeChild(node.lastChild);
node.appendChild(document.createTextNode(s));
}
function formatNumber(n) {
var s = "";
var neg = false;
if (n < 0) {
neg = true;
n = -n;
}
do {
var k = n % 1000;
if (n > 999) {
if (k > 99)
s = k + s;
else if (k > 9)
s = "0" + k + s;
else
s = "00" + k + s;
} else {
s = k + s;
}
n = Math.floor(n / 1000);
if (n > 0)
s = "," + s;
} while (n > 0);
return s;
function $(n)
{
return document.getElementById(n);
}
/**
* Updates the content of the document with the most current memory information.
* Top-level function that does the work of generating the page.
*/
function updateMemoryStatus()
function update()
{
// if we have the standard reporters for mapped/allocated, put
// those at the top
if ("malloc/mapped" in gMemReporters &&
"malloc/allocated" in gMemReporters)
{
// committed is the total amount of memory that we've touched, that is that we have
// some kind of backing store for
setTextContent($("memMappedValue"),
formatNumber(gMemReporters["malloc/mapped"][0].memoryUsed));
// First, clear the page contents. Necessary because update() might be
// called more than once due to ChildMemoryListener.
var content = $("content");
content.parentNode.replaceChild(content.cloneNode(false), content);
content = $("content");
// allocated is the amount of committed memory that we're actively using (i.e., that isn't free)
setTextContent($("memInUseValue"),
formatNumber(gMemReporters["malloc/allocated"][0].memoryUsed));
var mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
getService(Ci.nsIMemoryReporterManager);
// Process each memory reporter:
// - Make a copy of it into a sub-table indexed by its process. Each copy
// looks like this:
//
// interface Tmr {
// _tpath: string;
// _description: string;
// _memoryUsed: number;
// }
//
// - The .path property is renamed ._tpath ("truncated path") in the copy
// because the process name and ':' (if present) are removed.
// - Note that copying mr.memoryUsed (which calls a C++ function under the
// IDL covers) to tmr._memoryUsed for every reporter now means that the
// results as consistent as possible -- measurements are made all at
// once before most of the memory required to generate this page is
// allocated.
var tmrTable = {};
var e = mgr.enumerateReporters();
while (e.hasMoreElements()) {
var mr = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
var process;
var tmr = {};
var i = mr.path.indexOf(':');
if (i === -1) {
process = "Main";
tmr._tpath = mr.path;
} else {
$("memOverview").style.display = "none";
process = mr.path.slice(0, i);
tmr._tpath = mr.path.slice(i + 1);
}
tmr._description = mr.description;
tmr._memoryUsed = mr.memoryUsed;
var mo = $("memOtherRows");
while (mo.lastChild)
mo.removeChild(mo.lastChild);
var otherCount = 0;
for each (var reporters in gMemReporters) {
// There may be more than one reporter for each path. We take the sum
// of them all for each description.
var total = 0;
reporters.forEach(function(reporter) {
total += reporter.memoryUsed;
});
var row = makeTableRow([reporters[0].path, reporters[0].description],
makeTableCell(formatNumber(total), "memValue"));
mo.appendChild(row);
otherCount++;
if (!tmrTable[process]) {
tmrTable[process] = {};
}
if (otherCount == 0) {
var row = makeTableRow("No other information available.");
mo.appendChild(row);
var tmrs = tmrTable[process];
if (tmrs[tmr._tpath]) {
// Already an entry; must be a duplicated reporter. This can
// happen legitimately. Sum the sizes.
tmrs[tmr._tpath]._memoryUsed += tmr._memoryUsed;
} else {
tmrs[tmr._tpath] = tmr;
}
}
// Generate output for one process at a time. Always start with the
// Main process.
var text = genProcessText("Main", tmrTable["Main"]);
for (var process in tmrTable) {
if (process !== "Main") {
text += genProcessText(process, tmrTable[process]);
}
}
// Generate verbosity option link at the bottom.
text += gVerbose
? "<span class='option'><a href='about:memory'>Less verbose</a></span>"
: "<span class='option'><a href='about:memory?verbose'>More verbose</a></span>";
var div = document.createElement("div");
div.innerHTML = text;
content.appendChild(div);
}
/**
* Updates gMemReporters to contain all the known memory reporters.
* Generates the text for a single process.
*
* @param aProcess
* The name of the process
* @param aTmrs
* Table of Tmrs for this process
* @return The generated text
*/
function updateMemoryReporters()
function genProcessText(aProcess, aTmrs)
{
gMemReporters = [];
var mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
getService(Ci.nsIMemoryReporterManager);
var e = mgr.enumerateReporters();
while (e.hasMoreElements()) {
var reporter = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
if (!gMemReporters[reporter.path]) {
gMemReporters[reporter.path] = [];
/**
* From a list of memory reporters, builds a tree that mirrors the tree
* structure that will be shown as output.
*
* @param aTreeName
* The name of the tree; either "mapped" or "heap-used"
* @param aTreeRootTpath
* The tpath of the top node in the tree
* @param aTreeRootDesc
* The description of the top node in the tree
* @param aOtherDescTail
* Extra description for the end of the "aTreeName/other" entry
* @param aOmitThresholdPerc
* The threshold percentage; entries that account for less than
* this fraction are aggregated
* @return The built tree. The tree nodes have this structure:
* interface Node {
* _name: string;
* _description: string;
* _memoryUsed: number;
* _kids: [Node];
* }
*/
function buildTree(aTreeName, aTreeRootTpath, aTreeRootDesc, aOtherDescTail,
aOmitThresholdPerc)
{
function findKid(aName, aKids)
{
for (var i = 0; i < aKids.length; i++) {
if (aKids[i]._name === aName) {
return aKids[i];
}
gMemReporters[reporter.path].push(reporter);
}
return undefined;
}
// We want to process all reporters that begin with 'aTreeName'.
// First we build the tree but only filling in '_name' and '_kids'.
var t = { _name:aTreeName, _kids:[] };
for (var _tpath in aTmrs) {
var tmr = aTmrs[_tpath];
if (tmr._tpath.slice(0, aTreeName.length + 1) === aTreeName + "/") {
var names = tmr._tpath.slice(aTreeName.length + 1).split('/');
var u = t;
for (var i = 0; i < names.length; i++) {
var name = names[i];
var uMatch = findKid(name, u._kids);
if (uMatch) {
u = uMatch;
} else {
var v = { _name:name, _kids:[] };
u._kids.push(v);
u = v;
}
}
}
}
// Next, fill in '_description' and '_memoryUsed' for each node. For
// interior nodes, '_memoryUsed' is computed by summing child nodes.
function fillInTree(aT, aPretpath)
{
var tpath = aPretpath ? aPretpath + '/' + aT._name : aT._name;
if (aT._kids.length === 0) {
aT._memoryUsed = getBytes(aTmrs, tpath);
aT._description = getDescription(aTmrs, tpath);
} else {
var bytes = 0;
for (var i = 0; i < aT._kids.length; i++) {
// Allow for -1 (ie. "unknown"), treat it like 0.
var b = fillInTree(aT._kids[i], tpath);
bytes += (b === -1 ? 0 : b);
}
aT._memoryUsed = bytes;
aT._description = "The sum of all entries below " + tpath + ".";
}
return aT._memoryUsed;
}
fillInTree(t, "");
// Add the "aTreeName/other" node, which is derived from existing
// nodes, then update the root node accordingly. (But don't do this
// if the root node byte count is -1, ie. unknown).
var nonOtherBytes = t._memoryUsed;
var treeBytes = getBytes(aTmrs, aTreeRootTpath);
if (treeBytes !== -1) {
var otherBytes = treeBytes - nonOtherBytes;
var other = {
_name:"other",
_description:"All unclassified " + aTreeName + " memory." +
aOtherDescTail,
_memoryUsed:otherBytes,
_kids:[]
};
t._kids.push(other);
}
t._memoryUsed = treeBytes;
t._description = aTreeRootDesc;
function shouldOmit(aBytes)
{
return !gVerbose &&
treeBytes !== -1 &&
(100 * aBytes / treeBytes) < aOmitThresholdPerc;
}
/**
* Sort all kid nodes from largest to smallest and aggregate
* insignificant nodes.
*
* @param aT
* The tree
*/
function filterTree(aT)
{
var cmpTmrs = function(a, b) { return b._memoryUsed - a._memoryUsed };
aT._kids.sort(cmpTmrs);
for (var i = 0; i < aT._kids.length; i++) {
if (shouldOmit(aT._kids[i]._memoryUsed)) {
// This sub-tree is below the significance threshold
// Remove it and all remaining (smaller) sub-trees, and
// 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]._memoryUsed;
aggNames.push(aT._kids[i]._name);
}
aT._kids.splice(i0);
var n = i - i0;
var tmrSub = {
_name: "(" + n + " omitted)",
_description: "Omitted sub-trees: " + aggNames.join(", ") + ".",
_memoryUsed: aggBytes,
_kids:[]
};
aT._kids[i0] = tmrSub;
break;
}
filterTree(aT._kids[i]);
}
}
filterTree(t);
return t;
}
var mappedOtherDescTail =
" This includes code and data segments, and thread stacks."
var mappedRootDesc = getDescription(aTmrs, "mapped");
// The threshold used here is much lower than the one for the heap-used
// tree, because the "mapped" total size is so much bigger relative to
// the interesting entries in the "mapped" tree.
var mappedTree = buildTree("mapped", "mapped", mappedRootDesc,
mappedOtherDescTail, 0.01);
var heapUsedOtherDescTail = "";
var heapUsedRootDesc = "See mapped/heap/used above.";
var heapUsedTree = buildTree("heap-used", "mapped/heap/used",
heapUsedRootDesc, heapUsedOtherDescTail, 0.1);
// Nb: the newlines give nice spacing if we cut+paste into a text buffer.
var text = "";
text += "<h1>" + aProcess + " Process</h1>\n\n";
text += genTreeText(mappedTree, "Mapped Memory");
text += genTreeText(heapUsedTree, "Used Heap Memory");
text += genOtherText(aTmrs);
text += "<hr></hr>";
return text;
}
function ChildMemoryListener(subject, topic, data) {
updateMemoryReporters();
updateMemoryStatus();
}
function doLoad()
/**
* Converts a byte count to an appropriate string representation.
*
* @param aBytes
* The byte count
* @return The string representation
*/
function formatBytes(aBytes)
{
var os = Components.classes["@mozilla.org/observer-service;1"].
getService(Components.interfaces.nsIObserverService);
os.notifyObservers(null, "child-memory-reporter-request", null);
var unit = gVerbose ? "B" : "MB";
os.addObserver(ChildMemoryListener, "child-memory-reporter-update", false);
if (aBytes === -1) {
return "??? " + unit;
}
updateMemoryReporters();
updateMemoryStatus();
function formatInt(aN)
{
var neg = false;
if (aN < 0) {
neg = true;
aN = -aN;
}
var s = "";
while (true) {
var k = aN % 1000;
aN = Math.floor(aN / 1000);
if (aN > 0) {
if (k < 10) {
s = ",00" + k + s;
} else if (k < 100) {
s = ",0" + k + s;
} else {
s = "," + k + s;
}
} else {
s = k + s;
break;
}
}
return neg ? "-" + s : s;
}
var s;
if (gVerbose) {
s = formatInt(aBytes) + " " + unit;
} else {
var mbytes = (aBytes / (1024 * 1024)).toFixed(2);
var a = String(mbytes).split(".");
s = formatInt(a[0]) + "." + a[1] + " " + unit;
}
return s;
}
function doUnload()
/**
* Right-justifies a string in a field of a given width, padding as necessary
*
* @param aS
* The string
* @param aN
* The field width
* @param aC
* The char used to pad
* @return The string representation
*/
function pad(aS, aN, aC)
{
var os = Components.classes["@mozilla.org/observer-service;1"].
getService(Components.interfaces.nsIObserverService);
os.removeObserver(ChildMemoryListener, "child-memory-reporter-update");
var padding = "";
var n2 = aN - aS.length;
for (var i = 0; i < n2; i++) {
padding += aC;
}
return padding + aS;
}
/**
* Gets the byte count for a particular memory reporter.
*
* @param aTmrs
* Table of Tmrs for this process
* @param aTpath
* The tpath of the memory reporter
* @return The byte count
*/
function getBytes(aTmrs, aTpath)
{
var tmr = aTmrs[aTpath];
if (tmr) {
var bytes = tmr._memoryUsed;
tmr.done = true;
return bytes;
}
// Nb: this should never occur; "mapped" and "mapped/heap/used" should
// always be registered, and all other tpaths have been extracted from
// aTmrs and so the lookup will succeed. Return an obviously wrong
// number that will likely be noticed.
return -2 * 1024 * 1024;
}
/**
* Gets the description for a particular memory reporter.
*
* @param aTmrs
* Table of Tmrs for this process
* @param aTpath
* The tpath of the memory reporter
* @return The description
*/
function getDescription(aTmrs, aTpath)
{
var r = aTmrs[aTpath];
return r ? r._description : "???";
}
function genMrValueText(aValue)
{
return "<span class='mrValue'>" + aValue + "</span>";
}
function genMrNameText(aDesc, aName)
{
return "-- <span class='mrName' title=\"" + aDesc + "\">" +
aName + "</span>\n";
}
/**
* Generates the text for a particular tree, including its heading.
*
* @param aT
* The tree
* @param aTreeName
* The tree's name
* @return The generated text
*/
function genTreeText(aT, aTreeName)
{
var treeBytes = aT._memoryUsed;
var treeBytesLength = formatBytes(treeBytes).length;
/**
* Generates the text for a particular tree, without a heading.
*
* @param aT
* The tree
* @param aIndentGuide
* Records what indentation is required for this tree. It has one
* entry per level of indentation. For each entry, ._isLastKid
* records whether the node in question is the last child, and
* ._depth records how many chars of indentation are required.
* @param aParentBytesLength
* The length of the formatted byte count of the top node in the tree
* @return The generated text
*/
function genTreeText2(aT, aIndentGuide, aParentBytesLength)
{
function repeatStr(aC, aN)
{
var s = "";
for (var i = 0; i < aN; i++) {
s += aC;
}
return s;
}
// Generate the indent. There's a subset of the Unicode "light"
// box-drawing chars that are widely implemented in terminals, and
// this code sticks to that subset to maximize the chance that
// cutting and pasting about:memory output to a terminal will work
// correctly:
const kHorizontal = "\u2500",
kVertical = "\u2502",
kUpAndRight = "\u2514",
kVerticalAndRight = "\u251c";
var indent = "<span class='treeLine'>";
if (aIndentGuide.length > 0) {
for (var i = 0; i < aIndentGuide.length - 1; i++) {
indent += aIndentGuide[i]._isLastKid ? " " : kVertical;
indent += repeatStr(" ", aIndentGuide[i]._depth - 1);
}
indent += aIndentGuide[i]._isLastKid ? kUpAndRight : kVerticalAndRight;
indent += repeatStr(kHorizontal, aIndentGuide[i]._depth - 1);
}
// Indent more if this entry is narrower than its parent, and update
// aIndentGuide accordingly.
var tMemoryUsedStr = formatBytes(aT._memoryUsed);
var tBytesLength = tMemoryUsedStr.length;
var extraIndentLength = Math.max(aParentBytesLength - tBytesLength, 0);
if (extraIndentLength > 0) {
for (var i = 0; i < extraIndentLength; i++) {
indent += kHorizontal;
}
aIndentGuide[aIndentGuide.length - 1]._depth += extraIndentLength;
}
indent += "</span>";
// Generate the percentage.
var perc = "";
if (treeBytes !== -1) {
if (aT._memoryUsed === treeBytes) {
perc = "100.0";
} else {
perc = (100 * aT._memoryUsed / treeBytes).toFixed(2);
perc = pad(perc, 5, '0');
}
perc = "<span class='mrPerc'>(" + perc + "%)</span> ";
}
var text = indent + genMrValueText(tMemoryUsedStr) + " " + perc +
genMrNameText(aT._description, aT._name);
for (var i = 0; i < aT._kids.length; i++) {
// 3 is the standard depth, the callee adjusts it if necessary.
aIndentGuide.push({ _isLastKid:(i === aT._kids.length - 1), _depth:3 });
text += genTreeText2(aT._kids[i], aIndentGuide, tBytesLength);
aIndentGuide.pop();
}
return text;
}
var text = genTreeText2(aT, [], treeBytesLength);
// Nb: the newlines give nice spacing if we cut+paste into a text buffer.
return "<h2>" + aTreeName + "</h2>\n<pre>" + text + "</pre>\n";
}
/**
* Generates the text for the "Other Measurements" section.
*
* @param aTmrs
* Table of Tmrs for this process
* @return The generated text
*/
function genOtherText(aTmrs)
{
// Get the biggest not-yet-printed value, to determine the field width
// for all these entries. These should all be "other" values, assuming
// all paths are well-formed.
var maxBytes = 0;
for (var tpath in aTmrs) {
var tmr = aTmrs[tpath];
if (!tmr.done && tmr._memoryUsed > maxBytes) {
maxBytes = tmr._memoryUsed;
}
}
// Generate text for the not-yet-yet-printed values.
var maxBytesLength = formatBytes(maxBytes).length;
var text = "";
for (var tpath in aTmrs) {
var tmr = aTmrs[tpath];
if (!tmr.done) {
text += genMrValueText(
pad(formatBytes(tmr._memoryUsed), maxBytesLength, ' ')) + " ";
text += genMrNameText(tmr._description, tmr._tpath);
}
}
// Nb: the newlines give nice spacing if we cut+paste into a text buffer.
return "<h2>Other Measurements</h2>\n<pre>" + text + "</pre>\n";
}

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

@ -21,6 +21,7 @@
-
- Contributor(s):
- Vladimir Vukicevic <vladimir@pobox.com>
- Nicholas Nethercote <nnethercote@mozilla.com>
-
- Alternatively, the contents of this file may be used under the terms of
- either the GNU General Public License Version 2 or later (the "GPL"), or
@ -39,45 +40,12 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>about:memory</title>
<link rel="stylesheet" href="chrome://global/skin/aboutMemory.css"
type="text/css"/>
<script type="text/javascript"
src="chrome://global/content/aboutMemory.js"/>
<link rel="stylesheet" href="chrome://global/skin/aboutMemory.css" type="text/css"/>
<link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css"/>
<script type="text/javascript" src="chrome://global/content/aboutMemory.js"/>
</head>
<body onload="doLoad()" onunload="doUnload()">
<h1>Memory Usage</h1>
<div id="memOverview" class="memOverview memBox">
<h2>Overview</h2>
<table border="0">
<tbody>
<tr>
<td width="100%">Memory mapped:</td>
<td id="memMappedValue" class="memValue"></td>
</tr>
<tr>
<td>Memory in use:</td>
<td id="memInUseValue" class="memValue"></td>
</tr>
</tbody>
</table>
</div>
<div id="memOther" class="memOther memBox">
<h2>Other Information</h2>
<table border="0">
<thead>
<tr>
<th style="text-align: left" width="100%">Description</th>
<th>Value</th>
</tr>
</thead>
<tbody id="memOtherRows">
</tbody>
</table>
</div>
</body>
<!-- No newline before the div element! This avoids extraneous spaces when
pasting the entire output after selecting it with Ctrl-a. -->
<body onload="onLoad()" onunload="onUnload()"><div id="content"></div></body>
</html>

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

@ -0,0 +1,51 @@
#
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is mozilla.org code.
#
# The Initial Developer of the Original Code is
# Mozilla.org.
# Portions created by the Initial Developer are Copyright (C) 2011
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Boris Zbarsky <bzbarsky@mit.edu> (Original author)
#
# Alternatively, the contents of this file may be used under the terms of
# either of the GNU General Public License Version 2 or later (the "GPL"),
# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# 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 *****
DEPTH = ../../../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
relativesrcdir = toolkit/components/aboutmemory/tests
include $(DEPTH)/config/autoconf.mk
DIRS = \
chrome \
$(NULL)
include $(topsrcdir)/config/rules.mk

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

@ -0,0 +1,53 @@
#
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is mozilla.org code.
#
# The Initial Developer of the Original Code is
# Mozilla.org.
# Portions created by the Initial Developer are Copyright (C) 2005
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either of the GNU General Public License Version 2 or later (the "GPL"),
# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# 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 *****
DEPTH = ../../../../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
relativesrcdir = toolkit/components/aboutmemory/tests/chrome
include $(DEPTH)/config/autoconf.mk
include $(topsrcdir)/config/rules.mk
_CHROME_FILES = \
test_aboutmemory.xul \
$(NULL)
libs:: $(_CHROME_FILES)
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/chrome/$(relativesrcdir)

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

@ -0,0 +1,267 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
<window title="about:memory"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://mochikit/content/MochiKit/packed.js"/>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml"></body>
<!-- test code goes here -->
<script type="application/javascript">
<![CDATA[
const Cc = Components.classes;
const Ci = Components.interfaces;
var mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
getService(Ci.nsIMemoryReporterManager);
// Remove all the real reporters; save them to restore at the end.
var e = mgr.enumerateReporters();
var realReporters = [];
while (e.hasMoreElements()) {
var mr = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
mgr.unregisterReporter(mr);
realReporters.push(mr);
}
// Setup various fake-but-deterministic reporters.
const KB = 1024;
const MB = KB * KB;
fakeReporters = [
{ path: "mapped", memoryUsed: 1000 * MB },
{ path: "mapped/heap/used", memoryUsed: 500 * MB },
{ path: "mapped/heap/unused", memoryUsed: 100 * MB },
{ path: "mapped/a", memoryUsed: 222 * MB },
{ path: "heap-used/a", memoryUsed: 99 * MB },
{ path: "heap-used/b/a", memoryUsed: 80 * MB },
{ path: "heap-used/b/b", memoryUsed: 75 * MB },
{ path: "heap-used/b/c/a", memoryUsed: 44 * MB },
{ path: "heap-used/b/c/b", memoryUsed: 33 * MB }, // aggregated
{ path: "heap-used/c", memoryUsed: 123 * MB },
{ path: "heap-used/d", memoryUsed: 499 * KB }, // aggregated
{ path: "heap-used/e", memoryUsed: 100 * KB }, // aggregated
{ path: "heap-used/f/g/h/i", memoryUsed: 20 * MB },
{ path: "other1", memoryUsed: 111 * MB },
{ path: "other2", memoryUsed: 222 * MB },
{ path: "2nd:mapped", memoryUsed: 1000 * MB },
{ path: "2nd:mapped/heap/used", memoryUsed: 500 * MB },
{ path: "2nd:mapped/a/b/c", memoryUsed: 499 * MB },
{ path: "2nd:heap-used/a", memoryUsed: 400 * MB },
{ path: "2nd:other1", memoryUsed: 777 * MB },
// -1 means "don't know"; this should be handled gracefully for
// "mapped" and "mapped/heap/used".
{ path: "3rd:mapped", memoryUsed: -1 },
{ path: "3rd:mapped/heap/used", memoryUsed: -1 },
{ path: "3rd:mapped/a/b", memoryUsed: 333 * MB },
{ path: "3rd:heap-used/a/b", memoryUsed: 444 * MB },
{ path: "3rd:other1", memoryUsed: 555 * MB }
];
for (var i = 0; i < fakeReporters.length; i++) {
mgr.registerReporter(fakeReporters[i]);
}
]]>
</script>
<iframe id="amFrame" src="about:memory"></iframe>
<iframe id="amvFrame" src="about:memory?verbose"></iframe>
<script type="application/javascript">
<![CDATA[
var amExpectedText =
"\
Main Process\n\
\n\
Mapped Memory\n\
1,000.00 MB (100.0%) -- mapped\n\
├────600.00 MB (60.00%) -- heap\n\
│ ├──500.00 MB (50.00%) -- used\n\
│ └──100.00 MB (10.00%) -- unused\n\
├────222.00 MB (22.20%) -- a\n\
└────178.00 MB (17.80%) -- other\n\
\n\
Used Heap Memory\n\
500.00 MB (100.0%) -- heap-used\n\
├──232.00 MB (46.40%) -- b\n\
│ ├───80.00 MB (16.00%) -- a\n\
│ ├───77.00 MB (15.40%) -- c\n\
│ │ ├──44.00 MB (08.80%) -- a\n\
│ │ └──33.00 MB (06.60%) -- b\n\
│ └───75.00 MB (15.00%) -- b\n\
├──123.00 MB (24.60%) -- c\n\
├───99.00 MB (19.80%) -- a\n\
├───25.42 MB (05.08%) -- other\n\
├───20.00 MB (04.00%) -- f\n\
│ └──20.00 MB (04.00%) -- g\n\
│ └──20.00 MB (04.00%) -- h\n\
│ └──20.00 MB (04.00%) -- i\n\
└────0.58 MB (00.12%) -- (2 omitted)\n\
\n\
Other Measurements\n\
111.00 MB -- other1\n\
222.00 MB -- other2\n\
\n\
2nd Process\n\
\n\
Mapped Memory\n\
1,000.00 MB (100.0%) -- mapped\n\
├────500.00 MB (50.00%) -- heap\n\
│ └──500.00 MB (50.00%) -- used\n\
├────499.00 MB (49.90%) -- a\n\
│ └──499.00 MB (49.90%) -- b\n\
│ └──499.00 MB (49.90%) -- c\n\
└──────1.00 MB (00.10%) -- other\n\
\n\
Used Heap Memory\n\
500.00 MB (100.0%) -- heap-used\n\
├──400.00 MB (80.00%) -- a\n\
└──100.00 MB (20.00%) -- other\n\
\n\
Other Measurements\n\
777.00 MB -- other1\n\
\n\
3rd Process\n\
\n\
Mapped Memory\n\
??? MB -- mapped\n\
├──333.00 MB -- a\n\
│ └──333.00 MB -- b\n\
└──0.00 MB -- heap\n\
└───??? MB -- used\n\
\n\
Used Heap Memory\n\
??? MB -- heap-used\n\
└──444.00 MB -- a\n\
└──444.00 MB -- b\n\
\n\
Other Measurements\n\
555.00 MB -- other1\n\
\n\
";
var amvExpectedText =
"\
Main Process\n\
\n\
Mapped Memory\n\
1,048,576,000 B (100.0%) -- mapped\n\
├────629,145,600 B (60.00%) -- heap\n\
│ ├──524,288,000 B (50.00%) -- used\n\
│ └──104,857,600 B (10.00%) -- unused\n\
├────232,783,872 B (22.20%) -- a\n\
└────186,646,528 B (17.80%) -- other\n\
\n\
Used Heap Memory\n\
524,288,000 B (100.0%) -- heap-used\n\
├──243,269,632 B (46.40%) -- b\n\
│ ├───83,886,080 B (16.00%) -- a\n\
│ ├───80,740,352 B (15.40%) -- c\n\
│ │ ├──46,137,344 B (08.80%) -- a\n\
│ │ └──34,603,008 B (06.60%) -- b\n\
│ └───78,643,200 B (15.00%) -- b\n\
├──128,974,848 B (24.60%) -- c\n\
├──103,809,024 B (19.80%) -- a\n\
├───26,649,600 B (05.08%) -- other\n\
├───20,971,520 B (04.00%) -- f\n\
│ └──20,971,520 B (04.00%) -- g\n\
│ └──20,971,520 B (04.00%) -- h\n\
│ └──20,971,520 B (04.00%) -- i\n\
├──────510,976 B (00.10%) -- d\n\
└──────102,400 B (00.02%) -- e\n\
\n\
Other Measurements\n\
116,391,936 B -- other1\n\
232,783,872 B -- other2\n\
\n\
2nd Process\n\
\n\
Mapped Memory\n\
1,048,576,000 B (100.0%) -- mapped\n\
├────524,288,000 B (50.00%) -- heap\n\
│ └──524,288,000 B (50.00%) -- used\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\
└──────1,048,576 B (00.10%) -- other\n\
\n\
Used Heap Memory\n\
524,288,000 B (100.0%) -- heap-used\n\
├──419,430,400 B (80.00%) -- a\n\
└──104,857,600 B (20.00%) -- other\n\
\n\
Other Measurements\n\
814,743,552 B -- other1\n\
\n\
3rd Process\n\
\n\
Mapped Memory\n\
??? B -- mapped\n\
├──349,175,808 B -- a\n\
│ └──349,175,808 B -- b\n\
└────0 B -- heap\n\
└──??? B -- used\n\
\n\
Used Heap Memory\n\
??? B -- heap-used\n\
└──465,567,744 B -- a\n\
└──465,567,744 B -- b\n\
\n\
Other Measurements\n\
581,959,680 B -- other1\n\
\n\
"
function finish()
{
// Unregister fake reporters, re-register the real reporters, just in
// case subsequent tests rely on them.
for (var i = 0; i < fakeReporters.length; i++) {
mgr.unregisterReporter(fakeReporters[i]);
}
for (var i = 0; i < realReporters.length; i++) {
mgr.registerReporter(realReporters[i]);
}
SimpleTest.finish();
}
// Cut+paste the entire page and check that the cut text matches what we
// expect. This tests the output in general and also that the cutting and
// pasting works as expected.
function test(aFrame, aExpectedText, aNext) {
document.querySelector("#" + aFrame).focus();
SimpleTest.waitForClipboard(aExpectedText,
function() {
synthesizeKey("A", {accelKey: true});
synthesizeKey("C", {accelKey: true});
},
aNext,
function() {
ok(false, "pasted text doesn't match for " + aFrame);
finish();
}
);
}
addLoadEvent(function() {
test(
"amFrame",
amExpectedText,
function() {
test(
"amvFrame",
amvExpectedText,
function() {
finish()
}
)
}
);
});
SimpleTest.waitForExplicitFinish();
]]>
</script>
</window>

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

@ -44,9 +44,39 @@ interface nsISimpleEnumerator;
interface nsIMemoryReporter : nsISupports
{
/*
* The path that this memory usage should be reported under.
* The path that this memory usage should be reported under. Paths can
* begin with a process name plus a colon, eg "Content:", but this is not
* necessary for the main process. After the process name, paths are
* '/'-delimited, eg. "a/b/c". There are three categories of paths.
*
* Normally "/"-delimited for organization.
* - Paths starting with "mapped" represent non-overlapping regions of mapped
* memory. Each one can be viewed as representing a path in a tree from
* the root node ("mapped") to a leaf node. The one exception is the
* special "mapped" path which represents the root of that tree. So, for
* example, "mapped/heap/used", "mapped/heap/unused",
* "mapped/js/mjit-code", mapped/js/tjit-code", plus the special "mapped"
* path, define this tree:
*
* mapped
* |--heap
* | |--used
* | \--unused
* \--js
* |--mjit-code
* \--tjit-code
*
* "mapped/heap" would not be a valid path in this example because "heap"
* is not a leaf node.
*
* - Paths starting with "heap-used" represent non-overlapping regions of
* used heap memory. The "mapped" rules above apply equally here. The
* exceptional path for this tree is "mapped/heap/used" (and so this tree
* is a sub-tree of the "mapped" tree). When shown in about:memory, this
* tree's root node is given the synonym "heap-used".
*
* - All other paths represent cross-cuttings memory regions, ie. ones that
* may overlap arbitrarily with regions in the "mapped" and "heap-used"
* trees.
*/
readonly attribute string path;

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

@ -41,6 +41,135 @@
#include "nsMemoryReporterManager.h"
#include "nsArrayEnumerator.h"
#if defined(XP_LINUX)
#include <unistd.h>
static PRInt64 GetProcSelfStatmField(int n)
{
// There are more than two fields, but we're only interested in the first
// two.
static const int MAX_FIELD = 2;
size_t fields[MAX_FIELD];
NS_ASSERTION(n < MAX_FIELD, "bad field number");
FILE *f = fopen("/proc/self/statm", "r");
if (f) {
int nread = fscanf(f, "%lu %lu", &fields[0], &fields[1]);
fclose(f);
return (PRInt64) ((nread == MAX_FIELD) ? fields[n]*getpagesize() : -1);
}
return (PRInt64) -1;
}
static PRInt64 GetMapped(void *)
{
return GetProcSelfStatmField(0);
}
static PRInt64 GetResident(void *)
{
return GetProcSelfStatmField(1);
}
#elif defined(XP_MACOSX)
#include <mach/mach_init.h>
#include <mach/task.h>
static bool GetTaskBasicInfo(struct task_basic_info *ti)
{
mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT;
kern_return_t kr = task_info(mach_task_self(), TASK_BASIC_INFO,
(task_info_t)ti, &count);
return kr == KERN_SUCCESS;
}
// Getting a sensible "mapped" number on Mac is difficult. The following is
// easy and (I think) corresponds to the VSIZE figure reported by 'top' and
// 'ps', but that includes shared memory and so is always absurdly high. This
// doesn't really matter as the "mapped" figure is never that useful.
static PRInt64 GetMapped(void *)
{
task_basic_info ti;
return (PRInt64) (GetTaskBasicInfo(&ti) ? ti.virtual_size : -1);
}
static PRInt64 GetResident(void *)
{
task_basic_info ti;
return (PRInt64) (GetTaskBasicInfo(&ti) ? ti.resident_size : -1);
}
#elif defined(XP_WIN)
#include <windows.h>
#include <psapi.h>
static PRInt64 GetMapped(void *)
{
#if MOZ_WINSDK_TARGETVER >= MOZ_NTDDI_LONGHORN
PROCESS_MEMORY_COUNTERS_EX pmcex;
pmcex.cb = sizeof(PROCESS_MEMORY_COUNTERS_EX);
if (!GetProcessMemoryInfo(GetCurrentProcess(),
(PPROCESS_MEMORY_COUNTERS) &pmcex, sizeof(pmcex)))
return (PRInt64) -1;
return pmcex.PrivateUsage;
#else
return (PRInt64) -1;
#endif
}
static PRInt64 GetResident(void *)
{
PROCESS_MEMORY_COUNTERS pmc;
pmc.cb = sizeof(PROCESS_MEMORY_COUNTERS);
if (!GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc)))
return (PRInt64) -1;
return pmc.WorkingSetSize;
}
#else
static PRInt64 GetMapped(void *)
{
return (PRInt64) -1;
}
static PRInt64 GetResident(void *)
{
return (PRInt64) -1;
}
#endif
// aboutMemory.js requires that this reporter always be registered, even if the
// byte count returned is always -1.
NS_MEMORY_REPORTER_IMPLEMENT(Mapped,
"mapped",
"Memory mapped by the process. Note that 'resident' is a better measure "
"of memory resources used by the process. "
"On Windows (XP SP2 or later only) this is the private usage and does not "
"include memory shared with other processes. "
"On Mac and Linux this is the vsize figure as reported by 'top' or 'ps' "
"and includes memory shared with other processes; on Mac the amount of "
"shared memory can be very high and so this figure is of limited use.",
GetMapped,
NULL)
NS_MEMORY_REPORTER_IMPLEMENT(Resident,
"resident",
"Memory mapped by the process that is present in physical memory, "
"also known as the resident set size (RSS). This is the best single "
"figure to use when considering the memory resources used by the process, "
"but it depends both on other processes being run and details of the OS "
"kernel and so is best used for comparing the memory usage of a single "
"process at different points in time.",
GetResident,
NULL)
/**
** memory reporter implementation for jemalloc and OSX malloc,
** to obtain info on total memory in use (that we know about,
@ -66,145 +195,128 @@ extern void jemalloc_stats(jemalloc_stats_t* stats)
#endif // MOZ_MEMORY
#if HAVE_JEMALLOC_STATS
# define HAVE_MALLOC_REPORTERS 1
PRInt64 getMallocMapped(void *) {
jemalloc_stats_t stats;
jemalloc_stats(&stats);
return (PRInt64) stats.mapped;
}
PRInt64 getMallocAllocated(void *) {
static PRInt64 GetMappedHeapUsed(void *)
{
jemalloc_stats_t stats;
jemalloc_stats(&stats);
return (PRInt64) stats.allocated;
}
PRInt64 getMallocCommitted(void *) {
static PRInt64 GetMappedHeapUnused(void *)
{
jemalloc_stats_t stats;
jemalloc_stats(&stats);
return (PRInt64) (stats.mapped - stats.allocated);
}
static PRInt64 GetHeapCommitted(void *)
{
jemalloc_stats_t stats;
jemalloc_stats(&stats);
return (PRInt64) stats.committed;
}
PRInt64 getMallocDirty(void *) {
static PRInt64 GetHeapDirty(void *)
{
jemalloc_stats_t stats;
jemalloc_stats(&stats);
return (PRInt64) stats.dirty;
}
NS_MEMORY_REPORTER_IMPLEMENT(HeapCommitted,
"heap-committed",
"Memory mapped by the heap allocator that is "
"committed, i.e. in physical memory or paged to "
"disk.",
GetHeapCommitted,
NULL)
NS_MEMORY_REPORTER_IMPLEMENT(HeapDirty,
"heap-dirty",
"Memory mapped by the heap allocator that is "
"committed but unused.",
GetHeapDirty,
NULL)
#elif defined(XP_MACOSX) && !defined(MOZ_MEMORY)
#define HAVE_MALLOC_REPORTERS 1
#include <malloc/malloc.h>
static PRInt64 getMallocAllocated(void *) {
static PRInt64 GetMappedHeapUsed(void *)
{
struct mstats stats = mstats();
return (PRInt64) stats.bytes_used;
}
static PRInt64 getMallocMapped(void *) {
static PRInt64 GetMappedHeapUnused(void *)
{
struct mstats stats = mstats();
return (PRInt64) stats.bytes_total;
return (PRInt64) (stats.bytes_total - stats.bytes_used);
}
static PRInt64 getMallocDefaultCommitted(void *) {
static PRInt64 GetHeapZone0Committed(void *)
{
malloc_statistics_t stats;
malloc_zone_statistics(malloc_default_zone(), &stats);
return stats.size_in_use;
}
static PRInt64 getMallocDefaultAllocated(void *) {
static PRInt64 GetHeapZone0Used(void *)
{
malloc_statistics_t stats;
malloc_zone_statistics(malloc_default_zone(), &stats);
return stats.size_allocated;
}
#endif
#ifdef HAVE_MALLOC_REPORTERS
NS_MEMORY_REPORTER_IMPLEMENT(MallocAllocated,
"malloc/allocated",
"Malloc bytes allocated (in use by application)",
getMallocAllocated,
NS_MEMORY_REPORTER_IMPLEMENT(HeapZone0Committed,
"heap-zone0-committed",
"Memory mapped by the heap allocator that is "
"committed in the default zone.",
GetHeapZone0Committed,
NULL)
NS_MEMORY_REPORTER_IMPLEMENT(MallocMapped,
"malloc/mapped",
"Malloc bytes mapped (not necessarily committed)",
getMallocMapped,
NS_MEMORY_REPORTER_IMPLEMENT(HeapZone0Used,
"heap-zone0-used",
"Memory mapped by the heap allocator in the "
"default zone that is available for use by the "
"application.",
GetHeapZone0Used,
NULL)
#if defined(HAVE_JEMALLOC_STATS)
NS_MEMORY_REPORTER_IMPLEMENT(MallocCommitted,
"malloc/committed",
"Malloc bytes committed (readable/writable)",
getMallocCommitted,
NULL)
NS_MEMORY_REPORTER_IMPLEMENT(MallocDirty,
"malloc/dirty",
"Malloc bytes dirty (committed unused pages)",
getMallocDirty,
NULL)
#elif defined(XP_MACOSX) && !defined(MOZ_MEMORY)
NS_MEMORY_REPORTER_IMPLEMENT(MallocDefaultCommitted,
"malloc/zone0/committed",
"Malloc bytes committed (r/w) in default zone",
getMallocDefaultCommitted,
NULL)
NS_MEMORY_REPORTER_IMPLEMENT(MallocDefaultAllocated,
"malloc/zone0/allocated",
"Malloc bytes allocated (in use) in default zone",
getMallocDefaultAllocated,
NULL)
#endif
#endif
#ifdef XP_WIN
#include <windows.h>
#include <psapi.h>
static PRInt64 GetWin32PrivateBytes(void *) {
#if MOZ_WINSDK_TARGETVER >= MOZ_NTDDI_LONGHORN
PROCESS_MEMORY_COUNTERS_EX pmcex;
pmcex.cb = sizeof(PROCESS_MEMORY_COUNTERS_EX);
if (!GetProcessMemoryInfo(GetCurrentProcess(),
(PPROCESS_MEMORY_COUNTERS) &pmcex,
sizeof(PROCESS_MEMORY_COUNTERS_EX)))
return 0;
return pmcex.PrivateUsage;
#else
return 0;
#endif
static PRInt64 GetMappedHeapUsed(void *)
{
return (PRInt64) -1;
}
static PRInt64 GetWin32WorkingSetSize(void *) {
PROCESS_MEMORY_COUNTERS pmc;
pmc.cb = sizeof(PROCESS_MEMORY_COUNTERS);
if (!GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc)))
return 0;
return pmc.WorkingSetSize;
static PRInt64 GetMappedHeapUnused(void *)
{
return (PRInt64) -1;
}
NS_MEMORY_REPORTER_IMPLEMENT(Win32WorkingSetSize,
"win32/workingset",
"Win32 working set size",
GetWin32WorkingSetSize,
nsnull);
NS_MEMORY_REPORTER_IMPLEMENT(Win32PrivateBytes,
"win32/privatebytes",
"Win32 private bytes (cannot be shared with other processes). (Available only on Windows XP SP2 or later.)",
GetWin32PrivateBytes,
nsnull);
#endif
// aboutMemory.js requires that this reporter always be registered, even if the
// byte count returned is always -1.
NS_MEMORY_REPORTER_IMPLEMENT(MappedHeapUsed,
"mapped/heap/used",
"Memory mapped by the heap allocator that is available for use by the "
"application. This may exceed the amount of memory requested by the "
"application due to the allocator rounding up request sizes. "
"(The exact amount requested is not measured.) "
"This is usually the best figure for developers to focus on when trying "
"to reduce memory consumption.",
GetMappedHeapUsed,
NULL)
NS_MEMORY_REPORTER_IMPLEMENT(MappedHeapUnused,
"mapped/heap/unused",
"Memory mapped by the heap allocator and not available for use by the "
"application. This can grow large if the heap allocator is holding onto "
"memory that the application has freed.",
GetMappedHeapUnused,
NULL)
/**
** nsMemoryReporterManager implementation
**/
@ -218,32 +330,20 @@ nsMemoryReporterManager::Init()
if (!jemalloc_stats)
return NS_ERROR_FAILURE;
#endif
/*
* Register our core reporters
*/
#define REGISTER(_x) RegisterReporter(new NS_MEMORY_REPORTER_NAME(_x))
/*
* Register our core jemalloc/malloc reporters
*/
#ifdef HAVE_MALLOC_REPORTERS
REGISTER(MallocAllocated);
REGISTER(MallocMapped);
REGISTER(Mapped);
REGISTER(MappedHeapUsed);
REGISTER(MappedHeapUnused);
REGISTER(Resident);
#if defined(HAVE_JEMALLOC_STATS)
REGISTER(MallocCommitted);
REGISTER(MallocDirty);
REGISTER(HeapCommitted);
REGISTER(HeapDirty);
#elif defined(XP_MACOSX) && !defined(MOZ_MEMORY)
REGISTER(MallocDefaultCommitted);
REGISTER(MallocDefaultAllocated);
#endif
#endif
#ifdef XP_WIN
#if MOZ_WINSDK_TARGETVER >= MOZ_NTDDI_LONGHORN
REGISTER(Win32PrivateBytes);
#endif
REGISTER(Win32WorkingSetSize);
REGISTER(HeapZone0Committed);
REGISTER(HeapZone0Used);
#endif
return NS_OK;
@ -262,7 +362,7 @@ NS_IMETHODIMP
nsMemoryReporterManager::EnumerateReporters(nsISimpleEnumerator **result)
{
nsresult rv;
mozilla::MutexAutoLock autoLock(mMutex);
mozilla::MutexAutoLock autoLock(mMutex);
rv = NS_NewArrayEnumerator(result, mReporters);
return rv;
}
@ -270,7 +370,7 @@ nsMemoryReporterManager::EnumerateReporters(nsISimpleEnumerator **result)
NS_IMETHODIMP
nsMemoryReporterManager::RegisterReporter(nsIMemoryReporter *reporter)
{
mozilla::MutexAutoLock autoLock(mMutex);
mozilla::MutexAutoLock autoLock(mMutex);
if (mReporters.IndexOf(reporter) != -1)
return NS_ERROR_FAILURE;
@ -281,7 +381,7 @@ nsMemoryReporterManager::RegisterReporter(nsIMemoryReporter *reporter)
NS_IMETHODIMP
nsMemoryReporterManager::UnregisterReporter(nsIMemoryReporter *reporter)
{
mozilla::MutexAutoLock autoLock(mMutex);
mozilla::MutexAutoLock autoLock(mMutex);
if (!mReporters.RemoveObject(reporter))
return NS_ERROR_FAILURE;
@ -295,11 +395,11 @@ nsMemoryReporter::nsMemoryReporter(nsCString& prefix,
nsCString& desc,
PRInt64 memoryUsed)
: mDesc(desc)
, mMemoryUsed(memoryUsed)
, mMemoryUsed(memoryUsed)
{
if (!prefix.IsEmpty()) {
mPath.Append(prefix);
mPath.Append(NS_LITERAL_CSTRING(" - "));
mPath.Append(prefix);
mPath.Append(NS_LITERAL_CSTRING(":"));
}
mPath.Append(path);
}
@ -310,20 +410,20 @@ nsMemoryReporter::~nsMemoryReporter()
NS_IMETHODIMP nsMemoryReporter::GetPath(char **aPath)
{
*aPath = strdup(mPath.get());
return NS_OK;
*aPath = strdup(mPath.get());
return NS_OK;
}
NS_IMETHODIMP nsMemoryReporter::GetDescription(char **aDescription)
{
*aDescription = strdup(mDesc.get());
return NS_OK;
*aDescription = strdup(mDesc.get());
return NS_OK;
}
NS_IMETHODIMP nsMemoryReporter::GetMemoryUsed(PRInt64 *aMemoryUsed)
{
*aMemoryUsed = mMemoryUsed;
return NS_OK;
*aMemoryUsed = mMemoryUsed;
return NS_OK;
}