Bug 1480293 - Basic Memory Reporting for WebRender. r=jrmuizel
☠☠ backed out by a1d4f580dfbc ☠ ☠
authorBobby Holley <bobbyholley@gmail.com>
Fri, 07 Sep 2018 16:03:13 -0700
changeset 436482 9762d76da9b33ab583c4e89b4fb642be6fadb981
parent 436481 1cff3816f35ac450f103aa1a8c017afce69cba01
child 436483 1a98e90a6464d389bc544ba1c2f01a9774882c17
push id34645
push userdluca@mozilla.com
push dateSat, 15 Sep 2018 09:47:39 +0000
treeherdermozilla-central@73a2f427e2fd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1480293
milestone64.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 1480293 - Basic Memory Reporting for WebRender. r=jrmuizel Reviewers: jrmuizel, gw Tags: #secure-revision Bug #: 1480293 Differential Revision: https://phabricator.services.mozilla.com/D5719 MozReview-Commit-ID: 1vGl3890CjR
gfx/layers/ipc/CompositorBridgeParent.cpp
gfx/layers/ipc/CompositorBridgeParent.h
gfx/layers/ipc/CompositorManagerParent.cpp
gfx/layers/ipc/CompositorManagerParent.h
gfx/layers/ipc/PCompositorManager.ipdl
gfx/layers/wr/WebRenderMessageUtils.h
gfx/thebes/gfxPlatform.cpp
gfx/webrender_bindings/RenderThread.cpp
gfx/webrender_bindings/RenderThread.h
gfx/webrender_bindings/WebRenderAPI.cpp
gfx/webrender_bindings/WebRenderAPI.h
gfx/webrender_bindings/src/bindings.rs
gfx/webrender_bindings/webrender_ffi_generated.h
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -1930,16 +1930,27 @@ CompositorBridgeParent::NotifyMemoryPres
   if (mWrBridge) {
     RefPtr<wr::WebRenderAPI> api = mWrBridge->GetWebRenderAPI();
     if (api) {
       api->NotifyMemoryPressure();
     }
   }
 }
 
+void
+CompositorBridgeParent::AccumulateMemoryReport(wr::MemoryReport* aReport)
+{
+  if (mWrBridge) {
+    RefPtr<wr::WebRenderAPI> api = mWrBridge->GetWebRenderAPI();
+    if (api) {
+      api->AccumulateMemoryReport(aReport);
+    }
+  }
+}
+
 RefPtr<WebRenderBridgeParent>
 CompositorBridgeParent::GetWebRenderBridgeParent() const
 {
   return mWrBridge;
 }
 
 Maybe<TimeStamp>
 CompositorBridgeParent::GetTestingTimeStamp() const
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -171,16 +171,17 @@ public:
     return false;
   }
 
   virtual void ForceComposeToTarget(gfx::DrawTarget* aTarget, const gfx::IntRect* aRect = nullptr) {
     MOZ_CRASH();
   }
 
   virtual void NotifyMemoryPressure() {}
+  virtual void AccumulateMemoryReport(wr::MemoryReport*) {}
 
 protected:
   ~CompositorBridgeParentBase() override;
 
   bool mCanSend;
 
 private:
   RefPtr<CompositorManagerParent> mCompositorManager;
@@ -234,16 +235,17 @@ public:
 
   // Unused for chrome <-> compositor communication (which this class does).
   // @see CrossProcessCompositorBridgeParent::RecvRequestNotifyAfterRemotePaint
   mozilla::ipc::IPCResult RecvRequestNotifyAfterRemotePaint() override { return IPC_OK(); };
 
   mozilla::ipc::IPCResult RecvAllPluginsCaptured() override;
 
   virtual void NotifyMemoryPressure() override;
+  virtual void AccumulateMemoryReport(wr::MemoryReport*) override;
 
   void ActorDestroy(ActorDestroyReason why) override;
 
   void ShadowLayersUpdated(LayerTransactionParent* aLayerTree,
                            const TransactionInfo& aInfo,
                            bool aHitTestUpdate) override;
   void ScheduleComposite(LayerTransactionParent* aLayerTree) override;
   bool SetTestSampleTime(const LayersId& aId,
--- a/gfx/layers/ipc/CompositorManagerParent.cpp
+++ b/gfx/layers/ipc/CompositorManagerParent.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/layers/CompositorManagerParent.h"
 #include "mozilla/gfx/GPUParent.h"
+#include "mozilla/webrender/RenderThread.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/layers/CrossProcessCompositorBridgeParent.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/SharedSurfacesParent.h"
 #include "nsAutoPtr.h"
 #include "VsyncSource.h"
 
 namespace mozilla {
@@ -294,10 +295,42 @@ CompositorManagerParent::RecvNotifyMemor
   nsTArray<PCompositorBridgeParent*> compositorBridges;
   ManagedPCompositorBridgeParent(compositorBridges);
   for (auto bridge : compositorBridges) {
     static_cast<CompositorBridgeParentBase*>(bridge)->NotifyMemoryPressure();
   }
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+CompositorManagerParent::RecvReportMemory(ReportMemoryResolver&& aResolver)
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+  MemoryReport aggregate;
+  PodZero(&aggregate);
+
+  // Accumulate RenderBackend usage.
+  nsTArray<PCompositorBridgeParent*> compositorBridges;
+  ManagedPCompositorBridgeParent(compositorBridges);
+  for (auto bridge : compositorBridges) {
+    static_cast<CompositorBridgeParentBase*>(bridge)->AccumulateMemoryReport(&aggregate);
+  }
+
+  // Accumulate Renderer usage asynchronously, and resolve.
+  //
+  // Note that the IPDL machinery requires aResolver to be called on this
+  // thread, so we can't just pass it over to the renderer thread. We use
+  // an intermediate MozPromise instead.
+  wr::RenderThread::AccumulateMemoryReport(aggregate)->Then(
+    CompositorThreadHolder::Loop()->SerialEventTarget(), __func__,
+    [resolver = std::move(aResolver)](MemoryReport aReport) {
+      resolver(aReport);
+    },
+    [](bool) {
+      MOZ_ASSERT_UNREACHABLE();
+    }
+  );
+
+  return IPC_OK();
+}
+
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/ipc/CompositorManagerParent.h
+++ b/gfx/layers/ipc/CompositorManagerParent.h
@@ -41,16 +41,18 @@ public:
                                           const gfx::IntSize& aSurfaceSize);
 
   mozilla::ipc::IPCResult RecvAddSharedSurface(const wr::ExternalImageId& aId,
                                                const SurfaceDescriptorShared& aDesc) override;
   mozilla::ipc::IPCResult RecvRemoveSharedSurface(const wr::ExternalImageId& aId) override;
 
   virtual mozilla::ipc::IPCResult RecvNotifyMemoryPressure() override;
 
+  virtual mozilla::ipc::IPCResult RecvReportMemory(ReportMemoryResolver&&) override;
+
   void BindComplete();
   void ActorDestroy(ActorDestroyReason aReason) override;
 
   bool DeallocPCompositorBridgeParent(PCompositorBridgeParent* aActor) override;
   PCompositorBridgeParent* AllocPCompositorBridgeParent(const CompositorBridgeOptions& aOpt) override;
 
 private:
   static StaticRefPtr<CompositorManagerParent> sInstance;
--- a/gfx/layers/ipc/PCompositorManager.ipdl
+++ b/gfx/layers/ipc/PCompositorManager.ipdl
@@ -12,16 +12,17 @@ include "mozilla/layers/WebRenderMessage
 
 using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
 using mozilla::TimeDuration from "mozilla/TimeStamp.h";
 using mozilla::CSSToLayoutDeviceScale from "Units.h";
 using mozilla::gfx::IntSize from "mozilla/gfx/2D.h";
 using mozilla::ipc::SharedMemoryBasic::Handle from "mozilla/ipc/SharedMemoryBasic.h";
 using mozilla::layers::CompositorOptions from "mozilla/layers/CompositorOptions.h";
 using mozilla::wr::ExternalImageId from "mozilla/webrender/WebRenderTypes.h";
+using mozilla::wr::MemoryReport from "mozilla/webrender/WebRenderTypes.h";
 
 namespace mozilla {
 namespace layers {
 
 struct WidgetCompositorOptions {
   CSSToLayoutDeviceScale scale;
   TimeDuration vsyncRate;
   CompositorOptions options;
@@ -72,12 +73,14 @@ parent:
    * See gfx/layers/ipc/PCompositorBridge.ipdl for more details.
    */
   async PCompositorBridge(CompositorBridgeOptions options);
 
   async AddSharedSurface(ExternalImageId aId, SurfaceDescriptorShared aDesc);
   async RemoveSharedSurface(ExternalImageId aId);
 
   async NotifyMemoryPressure();
+
+  async ReportMemory() returns (MemoryReport aReport);
 };
 
 } // layers
 } // mozilla
--- a/gfx/layers/wr/WebRenderMessageUtils.h
+++ b/gfx/layers/wr/WebRenderMessageUtils.h
@@ -172,11 +172,17 @@ template<>
 struct ParamTraits<mozilla::wr::WebRenderError>
   : public ContiguousEnumSerializer<
         mozilla::wr::WebRenderError,
         mozilla::wr::WebRenderError::INITIALIZE,
         mozilla::wr::WebRenderError::Sentinel>
 {
 };
 
+template<>
+struct ParamTraits<mozilla::wr::MemoryReport>
+  : public PlainOldDataSerializer<mozilla::wr::MemoryReport>
+{
+};
+
 } // namespace IPC
 
 #endif // GFX_WEBRENDERMESSAGEUTILS_H
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -626,16 +626,99 @@ static uint32_t GetSkiaGlyphCacheSize()
 
     return cacheSize;
 #else
     return kDefaultGlyphCacheSize;
 #endif // MOZ_WIDGET_ANDROID
 }
 #endif
 
+class WebRenderMemoryReporter final : public nsIMemoryReporter {
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIMEMORYREPORTER
+
+private:
+  ~WebRenderMemoryReporter() = default;
+};
+
+// Memory reporter for WebRender.
+//
+// The reporting within WebRender is manual and incomplete. We could do a much
+// more thorough job by depending on the malloc_size_of crate, but integrating
+// that into WebRender is tricky [1].
+//
+// So the idea is to start with manual reporting for the large allocations
+// detected by DMD, and see how much that can cover in practice (which may
+// require a few rounds of iteration). If that approach turns out to be
+// fundamentally insufficient, we can either duplicate more of the malloc_size_of
+// functionality in WebRender, or deal with the complexity of a gecko-only
+// crate dependency.
+//
+// [1] See https://bugzilla.mozilla.org/show_bug.cgi?id=1480293#c1
+struct WebRenderMemoryReporterHelper {
+  WebRenderMemoryReporterHelper(nsIHandleReportCallback* aCallback, nsISupports* aData)
+    : mCallback(aCallback), mData(aData)
+  {}
+  nsCOMPtr<nsIHandleReportCallback> mCallback;
+  nsCOMPtr<nsISupports> mData;
+
+  void Report(size_t aBytes, const char* aName) const
+  {
+    nsPrintfCString path("explicit/gfx/webrender/%s", aName);
+    mCallback->Callback(EmptyCString(), path,
+                        nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES,
+                        aBytes, EmptyCString(), mData);
+  }
+};
+
+static void
+FinishAsyncMemoryReport()
+{
+  nsCOMPtr<nsIMemoryReporterManager> imgr =
+    do_GetService("@mozilla.org/memory-reporter-manager;1");
+  if (imgr) {
+    imgr->EndReport();
+  }
+}
+
+NS_IMPL_ISUPPORTS(WebRenderMemoryReporter, nsIMemoryReporter)
+
+NS_IMETHODIMP
+WebRenderMemoryReporter::CollectReports(nsIHandleReportCallback* aHandleReport,
+                                        nsISupports* aData, bool aAnonymize)
+{
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(NS_IsMainThread());
+  layers::CompositorManagerChild* manager = CompositorManagerChild::GetInstance();
+  if (!manager) {
+    FinishAsyncMemoryReport();
+    return NS_OK;
+  }
+
+  WebRenderMemoryReporterHelper helper(aHandleReport, aData);
+  manager->SendReportMemory(
+    [=](wr::MemoryReport aReport) {
+      helper.Report(aReport.primitive_stores, "primitive-stores");
+      helper.Report(aReport.clip_stores, "clip-stores");
+      helper.Report(aReport.gpu_cache_metadata, "gpu-cache/metadata");
+      helper.Report(aReport.gpu_cache_cpu_mirror, "gpu-cache/cpu-mirror");
+      helper.Report(aReport.render_tasks, "render-tasks");
+      helper.Report(aReport.hit_testers, "hit-testers");
+      FinishAsyncMemoryReport();
+    },
+    [](mozilla::ipc::ResponseRejectReason aReason) {
+      FinishAsyncMemoryReport();
+    }
+  );
+
+  return NS_OK;
+}
+
+
 void
 gfxPlatform::Init()
 {
     MOZ_RELEASE_ASSERT(!XRE_IsGPUProcess(), "GFX: Not allowed in GPU process.");
     MOZ_RELEASE_ASSERT(NS_IsMainThread(), "GFX: Not in main thread.");
 
     if (gEverInitialized) {
         MOZ_CRASH("Already started???");
@@ -818,16 +901,20 @@ gfxPlatform::Init()
 
     // Request the imgITools service, implicitly initializing ImageLib.
     nsCOMPtr<imgITools> imgTools = do_GetService("@mozilla.org/image/tools;1");
     if (!imgTools) {
       MOZ_CRASH("Could not initialize ImageLib");
     }
 
     RegisterStrongMemoryReporter(new GfxMemoryImageReporter());
+    if (XRE_IsParentProcess()) {
+      RegisterStrongAsyncMemoryReporter(new WebRenderMemoryReporter());
+    }
+
 #ifdef USE_SKIA
     RegisterStrongMemoryReporter(new SkMemoryReporter());
 #endif
     mlg::InitializeMemoryReporters();
 
 #ifdef USE_SKIA
     uint32_t skiaCacheSize = GetSkiaGlyphCacheSize();
     if (skiaCacheSize != kDefaultGlyphCacheSize) {
--- a/gfx/webrender_bindings/RenderThread.cpp
+++ b/gfx/webrender_bindings/RenderThread.cpp
@@ -138,16 +138,45 @@ RenderThread::Loop()
 // static
 bool
 RenderThread::IsInRenderThread()
 {
   return sRenderThread && sRenderThread->mThread->thread_id() == PlatformThread::CurrentId();
 }
 
 void
+RenderThread::DoAccumulateMemoryReport(MemoryReport aReport, const RefPtr<MemoryReportPromise::Private>& aPromise)
+{
+  MOZ_ASSERT(IsInRenderThread());
+  for (auto& r: mRenderers) {
+    wr_renderer_accumulate_memory_report(r.second->GetRenderer(), &aReport);
+  }
+
+  aPromise->Resolve(aReport, __func__);
+}
+
+// static
+RefPtr<MemoryReportPromise>
+RenderThread::AccumulateMemoryReport(MemoryReport aInitial)
+{
+  RefPtr<MemoryReportPromise::Private> p = new MemoryReportPromise::Private(__func__);
+  MOZ_ASSERT(!IsInRenderThread());
+  Get()->Loop()->PostTask(
+    NewRunnableMethod<MemoryReport, RefPtr<MemoryReportPromise::Private>>(
+      "wr::RenderThread::DoAccumulateMemoryReport",
+      Get(),
+      &RenderThread::DoAccumulateMemoryReport,
+      aInitial, p
+    )
+  );
+
+  return p;
+}
+
+void
 RenderThread::AddRenderer(wr::WindowId aWindowId, UniquePtr<RendererOGL> aRenderer)
 {
   MOZ_ASSERT(IsInRenderThread());
 
   if (mHasShutdown) {
     return;
   }
 
--- a/gfx/webrender_bindings/RenderThread.h
+++ b/gfx/webrender_bindings/RenderThread.h
@@ -8,29 +8,32 @@
 #define MOZILLA_LAYERS_RENDERTHREAD_H
 
 #include "base/basictypes.h"            // for DISALLOW_EVIL_CONSTRUCTORS
 #include "base/platform_thread.h"       // for PlatformThreadId
 #include "base/thread.h"                // for Thread
 #include "base/message_loop.h"
 #include "nsISupportsImpl.h"
 #include "ThreadSafeRefcountingWithMainThreadDestruction.h"
+#include "mozilla/MozPromise.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/webrender/webrender_ffi.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/webrender/WebRenderTypes.h"
 #include "mozilla/layers/SynchronousTask.h"
 
 #include <list>
 #include <queue>
 #include <unordered_map>
 
 namespace mozilla {
 namespace wr {
 
+typedef MozPromise<MemoryReport, bool, true> MemoryReportPromise;
+
 class RendererOGL;
 class RenderTextureHost;
 class RenderThread;
 
 /// A rayon thread pool that is shared by all WebRender instances within a process.
 class WebRenderThreadPool {
 public:
   WebRenderThreadPool();
@@ -101,16 +104,21 @@ public:
   /// In most cases it is best to post RendererEvents through WebRenderAPI instead
   /// of scheduling directly to this message loop (so as to preserve the ordering
   /// of the messages).
   static MessageLoop* Loop();
 
   /// Can be called from any thread.
   static bool IsInRenderThread();
 
+  // Can be called from any thread. Dispatches an event to the Renderer thread
+  // to iterate over all Renderers, accumulates memory statistics, and resolves
+  // the return promise.
+  static RefPtr<MemoryReportPromise> AccumulateMemoryReport(MemoryReport aInitial);
+
   /// Can only be called from the render thread.
   void AddRenderer(wr::WindowId aWindowId, UniquePtr<RendererOGL> aRenderer);
 
   /// Can only be called from the render thread.
   void RemoveRenderer(wr::WindowId aWindowId);
 
   /// Can only be called from the render thread.
   RendererOGL* GetRenderer(wr::WindowId aWindowId);
@@ -182,16 +190,18 @@ public:
 
 private:
   explicit RenderThread(base::Thread* aThread);
 
   void DeferredRenderTextureHostDestroy();
   void ShutDownTask(layers::SynchronousTask* aTask);
   void ProgramCacheTask();
 
+  void DoAccumulateMemoryReport(MemoryReport, const RefPtr<MemoryReportPromise::Private>&);
+
   ~RenderThread();
 
   base::Thread* const mThread;
 
   WebRenderThreadPool mThreadPool;
   UniquePtr<WebRenderProgramCache> mProgramCache;
 
   std::map<wr::WindowId, UniquePtr<RendererOGL>> mRenderers;
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -19,16 +19,18 @@
 //#define WRDL_LOG(...) printf_stderr("WRDL(%p): " __VA_ARGS__)
 //#define WRDL_LOG(...) if (XRE_IsContentProcess()) printf_stderr("WRDL(%p): " __VA_ARGS__)
 
 namespace mozilla {
 namespace wr {
 
 using layers::Stringify;
 
+MOZ_DEFINE_MALLOC_SIZE_OF(WebRenderMallocSizeOf)
+
 class NewRenderer : public RendererEvent
 {
 public:
   NewRenderer(wr::DocumentHandle** aDocHandle,
               layers::CompositorBridgeParent* aBridge,
               uint32_t* aMaxTextureSize,
               bool* aUseANGLE,
               bool* aUseDComp,
@@ -67,16 +69,17 @@ public:
     *mUseANGLE = compositor->UseANGLE();
     *mUseDComp = compositor->UseDComp();
 
     bool supportLowPriorityTransactions = true; // TODO only for main windows.
     wr::Renderer* wrRenderer = nullptr;
     if (!wr_window_new(aWindowId, mSize.width, mSize.height, supportLowPriorityTransactions,
                        compositor->gl(),
                        aRenderThread.ThreadPool().Raw(),
+                       &WebRenderMallocSizeOf,
                        mDocHandle, &wrRenderer,
                        mMaxTextureSize)) {
       // wr_window_new puts a message into gfxCriticalNote if it returns false
       return;
     }
     MOZ_ASSERT(wrRenderer);
 
     RefPtr<RenderThread> thread = &aRenderThread;
@@ -512,16 +515,22 @@ WebRenderAPI::Resume()
 
 void
 WebRenderAPI::NotifyMemoryPressure()
 {
   wr_api_notify_memory_pressure(mDocHandle);
 }
 
 void
+WebRenderAPI::AccumulateMemoryReport(MemoryReport* aReport)
+{
+  wr_api_accumulate_memory_report(mDocHandle, aReport);
+}
+
+void
 WebRenderAPI::WakeSceneBuilder()
 {
     wr_api_wake_scene_builder(mDocHandle);
 }
 
 void
 WebRenderAPI::FlushSceneBuilder()
 {
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -204,16 +204,17 @@ public:
 
   void Pause();
   bool Resume();
 
   void WakeSceneBuilder();
   void FlushSceneBuilder();
 
   void NotifyMemoryPressure();
+  void AccumulateMemoryReport(wr::MemoryReport*);
 
   wr::WrIdNamespace GetNamespace();
   uint32_t GetMaxTextureSize() const { return mMaxTextureSize; }
   bool GetUseANGLE() const { return mUseANGLE; }
   bool GetUseDComp() const { return mUseDComp; }
   layers::SyncHandle GetSyncHandle() const { return mSyncHandle; }
 
   void Capture();
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -633,16 +633,22 @@ pub extern "C" fn wr_renderer_current_ep
 /// cbindgen:postfix=WR_DESTRUCTOR_SAFE_FUNC
 #[no_mangle]
 pub unsafe extern "C" fn wr_renderer_delete(renderer: *mut Renderer) {
     let renderer = Box::from_raw(renderer);
     renderer.deinit();
     // let renderer go out of scope and get dropped
 }
 
+#[no_mangle]
+pub unsafe extern "C" fn wr_renderer_accumulate_memory_report(renderer: &mut Renderer,
+                                                              report: &mut MemoryReport) {
+    *report += renderer.report_memory();
+}
+
 // cbindgen doesn't support tuples, so we have a little struct instead, with
 // an Into implementation to convert from the tuple to the struct.
 #[repr(C)]
 pub struct WrPipelineEpoch {
     pipeline_id: WrPipelineId,
     epoch: WrEpoch,
 }
 
@@ -892,16 +898,17 @@ fn env_var_to_bool(key: &'static str) ->
 // Call MakeCurrent before this.
 #[no_mangle]
 pub extern "C" fn wr_window_new(window_id: WrWindowId,
                                 window_width: u32,
                                 window_height: u32,
                                 support_low_priority_transactions: bool,
                                 gl_context: *mut c_void,
                                 thread_pool: *mut WrThreadPool,
+                                size_of_op: VoidPtrToSizeFn,
                                 out_handle: &mut *mut DocumentHandle,
                                 out_renderer: &mut *mut Renderer,
                                 out_max_texture_size: *mut u32)
                                 -> bool {
     assert!(unsafe { is_in_render_thread() });
 
     let recorder: Option<Box<ApiRecordingReceiver>> = if unsafe { gfx_use_wrench() } {
         let name = format!("wr-record-{}.bin", window_id.0);
@@ -935,16 +942,17 @@ pub extern "C" fn wr_window_new(window_i
     let opts = RendererOptions {
         enable_aa: true,
         enable_subpixel_aa: true,
         support_low_priority_transactions,
         recorder: recorder,
         blob_image_handler: Some(Box::new(Moz2dBlobImageHandler::new(workers.clone()))),
         workers: Some(workers.clone()),
         thread_listener: Some(Box::new(GeckoProfilerThreadListener::new())),
+        size_of_op: Some(size_of_op),
         resource_override_path: unsafe {
             let override_charptr = gfx_wr_resource_path_override();
             if override_charptr.is_null() {
                 None
             } else {
                 match CStr::from_ptr(override_charptr).to_str() {
                     Ok(override_str) => Some(PathBuf::from(override_str)),
                     _ => None
@@ -1036,16 +1044,24 @@ pub unsafe extern "C" fn wr_api_shut_dow
     dh.api.shut_down();
 }
 
 #[no_mangle]
 pub unsafe extern "C" fn wr_api_notify_memory_pressure(dh: &mut DocumentHandle) {
     dh.api.notify_memory_pressure();
 }
 
+#[no_mangle]
+pub unsafe extern "C" fn wr_api_accumulate_memory_report(
+    dh: &mut DocumentHandle,
+    report: &mut MemoryReport
+) {
+    *report += dh.api.report_memory();
+}
+
 /// cbindgen:postfix=WR_DESTRUCTOR_SAFE_FUNC
 #[no_mangle]
 pub unsafe extern "C" fn wr_api_clear_all_caches(dh: &mut DocumentHandle) {
     dh.api.send_debug_cmd(DebugCommand::ClearCaches(ClearCache::all()));
 }
 
 fn make_transaction(do_async: bool) -> Transaction {
     let mut transaction = Transaction::new();
--- a/gfx/webrender_bindings/webrender_ffi_generated.h
+++ b/gfx/webrender_bindings/webrender_ffi_generated.h
@@ -466,16 +466,35 @@ struct WrPipelineInfo {
   FfiVec<PipelineId> removed_pipelines;
 
   bool operator==(const WrPipelineInfo& aOther) const {
     return epochs == aOther.epochs &&
            removed_pipelines == aOther.removed_pipelines;
   }
 };
 
+// Collection of heap sizes, in bytes.
+struct MemoryReport {
+  uintptr_t primitive_stores;
+  uintptr_t clip_stores;
+  uintptr_t gpu_cache_metadata;
+  uintptr_t gpu_cache_cpu_mirror;
+  uintptr_t render_tasks;
+  uintptr_t hit_testers;
+
+  bool operator==(const MemoryReport& aOther) const {
+    return primitive_stores == aOther.primitive_stores &&
+           clip_stores == aOther.clip_stores &&
+           gpu_cache_metadata == aOther.gpu_cache_metadata &&
+           gpu_cache_cpu_mirror == aOther.gpu_cache_cpu_mirror &&
+           render_tasks == aOther.render_tasks &&
+           hit_testers == aOther.hit_testers;
+  }
+};
+
 template<typename T, typename U>
 struct TypedSize2D {
   T width;
   T height;
 
   bool operator==(const TypedSize2D& aOther) const {
     return width == aOther.width &&
            height == aOther.height;
@@ -979,16 +998,22 @@ struct WrOpacityProperty {
   float opacity;
 
   bool operator==(const WrOpacityProperty& aOther) const {
     return id == aOther.id &&
            opacity == aOther.opacity;
   }
 };
 
+// A C function that takes a pointer to a heap allocation and returns its size.
+//
+// This is borrowed from the malloc_size_of crate, upon which we want to avoid
+// a dependency from WebRender.
+using VoidPtrToSizeFn = uintptr_t(*)(const void*);
+
 extern "C" {
 
 extern void AddBlobFont(WrFontInstanceKey aInstanceKey,
                         WrFontKey aFontKey,
                         float aSize,
                         const FontInstanceOptions *aOptions,
                         const FontInstancePlatformOptions *aPlatformOptions,
                         const FontVariation *aVariations,
@@ -1057,16 +1082,21 @@ WR_INLINE
 bool remove_program_binary_disk_cache(const nsAString *aProfPath)
 WR_FUNC;
 
 WR_INLINE
 const VecU8 *wr_add_ref_arc(const ArcVecU8 *aArc)
 WR_FUNC;
 
 WR_INLINE
+void wr_api_accumulate_memory_report(DocumentHandle *aDh,
+                                     MemoryReport *aReport)
+WR_FUNC;
+
+WR_INLINE
 void wr_api_capture(DocumentHandle *aDh,
                     const char *aPath,
                     uint32_t aBitsRaw)
 WR_FUNC;
 
 WR_INLINE
 void wr_api_clear_all_caches(DocumentHandle *aDh)
 WR_DESTRUCTOR_SAFE_FUNC;
@@ -1486,16 +1516,21 @@ void wr_program_cache_delete(WrProgramCa
 WR_DESTRUCTOR_SAFE_FUNC;
 
 WR_INLINE
 WrProgramCache *wr_program_cache_new(const nsAString *aProfPath,
                                      WrThreadPool *aThreadPool)
 WR_FUNC;
 
 WR_INLINE
+void wr_renderer_accumulate_memory_report(Renderer *aRenderer,
+                                          MemoryReport *aReport)
+WR_FUNC;
+
+WR_INLINE
 bool wr_renderer_current_epoch(Renderer *aRenderer,
                                WrPipelineId aPipelineId,
                                WrEpoch *aOutEpoch)
 WR_FUNC;
 
 WR_INLINE
 void wr_renderer_delete(Renderer *aRenderer)
 WR_DESTRUCTOR_SAFE_FUNC;
@@ -1774,16 +1809,17 @@ WR_FUNC;
 
 WR_INLINE
 bool wr_window_new(WrWindowId aWindowId,
                    uint32_t aWindowWidth,
                    uint32_t aWindowHeight,
                    bool aSupportLowPriorityTransactions,
                    void *aGlContext,
                    WrThreadPool *aThreadPool,
+                   VoidPtrToSizeFn aSizeOfOp,
                    DocumentHandle **aOutHandle,
                    Renderer **aOutRenderer,
                    uint32_t *aOutMaxTextureSize)
 WR_FUNC;
 
 } // extern "C"
 
 } // namespace wr