Bug 798510 - Part 1: Use mincore to correctly calculate js::StackSpace::sizeOfCommitted() on *nix. r=luke,njn
authorJustin Lebar <justin.lebar@gmail.com>
Fri, 12 Oct 2012 10:26:06 -0400
changeset 110087 b2d8835eb55ffb81f1ce00ccd0061e97d3c61bd5
parent 110086 5540b310d435b0616b878b5a279f9ae65c2baa3f
child 110088 f394798849d962921c7cd8eee46df87fba6210e3
push id23671
push userryanvm@gmail.com
push dateSat, 13 Oct 2012 08:36:40 +0000
treeherdermozilla-central@0be7bfea4744 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke, njn
bugs798510
milestone19.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 798510 - Part 1: Use mincore to correctly calculate js::StackSpace::sizeOfCommitted() on *nix. r=luke,njn 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,33 +48,33 @@ 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)
     {}
 
     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;
 };
 
 struct CompartmentStats
 {
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -120,17 +120,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());
@@ -139,17 +139,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
@@ -805,21 +805,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
     /* Only used in assertion of debuggers API. */
     bool containsSlow(StackFrame *fp);
 #endif
 };
 
 /*****************************************************************************/
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -1738,21 +1738,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.");