Bug 925416 - Report on memory used by blob URLs. r=khuey
☠☠ backed out by 441af05d123b ☠ ☠
authorJed Davis <jld@mozilla.com>
Fri, 10 Jan 2014 08:40:48 -0500
changeset 162858 8ce9e632939e09000280042d19ba010718ac92b6
parent 162857 508ad49212c05a35643656ac554ba7a480a48020
child 162859 1da908db059b5d902d3117b98a7a89e70563cd96
push id38320
push userryanvm@gmail.com
push dateFri, 10 Jan 2014 13:42:45 +0000
treeherdermozilla-inbound@12a4941ca425 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskhuey
bugs925416
milestone29.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 925416 - Report on memory used by blob URLs. r=khuey By default, the reporter path includes the blob owner's URI and the blob URI in the path. If a blob has multiple references, its size is divided among them so that the totals still work; the blob's address and refcount are included in the path (to find the other refs), and all of this is noted in the description. If memory.blob_report.stack_frames is a nonzero value, that many JS stack frames (file and line number, if available) will be added to the path, with the file root-relative to the owner if same-origin.
content/base/src/nsHostObjectProtocolHandler.cpp
modules/libpref/src/init/all.js
--- a/content/base/src/nsHostObjectProtocolHandler.cpp
+++ b/content/base/src/nsHostObjectProtocolHandler.cpp
@@ -9,24 +9,26 @@
 #include "nsError.h"
 #include "nsClassHashtable.h"
 #include "nsNetUtil.h"
 #include "nsIPrincipal.h"
 #include "nsIDOMFile.h"
 #include "nsIDOMMediaStream.h"
 #include "mozilla/dom/MediaSource.h"
 #include "nsIMemoryReporter.h"
+#include "mozilla/Preferences.h"
 
 // -----------------------------------------------------------------------
 // Hash table
 struct DataInfo
 {
   // mObject is expected to be an nsIDOMBlob, nsIDOMMediaStream, or MediaSource
   nsCOMPtr<nsISupports> mObject;
   nsCOMPtr<nsIPrincipal> mPrincipal;
+  nsCString mStack;
 };
 
 static nsClassHashtable<nsCStringHashKey, DataInfo>* gDataTable;
 
 // Memory reporting for the hash table.
 namespace mozilla {
 
 class HostObjectURLsReporter MOZ_FINAL : public nsIMemoryReporter
@@ -42,28 +44,213 @@ class HostObjectURLsReporter MOZ_FINAL :
       gDataTable ? gDataTable->Count() : 0,
       "The number of host objects stored for access via URLs "
       "(e.g. blobs passed to URL.createObjectURL).");
   }
 };
 
 NS_IMPL_ISUPPORTS1(HostObjectURLsReporter, nsIMemoryReporter)
 
+class BlobURLsReporter MOZ_FINAL : public nsIMemoryReporter
+{
+ public:
+  NS_DECL_ISUPPORTS
+
+  NS_IMETHOD CollectReports(nsIHandleReportCallback* aCallback,
+                            nsISupports* aData)
+  {
+    EnumArg env;
+    env.mCallback = aCallback;
+    env.mData = aData;
+
+    if (gDataTable) {
+      gDataTable->EnumerateRead(CountCallback, &env);
+      gDataTable->EnumerateRead(ReportCallback, &env);
+    }
+    return NS_OK;
+  }
+
+  // Initialize info->mStack to record JS stack info, if enabled.
+  // The string generated here is used in ReportCallback, below.
+  static void GetJSStackForBlob(DataInfo* aInfo)
+  {
+    nsCString& stack = aInfo->mStack;
+    MOZ_ASSERT(stack.IsEmpty());
+    const uint32_t maxFrames = Preferences::GetUint("memory.blob_report.stack_frames");
+
+    if (maxFrames == 0) {
+      return;
+    }
+
+    nsresult rv;
+    nsIXPConnect* xpc = nsContentUtils::XPConnect();
+    nsCOMPtr<nsIStackFrame> frame;
+    rv = xpc->GetCurrentJSStack(getter_AddRefs(frame));
+    NS_ENSURE_SUCCESS_VOID(rv);
+
+    nsAutoCString origin;
+    nsCOMPtr<nsIURI> principalURI;
+    if (NS_SUCCEEDED(aInfo->mPrincipal->GetURI(getter_AddRefs(principalURI)))
+        && principalURI) {
+      principalURI->GetPrePath(origin);
+    }
+
+    for (uint32_t i = 0; i < maxFrames && frame; ++i) {
+      nsAutoCString fileNameEscaped;
+      char* fileName = nullptr;
+      int32_t lineNumber = 0;
+
+      frame->GetFilename(&fileName);
+      frame->GetLineNumber(&lineNumber);
+
+      if (fileName != nullptr && fileName[0] != '\0') {
+        stack += "js(";
+        fileNameEscaped = fileName;
+        if (!origin.IsEmpty()) {
+          // Make the file name root-relative for conciseness if possible.
+          const char* originData;
+          uint32_t originLen;
+
+          originLen = origin.GetData(&originData);
+          // If fileName starts with origin + "/", cut up to that "/".
+          if (strlen(fileName) >= originLen + 1 &&
+              memcmp(fileName, originData, originLen) == 0 &&
+              fileName[originLen] == '/') {
+            fileNameEscaped.Cut(0, originLen);
+          }
+        }
+        fileNameEscaped.ReplaceChar('/', '\\');
+        stack += fileNameEscaped;
+        if (lineNumber > 0) {
+          stack += ", line=";
+          stack.AppendInt(lineNumber);
+        }
+        stack += ")/";
+      }
+
+      rv = frame->GetCaller(getter_AddRefs(frame));
+      NS_ENSURE_SUCCESS_VOID(rv);
+    }
+  }
+
+ private:
+  struct EnumArg {
+    nsIHandleReportCallback* mCallback;
+    nsISupports* mData;
+    nsDataHashtable<nsPtrHashKey<nsIDOMBlob>, size_t> mRefCounts;
+  };
+
+  // Determine number of URLs per blob, to handle the case where it's > 1.
+  static PLDHashOperator CountCallback(nsCStringHashKey::KeyType aKey,
+                                       DataInfo* aInfo,
+                                       void* aUserArg)
+  {
+    EnumArg* envp = static_cast<EnumArg*>(aUserArg);
+    nsCOMPtr<nsIDOMBlob> blob;
+
+    blob = do_QueryInterface(aInfo->mObject);
+    if (blob) {
+      envp->mRefCounts.Put(blob, envp->mRefCounts.Get(blob) + 1);
+    }
+    return PL_DHASH_NEXT;
+  }
+
+  static PLDHashOperator ReportCallback(nsCStringHashKey::KeyType aKey,
+                                        DataInfo* aInfo,
+                                        void* aUserArg)
+  {
+    EnumArg* envp = static_cast<EnumArg*>(aUserArg);
+    nsCOMPtr<nsIDOMBlob> blob;
+
+    blob = do_QueryInterface(aInfo->mObject);
+    if (blob) {
+      NS_NAMED_LITERAL_CSTRING
+        (desc, "A blob URL allocated with URL.createObjectURL; the referenced "
+         "blob cannot be freed until all URLs for it have been explicitly "
+         "invalidated with URL.revokeObjectURL.");
+      nsAutoCString path, url, owner, specialDesc;
+      nsCOMPtr<nsIURI> principalURI;
+      uint64_t size;
+      size_t refCount = 1;
+      DebugOnly<bool> blobWasCounted;
+
+      blobWasCounted = envp->mRefCounts.Get(blob, &refCount);
+      MOZ_ASSERT(blobWasCounted);
+      MOZ_ASSERT(refCount > 0);
+
+      if (NS_FAILED(blob->GetSize(&size))) {
+        size = 0;
+      }
+
+      path = "blob-urls/";
+      if (NS_SUCCEEDED(aInfo->mPrincipal->GetURI(getter_AddRefs(principalURI))) &&
+          NS_SUCCEEDED(principalURI->GetSpec(owner)) &&
+          !owner.IsEmpty()) {
+        owner.ReplaceChar('/', '\\');
+        path += "owner(";
+        path += owner;
+        path += ")";
+      } else {
+        path += "owner unknown";
+      }
+      path += "/";
+      path += aInfo->mStack;
+      url = aKey;
+      url.ReplaceChar('/', '\\');
+      path += url;
+      if (refCount > 1) {
+        nsAutoCString addrStr;
+
+        addrStr = "0x";
+        addrStr.AppendInt(reinterpret_cast<uintptr_t>((nsIDOMBlob*)blob), 16);
+
+        path += " ";
+        path.AppendInt(refCount);
+        path += "@";
+        path += addrStr;
+
+        specialDesc = desc;
+        specialDesc += "\n\nNOTE: This blob (address ";
+        specialDesc += addrStr;
+        specialDesc += ") has ";
+        specialDesc.AppendInt(refCount);
+        specialDesc += " URLs; its size is divided ";
+        specialDesc += refCount > 2 ? "among" : "between";
+        specialDesc += " them in this report.";
+      }
+      envp->mCallback->Callback(EmptyCString(),
+                                path,
+                                KIND_OTHER,
+                                UNITS_BYTES,
+                                size / refCount,
+                                (specialDesc.IsEmpty()
+                                 ? static_cast<const nsACString&>(desc)
+                                 : static_cast<const nsACString&>(specialDesc)),
+                                envp->mData);
+    }
+    return PL_DHASH_NEXT;
+  }
+};
+
+NS_IMPL_ISUPPORTS1(BlobURLsReporter, nsIMemoryReporter)
+
 }
 
 nsHostObjectProtocolHandler::nsHostObjectProtocolHandler()
 {
   static bool initialized = false;
 
   if (!initialized) {
     initialized = true;
     RegisterStrongMemoryReporter(new mozilla::HostObjectURLsReporter());
+    RegisterStrongMemoryReporter(new mozilla::BlobURLsReporter());
   }
 }
 
+
 nsresult
 nsHostObjectProtocolHandler::AddDataEntry(const nsACString& aScheme,
                                           nsISupports* aObject,
                                           nsIPrincipal* aPrincipal,
                                           nsACString& aUri)
 {
   nsresult rv = GenerateURIString(aScheme, aUri);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -71,16 +258,17 @@ nsHostObjectProtocolHandler::AddDataEntr
   if (!gDataTable) {
     gDataTable = new nsClassHashtable<nsCStringHashKey, DataInfo>;
   }
 
   DataInfo* info = new DataInfo;
 
   info->mObject = aObject;
   info->mPrincipal = aPrincipal;
+  mozilla::BlobURLsReporter::GetJSStackForBlob(info);
 
   gDataTable->Put(aUri, info);
   return NS_OK;
 }
 
 void
 nsHostObjectProtocolHandler::RemoveDataEntry(const nsACString& aUri)
 {
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -4281,16 +4281,19 @@ pref("memory.ghost_window_timeout_second
 // Disable freeing dirty pages when minimizing memory.
 pref("memory.free_dirty_pages", false);
 
 // Disable the Linux-specific, system-wide memory reporter.
 #ifdef XP_LINUX
 pref("memory.system_memory_reporter", false);
 #endif
 
+// Number of stack frames to capture in createObjectURL for about:memory.
+pref("memory.blob_report.stack_frames", 0);
+
 pref("social.enabled", false);
 // comma separated list of domain origins (e.g. https://domain.com) for
 // providers that can install from their own website without user warnings.
 // entries are
 pref("social.whitelist", "https://mozsocial.cliqz.com,https://now.msn.com,https://mixi.jp");
 // omma separated list of domain origins (e.g. https://domain.com) for directory
 // websites (e.g. AMO) that can install providers for other sites
 pref("social.directories", "https://addons.mozilla.org");