Bug 666075 - Add memory multi-reporters. r=khuey, sr=bz.
authorNicholas Nethercote <nnethercote@mozilla.com>
Thu, 30 Jun 2011 09:39:32 +1000
changeset 72051 a053ab0d863366389a0e70a24ddc8c86d8fd48c1
parent 72050 ab22fa0762ae7d4650bb66372ead306bc07175b1
child 72052 e6f9648715c163aed0c42058cec55b05ef7a493c
push idunknown
push userunknown
push dateunknown
reviewerskhuey, bz
bugs666075
milestone7.0a1
Bug 666075 - Add memory multi-reporters. r=khuey, sr=bz.
dom/ipc/ContentChild.cpp
toolkit/components/aboutmemory/content/aboutMemory.js
toolkit/components/aboutmemory/tests/chrome/test_aboutmemory.xul
xpcom/base/nsIMemoryReporter.idl
xpcom/base/nsMemoryReporterManager.cpp
xpcom/base/nsMemoryReporterManager.h
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -294,52 +294,102 @@ ContentChild::InitXPCOM()
 }
 
 PMemoryReportRequestChild*
 ContentChild::AllocPMemoryReportRequest()
 {
     return new MemoryReportRequestChild();
 }
 
+// This is just a wrapper for InfallibleTArray<MemoryReport> that implements
+// nsISupports, so it can be passed to nsIMemoryMultiReporter::CollectReports.
+class MemoryReportsWrapper : public nsISupports {
+public:
+    NS_DECL_ISUPPORTS
+    MemoryReportsWrapper(InfallibleTArray<MemoryReport> *r) : mReports(r) { }
+    InfallibleTArray<MemoryReport> *mReports;
+};
+NS_IMPL_ISUPPORTS0(MemoryReportsWrapper)
+
+class MemoryReportCallback : public nsIMemoryMultiReporterCallback
+{
+public:
+    NS_DECL_ISUPPORTS
+
+    MemoryReportCallback(const nsACString &aProcess)
+    : mProcess(aProcess)
+    {
+    }
+
+    NS_IMETHOD Callback(const nsACString &aProcess, const nsACString &aPath,
+                        PRInt32 aKind, PRInt32 aUnits, PRInt64 aAmount,
+                        const nsACString &aDescription,
+                        nsISupports *aiWrappedReports)
+    {
+        MemoryReportsWrapper *wrappedReports =
+            static_cast<MemoryReportsWrapper *>(aiWrappedReports);
+
+        MemoryReport memreport(mProcess, nsCString(aPath), aKind, aUnits,
+                               aAmount, nsCString(aDescription));
+        wrappedReports->mReports->AppendElement(memreport);
+        return NS_OK;
+    }
+private:
+    const nsCString mProcess;
+};
+NS_IMPL_ISUPPORTS1(
+  MemoryReportCallback
+, nsIMemoryMultiReporterCallback
+)
+
 bool
 ContentChild::RecvPMemoryReportRequestConstructor(PMemoryReportRequestChild* child)
 {
-    InfallibleTArray<MemoryReport> reports;
     
     nsCOMPtr<nsIMemoryReporterManager> mgr = do_GetService("@mozilla.org/memory-reporter-manager;1");
-    nsCOMPtr<nsISimpleEnumerator> r;
-    mgr->EnumerateReporters(getter_AddRefs(r));
+
+    InfallibleTArray<MemoryReport> reports;
 
+    static const int maxLength = 31;   // big enough; pid is only a few chars
+    nsPrintfCString process(maxLength, "Content (%d)", getpid());
+
+    // First do the vanilla memory reporters.
+    nsCOMPtr<nsISimpleEnumerator> e;
+    mgr->EnumerateReporters(getter_AddRefs(e));
     PRBool more;
-    while (NS_SUCCEEDED(r->HasMoreElements(&more)) && more) {
-      nsCOMPtr<nsIMemoryReporter> report;
-      r->GetNext(getter_AddRefs(report));
+    while (NS_SUCCEEDED(e->HasMoreElements(&more)) && more) {
+      nsCOMPtr<nsIMemoryReporter> r;
+      e->GetNext(getter_AddRefs(r));
 
       nsCString path;
       PRInt32 kind;
       PRInt32 units;
+      PRInt64 amount;
       nsCString desc;
-      PRInt64 amount;
-      report->GetPath(getter_Copies(path));
-      report->GetKind(&kind);
-      report->GetUnits(&units);
-      report->GetAmount(&amount);
-      report->GetDescription(getter_Copies(desc));
+      r->GetPath(getter_Copies(path));
+      r->GetKind(&kind);
+      r->GetUnits(&units);
+      r->GetAmount(&amount);
+      r->GetDescription(getter_Copies(desc));
+
+      MemoryReport memreport(process, path, kind, units, amount, desc);
+      reports.AppendElement(memreport);
+    }
 
-      static const int maxLength = 31;   // big enough; pid is only a few chars
-      MemoryReport memreport(nsPrintfCString(maxLength, "Content (%d)",
-                                             getpid()),
-                             path,
-                             kind,
-                             units,
-                             amount,
-                             desc);
+    // Then do the memory multi-reporters, by calling CollectReports on each
+    // one, whereupon the callback will turn each measurement into a
+    // MemoryReport.
+    mgr->EnumerateMultiReporters(getter_AddRefs(e));
+    MemoryReportsWrapper wrappedReports(&reports);
+    MemoryReportCallback cb(process);
+    while (NS_SUCCEEDED(e->HasMoreElements(&more)) && more) {
+      nsCOMPtr<nsIMemoryMultiReporter> r;
+      e->GetNext(getter_AddRefs(r));
 
-      reports.AppendElement(memreport);
-
+      r->CollectReports(&cb, &wrappedReports);
     }
 
     child->Send__delete__(child, reports);
     return true;
 }
 
 bool
 ContentChild::DeallocPMemoryReportRequest(PMemoryReportRequestChild* actor)
--- a/toolkit/components/aboutmemory/content/aboutMemory.js
+++ b/toolkit/components/aboutmemory/content/aboutMemory.js
@@ -162,40 +162,53 @@ function update()
   //   After this point we never use the original memory reporter again.
   //
   // - Note that copying rOrig.amount (which calls a C++ function under the
   //   IDL covers) to r._amount for every reporter now means that the
   //   results as consistent as possible -- measurements are made all at
   //   once before most of the memory required to generate this page is
   //   allocated.
   var reportersByProcess = {};
-  var e = mgr.enumerateReporters();
-  while (e.hasMoreElements()) {
-    var rOrig = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
-    var process = rOrig.process === "" ? "Main" : rOrig.process;
+
+  function addReporter(aProcess, aPath, aKind, aUnits, aAmount, aDescription)
+  {
+    var process = aProcess === "" ? "Main" : aProcess;
     var r = {
-      _path:        rOrig.path,
-      _kind:        rOrig.kind,
-      _units:       rOrig.units,
-      _amount:      rOrig.amount,
-      _description: rOrig.description
+      _path:        aPath,
+      _kind:        aKind,
+      _units:       aUnits,
+      _amount:      aAmount,
+      _description: aDescription
     };
     if (!reportersByProcess[process]) {
       reportersByProcess[process] = {};
     }
     var reporters = reportersByProcess[process];
     if (reporters[r._path]) {
       // Already an entry;  must be a duplicated reporter.  This can
       // happen legitimately.  Sum the values.
       reporters[r._path]._amount += r._amount;
     } else {
       reporters[r._path] = r;
     }
   }
 
+  // Process vanilla reporters first, then multi-reporters.
+  var e = mgr.enumerateReporters();
+  while (e.hasMoreElements()) {
+    var rOrig = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
+    addReporter(rOrig.process, rOrig.path, rOrig.kind, rOrig.units,
+                rOrig.amount, rOrig.description);
+  }
+  var e = mgr.enumerateMultiReporters();
+  while (e.hasMoreElements()) {
+    var r = e.getNext().QueryInterface(Ci.nsIMemoryMultiReporter);
+    r.collectReports(addReporter, null);
+  }
+
   // Generate output for one process at a time.  Always start with the
   // Main process.
   var text = genProcessText("Main", reportersByProcess["Main"]);
   for (var process in reportersByProcess) {
     if (process !== "Main") {
       text += genProcessText(process, reportersByProcess[process]);
     }
   }
@@ -237,17 +250,17 @@ function update()
 }
 
 // Compare two memory reporter nodes.  We want to group together measurements
 // with the same units, so sort first by the nodes' _units field, then sort by
 // the amount if the units are equal.
 function cmp_amount(a, b)
 {
   if (a._units != b._units)
-    return b._units - a._units;
+    return a._units - b._units;   // use the enum order from nsIMemoryReporter
   else
     return b._amount - a._amount;
 };
 
 /**
  * Generates the text for a single process.
  *
  * @param aProcess
--- a/toolkit/components/aboutmemory/tests/chrome/test_aboutmemory.xul
+++ b/toolkit/components/aboutmemory/tests/chrome/test_aboutmemory.xul
@@ -13,27 +13,39 @@
   <!-- test code goes here -->
   <script type="application/javascript">
   <![CDATA[
   const Cc = Components.classes;
   const Ci = Components.interfaces;
   var mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
             getService(Ci.nsIMemoryReporterManager);
 
-  // Remove all the real reporters;  save them to restore at the end.
+  // Remove all the real reporters and multi-reporters;  save them to
+  // restore at the end.
   var e = mgr.enumerateReporters(); 
   var realReporters = [];
   var dummy = 0;
   while (e.hasMoreElements()) {
-    var mr = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
-    // Get the memoryUsed field, even though we don't use it, just to test
+    var r = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
+    // Get the |amount| field, even though we don't use it, just to test
     // that the reporter doesn't crash or anything.
-    dummy += mr.memoryUsed;
-    mgr.unregisterReporter(mr);
-    realReporters.push(mr);
+    dummy += r.amount;
+    mgr.unregisterReporter(r);
+    realReporters.push(r);
+  }
+  e = mgr.enumerateMultiReporters(); 
+  var realMultiReporters = [];
+  var dummy = 0;
+  while (e.hasMoreElements()) {
+    var r = e.getNext().QueryInterface(Ci.nsIMemoryMultiReporter);
+    // Call collectReports, even though we don't use its results, just to
+    // test that the multi-reporter doesn't crash or anything.
+    r.collectReports(function(){}, null);
+    mgr.unregisterMultiReporter(r);
+    realMultiReporters.push(r);
   }
 
   // Setup various fake-but-deterministic reporters.
   const KB = 1024;
   const MB = KB * KB;
   const kUnknown = -1;
   const MAPPED = Ci.nsIMemoryReporter.KIND_MAPPED;
   const HEAP   = Ci.nsIMemoryReporter.KIND_HEAP;
@@ -219,24 +231,27 @@ 814,743,552 B (100.0%) -- explicit\n\
 Other Measurements\n\
 0 B -- heap-used [*]\n\
 0 B -- other1 [*]\n\
 \n\
 "
 
   function finish()
   {
-    // Unregister fake reporters, re-register the real reporters, just in
-    // case subsequent tests rely on them.
+    // Unregister fake reporters, re-register the real reporters and
+    // multi-reporters, just in case subsequent tests rely on them.
     for (var i = 0; i < fakeReporters.length; i++) {
       mgr.unregisterReporter(fakeReporters[i]);
     }
     for (var i = 0; i < realReporters.length; i++) {
       mgr.registerReporter(realReporters[i]);
     }
+    for (var i = 0; i < realMultiReporters.length; i++) {
+      mgr.registerMultiReporter(realMultiReporters[i]);
+    }
     SimpleTest.finish();
   }
 
   // Cut+paste the entire page and check that the cut text matches what we
   // expect.  This tests the output in general and also that the cutting and
   // pasting works as expected.
   function test(aFrame, aExpectedText, aNext) {
     document.querySelector("#" + aFrame).focus();
--- a/xpcom/base/nsIMemoryReporter.idl
+++ b/xpcom/base/nsIMemoryReporter.idl
@@ -35,16 +35,26 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
 interface nsISimpleEnumerator;
 
+/*
+ * An nsIMemoryReporter reports a single memory measurement as an object.
+ * Use this when it makes sense to gather this measurement without gathering
+ * related measurements at the same time.
+ *
+ * Note that the |amount| field may be implemented as a function, and so
+ * accessing it can trigger significant computation;  the other fields can
+ * be accessed without triggering this computation.  (Compare and contrast
+ * this with nsIMemoryMultiReporter.)  
+ */
 [scriptable, uuid(37d18434-9819-4ce1-922f-15d8b63da066)]
 interface nsIMemoryReporter : nsISupports
 {
   /*
    * The name of the process containing this reporter.  Each reporter initially
    * has "" in this field, indicating that it applies to the current process.
    * (This is true even for reporters in a child process.)  When a reporter
    * from a child process is copied into the main process, the copy has its
@@ -128,46 +138,91 @@ interface nsIMemoryReporter : nsISupport
   /*
    * The units on the reporter's amount.  See UNITS_* above.
    */
   readonly attribute PRInt32 units;
 
   /*
    * The numeric value reported by this memory reporter.
    */
-  readonly attribute long long amount;
+  readonly attribute PRInt64 amount;
 
   /*
    * A human-readable description of this memory usage report.
    */
   readonly attribute string description;
 };
 
-[scriptable, uuid(7c62de18-1edd-40f8-9da2-a8c622763074)]
+[scriptable, function, uuid(5b15f3fa-ba15-443c-8337-7770f5f0ce5d)]
+interface nsIMemoryMultiReporterCallback : nsISupports
+{
+  void callback(in ACString process, in AUTF8String path, in PRInt32 kind,
+                in PRInt32 units, in PRInt64 amount,
+                in AUTF8String description, in nsISupports closure);
+};
+
+/*
+ * An nsIMemoryMultiReporter reports multiple memory measurements via a
+ * callback function which is called once for each measurement.  Use this
+ * when you want to gather multiple measurements in a single operation (eg.
+ * a single traversal of a large data structure).
+ *
+ * The arguments to the callback deliberately match the fields in
+ * nsIMemoryReporter, but note that seeing any of these arguments requires
+ * calling collectReports which will trigger all relevant computation.
+ * (Compare and contrast this with nsIMemoryReporter, which allows all
+ * fields except |amount| to be accessed without triggering computation.)
+ */
+[scriptable, uuid(eae277ad-b67d-4389-95f4-03fa11c09d06)]
+interface nsIMemoryMultiReporter : nsISupports
+{
+  void collectReports(in nsIMemoryMultiReporterCallback callback,
+                      in nsISupports closure);
+};
+
+[scriptable, uuid(80a93b4c-6fff-4acd-8598-3891074a30ab)]
 interface nsIMemoryReporterManager : nsISupports
 {
   /*
    * Return an enumerator of nsIMemoryReporters that are currently registered.
    */
   nsISimpleEnumerator enumerateReporters ();
 
   /*
-   * Register the given nsIMemoryReporter.  It is an error to register
-   * more than one reporter with the same path.  After a reporter is
-   * registered, it will be available via enumerateReporters().  The
-   * Manager service will hold a strong reference to the given reporter.
+   * Return an enumerator of nsIMemoryMultiReporters that are currently
+   * registered.
+   */
+  nsISimpleEnumerator enumerateMultiReporters ();
+
+  /*
+   * Register the given nsIMemoryReporter.  After a reporter is registered,
+   * it will be available via enumerateReporters().  The Manager service
+   * will hold a strong reference to the given reporter.
    */
   void registerReporter (in nsIMemoryReporter reporter);
 
   /*
+   * Register the given nsIMemoryMultiReporter.  After a multi-reporter is
+   * registered, it will be available via enumerateMultiReporters().  The
+   * Manager service will hold a strong reference to the given
+   * multi-reporter.
+   */
+  void registerMultiReporter (in nsIMemoryMultiReporter reporter);
+
+  /*
    * Unregister the given memory reporter.
    */
   void unregisterReporter (in nsIMemoryReporter reporter);
 
   /*
+   * Unregister the given memory multi-reporter.
+   */
+  void unregisterMultiReporter (in nsIMemoryMultiReporter reporter);
+
+  /*
    * Initialize.
    */
   void init ();
 };
 
 %{C++
 
 /*
@@ -184,11 +239,14 @@ interface nsIMemoryReporterManager : nsI
       NS_IMETHOD GetAmount(PRInt64 *amount) { *amount = _usageFunction(); return NS_OK; }     \
       NS_IMETHOD GetDescription(char **desc) { *desc = strdup(_desc); return NS_OK; }         \
     };                                                                                        \
     NS_IMPL_ISUPPORTS1(MemoryReporter_##_classname, nsIMemoryReporter)
 
 #define NS_MEMORY_REPORTER_NAME(_classname)  MemoryReporter_##_classname
 
 NS_COM nsresult NS_RegisterMemoryReporter (nsIMemoryReporter *reporter);
+NS_COM nsresult NS_RegisterMemoryMultiReporter (nsIMemoryMultiReporter *reporter);
+
 NS_COM nsresult NS_UnregisterMemoryReporter (nsIMemoryReporter *reporter);
+NS_COM nsresult NS_UnregisterMemoryMultiReporter (nsIMemoryMultiReporter *reporter);
 
 %}
--- a/xpcom/base/nsMemoryReporterManager.cpp
+++ b/xpcom/base/nsMemoryReporterManager.cpp
@@ -433,36 +433,66 @@ nsMemoryReporterManager::EnumerateReport
 {
     nsresult rv;
     mozilla::MutexAutoLock autoLock(mMutex);
     rv = NS_NewArrayEnumerator(result, mReporters);
     return rv;
 }
 
 NS_IMETHODIMP
+nsMemoryReporterManager::EnumerateMultiReporters(nsISimpleEnumerator **result)
+{
+    nsresult rv;
+    mozilla::MutexAutoLock autoLock(mMutex);
+    rv = NS_NewArrayEnumerator(result, mMultiReporters);
+    return rv;
+}
+
+NS_IMETHODIMP
 nsMemoryReporterManager::RegisterReporter(nsIMemoryReporter *reporter)
 {
     mozilla::MutexAutoLock autoLock(mMutex);
     if (mReporters.IndexOf(reporter) != -1)
         return NS_ERROR_FAILURE;
 
     mReporters.AppendObject(reporter);
     return NS_OK;
 }
 
 NS_IMETHODIMP
+nsMemoryReporterManager::RegisterMultiReporter(nsIMemoryMultiReporter *reporter)
+{
+    mozilla::MutexAutoLock autoLock(mMutex);
+    if (mMultiReporters.IndexOf(reporter) != -1)
+        return NS_ERROR_FAILURE;
+
+    mMultiReporters.AppendObject(reporter);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
 nsMemoryReporterManager::UnregisterReporter(nsIMemoryReporter *reporter)
 {
     mozilla::MutexAutoLock autoLock(mMutex);
     if (!mReporters.RemoveObject(reporter))
         return NS_ERROR_FAILURE;
 
     return NS_OK;
 }
 
+NS_IMETHODIMP
+nsMemoryReporterManager::UnregisterMultiReporter(nsIMemoryMultiReporter *reporter)
+{
+    mozilla::MutexAutoLock autoLock(mMutex);
+    if (!mMultiReporters.RemoveObject(reporter))
+        return NS_ERROR_FAILURE;
+
+    return NS_OK;
+}
+
 NS_IMPL_ISUPPORTS1(nsMemoryReporter, nsIMemoryReporter)
 
 nsMemoryReporter::nsMemoryReporter(nsCString& process,
                                    nsCString& path,
                                    PRInt32 kind,
                                    PRInt32 units,
                                    PRInt64 amount,
                                    nsCString& desc)
@@ -510,27 +540,44 @@ NS_IMETHODIMP nsMemoryReporter::GetAmoun
 }
 
 NS_IMETHODIMP nsMemoryReporter::GetDescription(char **aDescription)
 {
     *aDescription = strdup(mDesc.get());
     return NS_OK;
 }
 
-
 NS_COM nsresult
 NS_RegisterMemoryReporter (nsIMemoryReporter *reporter)
 {
     nsCOMPtr<nsIMemoryReporterManager> mgr = do_GetService("@mozilla.org/memory-reporter-manager;1");
     if (mgr == nsnull)
         return NS_ERROR_FAILURE;
     return mgr->RegisterReporter(reporter);
 }
 
 NS_COM nsresult
+NS_RegisterMemoryMultiReporter (nsIMemoryMultiReporter *reporter)
+{
+    nsCOMPtr<nsIMemoryReporterManager> mgr = do_GetService("@mozilla.org/memory-reporter-manager;1");
+    if (mgr == nsnull)
+        return NS_ERROR_FAILURE;
+    return mgr->RegisterMultiReporter(reporter);
+}
+
+NS_COM nsresult
 NS_UnregisterMemoryReporter (nsIMemoryReporter *reporter)
 {
     nsCOMPtr<nsIMemoryReporterManager> mgr = do_GetService("@mozilla.org/memory-reporter-manager;1");
     if (mgr == nsnull)
         return NS_ERROR_FAILURE;
     return mgr->UnregisterReporter(reporter);
 }
 
+NS_COM nsresult
+NS_UnregisterMemoryMultiReporter (nsIMemoryMultiReporter *reporter)
+{
+    nsCOMPtr<nsIMemoryReporterManager> mgr = do_GetService("@mozilla.org/memory-reporter-manager;1");
+    if (mgr == nsnull)
+        return NS_ERROR_FAILURE;
+    return mgr->UnregisterMultiReporter(reporter);
+}
+
--- a/xpcom/base/nsMemoryReporterManager.h
+++ b/xpcom/base/nsMemoryReporterManager.h
@@ -35,15 +35,16 @@ class nsMemoryReporterManager : public n
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIMEMORYREPORTERMANAGER
 
     nsMemoryReporterManager();
     virtual ~nsMemoryReporterManager();
 
 private:
-    nsCOMArray<nsIMemoryReporter> mReporters;
-    Mutex                         mMutex;
+    nsCOMArray<nsIMemoryReporter>      mReporters;
+    nsCOMArray<nsIMemoryMultiReporter> mMultiReporters;
+    Mutex                              mMutex;
 };
 
 #define NS_MEMORY_REPORTER_MANAGER_CID \
 { 0xfb97e4f5, 0x32dd, 0x497a, \
 { 0xba, 0xa2, 0x7d, 0x1e, 0x55, 0x7, 0x99, 0x10 } }