Bug 697115 - return detailed CC results. r=smaug
authorAndrew McCreight <amccreight@mozilla.com>
Thu, 23 Feb 2012 20:16:37 -0800
changeset 90488 66a56113abeb9f5ebc43a029b087be95f7770899
parent 90487 aa70aeacde1f1429f3f6fc8d28b233506f9e50da
child 90489 ca7abbb11f4a893264e9ca89bfcd630a3a6fc62c
push idunknown
push userunknown
push dateunknown
reviewerssmaug
bugs697115
milestone13.0a1
Bug 697115 - return detailed CC results. r=smaug
dom/base/nsJSEnvironment.cpp
xpcom/base/nsCycleCollector.cpp
xpcom/base/nsCycleCollector.h
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -3270,18 +3270,19 @@ nsJSContext::CycleCollectNow(nsICycleCol
   for (PRInt32 i = 0; i < aExtraForgetSkippableCalls; ++i) {
     nsCycleCollector_forgetSkippable();
   }
 
   // nsCycleCollector_forgetSkippable may mark some gray js to black.
   if (!sCleanupSinceLastGC && aExtraForgetSkippableCalls >= 0) {
     nsCycleCollector_forgetSkippable();
   }
-  PRUint32 collected = nsCycleCollector_collect(aListener);
-  sCCollectedWaitingForGC += collected;
+  nsCycleCollectorResults ccResults;
+  nsCycleCollector_collect(&ccResults, aListener);
+  sCCollectedWaitingForGC += ccResults.mFreedRefCounted + ccResults.mFreedGCed;
 
   // If we collected a substantial amount of cycles, poke the GC since more objects
   // might be unreachable now.
   if (sCCollectedWaitingForGC > 250) {
     PokeGC(js::gcreason::CC_WAITING);
   }
 
   PRTime now = PR_Now();
@@ -3298,26 +3299,33 @@ nsJSContext::CycleCollectNow(nsICycleCol
   if (sPostGCEventsToConsole) {
     PRTime delta = 0;
     if (sFirstCollectionTime) {
       delta = now - sFirstCollectionTime;
     } else {
       sFirstCollectionTime = now;
     }
 
+    nsString gcmsg;
+    if (ccResults.mForcedGC) {
+      gcmsg.AssignLiteral(", forced a GC");
+    }
+
     NS_NAMED_MULTILINE_LITERAL_STRING(kFmt,
-      NS_LL("CC(T+%.1f) collected: %lu (%lu waiting for GC), suspected: %lu, duration: %llu ms.\n")
+      NS_LL("CC(T+%.1f) duration: %llums, suspected: %lu, visited: %lu RCed and %lu GCed, collected: %lu RCed and %lu GCed (%lu waiting for GC)%s\n")
       NS_LL("ForgetSkippable %lu times before CC, min: %lu ms, max: %lu ms, avg: %lu ms, total: %lu ms, removed: %lu"));
     nsString msg;
     PRUint32 cleanups = sForgetSkippableBeforeCC ? sForgetSkippableBeforeCC : 1;
     sMinForgetSkippableTime = (sMinForgetSkippableTime == PR_UINT32_MAX)
       ? 0 : sMinForgetSkippableTime;
     msg.Adopt(nsTextFormatter::smprintf(kFmt.get(), double(delta) / PR_USEC_PER_SEC,
-                                        collected, sCCollectedWaitingForGC, suspected,
-                                        (now - start) / PR_USEC_PER_MSEC,
+                                        (now - start) / PR_USEC_PER_MSEC, suspected,
+                                        ccResults.mVisitedRefCounted, ccResults.mVisitedGCed,
+                                        ccResults.mFreedRefCounted, ccResults.mFreedGCed,
+                                        sCCollectedWaitingForGC, gcmsg.get(),
                                         sForgetSkippableBeforeCC,
                                         sMinForgetSkippableTime / PR_USEC_PER_MSEC,
                                         sMaxForgetSkippableTime / PR_USEC_PER_MSEC,
                                         (sTotalForgetSkippableTime / cleanups) /
                                           PR_USEC_PER_MSEC,
                                         sTotalForgetSkippableTime / PR_USEC_PER_MSEC,
                                         sRemovedPurples));
     nsCOMPtr<nsIConsoleService> cs =
--- a/xpcom/base/nsCycleCollector.cpp
+++ b/xpcom/base/nsCycleCollector.cpp
@@ -1107,17 +1107,17 @@ struct nsCycleCollectionXPCOMRuntime :
 #endif
 };
 
 struct nsCycleCollector
 {
     bool mCollectionInProgress;
     bool mScanInProgress;
     bool mFollowupCollection;
-    PRUint32 mCollectedObjects;
+    nsCycleCollectorResults *mResults;
     TimeStamp mCollectionStart;
 
     nsCycleCollectionLanguageRuntime *mRuntimes[nsIProgrammingLanguage::MAX+1];
     nsCycleCollectionXPCOMRuntime mXPCOMRuntime;
 
     GCGraph mGraph;
 
     nsCycleCollectorParams mParams;
@@ -1154,21 +1154,23 @@ struct nsCycleCollector
 
     // The first pair of Suspect and Forget functions are only used by
     // old XPCOM binary components.
     bool Suspect(nsISupports *n);
     bool Forget(nsISupports *n);
     nsPurpleBufferEntry* Suspect2(nsISupports *n);
     bool Forget2(nsPurpleBufferEntry *e);
 
-    PRUint32 Collect(PRUint32 aTryCollections,
-                     nsICycleCollectorListener *aListener);
+    void Collect(nsCycleCollectorResults *aResults,
+                 PRUint32 aTryCollections,
+                 nsICycleCollectorListener *aListener);
 
     // Prepare for and cleanup after one or more collection(s).
-    bool PrepareForCollection(nsTArray<PtrInfo*> *aWhiteNodes);
+    bool PrepareForCollection(nsCycleCollectorResults *aResults,
+                              nsTArray<PtrInfo*> *aWhiteNodes);
     void GCIfNeeded(bool aForceGC);
     void CleanupAfterCollection();
 
     // Start and finish an individual collection.
     bool BeginCollection(nsICycleCollectorListener *aListener);
     bool FinishCollection(nsICycleCollectorListener *aListener);
 
     PRUint32 SuspectedCount();
@@ -2389,82 +2391,92 @@ nsCycleCollector::CollectWhite(nsICycleC
 
     nsresult rv;
     TimeLog timeLog;
 
     NS_ASSERTION(mWhiteNodes->IsEmpty(),
                  "FinishCollection wasn't called?");
 
     mWhiteNodes->SetCapacity(mWhiteNodeCount);
+    PRUint32 numWhiteGCed = 0;
 
     NodePool::Enumerator etor(mGraph.mNodes);
     while (!etor.IsDone())
     {
         PtrInfo *pinfo = etor.GetNext();
         if (pinfo->mColor == white && mWhiteNodes->AppendElement(pinfo)) {
             rv = pinfo->mParticipant->Root(pinfo->mPointer);
             if (NS_FAILED(rv)) {
                 Fault("Failed root call while unlinking", pinfo);
                 mWhiteNodes->RemoveElementAt(mWhiteNodes->Length() - 1);
+            } else if (pinfo->mRefCount == 0) {
+                // only JS objects have a refcount of 0
+                ++numWhiteGCed;
             }
         }
     }
+
+    PRUint32 count = mWhiteNodes->Length();
+    NS_ASSERTION(numWhiteGCed <= count,
+                 "More freed GCed nodes than total freed nodes.");
+    if (mResults) {
+        mResults->mFreedRefCounted += count - numWhiteGCed;
+        mResults->mFreedGCed += numWhiteGCed;
+    }
+
     timeLog.Checkpoint("CollectWhite::Root");
 
     if (mBeforeUnlinkCB) {
         mBeforeUnlinkCB();
         timeLog.Checkpoint("CollectWhite::BeforeUnlinkCB");
     }
 #if defined(DEBUG_CC) && !defined(__MINGW32__) && defined(WIN32)
     struct _CrtMemState ms1, ms2;
     _CrtMemCheckpoint(&ms1);
 #endif
 
-    PRUint32 i, count = mWhiteNodes->Length();
-
     if (aListener) {
-        for (i = 0; i < count; ++i) {
+        for (PRUint32 i = 0; i < count; ++i) {
             PtrInfo *pinfo = mWhiteNodes->ElementAt(i);
             aListener->DescribeGarbage((PRUint64)pinfo->mPointer);
         }
         aListener->End();
     }
 
-    for (i = 0; i < count; ++i) {
+    for (PRUint32 i = 0; i < count; ++i) {
         PtrInfo *pinfo = mWhiteNodes->ElementAt(i);
         rv = pinfo->mParticipant->Unlink(pinfo->mPointer);
         if (NS_FAILED(rv)) {
             Fault("Failed unlink call while unlinking", pinfo);
 #ifdef DEBUG_CC
             mStats.mFailedUnlink++;
 #endif
         }
         else {
 #ifdef DEBUG_CC
             ++mStats.mCollectedNode;
 #endif
         }
     }
     timeLog.Checkpoint("CollectWhite::Unlink");
 
-    for (i = 0; i < count; ++i) {
+    for (PRUint32 i = 0; i < count; ++i) {
         PtrInfo *pinfo = mWhiteNodes->ElementAt(i);
         rv = pinfo->mParticipant->Unroot(pinfo->mPointer);
         if (NS_FAILED(rv))
             Fault("Failed unroot call while unlinking", pinfo);
     }
     timeLog.Checkpoint("CollectWhite::Unroot");
 
 #if defined(DEBUG_CC) && !defined(__MINGW32__) && defined(WIN32)
     _CrtMemCheckpoint(&ms2);
     if (ms2.lTotalCount < ms1.lTotalCount)
         mStats.mFreedBytes += (ms1.lTotalCount - ms2.lTotalCount);
 #endif
 
-    mCollectedObjects += count;
     return count > 0;
 }
 
 
 #ifdef DEBUG_CC
 ////////////////////////////////////////////////////////////////////////
 // Memory-hooking stuff
 // When debugging wild pointers, it sometimes helps to hook malloc and
@@ -2657,20 +2669,20 @@ InitMemHook(void)
 
 #endif // GLIBC / WIN32 / OSX
 #endif // DEBUG_CC
 
 ////////////////////////////////////////////////////////////////////////
 // Collector implementation
 ////////////////////////////////////////////////////////////////////////
 
-nsCycleCollector::nsCycleCollector() : 
+nsCycleCollector::nsCycleCollector() :
     mCollectionInProgress(false),
     mScanInProgress(false),
-    mCollectedObjects(0),
+    mResults(nsnull),
     mWhiteNodes(nsnull),
     mWhiteNodeCount(0),
     mVisitedRefCounted(0),
     mVisitedGCed(0),
 #ifdef DEBUG_CC
     mPurpleBuf(mParams, mStats),
     mPtrLog(nsnull),
 #else
@@ -3031,29 +3043,32 @@ nsCycleCollector::GCIfNeeded(bool aForce
         static_cast<nsCycleCollectionJSRuntime*>
             (mRuntimes[nsIProgrammingLanguage::JAVASCRIPT]);
     if (!aForceGC) {
         bool needGC = rt->NeedCollect();
         // Only do a telemetry ping for non-shutdown CCs.
         Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_NEED_GC, needGC);
         if (!needGC)
             return;
+        if (mResults)
+            mResults->mForcedGC = true;
     }
 
     TimeLog timeLog;
 
     // rt->Collect() must be called from the main thread,
     // because it invokes XPCJSRuntime::GCCallback(cx, JSGC_BEGIN)
     // which returns false if not in the main thread.
     rt->Collect(js::gcreason::CC_FORCED, nsGCNormal);
     timeLog.Checkpoint("GC()");
 }
 
 bool
-nsCycleCollector::PrepareForCollection(nsTArray<PtrInfo*> *aWhiteNodes)
+nsCycleCollector::PrepareForCollection(nsCycleCollectorResults *aResults,
+                                       nsTArray<PtrInfo*> *aWhiteNodes)
 {
 #if defined(DEBUG_CC) && !defined(__MINGW32__)
     if (!mParams.mDoNothing && mParams.mHookMalloc)
         InitMemHook();
 #endif
 
     // This can legitimately happen in a few cases. See bug 383651.
     if (mCollectionInProgress)
@@ -3068,18 +3083,18 @@ nsCycleCollector::PrepareForCollection(n
     mCollectionInProgress = true;
 
     nsCOMPtr<nsIObserverService> obs =
         mozilla::services::GetObserverService();
     if (obs)
         obs->NotifyObservers(nsnull, "cycle-collector-begin", nsnull);
 
     mFollowupCollection = false;
-    mCollectedObjects = 0;
-
+
+    mResults = aResults;
     mWhiteNodes = aWhiteNodes;
 
     timeLog.Checkpoint("PrepareForCollection()");
 
     return true;
 }
 
 void
@@ -3093,55 +3108,65 @@ nsCycleCollector::CleanupAfterCollection
     // force the C library to give back as much memory to the system as
     // possible.
     _heapmin();
 #endif
 
     PRUint32 interval = (PRUint32) ((TimeStamp::Now() - mCollectionStart).ToMilliseconds());
 #ifdef COLLECT_TIME_DEBUG
     printf("cc: total cycle collector time was %ums\n", interval);
-    printf("cc: visited %u ref counted and %u GCed objects, freed %d.\n",
-           mVisitedRefCounted, mVisitedGCed, mWhiteNodeCount);
+    if (mResults) {
+        printf("cc: visited %u ref counted and %u GCed objects, freed %d ref counted and %d GCed objects.\n",
+               mVisitedRefCounted, mVisitedGCed,
+               mResults->mFreedRefCounted, mResults->mFreedGCed);
+    } else {
+        printf("cc: visited %u ref counted and %u GCed objects, freed %d.\n",
+               mVisitedRefCounted, mVisitedGCed, mWhiteNodeCount);
+    }
     printf("cc: \n");
 #endif
+    if (mResults) {
+        mResults->mVisitedRefCounted = mVisitedRefCounted;
+        mResults->mVisitedGCed = mVisitedGCed;
+        mResults = nsnull;
+    }
     Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR, interval);
     Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_VISITED_REF_COUNTED, mVisitedRefCounted);
     Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_VISITED_GCED, mVisitedGCed);
     Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_COLLECTED, mWhiteNodeCount);
 
 #ifdef DEBUG_CC
     ExplainLiveExpectedGarbage();
 #endif
 }
 
-PRUint32
-nsCycleCollector::Collect(PRUint32 aTryCollections,
+void
+nsCycleCollector::Collect(nsCycleCollectorResults *aResults,
+                          PRUint32 aTryCollections,
                           nsICycleCollectorListener *aListener)
 {
     nsAutoTArray<PtrInfo*, 4000> whiteNodes;
 
-    if (!PrepareForCollection(&whiteNodes))
-        return 0;
+    if (!PrepareForCollection(aResults, &whiteNodes))
+        return;
 
     PRUint32 totalCollections = 0;
     while (aTryCollections > totalCollections) {
         // Synchronous cycle collection. Always force a JS GC beforehand.
         GCIfNeeded(true);
         if (aListener && NS_FAILED(aListener->Begin()))
             aListener = nsnull;
         if (!(BeginCollection(aListener) &&
               FinishCollection(aListener)))
             break;
 
         ++totalCollections;
     }
 
     CleanupAfterCollection();
-
-    return mCollectedObjects;
 }
 
 bool
 nsCycleCollector::BeginCollection(nsICycleCollectorListener *aListener)
 {
     // aListener should be Begin()'d before this
     TimeLog timeLog;
 
@@ -3308,17 +3333,17 @@ nsCycleCollector::Shutdown()
 {
     // Here we want to run a final collection and then permanently
     // disable the collector because the program is shutting down.
 
     nsCOMPtr<nsCycleCollectorLogger> listener;
     if (mParams.mLogGraphs) {
         listener = new nsCycleCollectorLogger();
     }
-    Collect(SHUTDOWN_COLLECTIONS(mParams), listener);
+    Collect(nsnull, SHUTDOWN_COLLECTIONS(mParams), listener);
 
 #ifdef DEBUG_CC
     GCGraphBuilder builder(mGraph, mRuntimes, nsnull);
     mScanInProgress = true;
     SelectPurple(builder);
     mScanInProgress = false;
     if (builder.Count() != 0) {
         printf("Might have been able to release more cycles if the cycle collector would "
@@ -3914,36 +3939,37 @@ public:
           mReply(mLock, "cycle collector reply condvar"),
           mRunning(false),
           mShutdown(false),
           mCollected(false)
     {
         NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
     }
 
-    PRUint32 Collect(nsICycleCollectorListener* aListener)
+    void Collect(nsCycleCollectorResults *aResults,
+                 nsICycleCollectorListener *aListener)
     {
         NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
         // On a WantAllTraces CC, force a synchronous global GC to prevent
         // hijinks from ForgetSkippable and compartmental GCs.
         bool wantAllTraces = false;
         if (aListener) {
             aListener->GetWantAllTraces(&wantAllTraces);
         }
         mCollector->GCIfNeeded(wantAllTraces);
 
         MutexAutoLock autoLock(mLock);
 
         if (!mRunning)
-            return 0;
+            return;
 
         nsAutoTArray<PtrInfo*, 4000> whiteNodes;
-        if (!mCollector->PrepareForCollection(&whiteNodes))
-            return 0;
+        if (!mCollector->PrepareForCollection(aResults, &whiteNodes))
+            return;
 
         NS_ASSERTION(!mListener, "Should have cleared this already!");
         if (aListener && NS_FAILED(aListener->Begin()))
             aListener = nsnull;
         mListener = aListener;
 
         if (GetJSRuntime()->NotifyLeaveMainThread()) {
             mRequest.Notify();
@@ -3951,24 +3977,19 @@ public:
             GetJSRuntime()->NotifyEnterMainThread();
         } else {
             mCollected = mCollector->BeginCollection(mListener);
         }
 
         mListener = nsnull;
 
         if (mCollected) {
-            mCollected = mCollector->FinishCollection(aListener);
-
+            mCollector->FinishCollection(aListener);
             mCollector->CleanupAfterCollection();
-
-            return mCollected ? mCollector->mCollectedObjects : 0;
         }
-
-        return 0;
     }
 
     void Shutdown()
     {
         NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
         MutexAutoLock autoLock(mLock);
 
@@ -4032,29 +4053,32 @@ nsCycleCollector_forgetSkippable()
     if (sCollector) {
         SAMPLE_LABEL("CC", "nsCycleCollector_forgetSkippable");
         TimeLog timeLog;
         sCollector->ForgetSkippable();
         timeLog.Checkpoint("ForgetSkippable()");
     }
 }
 
-PRUint32
-nsCycleCollector_collect(nsICycleCollectorListener *aListener)
+void
+nsCycleCollector_collect(nsCycleCollectorResults *aResults,
+                         nsICycleCollectorListener *aListener)
 {
     NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
     SAMPLE_LABEL("CC", "nsCycleCollector_collect");
     nsCOMPtr<nsICycleCollectorListener> listener(aListener);
     if (!aListener && sCollector && sCollector->mParams.mLogGraphs) {
         listener = new nsCycleCollectorLogger();
     }
 
-    if (sCollectorRunner)
-        return sCollectorRunner->Collect(listener);
-    return sCollector ? sCollector->Collect(1, listener) : 0;
+    if (sCollectorRunner) {
+        sCollectorRunner->Collect(aResults, listener);
+    } else if (sCollector) {
+        sCollector->Collect(aResults, 1, listener);
+    }
 }
 
 void
 nsCycleCollector_shutdownThreads()
 {
     NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
     if (sCollectorRunner) {
         nsRefPtr<nsCycleCollectorRunner> runner;
--- a/xpcom/base/nsCycleCollector.h
+++ b/xpcom/base/nsCycleCollector.h
@@ -57,32 +57,46 @@ struct nsCycleCollectionLanguageRuntime
     virtual nsresult FinishTraverse() = 0;
     virtual nsresult FinishCycleCollection() = 0;
     virtual nsCycleCollectionParticipant *ToParticipant(void *p) = 0;
 #ifdef DEBUG_CC
     virtual void PrintAllReferencesTo(void *p) = 0;
 #endif
 };
 
+// Contains various stats about the cycle collection.
+class nsCycleCollectorResults
+{
+public:
+    nsCycleCollectorResults() :
+        mForcedGC(false), mVisitedRefCounted(0), mVisitedGCed(0),
+        mFreedRefCounted(0), mFreedGCed(0) {}
+    bool mForcedGC;
+    PRUint32 mVisitedRefCounted;
+    PRUint32 mVisitedGCed;
+    PRUint32 mFreedRefCounted;
+    PRUint32 mFreedGCed;
+};
+
 nsresult nsCycleCollector_startup();
 
 typedef void (*CC_BeforeUnlinkCallback)(void);
 void nsCycleCollector_setBeforeUnlinkCallback(CC_BeforeUnlinkCallback aCB);
 
 typedef void (*CC_ForgetSkippableCallback)(void);
 void nsCycleCollector_setForgetSkippableCallback(CC_ForgetSkippableCallback aCB);
 
 void nsCycleCollector_forgetSkippable();
 
 #ifdef DEBUG_CC
 void nsCycleCollector_logPurpleRemoval(void* aObject);
 #endif
 
-// Returns the number of collected nodes.
-PRUint32 nsCycleCollector_collect(nsICycleCollectorListener *aListener);
+void nsCycleCollector_collect(nsCycleCollectorResults *aResults,
+                              nsICycleCollectorListener *aListener);
 PRUint32 nsCycleCollector_suspectedCount();
 void nsCycleCollector_shutdownThreads();
 void nsCycleCollector_shutdown();
 
 // The JS runtime is special, it needs to call cycle collection during its GC.
 // If the JS runtime is registered nsCycleCollector_collect will call
 // nsCycleCollectionJSRuntime::Collect which will call
 // nsCycleCollector_doCollect, else nsCycleCollector_collect will call