Bug 571249 - Add memory reporters for JSScripts, non-fixed object slot arrays, and string chars. r=igor.
authorNicholas Nethercote <nnethercote@mozilla.com>
Thu, 16 Jun 2011 13:01:04 +1000
changeset 71361 b35005673847d8f3deb28418c2b9c4a80bc12ced
parent 71360 21e26c404883cebe55abb553cd2ec49298a2b19f
child 71362 b65724d6c32633b356471a31f7ff01ff74a14312
push id20538
push usercleary@mozilla.com
push dateMon, 20 Jun 2011 23:59:42 +0000
treeherdermozilla-central@a285146675dc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersigor
bugs571249
milestone7.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 571249 - Add memory reporters for JSScripts, non-fixed object slot arrays, and string chars. r=igor.
js/src/jsgc.h
js/src/jsscript.cpp
js/src/jsscript.h
js/src/xpconnect/src/xpcjsruntime.cpp
toolkit/components/aboutmemory/tests/chrome/test_aboutmemory.xul
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -1253,17 +1253,17 @@ typedef void (*IterateCallback)(JSContex
 
 /*
  * This function calls |callback| on every cell in the GC heap. If |comp| is
  * non-null, then it only selects cells in that compartment. If |traceKindMask|
  * is non-zero, then only cells whose traceKind belongs to the mask will be
  * selected. The mask should be constructed by ORing |TraceKindMask(...)|
  * results.
  */
-void
+extern JS_FRIEND_API(void)
 IterateCells(JSContext *cx, JSCompartment *comp, uint64 traceKindMask,
              void *data, IterateCallback callback);
 
 } /* namespace js */
 
 extern void
 js_FinalizeStringRT(JSRuntime *rt, JSString *str);
 
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -315,17 +315,16 @@ js_XDRScript(JSXDRState *xdr, JSScript *
     JSBool ok;
     jsbytecode *code;
     uint32 length, lineno, nslots;
     uint32 natoms, nsrcnotes, ntrynotes, nobjects, nregexps, nconsts, i;
     uint32 prologLength, version, encodedClosedCount;
     uint16 nClosedArgs = 0, nClosedVars = 0;
     JSPrincipals *principals;
     uint32 encodeable;
-    jssrcnote *sn;
     JSSecurityCallbacks *callbacks;
     uint32 scriptBits = 0;
 
     JSContext *cx = xdr->cx;
     JSScript *script = *scriptp;
     nsrcnotes = ntrynotes = natoms = nobjects = nregexps = nconsts = 0;
     jssrcnote *notes = NULL;
     XDRScriptState *state = xdr->state;
@@ -466,22 +465,18 @@ js_XDRScript(JSXDRState *xdr, JSScript *
         prologLength = script->main - script->code;
         JS_ASSERT(script->getVersion() != JSVERSION_UNKNOWN);
         version = (uint32)script->getVersion() | (script->nfixed << 16);
         lineno = (uint32)script->lineno;
         nslots = (uint32)script->nslots;
         nslots = (uint32)((script->staticLevel << 16) | script->nslots);
         natoms = (uint32)script->atomMap.length;
 
-        /* Count the srcnotes, keeping notes pointing at the first one. */
         notes = script->notes();
-        for (sn = notes; !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn))
-            continue;
-        nsrcnotes = sn - notes;
-        nsrcnotes++;            /* room for the terminator */
+        nsrcnotes = script->numNotes();
 
         if (JSScript::isValidOffset(script->objectsOffset))
             nobjects = script->objects()->length;
         if (JSScript::isValidOffset(script->upvarsOffset))
             JS_ASSERT(script->bindings.countUpvars() == script->upvars()->length);
         if (JSScript::isValidOffset(script->regexpsOffset))
             nregexps = script->regexps()->length;
         if (JSScript::isValidOffset(script->trynotesOffset))
@@ -1417,16 +1412,39 @@ JSScript::NewScriptFromCG(JSContext *cx,
 
     return script;
 
 bad:
     js_DestroyScript(cx, script);
     return NULL;
 }
 
+size_t
+JSScript::totalSize()
+{
+    return code +
+           length * sizeof(jsbytecode) +
+           numNotes() * sizeof(jssrcnote) -
+           (uint8 *) this;
+}
+
+/*
+ * Nb: srcnotes are variable-length.  This function computes the number of
+ * srcnote *slots*, which may be greater than the number of srcnotes.
+ */
+uint32
+JSScript::numNotes()
+{
+    jssrcnote *sn;
+    jssrcnote *notes_ = notes();
+    for (sn = notes_; !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn))
+        continue;
+    return sn - notes_ + 1;    /* +1 for the terminator */
+}
+
 JS_FRIEND_API(void)
 js_CallNewScriptHook(JSContext *cx, JSScript *script, JSFunction *fun)
 {
     JSNewScriptHook hook;
 
     hook = cx->debugHooks->newScriptHook;
     if (hook) {
         AutoKeepAtoms keep(cx->runtime);
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -453,26 +453,27 @@ struct JSScript {
     /* FIXME: bug 586181 */
     JSCList         links;      /* Links for compartment script list */
     jsbytecode      *code;      /* bytecodes and their immediate operands */
     uint32          length;     /* length of code vector */
 
   private:
     uint16          version;    /* JS version under which script was compiled */
 
-    size_t          callCount_; /* Number of times the script has been called. */
-
   public:
     uint16          nfixed;     /* number of slots besides stack operands in
                                    slot array */
+  private:
+    size_t          callCount_; /* Number of times the script has been called. */
 
     /*
      * Offsets to various array structures from the end of this script, or
      * JSScript::INVALID_OFFSET if the array has length 0.
      */
+  public:
     uint8           objectsOffset;  /* offset to the array of nested function,
                                        block, scope, xml and one-time regexps
                                        objects */
     uint8           upvarsOffset;   /* offset of the array of display ("up")
                                        closure vars */
     uint8           regexpsOffset;  /* offset to the array of to-be-cloned
                                        regexps  */
     uint8           trynotesOffset; /* offset to the array of try notes */
@@ -571,16 +572,19 @@ struct JSScript {
         if (addr == NULL)
             return JITScript_None;
         if (addr == JS_UNJITTABLE_SCRIPT)
             return JITScript_Invalid;
         return JITScript_Valid;
     }
 #endif
 
+    JS_FRIEND_API(size_t) totalSize();  /* Size of the JSScript and all sections */
+    uint32 numNotes();                  /* Number of srcnote slots in the srcnotes section */
+
     /* Script notes are allocated right after the code. */
     jssrcnote *notes() { return (jssrcnote *)(code + length); }
 
     static const uint8 INVALID_OFFSET = 0xFF;
     static bool isValidOffset(uint8 offset) { return offset != INVALID_OFFSET; }
 
     JSObjectArray *objects() {
         JS_ASSERT(isValidOffset(objectsOffset));
--- a/js/src/xpconnect/src/xpcjsruntime.cpp
+++ b/js/src/xpconnect/src/xpcjsruntime.cpp
@@ -1298,16 +1298,129 @@ NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJS
     "explicit/js/stack",
     MR_MAPPED,
     "Memory used for the JavaScript stack.  This is the committed portion "
     "of the stack;  any uncommitted portion is not measured because it "
     "hardly costs anything.",
     GetJSStack,
     NULL)
 
+static PRInt64
+GetCompartmentScriptsSize(JSCompartment *c)
+{
+    PRInt64 n = 0;
+    for (JSScript *script = (JSScript *)c->scripts.next;
+         &script->links != &c->scripts;
+         script = (JSScript *)script->links.next)
+    {
+        n += script->totalSize(); 
+    }
+    return n;
+}
+
+static PRInt64
+GetJSScripts(void *data)
+{
+    return GetPerCompartmentSize(GetCompartmentScriptsSize);
+}
+
+struct PRInt64Data {
+    PRInt64Data() : n(0) { }
+    PRInt64 n;
+};
+
+void
+GetJSObjectSlotsCallback(JSContext *cx, void *v, size_t traceKind, void *thing)
+{
+    JS_ASSERT(traceKind == JSTRACE_OBJECT);
+    JSObject *obj = (JSObject *)thing;
+    if (obj->hasSlotsArray()) {
+        PRInt64Data *data = (PRInt64Data *) v;
+        data->n += obj->numSlots() * sizeof(js::Value);
+    }
+}
+
+static PRInt64
+GetJSObjectSlots(void *dummy)
+{
+    JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->GetJSRuntime();
+    JSContext *cx = JS_NewContext(rt, 0);
+    if (!cx) {
+        NS_ERROR("couldn't create context for memory tracing");
+        return (PRInt64) -1;
+    }
+
+    PRInt64Data data;
+    js::IterateCells(cx, NULL, js::TraceKindMask(JSTRACE_OBJECT), &data,
+                     *GetJSObjectSlotsCallback);
+
+    JS_DestroyContextNoGC(cx);
+
+    return data.n;
+}
+
+void
+GetJSStringCharsCallback(JSContext *cx, void *v, size_t traceKind, void *thing)
+{
+    JS_ASSERT(traceKind == JSTRACE_STRING);
+    JSString *str = (JSString *)thing;
+    PRInt64Data *data = (PRInt64Data *) v;
+    data->n += str->charsHeapSize();
+}
+ 
+static PRInt64
+GetJSStringChars(void *dummy)
+{
+    JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->GetJSRuntime();
+    JSContext *cx = JS_NewContext(rt, 0);
+    if (!cx) {
+        NS_ERROR("couldn't create context for memory tracing");
+        return (PRInt64) -1;
+    }
+
+    PRInt64Data data;
+    js::IterateCells(cx, NULL, js::TraceKindMask(JSTRACE_STRING), &data,
+                     *GetJSStringCharsCallback);
+
+    JS_DestroyContextNoGC(cx);
+
+    return data.n;
+}
+
+NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSScripts,
+    "explicit/js/scripts",
+    MR_HEAP,
+    "Memory allocated for JSScripts.  A JSScript is created for each "
+    "user-defined function in a script.  One is also created for "
+    "the top-level code in a script.  Each JSScript includes byte-code and "
+    "various other things.",
+    GetJSScripts,
+    NULL)
+
+NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSObjectSlots,
+    "explicit/js/object-slots",
+    MR_HEAP,
+    "Memory allocated for non-fixed object slot arrays, which are used "
+    "to represent object properties.  Some objects also contain a fixed "
+    "number of slots which are stored on the JavaScript heap;  those slots "
+    "are not counted here.",
+    GetJSObjectSlots,
+    NULL)
+
+NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSStringChars,
+    "explicit/js/string-chars",
+    MR_HEAP,
+    "Memory allocated to hold string characters.  Not all of this allocated "
+    "memory is necessarily used to hold characters.  Each string also "
+    "includes a header which is stored on the JavaScript heap;  that header "
+    "is not counted here.",
+    GetJSStringChars,
+    NULL)
+
+
 #ifdef JS_METHODJIT
 
 static PRInt64
 GetCompartmentMjitCodeSize(JSCompartment *c)
 {
     return c->getMjitCodeSize();
 }
 
@@ -1471,16 +1584,20 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* 
             NS_RUNTIMEABORT("JS_NEW_CONDVAR failed.");
 
         mJSRuntime->setActivityCallback(ActivityCallback, this);
 
         mJSRuntime->setCustomGCChunkAllocator(&gXPCJSChunkAllocator);
 
         NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSGCHeap));
         NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSStack));
+        NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSScripts));
+        // XXX: these two are disabled due to crashes.  See bug 664647.
+        //NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSObjectSlots));
+        //NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSStringChars));
 #ifdef JS_METHODJIT
         NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSMjitCode));
         NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSMjitData));
 #endif
 #ifdef JS_TRACER
         NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSTjitCode));
         NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSTjitDataAllocatorsMain));
         NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSTjitDataAllocatorsReserve));
--- a/toolkit/components/aboutmemory/tests/chrome/test_aboutmemory.xul
+++ b/toolkit/components/aboutmemory/tests/chrome/test_aboutmemory.xul
@@ -16,18 +16,22 @@
   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 = [];
+  var dummy = 0;
   while (e.hasMoreElements()) {
     var mr = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
+    // Get the memoryUsed field, even though we don't use it, just to test
+    // that the reporter doesn't crash or anything.
+    dummy += mr.memoryUsed;
     mgr.unregisterReporter(mr);
     realReporters.push(mr);
   }
 
   // Setup various fake-but-deterministic reporters.
   const KB = 1024;
   const MB = KB * KB;
   const kUnknown = -1;