changeset 50:853f80bd3b4b

Added tentative profileMemory() server, which suspends the current JS runtime, creates a new one and runs a web server in it.
author Atul Varma <varmaa@toolness.com>
date Wed, 24 Jun 2009 16:40:51 -0700
parents 45fd970860f2
children 8750e9ecf3af
files memory_profiler.cpp memory_profiler.h memory_profiler_server.js pavement.py spidermonkey-playground.cpp tcb.js
diffstat 6 files changed, 236 insertions(+), 155 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/memory_profiler.cpp	Wed Jun 24 16:40:51 2009 -0700
@@ -0,0 +1,178 @@
+#include "jsdhash.h"
+
+#include "memory_profiler.h"
+#include "server_socket.h"
+
+#define SERVER_FILENAME "memory_profiler_server.js"
+
+// TODO: Lots of code here is copied from spidermonkey-playground.cpp; we should
+// consolidate it.
+
+/* The error reporter callback. */
+static void reportError(JSContext *cx, const char *message, JSErrorReport *report)
+{
+    fprintf(stderr, "%s:%u:%s\n",
+            report->filename ? report->filename : "<no filename>",
+            (unsigned int) report->lineno,
+            message);
+}
+
+static JSClass global_class = {
+  "serverGlobal", JSCLASS_GLOBAL_FLAGS,
+  JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
+  JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
+  JSCLASS_NO_OPTIONAL_MEMBERS
+};
+
+// This native JS function prints the given string to the console.
+
+static JSBool print(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
+                    jsval *rval)
+{
+  char *str;
+
+  if (!JS_ConvertArguments(cx, argc, argv, "s", &str))
+    return JS_FALSE;
+
+  printf("%s\n", str);
+
+  return JS_TRUE;
+}
+
+// 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;
+
+  // Class to look for.
+  JSClass *classp;
+};
+
+// Static singleton for tracking the state of tracing the JS heap.
+static TracingState tracingState;
+
+// JSTraceCallback for heap dumping and other operations.
+
+static void traceCallback(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;
+      JSObject *obj = (JSObject *) thing;
+      // TODO: Do something?
+      JS_TraceChildren(trc, thing, kind);
+    }
+    break;
+  case JSTRACE_DOUBLE:
+    break;
+  case JSTRACE_STRING:
+    break;
+  }
+}
+
+static JSFunctionSpec server_global_functions[] = {
+  JS_FS("print",          print,              1, 0, 0),
+  JS_FS("ServerSocket",   createServerSocket, 0, 0, 0),
+  JS_FS_END
+};
+
+JSBool profileMemory(JSContext *cx, JSObject *obj, uintN argc,
+                     jsval *argv, jsval *rval)
+{
+  JSTracer tracer;
+
+  if (!JS_DHashTableInit(&tracingState.visited, JS_DHashGetStubOps(),
+                         NULL, sizeof(JSDHashEntryStub),
+                         JS_DHASH_DEFAULT_CAPACITY(100))) {
+    JS_ReportOutOfMemory(cx);
+    return JS_FALSE;
+  }
+
+  tracingState.result = JS_TRUE;
+  tracer.context = cx;
+  tracer.callback = traceCallback;
+  JS_TraceRuntime(&tracer);
+
+  JS_DHashTableFinish(&tracingState.visited);
+
+  if (!tracingState.result)
+    return JS_FALSE;
+
+  JSRuntime *serverRuntime;
+  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_SetErrorReporter(serverCx, reportError);
+
+  JSObject *serverGlobal = JS_NewObject(serverCx, &global_class, NULL, NULL);
+  if (serverGlobal == NULL) {
+    JS_ReportError(cx, "Couldn't create server JS global.");
+    return JS_FALSE;
+  }
+
+  if (!JS_InitStandardClasses(serverCx, serverGlobal)) {
+    JS_ReportError(cx, "Couldn't init standard classes on server JS global.");
+    return JS_FALSE;
+  }
+
+  if (!JS_DefineFunctions(serverCx, serverGlobal, server_global_functions)) {
+    JS_ReportError(cx, "Couldn't define functions on server JS global.");
+    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);
+
+  jsval serverRval;
+  if (!JS_EvaluateScript(serverCx, serverGlobal, source,
+                         fileSize, SERVER_FILENAME, 1,
+                         &serverRval)) {
+    JS_ReportError(cx, "Server failed.");
+    return JS_FALSE;
+  }
+
+  /* Cleanup. */
+  JS_DestroyContext(serverCx);
+  JS_DestroyRuntime(serverRuntime);
+
+  *rval = JSVAL_VOID;
+  return JS_TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/memory_profiler.h	Wed Jun 24 16:40:51 2009 -0700
@@ -0,0 +1,4 @@
+#include "jsapi.h"
+
+extern JSBool profileMemory(JSContext *cx, JSObject *obj, uintN argc,
+                            jsval *argv, jsval *rval);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/memory_profiler_server.js	Wed Jun 24 16:40:51 2009 -0700
@@ -0,0 +1,48 @@
+var socket = new ServerSocket();
+
+var PORT = 8080;
+
+socket.bind("127.0.0.1", PORT);
+socket.listen();
+
+var NEWLINE = "\r\n";
+var DOUBLE_NEWLINE = NEWLINE + NEWLINE;
+
+function getHeaders(conn) {
+  var headers = "";
+  while (1) {
+    var character = conn.recv(1);
+    if (character == null)
+      return null;
+    headers += character;
+    if (headers.indexOf(DOUBLE_NEWLINE) != -1)
+      return headers;
+  }
+}
+
+print("Waiting for requests on port " + PORT + ".");
+
+while (1) {
+  var conn = socket.accept();
+
+  if (conn) {
+    var requestHeaders = getHeaders(conn);
+    if (requestHeaders == null)
+      continue;
+    var requestLines = requestHeaders.split("\r\n");
+    print(requestLines[0]);
+    //print("headers: " + uneval(requestHeaders));
+    var toSend = "hello there.";
+    var headerLines = ["HTTP/1.0 200 OK",
+                       "Content-Type: text/plain",
+                       "Connection: close",
+                       "Content-Length: " + toSend.length];
+    var headers = headerLines.join("\r\n");
+    var response = headers + DOUBLE_NEWLINE + toSend;
+    //print("response: " + uneval(response));
+    conn.send(response);
+    conn.close();
+    //print("response sent.");
+  } else
+    throw new Error("Unexpected: conn is " + conn);
+}
--- a/pavement.py	Wed Jun 24 16:10:12 2009 -0700
+++ b/pavement.py	Wed Jun 24 16:40:51 2009 -0700
@@ -17,6 +17,7 @@
                "spidermonkey-playground.cpp",
                "wrapper.cpp",
                "server_socket.cpp",
+               "memory_profiler.cpp",
                "-lmozjs",
                "-lnspr4"]
 
--- a/spidermonkey-playground.cpp	Wed Jun 24 16:10:12 2009 -0700
+++ b/spidermonkey-playground.cpp	Wed Jun 24 16:40:51 2009 -0700
@@ -41,10 +41,10 @@
 #include "string.h"
 #include "jsapi.h"
 #include "jsdbgapi.h"
-#include "jsdhash.h"
 
 #include "wrapper.h"
 #include "server_socket.h"
+#include "memory_profiler.h"
 
 // The name of our JS script containing our Trusted Code Base (TCB).
 #define TCB_FILENAME "tcb.js"
@@ -57,7 +57,7 @@
 // the interpreter doesn't randomly crash or something.
 
 /* The class of the global object. */
-static JSClass global_class = {
+JSClass global_class = {
   "global", JSCLASS_GLOBAL_FLAGS,
   JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
   JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
@@ -79,116 +79,6 @@
   return JS_TRUE;
 }
 
-// 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;
-
-  // Class to look for.
-  JSClass *classp;
-};
-
-// Static singleton for tracking the state of tracing the JS heap.
-static TracingState tracingState;
-
-// JSTraceCallback for heap dumping and other operations.
-
-static void traceCallback(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;
-      JSObject *obj = (JSObject *) thing;
-      JSClass *classp = JS_GET_CLASS(trc->context, obj);
-      if (classp == tracingState.classp) {
-        JSObject *constructor = JS_GetConstructor(trc->context, obj);
-        if (constructor == NULL) {
-          tracingState.result = JS_FALSE;
-          return;
-        }
-        if (JS_ObjectIsFunction(trc->context, constructor)) {
-          JSFunction *func = JS_ValueToFunction(
-            trc->context,
-            OBJECT_TO_JSVAL(constructor)
-            );
-          if (func == NULL) {
-            tracingState.result = JS_FALSE;
-            return;
-          }
-          JSString *name = JS_GetFunctionId(func);
-          if (name != NULL) {
-            const char *str = JS_GetStringBytes(name);
-            printf("function: %s\n", str);
-          }
-        }
-      }
-      JS_TraceChildren(trc, thing, kind);
-    }
-    break;
-  case JSTRACE_DOUBLE:
-    break;
-  case JSTRACE_STRING:
-    break;
-  }
-}
-
-// This native JS function dumps the heap to stdout. It's available even on
-// non-debug builds of SpiderMonkey, but it's also not terribly robust.
-
-static JSBool dumpHeap(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
-                       jsval *rval)
-{
-  JSObject *object;
-
-  if (!JS_ConvertArguments(cx, argc, argv, "o", &object))
-    return JS_FALSE;
-
-  tracingState.classp = JS_GET_CLASS(cx, object);
-
-  if (tracingState.classp == NULL) {
-    JS_ReportError(cx, "Object has no class.");
-    return JS_FALSE;
-  }
-
-  JSTracer tracer;
-
-  if (!JS_DHashTableInit(&tracingState.visited, JS_DHashGetStubOps(),
-                         NULL, sizeof(JSDHashEntryStub),
-                         JS_DHASH_DEFAULT_CAPACITY(100))) {
-    JS_ReportOutOfMemory(cx);
-    return JS_FALSE;
-  }
-
-  tracingState.result = JS_TRUE;
-  tracer.context = cx;
-  tracer.callback = traceCallback;
-  JS_TraceRuntime(&tracer);
-
-  JS_DHashTableFinish(&tracingState.visited);
-
-  if (!tracingState.result)
-    return JS_FALSE;
-  *rval = JSVAL_VOID;
-  return JS_TRUE;
-}
-
 // This native JS function forces garbage collection.
 
 static JSBool forceGC(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
@@ -491,7 +381,7 @@
   JS_FS("functionInfo",   functionInfo,       1, 0, 0),
   JS_FS("enumerate",      enumerate,          1, 0, 0),
   JS_FS("forceGC",        forceGC,            0, 0, 0),
-  JS_FS("dumpHeap",       dumpHeap,           0, 0, 0),
+  JS_FS("profileMemory",  profileMemory,      0, 0, 0),
   JS_FS("ServerSocket",   createServerSocket, 0, 0, 0),
   JS_FS_END
 };
@@ -557,7 +447,7 @@
 
   jsval rval;
   if (!JS_EvaluateScript(tcb_cx, tcb_global, source,
-                         strlen(source), TCB_FILENAME, 1,
+                         fileSize, TCB_FILENAME, 1,
                          &rval)) {
     jsval handleError;
     if (JS_GetProperty(tcb_cx, tcb_global, "handleError", &handleError) &&
--- a/tcb.js	Wed Jun 24 16:10:12 2009 -0700
+++ b/tcb.js	Wed Jun 24 16:40:51 2009 -0700
@@ -233,50 +233,10 @@
     getWrapper(getWrapper(wrapped)) !== null)
   throw new Error("Getting the wrapper doesn't work!");
 
-dumpHeap(new Object());
-
 var socket = new ServerSocket();
 try {
   var boop = {bind: socket.bind};
   boop.bind("127.0.0.1", 8080);
 } catch (e) {}
-socket.bind("127.0.0.1", 8080);
-socket.listen();
 
-var NEWLINE = "\r\n";
-var DOUBLE_NEWLINE = NEWLINE + NEWLINE;
-
-function getHeaders(conn) {
-  var headers = "";
-  while (1) {
-    var character = conn.recv(1);
-    if (character == null)
-      return null;
-    headers += character;
-    if (headers.indexOf(DOUBLE_NEWLINE) != -1)
-      return headers;
-  }
-}
-
-while (1) {
-  var conn = socket.accept();
-
-  if (conn) {
-    var requestHeaders = getHeaders(conn);
-    if (requestHeaders == null)
-      continue;
-    //print("headers: " + uneval(requestHeaders));
-    var toSend = "hello there.";
-    var headerLines = ["HTTP/1.0 200 OK",
-                       "Content-Type: text/plain",
-                       "Connection: close",
-                       "Content-Length: " + toSend.length];
-    var headers = headerLines.join("\r\n");
-    var response = headers + DOUBLE_NEWLINE + toSend;
-    //print("response: " + uneval(response));
-    conn.send(response);
-    conn.close();
-    //print("response sent.");
-  } else
-    throw new Error("Unexpected: conn is " + conn);
-}
+profileMemory();