Bug 1515521 - Add memory reporters for interning data. r=kvark
authorBobby Holley <bobbyholley@gmail.com>
Fri, 21 Dec 2018 14:55:55 +0000
changeset 508865 ee1524718628d63277bb7da9a909e2f5f33f63dc
parent 508864 e90e92510da2b12149c2cb076b952a41015b423d
child 508866 ce0f2042c1e2239e065275769969574077a02516
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskvark
bugs1515521
milestone66.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 1515521 - Add memory reporters for interning data. r=kvark Differential Revision: https://phabricator.services.mozilla.com/D15044
gfx/thebes/gfxPlatform.cpp
gfx/webrender_bindings/WebRenderAPI.cpp
gfx/webrender_bindings/src/bindings.rs
gfx/webrender_bindings/webrender_ffi_generated.h
gfx/wr/webrender/src/intern.rs
gfx/wr/webrender/src/render_backend.rs
gfx/wr/webrender/src/renderer.rs
gfx/wr/webrender/src/scene_builder.rs
gfx/wr/webrender_api/src/api.rs
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -702,16 +702,18 @@ WebRenderMemoryReporter::CollectReports(
         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");
         helper.Report(aReport.fonts, "resource-cache/fonts");
         helper.Report(aReport.images, "resource-cache/images");
         helper.Report(aReport.rasterized_blobs,
                       "resource-cache/rasterized-blobs");
         helper.Report(aReport.shader_cache, "shader-cache");
+        helper.Report(aReport.data_stores, "interning/data-stores");
+        helper.Report(aReport.interners, "interning/interners");
 
         // GPU Memory.
         helper.ReportTexture(aReport.gpu_cache_textures, "gpu-cache");
         helper.ReportTexture(aReport.vertex_data_textures, "vertex-data");
         helper.ReportTexture(aReport.render_target_textures, "render-targets");
         helper.ReportTexture(aReport.texture_cache_textures, "texture-cache");
         helper.ReportTexture(aReport.depth_target_textures, "depth-targets");
         helper.ReportTexture(aReport.swap_chain, "swap-chains");
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -25,16 +25,17 @@
 */
 
 namespace mozilla {
 namespace wr {
 
 using layers::Stringify;
 
 MOZ_DEFINE_MALLOC_SIZE_OF(WebRenderMallocSizeOf)
+MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(WebRenderMallocEnclosingSizeOf)
 
 class NewRenderer : public RendererEvent {
  public:
   NewRenderer(wr::DocumentHandle** aDocHandle,
               layers::CompositorBridgeParent* aBridge, int32_t* aMaxTextureSize,
               bool* aUseANGLE, bool* aUseDComp, bool* aUseTripleBuffering,
               RefPtr<widget::CompositorWidget>&& aWidget,
               layers::SynchronousTask* aTask, LayoutDeviceIntSize aSize,
@@ -74,17 +75,18 @@ class NewRenderer : public RendererEvent
     if (!wr_window_new(
             aWindowId, mSize.width, mSize.height,
             supportLowPriorityTransactions, gfxPrefs::WebRenderPictureCaching(),
             compositor->gl(),
             aRenderThread.ProgramCache() ? aRenderThread.ProgramCache()->Raw()
                                          : nullptr,
             aRenderThread.Shaders() ? aRenderThread.Shaders()->RawShaders()
                                     : nullptr,
-            aRenderThread.ThreadPool().Raw(), &WebRenderMallocSizeOf,
+            aRenderThread.ThreadPool().Raw(),
+            &WebRenderMallocSizeOf, &WebRenderMallocEnclosingSizeOf,
             mDocHandle, &wrRenderer, mMaxTextureSize)) {
       // wr_window_new puts a message into gfxCriticalNote if it returns false
       return;
     }
     MOZ_ASSERT(wrRenderer);
 
     RefPtr<RenderThread> thread = &aRenderThread;
     auto renderer =
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -991,16 +991,17 @@ pub extern "C" fn wr_window_new(window_i
                                 window_height: i32,
                                 support_low_priority_transactions: bool,
                                 enable_picture_caching: bool,
                                 gl_context: *mut c_void,
                                 program_cache: Option<&mut WrProgramCache>,
                                 shaders: Option<&mut WrShaders>,
                                 thread_pool: *mut WrThreadPool,
                                 size_of_op: VoidPtrToSizeFn,
+                                enclosing_size_of_op: VoidPtrToSizeFn,
                                 out_handle: &mut *mut DocumentHandle,
                                 out_renderer: &mut *mut Renderer,
                                 out_max_texture_size: *mut i32)
                                 -> 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);
@@ -1046,16 +1047,17 @@ pub extern "C" fn wr_window_new(window_i
         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),
+        enclosing_size_of_op: Some(enclosing_size_of_op),
         cached_programs,
         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)),
--- a/gfx/webrender_bindings/webrender_ffi_generated.h
+++ b/gfx/webrender_bindings/webrender_ffi_generated.h
@@ -535,16 +535,18 @@ struct MemoryReport {
   uintptr_t gpu_cache_metadata;
   uintptr_t gpu_cache_cpu_mirror;
   uintptr_t render_tasks;
   uintptr_t hit_testers;
   uintptr_t fonts;
   uintptr_t images;
   uintptr_t rasterized_blobs;
   uintptr_t shader_cache;
+  uintptr_t data_stores;
+  uintptr_t interners;
   uintptr_t gpu_cache_textures;
   uintptr_t vertex_data_textures;
   uintptr_t render_target_textures;
   uintptr_t texture_cache_textures;
   uintptr_t depth_target_textures;
   uintptr_t swap_chain;
 
   bool operator==(const MemoryReport& aOther) const {
@@ -553,16 +555,18 @@ struct MemoryReport {
            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 &&
            fonts == aOther.fonts &&
            images == aOther.images &&
            rasterized_blobs == aOther.rasterized_blobs &&
            shader_cache == aOther.shader_cache &&
+           data_stores == aOther.data_stores &&
+           interners == aOther.interners &&
            gpu_cache_textures == aOther.gpu_cache_textures &&
            vertex_data_textures == aOther.vertex_data_textures &&
            render_target_textures == aOther.render_target_textures &&
            texture_cache_textures == aOther.texture_cache_textures &&
            depth_target_textures == aOther.depth_target_textures &&
            swap_chain == aOther.swap_chain;
   }
 };
@@ -1987,16 +1991,17 @@ bool wr_window_new(WrWindowId aWindowId,
                    int32_t aWindowHeight,
                    bool aSupportLowPriorityTransactions,
                    bool aEnablePictureCaching,
                    void *aGlContext,
                    WrProgramCache *aProgramCache,
                    WrShaders *aShaders,
                    WrThreadPool *aThreadPool,
                    VoidPtrToSizeFn aSizeOfOp,
+                   VoidPtrToSizeFn aEnclosingSizeOfOp,
                    DocumentHandle **aOutHandle,
                    Renderer **aOutRenderer,
                    int32_t *aOutMaxTextureSize)
 WR_FUNC;
 
 } // extern "C"
 
 } // namespace wr
--- a/gfx/wr/webrender/src/intern.rs
+++ b/gfx/wr/webrender/src/intern.rs
@@ -1,19 +1,21 @@
 /* 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/. */
 
 use api::{LayoutPrimitiveInfo, LayoutRect};
+use api::VoidPtrToSizeFn;
 use internal_types::FastHashMap;
 use profiler::ResourceProfileCounter;
 use std::fmt::Debug;
 use std::hash::Hash;
 use std::marker::PhantomData;
 use std::{mem, ops, u64};
+use std::os::raw::c_void;
 use std::sync::atomic::{AtomicUsize, Ordering};
 use util::VecHelper;
 
 /*
 
  The interning module provides a generic data structure
  interning container. It is similar in concept to a
  traditional string interning container, but it is
@@ -178,16 +180,21 @@ impl<S, T, M> DataStore<S, T, M> where S
             }
         }
 
         let per_item_size = mem::size_of::<S>() + mem::size_of::<T>();
         profile_counter.set(self.items.len(), per_item_size * self.items.len());
 
         debug_assert!(data_iter.next().is_none());
     }
+
+    /// Reports CPU heap usage.
+    pub fn malloc_size_of(&self, op: VoidPtrToSizeFn) -> usize {
+        unsafe { op(self.items.as_ptr() as *const c_void) }
+    }
 }
 
 /// Retrieve an item from the store via handle
 impl<S, T, M> ops::Index<Handle<M>> for DataStore<S, T, M>
 where M: Copy
 {
     type Output = T;
     fn index(&self, handle: Handle<M>) -> &T {
@@ -370,16 +377,27 @@ where
             epoch: self.current_epoch,
         };
 
         // Begin the next epoch
         self.current_epoch = Epoch(self.current_epoch.0 + 1);
 
         updates
     }
+
+    /// Reports CPU heap usage.
+    pub fn malloc_size_of(&self, op: VoidPtrToSizeFn, eop: VoidPtrToSizeFn) -> usize {
+        let mut bytes = 0;
+        unsafe {
+            bytes += op(self.local_data.as_ptr() as *const c_void);
+            bytes += self.map.values().next()
+                .map_or(0, |v| eop(v as *const _ as *const c_void));
+        }
+        bytes
+    }
 }
 
 /// Retrieve the local data for an item from the interner via handle
 impl<S, D, M> ops::Index<Handle<M>> for Interner<S, D, M>
 where
     S: Eq + Clone + Hash + Debug,
     M: Copy + Debug
 {
--- a/gfx/wr/webrender/src/render_backend.rs
+++ b/gfx/wr/webrender/src/render_backend.rs
@@ -264,16 +264,25 @@ impl FrameResources {
                 &prim_data.common
             }
             PrimitiveInstanceKind::YuvImage { data_handle, .. } => {
                 let prim_data = &self.yuv_image_data_store[data_handle];
                 &prim_data.common
             }
         }
     }
+
+    /// Reports CPU heap usage.
+    fn report_memory(&self, op: VoidPtrToSizeFn, r: &mut MemoryReport) {
+        r.data_stores += self.clip_data_store.malloc_size_of(op);
+        r.data_stores += self.prim_data_store.malloc_size_of(op);
+        r.data_stores += self.linear_grad_data_store.malloc_size_of(op);
+        r.data_stores += self.radial_grad_data_store.malloc_size_of(op);
+        r.data_stores += self.text_run_data_store.malloc_size_of(op);
+    }
 }
 
 struct Document {
     // The latest built scene, usable to build frames.
     // received from the scene builder thread.
     scene: Scene,
 
     // Temporary list of removed pipelines received from the scene builder
@@ -1000,17 +1009,17 @@ impl RenderBackend {
                 let msg = ResultMsg::UpdateResources {
                     updates: pending_update,
                     memory_pressure: true,
                 };
                 self.result_tx.send(msg).unwrap();
                 self.notifier.wake_up();
             }
             ApiMsg::ReportMemory(tx) => {
-                tx.send(self.report_memory()).unwrap();
+                self.report_memory(tx);
             }
             ApiMsg::DebugCommand(option) => {
                 let msg = match option {
                     DebugCommand::EnableDualSourceBlending(enable) => {
                         // Set in the config used for any future documents
                         // that are created.
                         self.frame_config
                             .dual_source_blending_is_enabled = enable;
@@ -1488,31 +1497,36 @@ impl RenderBackend {
             doc.clip_scroll_tree.print_with(&mut builder);
 
             debug_root.add(builder.build());
         }
 
         serde_json::to_string(&debug_root).unwrap()
     }
 
-    fn report_memory(&self) -> MemoryReport {
+    fn report_memory(&self, tx: ::api::channel::MsgSender<MemoryReport>) {
         let mut report = MemoryReport::default();
         let op = self.size_of_op.unwrap();
         report.gpu_cache_metadata = self.gpu_cache.malloc_size_of(op);
         for (_id, doc) in &self.documents {
             if let Some(ref fb) = doc.frame_builder {
                 report.clip_stores += fb.clip_store.malloc_size_of(op);
             }
             report.hit_testers +=
                 doc.hit_tester.as_ref().map_or(0, |ht| ht.malloc_size_of(op));
+
+            doc.resources.report_memory(op, &mut report)
         }
 
         report += self.resource_cache.report_memory(op);
 
-        report
+        // Send a message to report memory on the scene-builder thread, which
+        // will add its report to this one and send the result back to the original
+        // thread waiting on the request.
+        self.scene_tx.send(SceneBuilderRequest::ReportMemory(report, tx)).unwrap();
     }
 }
 
 fn get_blob_image_updates(updates: &[ResourceUpdate]) -> Vec<BlobImageKey> {
     let mut requests = Vec::new();
     for update in updates {
         match *update {
             ResourceUpdate::AddBlobImage(ref img) => {
--- a/gfx/wr/webrender/src/renderer.rs
+++ b/gfx/wr/webrender/src/renderer.rs
@@ -1572,19 +1572,28 @@ pub struct Renderer {
     /// application to provide external buffers for image data.
     external_image_handler: Option<Box<ExternalImageHandler>>,
 
     /// Optional trait object that allows the client
     /// application to provide a texture handle to
     /// copy the WR output to.
     output_image_handler: Option<Box<OutputImageHandler>>,
 
-    /// Optional function pointer for memory reporting.
+    /// Optional function pointer for measuring memory used by a given
+    /// heap-allocated pointer.
     size_of_op: Option<VoidPtrToSizeFn>,
 
+    /// Optional function pointer for measuring memory used by a given
+    /// heap-allocated region of memory. Unlike the above, pointers passed
+    /// to this function do not need to point to the start of the allocation,
+    /// and can be anywhere in the allocated region. This is useful for measuring
+    /// structures like hashmaps that don't expose pointers to the start of the
+    /// allocation, but do expose pointers to elements within the allocation.
+    _enclosing_size_of_op: Option<VoidPtrToSizeFn>,
+
     // Currently allocated FBOs for output frames.
     output_targets: FastHashMap<u32, FrameOutput>,
 
     pub renderer_errors: Vec<RendererError>,
 
     /// List of profile results from previous frames. Can be retrieved
     /// via get_frame_profiles().
     cpu_profiles: VecDeque<CpuProfile>,
@@ -1865,32 +1874,36 @@ impl Renderer {
                             thread_listener.thread_stopped(&format!("WRWorker#{}", idx));
                         }
                     })
                     .build();
                 Arc::new(worker.unwrap())
             });
         let sampler = options.sampler;
         let size_of_op = options.size_of_op;
+        let enclosing_size_of_op = options.enclosing_size_of_op;
         let namespace_alloc_by_client = options.namespace_alloc_by_client;
 
         let blob_image_handler = options.blob_image_handler.take();
         let thread_listener_for_render_backend = thread_listener.clone();
         let thread_listener_for_scene_builder = thread_listener.clone();
         let thread_listener_for_lp_scene_builder = thread_listener.clone();
         let scene_builder_hooks = options.scene_builder_hooks;
         let rb_thread_name = format!("WRRenderBackend#{}", options.renderer_id.unwrap_or(0));
         let scene_thread_name = format!("WRSceneBuilder#{}", options.renderer_id.unwrap_or(0));
         let lp_scene_thread_name = format!("WRSceneBuilderLP#{}", options.renderer_id.unwrap_or(0));
         let glyph_rasterizer = GlyphRasterizer::new(workers)?;
 
         let (scene_builder, scene_tx, scene_rx) = SceneBuilder::new(
             config,
             api_tx.clone(),
-            scene_builder_hooks);
+            scene_builder_hooks,
+            size_of_op,
+            enclosing_size_of_op,
+        );
         thread::Builder::new().name(scene_thread_name.clone()).spawn(move || {
             register_thread_with_profiler(scene_thread_name.clone());
             if let Some(ref thread_listener) = *thread_listener_for_scene_builder {
                 thread_listener.thread_started(&scene_thread_name);
             }
 
             let mut scene_builder = scene_builder;
             scene_builder.run();
@@ -2013,16 +2026,17 @@ impl Renderer {
             prim_header_i_texture,
             prim_header_f_texture,
             render_task_texture,
             pipeline_info: PipelineInfo::default(),
             dither_matrix_texture,
             external_image_handler: None,
             output_image_handler: None,
             size_of_op: options.size_of_op,
+            _enclosing_size_of_op: options.enclosing_size_of_op,
             output_targets: FastHashMap::default(),
             cpu_profiles: VecDeque::new(),
             gpu_profiles: VecDeque::new(),
             gpu_cache_texture,
             #[cfg(feature = "debug_renderer")]
             gpu_cache_debug_chunks: Vec::new(),
             gpu_cache_frame_id: FrameId::INVALID,
             gpu_cache_overflow: false,
@@ -4768,16 +4782,17 @@ pub struct RendererOptions {
     pub max_texture_size: Option<i32>,
     pub scatter_gpu_cache_updates: bool,
     pub upload_method: UploadMethod,
     pub workers: Option<Arc<ThreadPool>>,
     pub blob_image_handler: Option<Box<BlobImageHandler>>,
     pub recorder: Option<Box<ApiRecordingReceiver>>,
     pub thread_listener: Option<Box<ThreadListener + Send + Sync>>,
     pub size_of_op: Option<VoidPtrToSizeFn>,
+    pub enclosing_size_of_op: Option<VoidPtrToSizeFn>,
     pub cached_programs: Option<Rc<ProgramCache>>,
     pub debug_flags: DebugFlags,
     pub renderer_id: Option<u64>,
     pub disable_dual_source_blending: bool,
     pub scene_builder_hooks: Option<Box<SceneBuilderHooks + Send>>,
     pub sampler: Option<Box<AsyncPropertySampler + Send>>,
     pub chase_primitive: ChasePrimitive,
     pub support_low_priority_transactions: bool,
@@ -4805,16 +4820,17 @@ impl Default for RendererOptions {
             // This is best as `Immediate` on Angle, or `Pixelbuffer(Dynamic)` on GL,
             // but we are unable to make this decision here, so picking the reasonable medium.
             upload_method: UploadMethod::PixelBuffer(VertexUsageHint::Stream),
             workers: None,
             blob_image_handler: None,
             recorder: None,
             thread_listener: None,
             size_of_op: None,
+            enclosing_size_of_op: None,
             renderer_id: None,
             cached_programs: None,
             disable_dual_source_blending: false,
             scene_builder_hooks: None,
             sampler: None,
             chase_primitive: ChasePrimitive::Nothing,
             support_low_priority_transactions: false,
             namespace_alloc_by_client: false,
--- a/gfx/wr/webrender/src/scene_builder.rs
+++ b/gfx/wr/webrender/src/scene_builder.rs
@@ -1,15 +1,16 @@
 /* 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/. */
 
 use api::{AsyncBlobImageRasterizer, BlobImageRequest, BlobImageParams, BlobImageResult};
 use api::{DocumentId, PipelineId, ApiMsg, FrameMsg, ResourceUpdate, ExternalEvent, Epoch};
 use api::{BuiltDisplayList, ColorF, LayoutSize, NotificationRequest, Checkpoint, IdNamespace};
+use api::{MemoryReport, VoidPtrToSizeFn};
 use api::channel::MsgSender;
 #[cfg(feature = "capture")]
 use capture::CaptureConfig;
 use frame_builder::{FrameBuilderConfig, FrameBuilder};
 use clip::{ClipDataInterner, ClipDataUpdateList};
 use clip_scroll_tree::ClipScrollTree;
 use display_list_flattener::DisplayListFlattener;
 use intern::{Internable, Interner};
@@ -163,16 +164,17 @@ pub enum SceneBuilderRequest {
     DeleteDocument(DocumentId),
     WakeUp,
     Flush(MsgSender<()>),
     ClearNamespace(IdNamespace),
     SetFrameBuilderConfig(FrameBuilderConfig),
     SimulateLongSceneBuild(u32),
     SimulateLongLowPrioritySceneBuild(u32),
     Stop,
+    ReportMemory(MemoryReport, ::api::channel::MsgSender<MemoryReport>),
     #[cfg(feature = "capture")]
     SaveScene(CaptureConfig),
     #[cfg(feature = "replay")]
     LoadScenes(Vec<LoadScene>),
 }
 
 // Message from scene builder to render backend.
 pub enum SceneBuilderResult {
@@ -209,16 +211,32 @@ pub struct DocumentResources {
     pub linear_grad_interner: LinearGradientDataInterner,
     pub normal_border_interner: NormalBorderDataInterner,
     pub picture_interner: PictureDataInterner,
     pub radial_grad_interner: RadialGradientDataInterner,
     pub text_run_interner: TextRunDataInterner,
     pub yuv_image_interner: YuvImageDataInterner,
 }
 
+impl DocumentResources {
+    /// Reports CPU heap memory used by the interners.
+    fn report_memory(
+        &self,
+        op: VoidPtrToSizeFn,
+        eop: VoidPtrToSizeFn,
+        r: &mut MemoryReport,
+    ) {
+        r.interners += self.clip_interner.malloc_size_of(op, eop);
+        r.interners += self.prim_interner.malloc_size_of(op, eop);
+        r.interners += self.linear_grad_interner.malloc_size_of(op, eop);
+        r.interners += self.radial_grad_interner.malloc_size_of(op, eop);
+        r.interners += self.text_run_interner.malloc_size_of(op, eop);
+    }
+}
+
 // Access to `DocumentResources` interners by `Internable`
 pub trait InternerMut<I: Internable>
 {
     fn interner_mut(&mut self) -> &mut Interner<I::Source, I::InternData, I::Marker>;
 }
 
 macro_rules! impl_internet_mut {
     ($($ty:ident: $mem:ident,)*) => {
@@ -270,34 +288,40 @@ impl Document {
 pub struct SceneBuilder {
     documents: FastHashMap<DocumentId, Document>,
     rx: Receiver<SceneBuilderRequest>,
     tx: Sender<SceneBuilderResult>,
     api_tx: MsgSender<ApiMsg>,
     config: FrameBuilderConfig,
     hooks: Option<Box<SceneBuilderHooks + Send>>,
     simulate_slow_ms: u32,
+    size_of_op: Option<VoidPtrToSizeFn>,
+    enclosing_size_of_op: Option<VoidPtrToSizeFn>,
 }
 
 impl SceneBuilder {
     pub fn new(
         config: FrameBuilderConfig,
         api_tx: MsgSender<ApiMsg>,
         hooks: Option<Box<SceneBuilderHooks + Send>>,
+        size_of_op: Option<VoidPtrToSizeFn>,
+        enclosing_size_of_op: Option<VoidPtrToSizeFn>,
     ) -> (Self, Sender<SceneBuilderRequest>, Receiver<SceneBuilderResult>) {
         let (in_tx, in_rx) = channel();
         let (out_tx, out_rx) = channel();
         (
             SceneBuilder {
                 documents: FastHashMap::default(),
                 rx: in_rx,
                 tx: out_tx,
                 api_tx,
                 config,
                 hooks,
+                size_of_op,
+                enclosing_size_of_op,
                 simulate_slow_ms: 0,
             },
             in_tx,
             out_rx,
         )
     }
 
     /// Send a message to the render backend thread.
@@ -347,16 +371,20 @@ impl SceneBuilder {
                     self.send(SceneBuilderResult::ExternalEvent(evt));
                 }
                 Ok(SceneBuilderRequest::Stop) => {
                     self.tx.send(SceneBuilderResult::Stopped).unwrap();
                     // We don't need to send a WakeUp to api_tx because we only
                     // get the Stop when the RenderBackend loop is exiting.
                     break;
                 }
+                Ok(SceneBuilderRequest::ReportMemory(mut report, tx)) => {
+                    report += self.report_memory();
+                    tx.send(report).unwrap();
+                }
                 Ok(SceneBuilderRequest::SimulateLongSceneBuild(time_ms)) => {
                     self.simulate_slow_ms = time_ms
                 }
                 Ok(SceneBuilderRequest::SimulateLongLowPrioritySceneBuild(_)) => {}
                 Err(_) => {
                     break;
                 }
             }
@@ -728,16 +756,28 @@ impl SceneBuilder {
                 hooks.post_resource_update();
             }
         } else {
             if let &Some(ref hooks) = &self.hooks {
                 hooks.post_empty_scene_build();
             }
         }
     }
+
+    /// Reports CPU heap memory used by the SceneBuilder.
+    fn report_memory(&self) -> MemoryReport {
+        let op = self.size_of_op.unwrap();
+        let eop = self.enclosing_size_of_op.unwrap();
+        let mut report = MemoryReport::default();
+        for doc in self.documents.values() {
+            doc.resources.report_memory(op, eop, &mut report);
+        }
+
+        report
+    }
 }
 
 /// A scene builder thread which executes expensive operations such as blob rasterization
 /// with a lower priority than the normal scene builder thread.
 ///
 /// After rasterizing blobs, the secene building request is forwarded to the normal scene
 /// builder where the FrameBuilder is generated.
 pub struct LowPrioritySceneBuilder {
--- a/gfx/wr/webrender_api/src/api.rs
+++ b/gfx/wr/webrender_api/src/api.rs
@@ -843,16 +843,19 @@ pub struct MemoryReport {
     pub gpu_cache_metadata: usize,
     pub gpu_cache_cpu_mirror: usize,
     pub render_tasks: usize,
     pub hit_testers: usize,
     pub fonts: usize,
     pub images: usize,
     pub rasterized_blobs: usize,
     pub shader_cache: usize,
+    pub data_stores: usize,
+    pub interners: usize,
+
     //
     // GPU memory.
     //
     pub gpu_cache_textures: usize,
     pub vertex_data_textures: usize,
     pub render_target_textures: usize,
     pub texture_cache_textures: usize,
     pub depth_target_textures: usize,
@@ -866,16 +869,18 @@ impl ::std::ops::AddAssign for MemoryRep
         self.gpu_cache_metadata += other.gpu_cache_metadata;
         self.gpu_cache_cpu_mirror += other.gpu_cache_cpu_mirror;
         self.render_tasks += other.render_tasks;
         self.hit_testers += other.hit_testers;
         self.fonts += other.fonts;
         self.images += other.images;
         self.rasterized_blobs += other.rasterized_blobs;
         self.shader_cache += other.shader_cache;
+        self.data_stores += other.data_stores;
+        self.interners += other.interners;
         self.gpu_cache_textures += other.gpu_cache_textures;
         self.vertex_data_textures += other.vertex_data_textures;
         self.render_target_textures += other.render_target_textures;
         self.texture_cache_textures += other.texture_cache_textures;
         self.depth_target_textures += other.depth_target_textures;
         self.swap_chain += other.swap_chain;
     }
 }