Fix for bug 466157 (Enable dumping of cycle-collector graphs in any build). r=dbaron, a=jst.
authorRobert O'Callahan <robert@ocallahan.org>
Thu, 12 Aug 2010 12:03:23 +1200
changeset 53869 d33f1f98bbf315913cb5ad8384ba0a8a9b2bbb4b
parent 53868 1cc1070b27fff8b8051d66ccd7446028933476de
child 53870 11de95cfdc8aec33cfd62d20a855e67d2013818e
push id15720
push userpvanderbeken@mozilla.com
push dateWed, 15 Sep 2010 04:07:55 +0000
treeherdermozilla-central@c130135dcf02 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron, jst
bugs466157
milestone2.0b7pre
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
Fix for bug 466157 (Enable dumping of cycle-collector graphs in any build). r=dbaron, a=jst.
dom/base/nsDOMWindowUtils.cpp
dom/base/nsJSEnvironment.cpp
dom/base/nsJSEnvironment.h
dom/interfaces/base/nsIDOMWindowUtils.idl
xpcom/base/Makefile.in
xpcom/base/nsCycleCollector.cpp
xpcom/base/nsCycleCollector.h
xpcom/base/nsICycleCollectorListener.idl
xpcom/build/XPCOMModule.inc
xpcom/build/dlldeps.cpp
xpcom/build/nsXPCOMCID.h
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -570,26 +570,26 @@ nsDOMWindowUtils::Focus(nsIDOMElement* a
     else
       fm->ClearFocus(mWindow);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDOMWindowUtils::GarbageCollect()
+nsDOMWindowUtils::GarbageCollect(nsICycleCollectorListener *aListener)
 {
   // Always permit this in debug builds.
 #ifndef DEBUG
   if (!IsUniversalXPConnectCapable()) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 #endif
 
-  nsJSContext::CC();
+  nsJSContext::CC(aListener);
 
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsDOMWindowUtils::ProcessUpdates()
 {
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -302,17 +302,17 @@ public:
 };
 
 NS_IMPL_ISUPPORTS1(nsCCMemoryPressureObserver, nsIObserver)
 
 NS_IMETHODIMP
 nsCCMemoryPressureObserver::Observe(nsISupports* aSubject, const char* aTopic,
                                     const PRUnichar* aData)
 {
-  nsJSContext::CC();
+  nsJSContext::CC(nsnull);
   return NS_OK;
 }
 
 /****************************************************************
  ************************** AutoFree ****************************
  ****************************************************************/
 
 class AutoFree {
@@ -3507,32 +3507,32 @@ nsJSContext::ScriptExecuted()
 {
   ScriptEvaluated(!::JS_IsRunning(mContext));
 
   return NS_OK;
 }
 
 //static
 void
-nsJSContext::CC()
+nsJSContext::CC(nsICycleCollectorListener *aListener)
 {
   NS_TIME_FUNCTION_MIN(1.0);
 
   ++sCCollectCount;
 #ifdef DEBUG_smaug
   printf("Will run cycle collector (%i), %lldms since previous.\n",
          sCCollectCount, (PR_Now() - sPreviousCCTime) / PR_USEC_PER_MSEC);
 #endif
   sPreviousCCTime = PR_Now();
   sDelayedCCollectCount = 0;
   sCCSuspectChanges = 0;
   // nsCycleCollector_collect() no longer forces a JS garbage collection,
   // so we have to do it ourselves here.
   nsContentUtils::XPConnect()->GarbageCollect();
-  sCollectedObjectsCounts = nsCycleCollector_collect();
+  sCollectedObjectsCounts = nsCycleCollector_collect(aListener);
   sCCSuspectedCount = nsCycleCollector_suspectedCount();
   sSavedGCCount = JS_GetGCParameter(nsJSRuntime::sRuntime, JSGC_NUMBER);
 #ifdef DEBUG_smaug
   printf("Collected %u objects, %u suspected objects, took %lldms\n",
          sCollectedObjectsCounts, sCCSuspectedCount,
          (PR_Now() - sPreviousCCTime) / PR_USEC_PER_MSEC);
 #endif
 }
@@ -3610,17 +3610,17 @@ nsJSContext::CCIfUserInactive()
 }
 
 //static
 PRBool
 nsJSContext::IntervalCC()
 {
   if ((PR_Now() - sPreviousCCTime) >=
       PRTime(NS_MIN_CC_INTERVAL * PR_USEC_PER_MSEC)) {
-    nsJSContext::CC();
+    nsJSContext::CC(nsnull);
     return PR_TRUE;
   }
 #ifdef DEBUG_smaug
   printf("Running CC was delayed because of NS_MIN_CC_INTERVAL.\n");
 #endif
   return PR_FALSE;
 }
 
--- a/dom/base/nsJSEnvironment.h
+++ b/dom/base/nsJSEnvironment.h
@@ -183,17 +183,17 @@ public:
 
   NS_DECL_NSIXPCSCRIPTNOTIFY
 
   static void LoadStart();
   static void LoadEnd();
 
   // CC does always call cycle collector and it also updates the counters
   // that MaybeCC uses.
-  static void CC();
+  static void CC(nsICycleCollectorListener *aListener);
 
   // MaybeCC calls cycle collector if certain conditions are fulfilled.
   // The conditions are:
   // - The timer related to page load (sGCTimer) must not be active.
   // - At least NS_MIN_CC_INTERVAL milliseconds must have elapsed since the
   //   previous cycle collector call.
   // - Certain number of MaybeCC calls have occurred.
   //   The number of needed MaybeCC calls depends on the aHigherProbability
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -40,25 +40,26 @@
 /**
  * nsIDOMWindowUtils is intended for infrequently-used methods related
  * to the current nsIDOMWindow.  Some of the methods may require
  * elevated privileges; the method implementations should contain the
  * necessary security checks.  Access this interface by calling
  * getInterface on a DOMWindow.
  */
 
+interface nsICycleCollectorListener;
 interface nsIDOMNode;
 interface nsIDOMNodeList;
 interface nsIDOMElement;
 interface nsIDOMHTMLCanvasElement;
 interface nsIDOMEvent;
 interface nsITransferable;
 interface nsIQueryContentEventResult;
 
-[scriptable, uuid(8430e730-a085-421e-bb72-39fcf73fe57b)]
+[scriptable, uuid(bf4df3a0-4567-47ec-be6d-9ec60eaed63c)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -262,23 +263,26 @@ interface nsIDOMWindowUtils : nsISupport
    *
    * Do not use this method. Just use element.focus if available or
    * nsIFocusManager::SetFocus instead.
    *
    */
   void focus(in nsIDOMElement aElement);
 
   /**
-   * Force a garbage collection. This will run the cycle-collector twice to
-   * make sure all garbage is collected.
+   * Force a garbage collection followed by a cycle collection.
    *
    * Will throw a DOM security error if called without UniversalXPConnect
    * privileges in non-debug builds. Available to all callers in debug builds.
+   *
+   * @param aListener listener that receives information about the CC graph
+   *                  (see @mozilla.org/cycle-collector-logger;1 for a logger
+   *                   component)
    */
-  void garbageCollect();
+  void garbageCollect([optional] in nsICycleCollectorListener aListener);
 
   /**
    * Force processing of any queued paints
    */
 
   void processUpdates();
 
   /** Synthesize a simple gesture event for a window. The event types
--- a/xpcom/base/Makefile.in
+++ b/xpcom/base/Makefile.in
@@ -121,16 +121,17 @@ SDK_HEADERS     = \
 		nsAtomicRefcnt.h \
 		nsCycleCollector.h \
 		nsObjCExceptions.h \
 
 XPIDLSRCS	= \
 		nsIConsoleListener.idl \
 		nsIConsoleMessage.idl \
 		nsIConsoleService.idl \
+		nsICycleCollectorListener.idl \
 		nsIDebug2.idl \
 		nsIErrorService.idl \
 		nsIException.idl \
 		nsIExceptionService.idl \
 		nsIVersionComparator.idl \
 		nsIUUIDGenerator.idl \
 		nsIMutable.idl \
 		nsIMemoryReporter.idl \
--- a/xpcom/base/nsCycleCollector.cpp
+++ b/xpcom/base/nsCycleCollector.cpp
@@ -141,16 +141,17 @@
 #include "mozilla/FunctionTimer.h"
 #include "nsIObserverService.h"
 #include "nsIConsoleService.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "nsTPtrArray.h"
 #include "nsTArray.h"
 #include "mozilla/Services.h"
+#include "nsICycleCollectorListener.h"
 
 #include <stdio.h>
 #include <string.h>
 #ifdef WIN32
 #include <io.h>
 #include <process.h>
 #endif
 
@@ -646,21 +647,16 @@ struct GCGraph
     ~GCGraph() { 
     }
 };
 
 // XXX Would be nice to have an nsHashSet<KeyType> API that has
 // Add/Remove/Has rather than PutEntry/RemoveEntry/GetEntry.
 typedef nsTHashtable<nsVoidPtrHashKey> PointerSet;
 
-#ifdef DEBUG_CC
-static void
-WriteGraph(FILE *stream, GCGraph &graph, const void *redPtr);
-#endif
-
 static inline void
 ToParticipant(nsISupports *s, nsXPCOMCycleCollectionParticipant **cp);
 
 struct nsPurpleBuffer
 {
 private:
     struct Block {
         Block *mNext;
@@ -990,35 +986,35 @@ struct nsCycleCollector
 
     // The first pair of Suspect and Forget functions are only used by
     // old XPCOM binary components.
     PRBool Suspect(nsISupports *n);
     PRBool Forget(nsISupports *n);
     nsPurpleBufferEntry* Suspect2(nsISupports *n);
     PRBool Forget2(nsPurpleBufferEntry *e);
 
-    PRUint32 Collect(PRUint32 aTryCollections = 1);
-    PRBool BeginCollection();
+    PRUint32 Collect(PRUint32 aTryCollections,
+                     nsICycleCollectorListener *aListener);
+    PRBool BeginCollection(nsICycleCollectorListener *aListener);
     PRBool FinishCollection();
     PRUint32 SuspectedCount();
     void Shutdown();
 
     void ClearGraph()
     {
         mGraph.mNodes.Clear();
         mGraph.mEdges.Clear();
         mGraph.mRootCount = 0;
     }
 
 #ifdef DEBUG_CC
     nsCycleCollectorStats mStats;    
 
     FILE *mPtrLog;
 
-    void MaybeDrawGraphs();
     void Allocated(void *n, size_t sz);
     void Freed(void *n);
 
     void ExplainLiveExpectedGarbage();
     PRBool CreateReversedEdges();
     void DestroyReversedEdges();
     void ShouldBeFreed(nsISupports *n);
     void WasFreed(nsISupports *n);
@@ -1100,30 +1096,16 @@ Fault(const char *msg, const void *ptr=n
 
     if (sCollector->mParams.mFaultIsFatal) {
 
         if (ptr)
             printf("Fatal fault in cycle collector: %s (ptr: %p)\n", msg, ptr);
         else
             printf("Fatal fault in cycle collector: %s\n", msg);
 
-     
-        if (sCollector->mGraph.mRootCount > 0) {
-            FILE *stream;
-#ifdef WIN32
-            const char fname[] = "c:\\fault-graph.dot";
-#else
-            const char fname[] = "/tmp/fault-graph.dot";
-#endif
-            printf("depositing faulting cycle-collection graph in %s\n", fname);
-            stream = fopen(fname, "w+");
-            WriteGraph(stream, sCollector->mGraph, ptr);
-            fclose(stream);
-        } 
-
         exit(1);
     }
 #endif
 
     nsPrintfCString str(256, "Fault in cycle collector: %s (ptr: %p)\n",
                         msg, ptr);
     NS_NOTREACHED(str.get());
 
@@ -1258,16 +1240,105 @@ GraphWalker<Visitor>::DoWalk(nsDeque &aQ
     };
 
 #ifdef DEBUG_CC
     sCollector->mStats.mWalkedGraph++;
 #endif
 }
 
 
+class nsCycleCollectorLogger : public nsICycleCollectorListener
+{
+public:
+    nsCycleCollectorLogger() : mStream(nsnull)
+    {
+    }
+    ~nsCycleCollectorLogger()
+    {
+        if (mStream) {
+            fclose(mStream);
+        }
+    }
+    NS_DECL_ISUPPORTS
+
+    NS_IMETHOD Begin()
+    {
+        char name[255];
+        sprintf(name, "cc-edges-%d.log", ++gLogCounter);
+        mStream = fopen(name, "w");
+
+        return mStream ? NS_OK : NS_ERROR_FAILURE;
+    }
+    NS_IMETHOD NoteObject(PRUint64 aAddress, const char *aObjectDescription)
+    {
+        fprintf(mStream, "%p %s\n", (void*)aAddress, aObjectDescription);
+
+        return NS_OK;
+    }
+    NS_IMETHOD NoteEdge(PRUint64 aFromAddress, PRUint64 aToAddress,
+                        const char *aEdgeName)
+    {
+        fprintf(mStream, "> %p %s\n", (void*)aToAddress, aEdgeName);
+
+        return NS_OK;
+    }
+    NS_IMETHOD BeginDescriptions()
+    {
+        fputs("==========\n", mStream);
+
+        return NS_OK;
+    }
+    NS_IMETHOD DescribeRefcountedObject(PRUint64 aAddress, PRUint32 aKnownEdges,
+                                        PRUint32 aTotalEdges)
+    {
+        PRBool root = aKnownEdges != aTotalEdges;
+        fprintf(mStream, "%p", (void*)aAddress);
+        if (root) {
+            fprintf(mStream, " [root] [%u/%u]", aKnownEdges, aTotalEdges);
+        }
+        fputc('\n', mStream);
+
+        return NS_OK;
+    }
+    NS_IMETHOD DescribeGCedObject(PRUint64 aAddress, PRBool aMarked)
+    {
+        fprintf(mStream, "%p%s\n", (void*)aAddress, aMarked ? " [root]" : "");
+
+        return NS_OK;
+    }
+    NS_IMETHOD End()
+    {
+        fclose(mStream);
+        mStream = nsnull;
+
+        return NS_OK;
+    }
+
+private:
+    FILE *mStream;
+
+    static PRUint32 gLogCounter;
+};
+
+NS_IMPL_ISUPPORTS1(nsCycleCollectorLogger, nsICycleCollectorListener)
+
+PRUint32 nsCycleCollectorLogger::gLogCounter = 0;
+
+nsresult
+nsCycleCollectorLoggerConstructor(nsISupports* aOuter,
+                                  const nsIID& aIID,
+                                  void* *aInstancePtr)
+{
+    NS_ENSURE_TRUE(!aOuter, NS_ERROR_NO_AGGREGATION);
+
+    nsISupports *logger = new nsCycleCollectorLogger();
+
+    return logger->QueryInterface(aIID, aInstancePtr);
+}
+
 ////////////////////////////////////////////////////////////////////////
 // Bacon & Rajan's |MarkRoots| routine.
 ////////////////////////////////////////////////////////////////////////
 
 struct PtrToNodeEntry : public PLDHashEntryHdr
 {
     // The key is mNode->mPointer
     PtrInfo *mNode;
@@ -1296,23 +1367,23 @@ static PLDHashTableOps PtrNodeOps = {
 class GCGraphBuilder : public nsCycleCollectionTraversalCallback
 {
 private:
     NodePool::Builder mNodeBuilder;
     EdgePool::Builder mEdgeBuilder;
     PLDHashTable mPtrToNodeMap;
     PtrInfo *mCurrPi;
     nsCycleCollectionLanguageRuntime **mRuntimes; // weak, from nsCycleCollector
-#ifdef DEBUG_CC
     nsCString mNextEdgeName;
-#endif
+    nsCOMPtr<nsICycleCollectorListener> mListener;
 
 public:
     GCGraphBuilder(GCGraph &aGraph,
-                   nsCycleCollectionLanguageRuntime **aRuntimes);
+                   nsCycleCollectionLanguageRuntime **aRuntimes,
+                   nsICycleCollectorListener *aListener);
     ~GCGraphBuilder();
     bool Initialized();
 
     PRUint32 Count() const { return mPtrToNodeMap.entryCount; }
 
 #ifdef DEBUG_CC
     PtrInfo* AddNode(void *s, nsCycleCollectionParticipant *aParticipant,
                      PRUint32 aLangID);
@@ -1337,29 +1408,35 @@ private:
     NS_IMETHOD_(void) NoteXPCOMChild(nsISupports *child);
     NS_IMETHOD_(void) NoteNativeChild(void *child,
                                      nsCycleCollectionParticipant *participant);
     NS_IMETHOD_(void) NoteScriptChild(PRUint32 langID, void *child);
     NS_IMETHOD_(void) NoteNextEdgeName(const char* name);
 };
 
 GCGraphBuilder::GCGraphBuilder(GCGraph &aGraph,
-                               nsCycleCollectionLanguageRuntime **aRuntimes)
+                               nsCycleCollectionLanguageRuntime **aRuntimes,
+                               nsICycleCollectorListener *aListener)
     : mNodeBuilder(aGraph.mNodes),
       mEdgeBuilder(aGraph.mEdges),
-      mRuntimes(aRuntimes)
+      mRuntimes(aRuntimes),
+      mListener(aListener)
 {
     if (!PL_DHashTableInit(&mPtrToNodeMap, &PtrNodeOps, nsnull,
                            sizeof(PtrToNodeEntry), 32768))
         mPtrToNodeMap.ops = nsnull;
-#ifdef DEBUG_CC
-    // Do we need to set these all the time?
-    mFlags |= nsCycleCollectionTraversalCallback::WANT_DEBUG_INFO |
-              nsCycleCollectionTraversalCallback::WANT_ALL_TRACES;
+    // We want all edges and all info if DEBUG_CC is set or if we have a
+    // listener. Do we want them all the time?
+#ifndef DEBUG_CC
+    if (mListener)
 #endif
+    {
+        mFlags |= nsCycleCollectionTraversalCallback::WANT_DEBUG_INFO |
+                  nsCycleCollectionTraversalCallback::WANT_ALL_TRACES;
+    }
 }
 
 GCGraphBuilder::~GCGraphBuilder()
 {
     if (mPtrToNodeMap.ops)
         PL_DHashTableFinish(&mPtrToNodeMap);
 }
 
@@ -1456,16 +1533,20 @@ NS_IMETHODIMP_(void)
 GCGraphBuilder::DescribeNode(CCNodeType type, nsrefcnt refCount,
                              size_t objSz, const char *objName)
 {
 #ifdef DEBUG_CC
     mCurrPi->mBytes = objSz;
     mCurrPi->mName = PL_strdup(objName);
 #endif
 
+    if (mListener) {
+        mListener->NoteObject((PRUint64)mCurrPi->mPointer, objName);
+    }
+
     if (type == RefCounted) {
         if (refCount == 0 || refCount == PR_UINT32_MAX)
             Fault("zero or overflowing refcount", mCurrPi);
 
         mCurrPi->mRefCount = refCount;
     }
     else {
         mCurrPi->mRefCount = type == GCMarked ? PR_UINT32_MAX : 0;
@@ -1473,20 +1554,21 @@ GCGraphBuilder::DescribeNode(CCNodeType 
 #ifdef DEBUG_CC
     sCollector->mStats.mVisitedNode++;
 #endif
 }
 
 NS_IMETHODIMP_(void)
 GCGraphBuilder::NoteXPCOMChild(nsISupports *child) 
 {
-#ifdef DEBUG_CC
-    nsCString edgeName(mNextEdgeName);
-    mNextEdgeName.Truncate();
-#endif
+    nsCString edgeName;
+    if (WantDebugInfo()) {
+        edgeName.Assign(mNextEdgeName);
+        mNextEdgeName.Truncate();
+    }
     if (!child || !(child = canonicalize(child)))
         return; 
 
 #ifdef DEBUG_CC
     if (nsCycleCollector_shouldSuppress(child))
         return;
 #endif
     
@@ -1495,50 +1577,60 @@ GCGraphBuilder::NoteXPCOMChild(nsISuppor
     if (cp) {
         PtrInfo *childPi = AddNode(child, cp, nsIProgrammingLanguage::CPLUSPLUS);
         if (!childPi)
             return;
         mEdgeBuilder.Add(childPi);
 #ifdef DEBUG_CC
         mCurrPi->mEdgeNames.AppendElement(edgeName);
 #endif
+        if (mListener) {
+            mListener->NoteEdge((PRUint64)mCurrPi->mPointer, (PRUint64)child,
+                                edgeName.get());
+        }
         ++childPi->mInternalRefs;
     }
 }
 
 NS_IMETHODIMP_(void)
 GCGraphBuilder::NoteNativeChild(void *child,
                                 nsCycleCollectionParticipant *participant)
 {
-#ifdef DEBUG_CC
-    nsCString edgeName(mNextEdgeName);
-    mNextEdgeName.Truncate();
-#endif
+    nsCString edgeName;
+    if (WantDebugInfo()) {
+        edgeName.Assign(mNextEdgeName);
+        mNextEdgeName.Truncate();
+    }
     if (!child)
         return;
 
     NS_ASSERTION(participant, "Need a nsCycleCollectionParticipant!");
 
     PtrInfo *childPi = AddNode(child, participant, nsIProgrammingLanguage::CPLUSPLUS);
     if (!childPi)
         return;
     mEdgeBuilder.Add(childPi);
 #ifdef DEBUG_CC
     mCurrPi->mEdgeNames.AppendElement(edgeName);
 #endif
+    if (mListener) {
+        mListener->NoteEdge((PRUint64)mCurrPi->mPointer, (PRUint64)child,
+                            edgeName.get());
+    }
     ++childPi->mInternalRefs;
 }
 
 NS_IMETHODIMP_(void)
 GCGraphBuilder::NoteScriptChild(PRUint32 langID, void *child) 
 {
-#ifdef DEBUG_CC
-    nsCString edgeName(mNextEdgeName);
-    mNextEdgeName.Truncate();
-#endif
+    nsCString edgeName;
+    if (WantDebugInfo()) {
+        edgeName.Assign(mNextEdgeName);
+        mNextEdgeName.Truncate();
+    }
     if (!child)
         return;
 
     if (langID > nsIProgrammingLanguage::MAX) {
         Fault("traversing pointer for unknown language", child);
         return;
     }
 
@@ -1554,25 +1646,29 @@ GCGraphBuilder::NoteScriptChild(PRUint32
 
     PtrInfo *childPi = AddNode(child, cp, langID);
     if (!childPi)
         return;
     mEdgeBuilder.Add(childPi);
 #ifdef DEBUG_CC
     mCurrPi->mEdgeNames.AppendElement(edgeName);
 #endif
+    if (mListener) {
+        mListener->NoteEdge((PRUint64)mCurrPi->mPointer, (PRUint64)child,
+                            edgeName.get());
+    }
     ++childPi->mInternalRefs;
 }
 
 NS_IMETHODIMP_(void)
 GCGraphBuilder::NoteNextEdgeName(const char* name)
 {
-#ifdef DEBUG_CC
-    mNextEdgeName = name;
-#endif
+    if (WantDebugInfo()) {
+        mNextEdgeName = name;
+    }
 }
 
 static PRBool
 AddPurpleRoot(GCGraphBuilder &builder, nsISupports *root)
 {
     root = canonicalize(root);
     NS_ASSERTION(root,
                  "Don't add objects that don't participate in collection!");
@@ -2074,101 +2170,17 @@ nsCycleCollector::ForgetRuntime(PRUint32
         Fault("unknown language runtime in deregistration");
 
     if (! mRuntimes[langID])
         Fault("forgetting non-registered language runtime");
 
     mRuntimes[langID] = nsnull;
 }
 
-
 #ifdef DEBUG_CC
-static void
-WriteGraph(FILE *stream, GCGraph &graph, const void *redPtr)
-{
-    fprintf(stream, 
-            "digraph collection {\n"
-            "rankdir=LR\n"
-            "node [fontname=fixed, fontsize=10, style=filled, shape=box]\n"
-            );
-    
-    NodePool::Enumerator etor(graph.mNodes);
-    while (!etor.IsDone()) {
-        PtrInfo *pi = etor.GetNext();
-        const void *p = pi->mPointer;
-        fprintf(stream, 
-                "n%p [label=\"%s\\n%p\\n",
-                p,
-                pi->mName,
-                p);
-        if (pi->mRefCount != 0 && pi->mRefCount != PR_UINT32_MAX) {
-            fprintf(stream, 
-                    "%u/%u refs found",
-                    pi->mInternalRefs, pi->mRefCount);
-        }
-        fprintf(stream, 
-                "\", fillcolor=%s, fontcolor=%s]\n", 
-                (redPtr && redPtr == p ? "red" : (pi->mColor == black ? "black" : "white")),
-                (pi->mColor == black ? "white" : "black"));
-        for (EdgePool::Iterator child = pi->mFirstChild,
-                 child_end = pi->mLastChild;
-             child != child_end; ++child) {
-            fprintf(stream, "n%p -> n%p\n", p, (*child)->mPointer);
-        }
-    }
-    
-    fprintf(stream, "\n}\n");    
-}
-
-
-void 
-nsCycleCollector::MaybeDrawGraphs()
-{
-    if (mParams.mDrawGraphs) {
-        // We draw graphs only if there were any white nodes.
-        PRBool anyWhites = PR_FALSE;
-        NodePool::Enumerator fwetor(mGraph.mNodes);
-        while (!fwetor.IsDone())
-        {
-            PtrInfo *pinfo = fwetor.GetNext();
-            if (pinfo->mColor == white) {
-                anyWhites = PR_TRUE;
-                break;
-            }
-        }
-
-        if (anyWhites) {
-            // We can't just use _popen here because graphviz-for-windows
-            // doesn't set up its stdin stream properly, sigh.
-            FILE *stream;
-#ifdef WIN32
-            stream = fopen("c:\\cycle-graph.dot", "w+");
-#else
-            stream = popen("dotty -", "w");
-#endif
-            WriteGraph(stream, mGraph, nsnull);
-#ifdef WIN32
-            fclose(stream);
-            // Even dotty doesn't work terribly well on windows, since
-            // they execute lefty asynchronously. So we'll just run 
-            // lefty ourselves.
-            _spawnlp(_P_WAIT, 
-                     "lefty", 
-                     "lefty",
-                     "-e",
-                     "\"load('dotty.lefty');"
-                     "dotty.simple('c:\\cycle-graph.dot');\"",
-                     NULL);
-            unlink("c:\\cycle-graph.dot");
-#else
-            pclose(stream);
-#endif
-        }
-    }
-}
 
 class Suppressor :
     public nsCycleCollectionTraversalCallback
 {
 protected:
     static char *sSuppressionList;
     static PRBool sInitialized;
     PRBool mSuppressThisNode;
@@ -2411,17 +2423,18 @@ nsCycleCollector::Freed(void *n)
                 mPtrLog = fopen("pointer_log", "w");
             fprintf(mPtrLog, "R %p\n", n);
         }
     }
 }
 #endif
 
 PRUint32
-nsCycleCollector::Collect(PRUint32 aTryCollections)
+nsCycleCollector::Collect(PRUint32 aTryCollections,
+                          nsICycleCollectorListener *aListener)
 {
 #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)
@@ -2429,16 +2442,22 @@ nsCycleCollector::Collect(PRUint32 aTryC
 
     NS_TIME_FUNCTION;
 
 #ifdef COLLECT_TIME_DEBUG
     printf("cc: Starting nsCycleCollector::Collect(%d)\n", aTryCollections);
     PRTime start = PR_Now();
 #endif
 
+#ifdef DEBUG_CC
+    if (!aListener && mParams.mDrawGraphs) {
+        aListener = new nsCycleCollectorLogger();
+    }
+#endif
+
     mCollectionInProgress = PR_TRUE;
 
     nsCOMPtr<nsIObserverService> obs =
         mozilla::services::GetObserverService();
     if (obs)
         obs->NotifyObservers(nsnull, "cycle-collector-begin", nsnull);
 
     mFollowupCollection = PR_FALSE;
@@ -2463,17 +2482,17 @@ nsCycleCollector::Collect(PRUint32 aTryC
             static_cast<nsCycleCollectionJSRuntime*>
                 (mRuntimes[nsIProgrammingLanguage::JAVASCRIPT])->Collect();
             mFirstCollection = PR_FALSE;
 #ifdef COLLECT_TIME_DEBUG
             printf("cc: GC() took %lldms\n", (PR_Now() - start) / PR_USEC_PER_MSEC);
 #endif
         }
 
-        PRBool collected = BeginCollection() && FinishCollection();
+        PRBool collected = BeginCollection(aListener) && FinishCollection();
 
 #ifdef DEBUG_CC
         // We wait until after FinishCollection to check the white nodes because
         // some objects may outlive CollectWhite but then be freed by
         // FinishCycleCollection (like XPConnect's deferred release of native
         // objects).
         PRUint32 i, count = mWhiteNodes->Length();
         for (i = 0; i < count; ++i) {
@@ -2516,22 +2535,26 @@ nsCycleCollector::Collect(PRUint32 aTryC
 #endif
 #ifdef DEBUG_CC
     ExplainLiveExpectedGarbage();
 #endif
     return mCollectedObjects;
 }
 
 PRBool
-nsCycleCollector::BeginCollection()
+nsCycleCollector::BeginCollection(nsICycleCollectorListener *aListener)
 {
     if (mParams.mDoNothing)
         return PR_FALSE;
 
-    GCGraphBuilder builder(mGraph, mRuntimes);
+    if (aListener && NS_FAILED(aListener->Begin())) {
+        aListener = nsnull;
+    }
+
+    GCGraphBuilder builder(mGraph, mRuntimes, aListener);
     if (!builder.Initialized())
         return PR_FALSE;
 
 #ifdef COLLECT_TIME_DEBUG
     PRTime now = PR_Now();
 #endif
     for (PRUint32 i = 0; i <= nsIProgrammingLanguage::MAX; ++i) {
         if (mRuntimes[i])
@@ -2599,22 +2622,39 @@ nsCycleCollector::BeginCollection()
 
         ScanRoots();
 
 #ifdef COLLECT_TIME_DEBUG
         printf("cc: ScanRoots() took %lldms\n",
                (PR_Now() - now) / PR_USEC_PER_MSEC);
 #endif
 
-#ifdef DEBUG_CC
-        MaybeDrawGraphs();
-#endif
-
         mScanInProgress = PR_FALSE;
 
+        if (aListener) {
+            aListener->BeginDescriptions();
+
+            NodePool::Enumerator etor(mGraph.mNodes);
+            while (!etor.IsDone()) {
+                PtrInfo *pi = etor.GetNext();
+                if (pi->mColor == black) {
+                    PRUint64 p = (PRUint64)pi->mPointer;
+                    if (pi->mRefCount > 0 && pi->mRefCount < PR_UINT32_MAX) {
+                        aListener->DescribeRefcountedObject(p, pi->mInternalRefs,
+                                                           pi->mRefCount);
+                    }
+                    else {
+                        aListener->DescribeGCedObject(p, pi->mRefCount != 0);
+                    }
+                }
+            }
+
+            aListener->End();
+        }
+
 #ifdef DEBUG_CC
         if (mFollowupCollection && purpleStart != purpleEnd) {
             PRUint32 i = 0;
             NodePool::Enumerator queue(mGraph.mNodes);
             while (i++ < purpleStart) {
                 queue.GetNext();
             }
             while (i++ < purpleEnd) {
@@ -2687,20 +2727,20 @@ nsCycleCollector::Shutdown()
     // Here we want to run a final collection and then permanently
     // disable the collector because the program is shutting down.
 
     for (PRUint32 i = 0; i <= nsIProgrammingLanguage::MAX; ++i) {
         if (mRuntimes[i])
             mRuntimes[i]->CommenceShutdown();
     }
 
-    Collect(SHUTDOWN_COLLECTIONS(mParams));
+    Collect(SHUTDOWN_COLLECTIONS(mParams), nsnull);
 
 #ifdef DEBUG_CC
-    GCGraphBuilder builder(mGraph, mRuntimes);
+    GCGraphBuilder builder(mGraph, mRuntimes, nsnull);
     mScanInProgress = PR_TRUE;
     SelectPurple(builder);
     mScanInProgress = PR_FALSE;
     if (builder.Count() != 0) {
         printf("Might have been able to release more cycles if the cycle collector would "
                "run once more at shutdown.\n");
     }
     ClearGraph();
@@ -2765,17 +2805,17 @@ nsCycleCollector::ExplainLiveExpectedGar
                "  cycle collection disabled\n");
         return;
     }
 
     mCollectionInProgress = PR_TRUE;
     mScanInProgress = PR_TRUE;
 
     {
-        GCGraphBuilder builder(mGraph, mRuntimes);
+        GCGraphBuilder builder(mGraph, mRuntimes, nsnull);
 
         // Instead of adding roots from the purple buffer, we add them
         // from the list of nodes we were expected to collect.
         // Put the expected garbage in *before* calling
         // BeginCycleCollection so that we can separate the expected
         // garbage from the NoteRoot calls in such a way that something
         // that's in both is considered expected garbage.
         mExpectedGarbage.EnumerateEntries(&AddExpectedGarbage, &builder);
@@ -3158,19 +3198,19 @@ NS_CycleCollectorSuspect2(nsISupports *n
 PRBool
 NS_CycleCollectorForget2(nsPurpleBufferEntry *e)
 {
     return sCollector ? sCollector->Forget2(e) : PR_TRUE;
 }
 
 
 PRUint32
-nsCycleCollector_collect()
+nsCycleCollector_collect(nsICycleCollectorListener *aListener)
 {
-    return sCollector ? sCollector->Collect() : 0;
+    return sCollector ? sCollector->Collect(1, aListener) : 0;
 }
 
 PRUint32
 nsCycleCollector_suspectedCount()
 {
     return sCollector ? sCollector->SuspectedCount() : 0;
 }
 
--- a/xpcom/base/nsCycleCollector.h
+++ b/xpcom/base/nsCycleCollector.h
@@ -38,16 +38,17 @@
 #ifndef nsCycleCollector_h__
 #define nsCycleCollector_h__
 
 // NOTE: If you use header files to define DEBUG_CC, you must do so here
 // *and* in nsCycleCollectionParticipant.h
 //#define DEBUG_CC
 
 class nsISupports;
+class nsICycleCollectorListener;
 class nsCycleCollectionParticipant;
 class nsCycleCollectionTraversalCallback;
 
 // An nsCycleCollectionLanguageRuntime is a per-language object that
 // implements language-specific aspects of the cycle collection task.
 
 struct nsCycleCollectionLanguageRuntime
 {
@@ -58,17 +59,17 @@ struct nsCycleCollectionLanguageRuntime
     virtual void CommenceShutdown() = 0;
 #ifdef DEBUG_CC
     virtual void PrintAllReferencesTo(void *p) = 0;
 #endif
 };
 
 nsresult nsCycleCollector_startup();
 // Returns the number of collected nodes.
-NS_COM PRUint32 nsCycleCollector_collect();
+NS_COM PRUint32 nsCycleCollector_collect(nsICycleCollectorListener *aListener);
 NS_COM PRUint32 nsCycleCollector_suspectedCount();
 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
 // nsCycleCollector_doCollect directly.
@@ -86,9 +87,18 @@ NS_COM void nsCycleCollector_DEBUG_wasFr
 #endif
 
 // Helpers for interacting with language-identified scripts
 
 NS_COM void nsCycleCollector_registerRuntime(PRUint32 langID, nsCycleCollectionLanguageRuntime *rt);
 NS_COM nsCycleCollectionLanguageRuntime * nsCycleCollector_getRuntime(PRUint32 langID);
 NS_COM void nsCycleCollector_forgetRuntime(PRUint32 langID);
 
+#define NS_CYCLE_COLLECTOR_LOGGER_CID \
+{ 0x58be81b4, 0x39d2, 0x437c, \
+{ 0x94, 0xea, 0xae, 0xde, 0x2c, 0x62, 0x08, 0xd3 } }
+
+extern nsresult
+nsCycleCollectorLoggerConstructor(nsISupports* outer,
+                                  const nsIID& aIID,
+                                  void* *aInstancePtr);
+
 #endif // nsCycleCollector_h__
new file mode 100644
--- /dev/null
+++ b/xpcom/base/nsICycleCollectorListener.idl
@@ -0,0 +1,64 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is XPCOM cycle collector.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+
+/**
+ * Interface to pass to the cycle collector to get information about the CC
+ * graph while it's being built. The order of calls will be call to begin();
+ * then for every node in the graph a call to noteObject() and calls to
+ * noteEdge() for every edge starting at that node; then a call to
+ * beginDescriptions(); then for every black node in the CC graph a call to
+ * either describeRefcountedObject() or to describeGCedObject(); and then a
+ * call to end(). If begin() returns an error none of the other functions will
+ * be called.
+ */
+[scriptable, uuid(194b749a-4ceb-4dd1-928d-d30b5f14c23e)]
+interface nsICycleCollectorListener : nsISupports
+{
+    void begin();
+    void noteObject(in unsigned long long aAddress,
+                    in string aObjectDescription);
+    void noteEdge(in unsigned long long aFromAddress,
+                  in unsigned long long aToAddress,
+                  in string aEdgeName);
+    void beginDescriptions();
+    void describeRefcountedObject(in unsigned long long aAddress,
+                                  in unsigned long aKnownEdges,
+                                  in unsigned long aTotalEdges);
+    void describeGCedObject(in unsigned long long aAddress, in boolean aMarked);
+    void end();
+};
--- a/xpcom/build/XPCOMModule.inc
+++ b/xpcom/build/XPCOMModule.inc
@@ -82,8 +82,9 @@
 
 #ifdef XP_MACOSX
     COMPONENT(MACUTILSIMPL, nsMacUtilsImplConstructor)
 #endif
 
     COMPONENT(SYSTEMINFO, nsSystemInfoConstructor)
     COMPONENT(MEMORY_REPORTER_MANAGER, nsMemoryReporterManagerConstructor)
     COMPONENT(IOUTIL, nsIOUtilConstructor)
+    COMPONENT(CYCLE_COLLECTOR_LOGGER, nsCycleCollectorLoggerConstructor)
--- a/xpcom/build/dlldeps.cpp
+++ b/xpcom/build/dlldeps.cpp
@@ -271,17 +271,17 @@ void XXXNeverCalled()
       nsStringBuffer::Realloc(nsnull, 0);
       nsStringBuffer::FromString(x);
       nsStringBuffer::FromString(y);
       b.ToString(0, x);
       b.ToString(0, y);
     }
 
     nsXPCOMCycleCollectionParticipant();
-    nsCycleCollector_collect();
+    nsCycleCollector_collect(nsnull);
 #ifdef XP_WIN
     sXPCOMHasLoadedNewDLLs = !sXPCOMHasLoadedNewDLLs;
     NS_SetHasLoadedNewDLLs();
     NS_NewWindowsRegKey(nsnull);
 #if defined (DEBUG) && !defined (WINCE)
     PurePrintf(0);
 #endif
 #endif
--- a/xpcom/build/nsXPCOMCID.h
+++ b/xpcom/build/nsXPCOMCID.h
@@ -98,16 +98,21 @@
 #define NS_IOUTIL_CONTRACTID "@mozilla.org/io-util;1"
 
 /**
  * Memory reporter service CID
  */
 #define NS_MEMORY_REPORTER_MANAGER_CONTRACTID "@mozilla.org/memory-reporter-manager;1"
 
 /**
+ * Cycle collector logger contract id
+ */
+#define NS_CYCLE_COLLECTOR_LOGGER_CONTRACTID "@mozilla.org/cycle-collector-logger;1"
+
+/**
  * The following are the CIDs and Contract IDs of the nsISupports wrappers for 
  * primative types.  
  */
 #define NS_SUPPORTS_ID_CID \
 { 0xacf8dc40, 0x4a25, 0x11d3, \
 { 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 } }
 #define NS_SUPPORTS_ID_CONTRACTID "@mozilla.org/supports-id;1"