Bug 798510 - Part 1: Use mincore to correctly calculate js::StackSpace::sizeOfCommitted() on *nix. r=luke,njn a=akeybl
authorJustin Lebar <justin.lebar@gmail.com>
Fri, 12 Oct 2012 10:26:06 -0400
changeset 113325 674b97ffda67cdba43fdf3c08a65db06a44bf413
parent 113324 dd21c60ab0ff5b0852853fa4f0c01532e5bc839b
child 113326 97b1bdb2046df541efde7f02b6c608def5b3e060
push id2286
push userjlebar@mozilla.com
push dateSat, 13 Oct 2012 14:21:35 +0000
treeherdermozilla-aurora@2c579668df52 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke, njn, akeybl
bugs798510
milestone18.0a2
Bug 798510 - Part 1: Use mincore to correctly calculate js::StackSpace::sizeOfCommitted() on *nix. r=luke,njn a=akeybl Previously, we assumed all memory was committed, thus vastly overstating the memory used by our stack.
js/public/MemoryMetrics.h
js/src/jscntxt.cpp
js/src/vm/Stack.cpp
js/src/vm/Stack.h
js/xpconnect/src/XPCJSRuntime.cpp
--- a/js/public/MemoryMetrics.h
+++ b/js/public/MemoryMetrics.h
@@ -48,34 +48,34 @@ struct RuntimeSizes
       , atomsTable(0)
       , contexts(0)
       , dtoa(0)
       , temporary(0)
       , jaegerCode(0)
       , ionCode(0)
       , regexpCode(0)
       , unusedCode(0)
-      , stackCommitted(0)
+      , stack(0)
       , gcMarker(0)
       , mathCache(0)
       , scriptFilenames(0)
       , scriptSources(0)
       , compartmentObjects(0)
     {}
 
     size_t object;
     size_t atomsTable;
     size_t contexts;
     size_t dtoa;
     size_t temporary;
     size_t jaegerCode;
     size_t ionCode;
     size_t regexpCode;
     size_t unusedCode;
-    size_t stackCommitted;
+    size_t stack;
     size_t gcMarker;
     size_t mathCache;
     size_t scriptFilenames;
     size_t scriptSources;
 
     // This is the exception to the "RuntimeSizes doesn't measure things within
     // compartments" rule.  We combine the sizes of all the JSCompartment
     // objects into a single measurement because each one is fairly small, and
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -133,17 +133,17 @@ JSRuntime::sizeOfIncludingThis(JSMallocS
                                &rtSizes->unusedCode);
     } else {
         rtSizes->jaegerCode = 0;
         rtSizes->ionCode    = 0;
         rtSizes->regexpCode = 0;
         rtSizes->unusedCode = 0;
     }
 
-    rtSizes->stackCommitted = stackSpace.sizeOfCommitted();
+    rtSizes->stack = stackSpace.sizeOf();
 
     rtSizes->gcMarker = gcMarker.sizeOfExcludingThis(mallocSizeOf);
 
     rtSizes->mathCache = mathCache_ ? mathCache_->sizeOfIncludingThis(mallocSizeOf) : 0;
 
     rtSizes->scriptFilenames = scriptFilenameTable.sizeOfExcludingThis(mallocSizeOf);
     for (ScriptFilenameTable::Range r = scriptFilenameTable.all(); !r.empty(); r.popFront())
         rtSizes->scriptFilenames += mallocSizeOf(r.front());
@@ -157,17 +157,17 @@ JSRuntime::sizeOfIncludingThis(JSMallocS
 size_t
 JSRuntime::sizeOfExplicitNonHeap()
 {
     if (!execAlloc_)
         return 0;
 
     size_t jaegerCode, ionCode, regexpCode, unusedCode;
     execAlloc_->sizeOfCode(&jaegerCode, &ionCode, &regexpCode, &unusedCode);
-    return jaegerCode + ionCode + regexpCode + unusedCode + stackSpace.sizeOfCommitted();
+    return jaegerCode + ionCode + regexpCode + unusedCode + stackSpace.sizeOf();
 }
 
 void
 JSRuntime::triggerOperationCallback()
 {
     /*
      * Invalidate ionTop to trigger its over-recursion check. Note this must be
      * set before interrupt, to avoid racing with js_InvokeOperationCallback,
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -804,21 +804,68 @@ StackSpace::tryBumpLimit(JSContext *cx, 
 {
     if (!ensureSpace(cx, REPORT_ERROR, from, nvals))
         return false;
     *limit = conservativeEnd_;
     return true;
 }
 
 size_t
-StackSpace::sizeOfCommitted()
+StackSpace::sizeOf()
 {
-#ifdef XP_WIN
+#if defined(XP_UNIX)
+    /*
+     * Measure how many of our pages are resident in RAM using mincore, and
+     * return that as our size.  This is slow, but hopefully nobody expects
+     * this method to be fast.
+     *
+     * Note that using mincore means that we don't count pages of the stack
+     * which are swapped out to disk.  We really should, but what we have here
+     * is better than counting the whole stack!
+     */
+
+    const int pageSize = getpagesize();
+    size_t numBytes = (trustedEnd_ - base_) * sizeof(Value);
+    size_t numPages = (numBytes + pageSize - 1) / pageSize;
+
+    // On Linux, mincore's third argument has type unsigned char*.  On Mac, it
+    // has type char*.
+#if defined(XP_MACOSX)
+    typedef char MincoreArgType;
+#else
+    typedef unsigned char MincoreArgType;
+#endif
+
+    MincoreArgType *vec = (MincoreArgType *) js_malloc(numPages);
+    int result = mincore(base_, numBytes, vec);
+    if (result) {
+        js_free(vec);
+        /*
+         * If mincore fails us, return the vsize (like we do below if we're not
+         * on Windows or Unix).
+         */
+        return (trustedEnd_ - base_) * sizeof(Value);
+    }
+
+    size_t residentBytes = 0;
+    for (size_t i = 0; i < numPages; i++) {
+        /* vec[i] has its least-significant bit set iff page i is in RAM. */
+        if (vec[i] & 0x1)
+            residentBytes += pageSize;
+    }
+    js_free(vec);
+    return residentBytes;
+
+#elif defined(XP_WIN)
     return (commitEnd_ - base_) * sizeof(Value);
 #else
+    /*
+     * Return the stack's virtual size, which is at least an upper bound on its
+     * resident size.
+     */
     return (trustedEnd_ - base_) * sizeof(Value);
 #endif
 }
 
 #ifdef DEBUG
 bool
 StackSpace::containsSlow(StackFrame *fp)
 {
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -1434,18 +1434,22 @@ class StackSpace
     bool tryBumpLimit(JSContext *cx, Value *from, unsigned nvals, Value **limit);
 
     /* Called during GC: mark segments, frames, and slots under firstUnused. */
     void markAndClobber(JSTracer *trc);
 
     /* Called during GC: sets active flag on compartments with active frames. */
     void markActiveCompartments();
 
-    /* We only report the committed size;  uncommitted size is uninteresting. */
-    JS_FRIEND_API(size_t) sizeOfCommitted();
+    /*
+     * On Windows, report the committed size; on *nix, we report the resident
+     * size (which means that if part of the stack is swapped to disk, we say
+     * it's shrunk).
+     */
+    JS_FRIEND_API(size_t) sizeOf();
 
 #ifdef DEBUG
     bool containsSlow(StackFrame *fp);
 #endif
 };
 
 /*****************************************************************************/
 
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -1726,21 +1726,25 @@ ReportJSRuntimeExplicitTreeStats(const J
                   nsIMemoryReporter::KIND_NONHEAP, rtStats.runtime.regexpCode,
                   "Memory used by the regexp JIT to hold generated code.");
 
     RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/unused-code"),
                   nsIMemoryReporter::KIND_NONHEAP, rtStats.runtime.unusedCode,
                   "Memory allocated by one of the JITs to hold the "
                   "runtime's code, but which is currently unused.");
 
-    RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/stack-committed"),
-                  nsIMemoryReporter::KIND_NONHEAP, rtStats.runtime.stackCommitted,
+    RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/stack"),
+                  nsIMemoryReporter::KIND_NONHEAP, rtStats.runtime.stack,
                   "Memory used for the JS call stack.  This is the committed "
-                  "portion of the stack; the uncommitted portion is not "
-                  "measured because it hardly costs anything.");
+                  "portion of the stack on Windows; on *nix, it is the resident "
+                  "portion of the stack.  Therefore, on *nix, if part of the "
+                  "stack is swapped out to disk, we do not count it here.\n\n"
+                  "Note that debug builds usually have stack poisoning enabled, "
+                  "which causes the whole stack to be committed (and likely "
+                  "resident).");
 
     RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc-marker"),
                   nsIMemoryReporter::KIND_HEAP, rtStats.runtime.gcMarker,
                   "Memory used for the GC mark stack and gray roots.");
 
     RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/math-cache"),
                   nsIMemoryReporter::KIND_HEAP, rtStats.runtime.mathCache,
                   "Memory used for the math cache.");