Mercurial > spidermonkey-playground
view memory_profiler.cpp @ 73:b87b6ebb6e86
Memory profiler server is now much better at reporting errors.
author | Atul Varma <varmaa@toolness.com> |
---|---|
date | Thu, 25 Jun 2009 18:38:01 -0700 |
parents | eca8642eed4a |
children |
line wrap: on
line source
#include "jsdhash.h" #include "tcb.h" #include "memory_profiler.h" #include "server_socket.h" #define SERVER_FILENAME "memory_profiler_server.js" // Private structure to track the state of tracing the JS heap. typedef struct TracingState { // Keeps track of what objects we've visited so far. JSDHashTable visited; // Whether the tracing operation is successful or failed. JSBool result; // Runtime that we're tracing. JSRuntime *runtime; // Structure required to use JS tracing functions. JSTracer tracer; }; // Static singleton for tracking the state of tracing the JS heap. static TracingState tracingState; typedef struct ChildTracingState { int num; void **things; uint32 *kinds; JSTracer tracer; }; static ChildTracingState childTracingState; // JSTraceCallback to build a hashtable of children. static void childCountBuilder(JSTracer *trc, void *thing, uint32 kind) { childTracingState.num++; } // JSTraceCallback to build a hashtable of children. static void childBuilder(JSTracer *trc, void *thing, uint32 kind) { *childTracingState.kinds = kind; childTracingState.kinds++; *childTracingState.things = thing; childTracingState.things++; } // JSTraceCallback to build a hashtable of existing object references. static void visitedBuilder(JSTracer *trc, void *thing, uint32 kind) { switch (kind) { case JSTRACE_OBJECT: JSDHashEntryStub *entry = (JSDHashEntryStub *) JS_DHashTableOperate(&tracingState.visited, thing, JS_DHASH_LOOKUP); if (JS_DHASH_ENTRY_IS_FREE((JSDHashEntryHdr *)entry)) { entry = (JSDHashEntryStub *) JS_DHashTableOperate(&tracingState.visited, thing, JS_DHASH_ADD); if (entry == NULL) { JS_ReportOutOfMemory(trc->context); tracingState.result = JS_FALSE; return; } entry->key = thing; JS_TraceChildren(trc, thing, kind); } break; case JSTRACE_DOUBLE: break; case JSTRACE_STRING: break; } } static JSBool getChildrenInfo(JSContext *cx, JSObject *info, JSObject *target, JSContext *targetCx) { childTracingState.tracer.context = targetCx; childTracingState.tracer.callback = childCountBuilder; childTracingState.num = 0; JS_TraceChildren(&childTracingState.tracer, target, JSTRACE_OBJECT); void *things[childTracingState.num]; uint32 kinds[childTracingState.num]; childTracingState.things = things; childTracingState.kinds = kinds; childTracingState.tracer.callback = childBuilder; JS_TraceChildren(&childTracingState.tracer, target, JSTRACE_OBJECT); int numObjectChildren = 0; for (int i = 0; i < childTracingState.num; i++) { if (kinds[i] == JSTRACE_OBJECT) numObjectChildren++; } jsval childrenVals[numObjectChildren]; int currChild = 0; for (int i = 0; i < childTracingState.num; i++) { if (kinds[i] == JSTRACE_OBJECT) { childrenVals[currChild] = INT_TO_JSVAL((unsigned int) things[i]); currChild += 1; } } if (numObjectChildren != currChild) { JS_ReportError(cx, "Assertion failure, numObjectChildren != currChild"); return JS_FALSE; } JSObject *children = JS_NewArrayObject(cx, numObjectChildren, childrenVals); if (children == NULL) { JS_ReportOutOfMemory(cx); return JS_FALSE; } return JS_DefineProperty(cx, info, "children", OBJECT_TO_JSVAL(children), NULL, NULL, JSPROP_ENUMERATE); } static JSBool getPropertiesInfo(JSContext *cx, JSObject *info, JSObject *target, JSContext *targetCx) { JSObject *propInfo = JS_NewObject(cx, NULL, NULL, NULL); if (propInfo == NULL) { JS_ReportOutOfMemory(cx); return JS_FALSE; } if (!JS_DefineProperty(cx, info, "properties", OBJECT_TO_JSVAL(propInfo), NULL, NULL, JSPROP_ENUMERATE)) return JS_FALSE; JSPropertyDescArray properties; if (!JS_GetPropertyDescArray(targetCx, target, &properties)) { JS_ReportError(cx, "Couldn't get property desc array."); return JS_FALSE; } for (int i = 0; i < properties.length; i++) { jsval id = properties.array[i].id; // TODO: What if the property is a number? if (JSVAL_IS_STRING(id)) { jsval value = properties.array[i].value; if (JSVAL_IS_OBJECT(value)) { JSObject *valueObj = JSVAL_TO_OBJECT(value); value = INT_TO_JSVAL((unsigned int) valueObj); } else if (JSVAL_IS_STRING(value)) { JSString *valueStr = JS_NewUCStringCopyZ( cx, JS_GetStringChars(JSVAL_TO_STRING(value)) ); if (valueStr == NULL) { JS_ReportOutOfMemory(cx); return JS_FALSE; } value = STRING_TO_JSVAL(valueStr); } else value = JSVAL_NULL; if (!JS_DefineProperty(cx, propInfo, JS_GetStringBytes(JSVAL_TO_STRING(id)), value, NULL, NULL, JSPROP_ENUMERATE)) return JS_FALSE; } } // Unroot roots set by JS_GetPropertyDescArray(). JS_PutPropertyDescArray(targetCx, &properties); return JS_TRUE; } static JSBool getObjInfo(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { uint32 id; if (!JS_ConvertArguments(cx, argc, argv, "u", &id)) return JS_FALSE; JSDHashEntryStub *entry = (JSDHashEntryStub *) JS_DHashTableOperate(&tracingState.visited, (void *) id, JS_DHASH_LOOKUP); if (entry == NULL) { JS_ReportOutOfMemory(cx); return JS_FALSE; } if (JS_DHASH_ENTRY_IS_BUSY((JSDHashEntryHdr *)entry)) { JSObject *info = JS_NewObject(cx, NULL, NULL, NULL); *rval = OBJECT_TO_JSVAL(info); JSObject *target = (JSObject *) id; JSContext *targetCx = tracingState.tracer.context; JSClass *classp = JS_GET_CLASS(targetCx, target); if (classp != NULL) { // TODO: Should really be using an interned string here or something. JSString *name = JS_NewStringCopyZ(cx, classp->name); if (name == NULL) { JS_ReportOutOfMemory(cx); return JS_FALSE; } if (!JS_DefineProperty(cx, info, "nativeClass", STRING_TO_JSVAL(name), NULL, NULL, JSPROP_ENUMERATE)) { JS_ReportOutOfMemory(cx); return JS_FALSE; } } if (!JS_DefineProperty( cx, info, "size", INT_TO_JSVAL(JS_GetObjectTotalSize(targetCx, target)), NULL, NULL, JSPROP_ENUMERATE)) { JS_ReportOutOfMemory(cx); return JS_FALSE; } if (!getChildrenInfo(cx, info, target, targetCx)) return JS_FALSE; if (!getPropertiesInfo(cx, info, target, targetCx)) return JS_FALSE; *rval = OBJECT_TO_JSVAL(info); } else *rval = JSVAL_NULL; return JS_TRUE; } typedef struct RootMapStruct { JSBool rval; int length; JSContext *cx; JSObject *array; }; static intN rootMapFun(void *rp, const char *name, void *data) { // rp is a JS GC root. From the documentation for JS_AddRoot() in jsapi.h: // // A JS GC root is a pointer to a JSObject *, JSString *, or // jsdouble * that itself points into the GC heap (more recently, // we support this extension: a root may be a pointer to a jsval v // for which JSVAL_IS_GCTHING(v) is true). // // The public JSAPI appears to provide no way of actually determining // which it is, though, so we're just going to have to list them all, // and hope that a later tracing will give us more information about // them. RootMapStruct *roots = (RootMapStruct *) data; jsval id = INT_TO_JSVAL(*((unsigned int *)rp)); if (!JS_SetElement(roots->cx, roots->array, roots->length, &id)) { roots->rval = JS_FALSE; return JS_MAP_GCROOT_STOP; } roots->length++; return JS_MAP_GCROOT_NEXT; } static JSBool getGCRoots(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { RootMapStruct roots; roots.array = JS_NewArrayObject(cx, 0, NULL); roots.length = 0; roots.rval = JS_TRUE; roots.cx = cx; if (roots.array == NULL) { JS_ReportError(cx, "Creating array failed."); return JS_FALSE; } JS_MapGCRoots(tracingState.runtime, rootMapFun, &roots); if (roots.rval == JS_FALSE) return JS_FALSE; *rval = OBJECT_TO_JSVAL(roots.array); return JS_TRUE; } static JSFunctionSpec server_global_functions[] = { JS_FS("ServerSocket", createServerSocket, 0, 0, 0), JS_FS("getGCRoots", getGCRoots, 0, 0, 0), JS_FS("getObjectInfo", getObjInfo, 1, 0, 0), JS_FS_END }; JSBool profileMemory(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { if (!JS_DHashTableInit(&tracingState.visited, JS_DHashGetStubOps(), NULL, sizeof(JSDHashEntryStub), JS_DHASH_DEFAULT_CAPACITY(100))) { JS_ReportOutOfMemory(cx); return JS_FALSE; } tracingState.runtime = JS_GetRuntime(cx); tracingState.result = JS_TRUE; tracingState.tracer.context = cx; tracingState.tracer.callback = visitedBuilder; JS_TraceRuntime(&tracingState.tracer); if (!tracingState.result) return JS_FALSE; JSRuntime *serverRuntime = JS_NewRuntime(8L * 1024L * 1024L); if (serverRuntime == NULL) { JS_ReportError(cx, "Couldn't create server JS runtime."); return JS_FALSE; } JSContext *serverCx = JS_NewContext(serverRuntime, 8192); if (serverCx == NULL) { JS_ReportError(cx, "Couldn't create server JS context."); return JS_FALSE; } JS_SetOptions(serverCx, JSOPTION_VAROBJFIX); JS_SetVersion(serverCx, JSVERSION_LATEST); JS_BeginRequest(serverCx); jsval serverRval; if (!TCB_init(serverCx, &serverRval)) return JS_FALSE; JSObject *serverGlobal = JSVAL_TO_OBJECT(serverRval); if (!JS_DefineFunctions(serverCx, serverGlobal, server_global_functions)) return JS_FALSE; FILE *f = fopen(SERVER_FILENAME, "r"); if (!f) { JS_ReportError(cx, "Couldn't open " SERVER_FILENAME); return JS_FALSE; } fseek(f, 0, SEEK_END); long fileSize = ftell(f); fseek(f, 0, SEEK_SET); char source[fileSize]; fread(source, fileSize, 1, f); fclose(f); if (!JS_EvaluateScript(serverCx, serverGlobal, source, fileSize, SERVER_FILENAME, 1, &serverRval)) { TCB_handleError(serverCx, serverGlobal); JS_ReportError(cx, "Running server script failed."); return JS_FALSE; } /* Cleanup. */ JS_DHashTableFinish(&tracingState.visited); JS_EndRequest(serverCx); JS_DestroyContext(serverCx); JS_DestroyRuntime(serverRuntime); *rval = JSVAL_VOID; return JS_TRUE; }