bug 674480 - add memory reporter for the number of empty GC chunks. r=njn
authorIgor Bukanov <igor@mir2.org>
Sat, 30 Jul 2011 00:17:41 +0200
changeset 73764 50a3378c5940af0a910242866973bf1de797ccb7
parent 73745 07a247973487eaaa904f96d8c5b9c751d76c1a50
child 73765 5772d30e6894849ea459b637121f5f1c84206937
push id20917
push usermak77@bonardo.net
push dateThu, 04 Aug 2011 09:19:44 +0000
treeherdermozilla-central@5d742d2e4304 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnjn
bugs674480
milestone8.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 674480 - add memory reporter for the number of empty GC chunks. r=njn
dom/workers/RuntimeService.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/xpconnect/src/xpcjsruntime.cpp
js/src/xpconnect/src/xpcpublic.h
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -333,24 +333,17 @@ public:
 
     JS_TriggerAllOperationCallbacks(mRuntime);
 
     IterateData data;
     if (!CollectCompartmentStatsForRuntime(mRuntime, &data)) {
       return NS_ERROR_FAILURE;
     }
 
-    for (CompartmentStats *stats = data.compartmentStatsVector.begin();
-         stats != data.compartmentStatsVector.end();
-         ++stats)
-    {
-      ReportCompartmentStats(*stats, mPathPrefix, aCallback, aClosure);
-    }
-
-    ReportJSStackSizeForRuntime(mRuntime, mPathPrefix, aCallback, aClosure);
+    ReportJSRuntimeStats(data, mPathPrefix, aCallback, aClosure);
 
     return NS_OK;
   }
 };
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(WorkerMemoryReporter, nsIMemoryMultiReporter)
 
 class WorkerThreadRunnable : public nsRunnable
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -2683,16 +2683,18 @@ JS_GetGCParameter(JSRuntime *rt, JSGCPar
       case JSGC_STACKPOOL_LIFESPAN:
         return rt->gcEmptyArenaPoolLifespan;
       case JSGC_BYTES:
         return rt->gcBytes;
       case JSGC_MODE:
         return uint32(rt->gcMode);
       case JSGC_UNUSED_CHUNKS:
         return uint32(rt->gcChunksWaitingToExpire);
+      case JSGC_TOTAL_CHUNKS:
+        return uint32(rt->gcUserChunkSet.count() + rt->gcSystemChunkSet.count());
       default:
         JS_ASSERT(key == JSGC_NUMBER);
         return rt->gcNumber;
     }
 }
 
 JS_PUBLIC_API(void)
 JS_SetGCParameterForThread(JSContext *cx, JSGCParamKey key, uint32 value)
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1826,17 +1826,20 @@ typedef enum JSGCParamKey {
 
     /* Max size of the code cache in bytes. */
     JSGC_MAX_CODE_CACHE_BYTES = 5,
 
     /* Select GC mode. */
     JSGC_MODE = 6,
 
     /* Number of GC chunks waiting to expire. */
-    JSGC_UNUSED_CHUNKS = 7
+    JSGC_UNUSED_CHUNKS = 7,
+
+    /* Total number of allocated GC chunks. */
+    JSGC_TOTAL_CHUNKS = 8
 } JSGCParamKey;
 
 typedef enum JSGCMode {
     /* Perform only global GCs. */
     JSGC_MODE_GLOBAL = 0,
 
     /* Perform per-compartment GCs until too much garbage has accumulated. */
     JSGC_MODE_COMPARTMENT = 1
--- a/js/src/xpconnect/src/xpcjsruntime.cpp
+++ b/js/src/xpconnect/src/xpcjsruntime.cpp
@@ -1429,63 +1429,62 @@ MakeMemoryReporterPath(const nsACString 
 } // anonymous namespace
 
 class XPConnectGCChunkAllocator
     : public js::GCChunkAllocator
 {
 public:
     XPConnectGCChunkAllocator() {}
 
-    PRInt64 GetGCChunkBytesInUse() {
-        return mNumGCChunksInUse * js::GC_CHUNK_SIZE;
-    }
 private:
     virtual void *doAlloc() {
         void *chunk;
 #ifdef MOZ_MEMORY
         // posix_memalign returns zero on success, nonzero on failure.
         if (posix_memalign(&chunk, js::GC_CHUNK_SIZE, js::GC_CHUNK_SIZE))
             chunk = 0;
 #else
         chunk = js::AllocGCChunk();
 #endif
-        if (chunk)
-            mNumGCChunksInUse++;
         return chunk;
     }
 
     virtual void doFree(void *chunk) {
-        mNumGCChunksInUse--;
 #ifdef MOZ_MEMORY
         free(chunk);
 #else
         js::FreeGCChunk(chunk);
 #endif
     }
-
-protected:
-    PRUint32 mNumGCChunksInUse;
 };
 
 static XPConnectGCChunkAllocator gXPCJSChunkAllocator;
 
 #ifdef MOZ_MEMORY
 #define JS_GC_HEAP_KIND  nsIMemoryReporter::KIND_HEAP
 #else
 #define JS_GC_HEAP_KIND  nsIMemoryReporter::KIND_NONHEAP
 #endif
 
 // We have per-compartment GC heap totals, so we can't put the total GC heap
 // size in the explicit allocations tree.  But it's a useful figure, so put it
 // in the "others" list.
+
+static PRInt64
+GetGCChunkTotalBytes()
+{
+    JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->GetJSRuntime();
+    return PRInt64(JS_GetGCParameter(rt, JSGC_TOTAL_CHUNKS)) * js::GC_CHUNK_SIZE;
+}
+
 NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSGCHeap,
     "js-gc-heap",
     KIND_OTHER,
     nsIMemoryReporter::UNITS_BYTES,
-    gXPCJSChunkAllocator.GetGCChunkBytesInUse,
+    GetGCChunkTotalBytes,
     "Memory used by the garbage-collected JavaScript heap.")
 
 static PRInt64
 GetJSSystemCompartmentCount()
 {
     JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->GetJSRuntime();
     size_t n = 0;
     for (size_t i = 0; i < rt->compartments.length(); i++) {
@@ -1586,28 +1585,76 @@ CollectCompartmentStatsForRuntime(JSRunt
     JSContext *cx = JS_NewContext(rt, 0);
     if(!cx)
     {
         NS_ERROR("couldn't create context for memory tracing");
         return false;
     }
 
     {
-      JSAutoRequest ar(cx);
+        JSAutoRequest ar(cx);
+
+        if (!data->compartmentStatsVector.reserve(rt->compartments.length()))
+            return false;
 
-      data->compartmentStatsVector.reserve(rt->compartments.length());
-      js::IterateCompartmentsArenasCells(cx, data, CompartmentCallback,
-                                         ArenaCallback, CellCallback);
+        data->gcHeapChunkCleanUnused =
+            PRInt64(JS_GetGCParameter(rt, JSGC_UNUSED_CHUNKS)) *
+            js::GC_CHUNK_SIZE;
+        data->gcHeapChunkTotal =
+            PRInt64(JS_GetGCParameter(rt, JSGC_TOTAL_CHUNKS)) *
+            js::GC_CHUNK_SIZE;
+
+        js::IterateCompartmentsArenasCells(cx, data, CompartmentCallback,
+                                           ArenaCallback, CellCallback);
+
+        for(js::ThreadDataIter i(rt); !i.empty(); i.popFront())
+            data->stackSize += i.threadData()->stackSpace.committedSize();
     }
 
     JS_DestroyContextNoGC(cx);
+
+    // This is initialized to all bytes stored in used chunks, and then we
+    // subtract used space from it each time around the loop.
+    data->gcHeapChunkDirtyUnused = data->gcHeapChunkTotal -
+                                   data->gcHeapChunkCleanUnused;
+    data->gcHeapArenaUnused = 0;
+
+    for(CompartmentStats *stats = data->compartmentStatsVector.begin();
+        stats != data->compartmentStatsVector.end();
+        ++stats)
+    {
+        data->gcHeapChunkDirtyUnused -=
+            stats->gcHeapArenaHeaders + stats->gcHeapArenaPadding +
+            stats->gcHeapArenaUnused +
+            stats->gcHeapObjects + stats->gcHeapStrings +
+            stats->gcHeapShapes + stats->gcHeapXml;
+        
+        data->gcHeapArenaUnused += stats->gcHeapArenaUnused;
+    }
+
+    size_t numDirtyChunks = (data->gcHeapChunkTotal -
+                             data->gcHeapChunkCleanUnused) /
+                            js::GC_CHUNK_SIZE;
+    PRInt64 perChunkAdmin =
+        sizeof(js::gc::Chunk) - (sizeof(js::gc::Arena) * js::gc::ArenasPerChunk);
+    data->gcHeapChunkAdmin = numDirtyChunks * perChunkAdmin;
+    data->gcHeapChunkDirtyUnused -= data->gcHeapChunkAdmin;
+    
+    // Why 10000x?  100x because it's a percentage, and another 100x
+    // because nsIMemoryReporter requires that for percentage amounts so
+    // they can be fractional.
+    data->gcHeapUnusedPercentage = (data->gcHeapChunkCleanUnused +
+                                    data->gcHeapChunkDirtyUnused +
+                                    data->gcHeapArenaUnused) * 10000 /
+                                   data->gcHeapChunkTotal;
+
     return true;
 }
 
-void
+static void
 ReportCompartmentStats(const CompartmentStats &stats,
                        const nsACString &pathPrefix,
                        nsIMemoryMultiReporterCallback *callback,
                        nsISupports *closure)
 {
     ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
                                               "gc-heap/arena-headers"),
                        JS_GC_HEAP_KIND, stats.gcHeapArenaHeaders,
@@ -1734,30 +1781,55 @@ ReportCompartmentStats(const Compartment
                        stats.tjitDataAllocatorsReserve,
     "Memory used by the trace JIT and held in reserve for the compartment's "
     "VMAllocators in case of OOM.",
                        callback, closure);
 #endif
 }
 
 void
-ReportJSStackSizeForRuntime(JSRuntime *rt, const nsACString &pathPrefix,
-                            nsIMemoryMultiReporterCallback *callback,
-                            nsISupports *closure)
+ReportJSRuntimeStats(const IterateData &data, const nsACString &pathPrefix,
+                     nsIMemoryMultiReporterCallback *callback,
+                     nsISupports *closure)
 {
-    PRInt64 stackSize = 0;
-    for(js::ThreadDataIter i(rt); !i.empty(); i.popFront())
-        stackSize += i.threadData()->stackSpace.committedSize();
+    for(const CompartmentStats *stats = data.compartmentStatsVector.begin();
+        stats != data.compartmentStatsVector.end();
+        ++stats)
+    {
+        ReportCompartmentStats(*stats, pathPrefix, callback, closure);
+    }
 
     ReportMemoryBytes(pathPrefix + NS_LITERAL_CSTRING("stack"),
-                      nsIMemoryReporter::KIND_NONHEAP, stackSize,
+                      nsIMemoryReporter::KIND_NONHEAP, data.stackSize,
     "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.",
                       callback, closure);
+
+    ReportMemoryBytes(pathPrefix +
+                      NS_LITERAL_CSTRING("gc-heap-chunk-dirty-unused"),
+                      JS_GC_HEAP_KIND, data.gcHeapChunkDirtyUnused,
+    "Memory on the garbage-collected JavaScript heap, within chunks with at "
+    "least one allocated GC thing, that could be holding useful data but "
+    "currently isn't.",
+                      callback, closure);
+
+    ReportMemoryBytes(pathPrefix +
+                      NS_LITERAL_CSTRING("gc-heap-chunk-clean-unused"),
+                      JS_GC_HEAP_KIND, data.gcHeapChunkCleanUnused,
+    "Memory on the garbage-collected JavaScript heap taken by completely empty "
+     "chunks, that soon will be released unless claimed for new allocations.",
+                          callback, closure);
+
+    ReportMemoryBytes(pathPrefix +
+                      NS_LITERAL_CSTRING("gc-heap-chunk-admin"),
+                      JS_GC_HEAP_KIND, data.gcHeapChunkAdmin,
+    "Memory on the garbage-collected JavaScript heap, within chunks, that is "
+    "used to hold internal book-keeping information.",
+                          callback, closure);
 }
 
 } // namespace memory
 } // namespace xpconnect
 } // namespace mozilla
 
 class XPConnectJSCompartmentsMultiReporter : public nsIMemoryMultiReporter
 {
@@ -1773,91 +1845,51 @@ public:
         // data structure.  In the second step we pass all the stashed stats to
         // the callback.  Separating these steps is important because the
         // callback may be a JS function, and executing JS while getting these
         // stats seems like a bad idea.
         IterateData data;
         if(!CollectCompartmentStatsForRuntime(rt, &data))
             return NS_ERROR_FAILURE;
 
-        PRInt64 gcHeapChunkTotal = gXPCJSChunkAllocator.GetGCChunkBytesInUse();
-        // This is initialized to gcHeapChunkTotal, and then we subtract used
-        // space from it each time around the loop.
-        PRInt64 gcHeapChunkUnused = gcHeapChunkTotal;
-        PRInt64 gcHeapArenaUnused = 0;
-
         NS_NAMED_LITERAL_CSTRING(pathPrefix, "explicit/js/");
 
         // This is the second step (see above).
-        for(CompartmentStats *stats = data.compartmentStatsVector.begin();
-            stats != data.compartmentStatsVector.end();
-            ++stats)
-        {
-            gcHeapChunkUnused -=
-                stats->gcHeapArenaHeaders + stats->gcHeapArenaPadding +
-                stats->gcHeapArenaUnused +
-                stats->gcHeapObjects + stats->gcHeapStrings +
-                stats->gcHeapShapes + stats->gcHeapXml;
-
-            gcHeapArenaUnused += stats->gcHeapArenaUnused;
-
-            ReportCompartmentStats(*stats, pathPrefix, callback, closure);
-        }
+        ReportJSRuntimeStats(data, pathPrefix, callback, closure);
 
-        JS_ASSERT(gcHeapChunkTotal % js::GC_CHUNK_SIZE == 0);
-        size_t numChunks = gcHeapChunkTotal / js::GC_CHUNK_SIZE;
-        PRInt64 perChunkAdmin =
-            sizeof(js::gc::Chunk) - (sizeof(js::gc::Arena) * js::gc::ArenasPerChunk);
-        PRInt64 gcHeapChunkAdmin = numChunks * perChunkAdmin;
-        gcHeapChunkUnused -= gcHeapChunkAdmin;
-
-        // Why 10000x?  100x because it's a percentage, and another 100x
-        // because nsIMemoryReporter requires that for percentage amounts so
-        // they can be fractional.
-        PRInt64 gcHeapUnusedPercentage =
-            (gcHeapChunkUnused + gcHeapArenaUnused) * 10000 /
-            gXPCJSChunkAllocator.GetGCChunkBytesInUse();
-
-        ReportMemoryBytes(pathPrefix +
-                          NS_LITERAL_CSTRING("gc-heap-chunk-unused"),
-                          JS_GC_HEAP_KIND, gcHeapChunkUnused,
-    "Memory on the garbage-collected JavaScript heap, within chunks, that "
-    "could be holding useful data but currently isn't.",
+        ReportMemoryBytes(NS_LITERAL_CSTRING("js-gc-heap-chunk-dirty-unused"),
+                          nsIMemoryReporter::KIND_OTHER,
+                          data.gcHeapChunkDirtyUnused,
+    "The same as 'explicit/js/gc-heap-chunk-dirty-unused'.  Shown here for "
+    "easy comparison with other 'js-gc' reporters.",
                           callback, closure);
 
-        ReportMemoryBytes(NS_LITERAL_CSTRING("js-gc-heap-chunk-unused"),
-                          nsIMemoryReporter::KIND_OTHER, gcHeapChunkUnused,
-    "The same as 'explicit/js/gc-heap-chunk-unused'.  Shown here for "
-    "easy comparison with 'js-gc-heap' and 'js-gc-heap-arena-unused'.",
-                          callback, closure);
-
-        ReportMemoryBytes(pathPrefix +
-                          NS_LITERAL_CSTRING("gc-heap-chunk-admin"),
-                          JS_GC_HEAP_KIND, gcHeapChunkAdmin,
-    "Memory on the garbage-collected JavaScript heap, within chunks, that is "
-    "used to hold internal book-keeping information.",
+        ReportMemoryBytes(NS_LITERAL_CSTRING("js-gc-heap-chunk-clean-unused"),
+                          nsIMemoryReporter::KIND_OTHER,
+                          data.gcHeapChunkCleanUnused,
+    "The same as 'explicit/js/gc-heap-chunk-clean-unused'.  Shown here for "
+    "easy comparison with other 'js-gc' reporters.",
                           callback, closure);
 
         ReportMemoryBytes(NS_LITERAL_CSTRING("js-gc-heap-arena-unused"),
-                          nsIMemoryReporter::KIND_OTHER, gcHeapArenaUnused,
+                          nsIMemoryReporter::KIND_OTHER, data.gcHeapArenaUnused,
     "Memory on the garbage-collected JavaScript heap, within arenas, that "
     "could be holding useful data but currently isn't.  This is the sum of "
     "all compartments' 'gc-heap/arena-unused' numbers.",
                           callback, closure);
 
         ReportMemoryPercentage(NS_LITERAL_CSTRING("js-gc-heap-unused-fraction"),
                                nsIMemoryReporter::KIND_OTHER,
-                               gcHeapUnusedPercentage,
+                               data.gcHeapUnusedPercentage,
     "Fraction of the garbage-collected JavaScript heap that is unused. "
-    "Computed as ('js-gc-heap-chunk-unused' + 'js-gc-heap-arena-unused') / "
+    "Computed as ('js-gc-heap-chunk-clean-unused' + "
+    "'js-gc-heap-chunk-dirty-unused' + 'js-gc-heap-arena-unused') / "
     "'js-gc-heap'.",
                                callback, closure);
 
-        ReportJSStackSizeForRuntime(rt, pathPrefix, callback, closure);
-
         return NS_OK;
     }
 };
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(
   XPConnectJSCompartmentsMultiReporter
 , nsIMemoryMultiReporter
 )
--- a/js/src/xpconnect/src/xpcpublic.h
+++ b/js/src/xpconnect/src/xpcpublic.h
@@ -219,33 +219,43 @@ struct CompartmentStats
     PRInt64 tjitDataAllocatorsMain;
     PRInt64 tjitDataAllocatorsReserve;
 #endif
 };
 
 struct IterateData
 {
     IterateData()
-    : compartmentStatsVector(), currCompartmentStats(NULL) { }
+      : stackSize(0),
+        gcHeapChunkTotal(0),
+        gcHeapChunkCleanUnused(0),
+        gcHeapChunkDirtyUnused(0),
+        gcHeapArenaUnused(0),
+        gcHeapChunkAdmin(0),
+        gcHeapUnusedPercentage(0),
+        compartmentStatsVector(),
+        currCompartmentStats(NULL) { }
+
+    PRInt64 stackSize;
+    PRInt64 gcHeapChunkTotal;
+    PRInt64 gcHeapChunkCleanUnused;
+    PRInt64 gcHeapChunkDirtyUnused;
+    PRInt64 gcHeapArenaUnused;
+    PRInt64 gcHeapChunkAdmin;
+    PRInt64 gcHeapUnusedPercentage;
 
     js::Vector<CompartmentStats, 0, js::SystemAllocPolicy> compartmentStatsVector;
     CompartmentStats *currCompartmentStats;
 };
 
 JSBool
 CollectCompartmentStatsForRuntime(JSRuntime *rt, IterateData *data);
 
 void
-ReportCompartmentStats(const CompartmentStats &stats,
-                       const nsACString &pathPrefix,
-                       nsIMemoryMultiReporterCallback *callback,
-                       nsISupports *closure);
-
-void
-ReportJSStackSizeForRuntime(JSRuntime *rt, const nsACString &pathPrefix,
-                            nsIMemoryMultiReporterCallback *callback,
-                            nsISupports *closure);
+ReportJSRuntimeStats(const IterateData &data, const nsACString &pathPrefix,
+                     nsIMemoryMultiReporterCallback *callback,
+                     nsISupports *closure);
 
 } // namespace memory
 } // namespace xpconnect
 } // namespace mozilla
 
 #endif