diff options
-rw-r--r-- | components/compositing/compositor.rs | 8 | ||||
-rw-r--r-- | components/compositing/constellation.rs | 7 | ||||
-rw-r--r-- | components/compositing/pipeline.rs | 4 | ||||
-rw-r--r-- | components/gfx/display_list/mod.rs | 111 | ||||
-rw-r--r-- | components/layout/layout_task.rs | 54 | ||||
-rw-r--r-- | components/layout_traits/Cargo.toml | 3 | ||||
-rw-r--r-- | components/layout_traits/lib.rs | 5 | ||||
-rw-r--r-- | components/script/layout_interface.rs | 13 | ||||
-rw-r--r-- | components/servo/Cargo.lock | 1 | ||||
-rw-r--r-- | components/servo/lib.rs | 2 | ||||
-rw-r--r-- | components/util/memory.rs | 255 | ||||
-rw-r--r-- | ports/cef/Cargo.lock | 1 | ||||
-rw-r--r-- | ports/gonk/Cargo.lock | 1 | ||||
-rw-r--r-- | ports/gonk/src/lib.rs | 2 |
14 files changed, 453 insertions, 14 deletions
diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index af816d5a9c2..799f231417b 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -1348,12 +1348,8 @@ impl<Window> CompositorEventListener for IOCompositor<Window> where Window: Wind while self.port.try_recv_compositor_msg().is_some() {} // Tell the profiler, memory profiler, and scrolling timer to shut down. - let TimeProfilerChan(ref time_profiler_chan) = self.time_profiler_chan; - time_profiler_chan.send(time::TimeProfilerMsg::Exit).unwrap(); - - let MemoryProfilerChan(ref memory_profiler_chan) = self.memory_profiler_chan; - memory_profiler_chan.send(memory::MemoryProfilerMsg::Exit).unwrap(); - + self.time_profiler_chan.send(time::TimeProfilerMsg::Exit); + self.memory_profiler_chan.send(memory::MemoryProfilerMsg::Exit); self.scrolling_timer.shutdown(); } diff --git a/components/compositing/constellation.rs b/components/compositing/constellation.rs index df4a448d758..acfde8d1d53 100644 --- a/components/compositing/constellation.rs +++ b/components/compositing/constellation.rs @@ -27,6 +27,7 @@ use net::resource_task; use net::storage_task::{StorageTask, StorageTaskMsg}; use util::cursor::Cursor; use util::geometry::PagePx; +use util::memory::MemoryProfilerChan; use util::opts; use util::task::spawn_named; use util::time::TimeProfilerChan; @@ -91,6 +92,9 @@ pub struct Constellation<LTF, STF> { /// A channel through which messages can be sent to the time profiler. pub time_profiler_chan: TimeProfilerChan, + /// A channel through which messages can be sent to the memory profiler. + pub memory_profiler_chan: MemoryProfilerChan, + pub window_size: WindowSizeData, } @@ -167,6 +171,7 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> { image_cache_task: ImageCacheTask, font_cache_task: FontCacheTask, time_profiler_chan: TimeProfilerChan, + memory_profiler_chan: MemoryProfilerChan, devtools_chan: Option<DevtoolsControlChan>, storage_task: StorageTask) -> ConstellationChan { @@ -191,6 +196,7 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> { root_frame_id: None, next_frame_id: FrameId(0), time_profiler_chan: time_profiler_chan, + memory_profiler_chan: memory_profiler_chan, window_size: WindowSizeData { visible_viewport: opts::get().initial_window_size.as_f32() * ScaleFactor(1.0), initial_viewport: opts::get().initial_window_size.as_f32() * ScaleFactor(1.0), @@ -240,6 +246,7 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> { self.resource_task.clone(), self.storage_task.clone(), self.time_profiler_chan.clone(), + self.memory_profiler_chan.clone(), window_size, script_channel, load_data); diff --git a/components/compositing/pipeline.rs b/components/compositing/pipeline.rs index edfbae48a2a..1c817cc845a 100644 --- a/components/compositing/pipeline.rs +++ b/components/compositing/pipeline.rs @@ -19,6 +19,7 @@ use net::resource_task::ResourceTask; use net::storage_task::StorageTask; use url::Url; use util::geometry::{PagePx}; +use util::memory::MemoryProfilerChan; use util::time::TimeProfilerChan; use std::sync::mpsc::{Receiver, channel}; @@ -61,6 +62,7 @@ impl Pipeline { resource_task: ResourceTask, storage_task: StorageTask, time_profiler_chan: TimeProfilerChan, + memory_profiler_chan: MemoryProfilerChan, window_size: Option<WindowSizeData>, script_chan: Option<ScriptControlChan>, load_data: LoadData) @@ -123,6 +125,7 @@ impl Pipeline { LayoutTaskFactory::create(None::<&mut LTF>, id, + load_data.url.clone(), layout_pair, pipeline_port, constellation_chan, @@ -133,6 +136,7 @@ impl Pipeline { image_cache_task, font_cache_task, time_profiler_chan, + memory_profiler_chan, layout_shutdown_chan); Pipeline::new(id, diff --git a/components/gfx/display_list/mod.rs b/components/gfx/display_list/mod.rs index fde17c0aed5..6bdba843f91 100644 --- a/components/gfx/display_list/mod.rs +++ b/components/gfx/display_list/mod.rs @@ -37,6 +37,7 @@ use net::image::base::Image; use util::cursor::Cursor; use util::dlist as servo_dlist; use util::geometry::{self, Au, MAX_RECT, ZERO_RECT}; +use util::memory::SizeOf; use util::range::Range; use util::smallvec::{SmallVec, SmallVec8}; use std::fmt; @@ -198,6 +199,17 @@ impl DisplayList { } } +impl SizeOf for DisplayList { + fn size_of_excluding_self(&self) -> usize { + self.background_and_borders.size_of_excluding_self() + + self.block_backgrounds_and_borders.size_of_excluding_self() + + self.floats.size_of_excluding_self() + + self.content.size_of_excluding_self() + + self.outlines.size_of_excluding_self() + + self.children.size_of_excluding_self() + } +} + /// Represents one CSS stacking context, which may or may not have a hardware layer. pub struct StackingContext { /// The display items that make up this stacking context. @@ -501,6 +513,14 @@ impl StackingContext { } } +impl SizeOf for StackingContext { + fn size_of_excluding_self(&self) -> usize { + self.display_list.size_of_excluding_self() + + // FIXME(njn): other fields may be measured later, esp. `layer` + } +} + /// Returns the stacking context in the given tree of stacking contexts with a specific layer ID. pub fn find_stacking_context_with_layer_id(this: &Arc<StackingContext>, layer_id: LayerId) -> Option<Arc<StackingContext>> { @@ -556,6 +576,13 @@ impl BaseDisplayItem { } } +impl SizeOf for BaseDisplayItem { + fn size_of_excluding_self(&self) -> usize { + self.metadata.size_of_excluding_self() + + self.clip.size_of_excluding_self() + } +} + /// A clipping region for a display item. Currently, this can describe rectangles, rounded /// rectangles (for `border-radius`), or arbitrary intersections of the two. Arbitrary transforms /// are not supported because those are handled by the higher-level `StackingContext` abstraction. @@ -681,6 +708,18 @@ impl ClippingRegion { } } +impl SizeOf for ClippingRegion { + fn size_of_excluding_self(&self) -> usize { + self.complex.size_of_excluding_self() + } +} + +impl SizeOf for ComplexClippingRegion { + fn size_of_excluding_self(&self) -> usize { + 0 + } +} + /// Metadata attached to each display item. This is useful for performing auxiliary tasks with /// the display list involving hit testing: finding the originating DOM node and determining the /// cursor to use when the element is hovered over. @@ -712,6 +751,12 @@ impl DisplayItemMetadata { } } +impl SizeOf for DisplayItemMetadata { + fn size_of_excluding_self(&self) -> usize { + 0 + } +} + /// Paints a solid color. #[derive(Clone)] pub struct SolidColorDisplayItem { @@ -722,6 +767,12 @@ pub struct SolidColorDisplayItem { pub color: Color, } +impl SizeOf for SolidColorDisplayItem { + fn size_of_excluding_self(&self) -> usize { + self.base.size_of_excluding_self() + } +} + /// Paints text. #[derive(Clone)] pub struct TextDisplayItem { @@ -747,6 +798,13 @@ pub struct TextDisplayItem { pub blur_radius: Au, } +impl SizeOf for TextDisplayItem { + fn size_of_excluding_self(&self) -> usize { + self.base.size_of_excluding_self() + // We exclude `text_run` because it is non-owning. + } +} + #[derive(Clone, Eq, PartialEq)] pub enum TextOrientation { Upright, @@ -770,6 +828,13 @@ pub struct ImageDisplayItem { pub image_rendering: image_rendering::T, } +impl SizeOf for ImageDisplayItem { + fn size_of_excluding_self(&self) -> usize { + self.base.size_of_excluding_self() + // We exclude `image` here because it is non-owning. + } +} + /// Paints a gradient. #[derive(Clone)] pub struct GradientDisplayItem { @@ -786,6 +851,20 @@ pub struct GradientDisplayItem { pub stops: Vec<GradientStop>, } +impl SizeOf for GradientDisplayItem { + fn size_of_excluding_self(&self) -> usize { + use libc::c_void; + use util::memory::heap_size_of; + + // We can't measure `stops` via Vec's SizeOf implementation because GradientStop isn't + // defined in this module, and we don't want to import GradientStop into util::memory where + // the SizeOf trait is defined. So we measure the elements directly. + self.base.size_of_excluding_self() + + heap_size_of(self.stops.as_ptr() as *const c_void) + } +} + + /// Paints a border. #[derive(Clone)] pub struct BorderDisplayItem { @@ -807,6 +886,12 @@ pub struct BorderDisplayItem { pub radius: BorderRadii<Au>, } +impl SizeOf for BorderDisplayItem { + fn size_of_excluding_self(&self) -> usize { + self.base.size_of_excluding_self() + } +} + /// Information about the border radii. /// /// TODO(pcwalton): Elliptical radii. @@ -851,6 +936,12 @@ pub struct LineDisplayItem { pub style: border_style::T } +impl SizeOf for LineDisplayItem { + fn size_of_excluding_self(&self) -> usize { + self.base.size_of_excluding_self() + } +} + /// Paints a box shadow per CSS-BACKGROUNDS. #[derive(Clone)] pub struct BoxShadowDisplayItem { @@ -876,6 +967,12 @@ pub struct BoxShadowDisplayItem { pub clip_mode: BoxShadowClipMode, } +impl SizeOf for BoxShadowDisplayItem { + fn size_of_excluding_self(&self) -> usize { + self.base.size_of_excluding_self() + } +} + /// How a box shadow should be clipped. #[derive(Clone, Copy, Debug, PartialEq)] pub enum BoxShadowClipMode { @@ -1038,3 +1135,17 @@ impl fmt::Debug for DisplayItem { } } +impl SizeOf for DisplayItem { + fn size_of_excluding_self(&self) -> usize { + match *self { + SolidColorClass(ref item) => item.size_of_excluding_self(), + TextClass(ref item) => item.size_of_excluding_self(), + ImageClass(ref item) => item.size_of_excluding_self(), + BorderClass(ref item) => item.size_of_excluding_self(), + GradientClass(ref item) => item.size_of_excluding_self(), + LineClass(ref item) => item.size_of_excluding_self(), + BoxShadowClass(ref item) => item.size_of_excluding_self(), + } + } +} + diff --git a/components/layout/layout_task.rs b/components/layout/layout_task.rs index 5d1ab5781f8..d8c09a18218 100644 --- a/components/layout/layout_task.rs +++ b/components/layout/layout_task.rs @@ -56,6 +56,8 @@ use net::resource_task::{ResourceTask, load_bytes_iter}; use util::cursor::Cursor; use util::geometry::Au; use util::logical_geometry::LogicalPoint; +use util::memory::{MemoryProfilerChan, MemoryProfilerMsg, MemoryReport, MemoryReportsChan}; +use util::memory::{SizeOf}; use util::opts; use util::smallvec::{SmallVec, SmallVec1, VecLike}; use util::task::spawn_named_with_send_on_failure; @@ -117,6 +119,9 @@ pub struct LayoutTask { /// The ID of the pipeline that we belong to. pub id: PipelineId, + /// The URL of the pipeline that we belong to. + pub url: Url, + /// The port on which we receive messages from the script task. pub port: Receiver<Msg>, @@ -138,6 +143,12 @@ pub struct LayoutTask { /// The channel on which messages can be sent to the time profiler. pub time_profiler_chan: TimeProfilerChan, + /// The channel on which messages can be sent to the memory profiler. + pub memory_profiler_chan: MemoryProfilerChan, + + /// The name used for the task's memory reporter. + pub memory_reporter_name: String, + /// The channel on which messages can be sent to the resource task. pub resource_task: ResourceTask, @@ -181,6 +192,7 @@ impl LayoutTaskFactory for LayoutTask { /// Spawns a new layout task. fn create(_phantom: Option<&mut LayoutTask>, id: PipelineId, + url: Url, chan: OpaqueScriptLayoutChannel, pipeline_port: Receiver<LayoutControlMsg>, constellation_chan: ConstellationChan, @@ -191,6 +203,7 @@ impl LayoutTaskFactory for LayoutTask { img_cache_task: ImageCacheTask, font_cache_task: FontCacheTask, time_profiler_chan: TimeProfilerChan, + memory_profiler_chan: MemoryProfilerChan, shutdown_chan: Sender<()>) { let ConstellationChan(con_chan) = constellation_chan.clone(); spawn_named_with_send_on_failure("LayoutTask", task_state::LAYOUT, move || { @@ -199,6 +212,7 @@ impl LayoutTaskFactory for LayoutTask { let layout = LayoutTask::new( id, + url, chan.receiver(), LayoutChan(sender), pipeline_port, @@ -208,7 +222,8 @@ impl LayoutTaskFactory for LayoutTask { resource_task, img_cache_task, font_cache_task, - time_profiler_chan); + time_profiler_chan, + memory_profiler_chan); layout.start(); } shutdown_chan.send(()).unwrap(); @@ -249,6 +264,7 @@ impl<'a> DerefMut for RWGuard<'a> { impl LayoutTask { /// Creates a new `LayoutTask` structure. fn new(id: PipelineId, + url: Url, port: Receiver<Msg>, chan: LayoutChan, pipeline_port: Receiver<LayoutControlMsg>, @@ -258,7 +274,8 @@ impl LayoutTask { resource_task: ResourceTask, image_cache_task: ImageCacheTask, font_cache_task: FontCacheTask, - time_profiler_chan: TimeProfilerChan) + time_profiler_chan: TimeProfilerChan, + memory_profiler_chan: MemoryProfilerChan) -> LayoutTask { let local_image_cache = Arc::new(Mutex::new(LocalImageCache::new(image_cache_task.clone()))); @@ -271,8 +288,15 @@ impl LayoutTask { None }; + // Register this thread as a memory reporter, via its own channel. + let reporter = Box::new(chan.clone()); + let reporter_name = format!("layout-reporter-{}", id.0); + memory_profiler_chan.send(MemoryProfilerMsg::RegisterMemoryReporter(reporter_name.clone(), + reporter)); + LayoutTask { id: id, + url: url, port: port, pipeline_port: pipeline_port, chan: chan, @@ -280,6 +304,8 @@ impl LayoutTask { constellation_chan: constellation_chan.clone(), paint_chan: paint_chan, time_profiler_chan: time_profiler_chan, + memory_profiler_chan: memory_profiler_chan, + memory_reporter_name: reporter_name, resource_task: resource_task, image_cache_task: image_cache_task.clone(), font_cache_task: font_cache_task, @@ -423,6 +449,9 @@ impl LayoutTask { self.handle_reap_layout_data(dead_layout_data) } }, + Msg::CollectMemoryReports(reports_chan) => { + self.collect_memory_reports(reports_chan, possibly_locked_rw_data); + }, Msg::PrepareToExit(response_chan) => { debug!("layout: PrepareToExitMsg received"); self.prepare_to_exit(response_chan, possibly_locked_rw_data); @@ -438,6 +467,23 @@ impl LayoutTask { true } + fn collect_memory_reports<'a>(&'a self, + reports_chan: MemoryReportsChan, + possibly_locked_rw_data: + &mut Option<MutexGuard<'a, LayoutTaskData>>) { + let mut reports = vec![]; + + // FIXME(njn): Just measuring the display tree for now. + let rw_data = self.lock_rw_data(possibly_locked_rw_data); + let stacking_context = rw_data.stacking_context.as_ref(); + reports.push(MemoryReport { + name: format!("display-list::{}", self.url), + size: stacking_context.map_or(0, |sc| sc.size_of_excluding_self() as u64), + }); + + reports_chan.send(reports); + } + /// Enters a quiescent state in which no new messages except for `layout_interface::Msg::ReapLayoutData` will be /// processed until an `ExitNowMsg` is received. A pong is immediately sent on the given /// response channel. @@ -481,6 +527,10 @@ impl LayoutTask { LayoutTask::return_rw_data(possibly_locked_rw_data, rw_data); } + let unregister_msg = + MemoryProfilerMsg::UnregisterMemoryReporter(self.memory_reporter_name.clone()); + self.memory_profiler_chan.send(unregister_msg); + self.paint_chan.send(PaintMsg::Exit(Some(response_chan), exit_type)); response_port.recv().unwrap() } diff --git a/components/layout_traits/Cargo.toml b/components/layout_traits/Cargo.toml index 29556f9b975..4922db6639b 100644 --- a/components/layout_traits/Cargo.toml +++ b/components/layout_traits/Cargo.toml @@ -21,3 +21,6 @@ path = "../net" [dependencies.util] path = "../util" + +[dependencies] +url = "0.2.16" diff --git a/components/layout_traits/lib.rs b/components/layout_traits/lib.rs index 788032169be..d72063383f0 100644 --- a/components/layout_traits/lib.rs +++ b/components/layout_traits/lib.rs @@ -8,6 +8,7 @@ extern crate gfx; extern crate script_traits; extern crate msg; extern crate net; +extern crate url; extern crate util; // This module contains traits in layout used generically @@ -20,6 +21,8 @@ use gfx::paint_task::PaintChan; use msg::constellation_msg::{ConstellationChan, Failure, PipelineId, PipelineExitType}; use net::image_cache_task::ImageCacheTask; use net::resource_task::ResourceTask; +use url::Url; +use util::memory::MemoryProfilerChan; use util::time::TimeProfilerChan; use script_traits::{ScriptControlChan, OpaqueScriptLayoutChannel}; use std::sync::mpsc::{Sender, Receiver}; @@ -38,6 +41,7 @@ pub trait LayoutTaskFactory { // FIXME: use a proper static method fn create(_phantom: Option<&mut Self>, id: PipelineId, + url: Url, chan: OpaqueScriptLayoutChannel, pipeline_port: Receiver<LayoutControlMsg>, constellation_chan: ConstellationChan, @@ -48,5 +52,6 @@ pub trait LayoutTaskFactory { img_cache_task: ImageCacheTask, font_cache_task: FontCacheTask, time_profiler_chan: TimeProfilerChan, + memory_profiler_chan: MemoryProfilerChan, shutdown_chan: Sender<()>); } diff --git a/components/script/layout_interface.rs b/components/script/layout_interface.rs index e6e448dd800..52b5aed1b43 100644 --- a/components/script/layout_interface.rs +++ b/components/script/layout_interface.rs @@ -13,6 +13,7 @@ use geom::rect::Rect; use script_traits::{ScriptControlChan, OpaqueScriptLayoutChannel, UntrustedNodeAddress}; use msg::constellation_msg::{PipelineExitType, WindowSizeData}; use util::geometry::Au; +use util::memory::{MemoryReporter, MemoryReportsChan}; use std::any::Any; use std::sync::mpsc::{channel, Receiver, Sender}; use std::boxed::BoxAny; @@ -44,6 +45,10 @@ pub enum Msg { /// TODO(pcwalton): Maybe think about batching to avoid message traffic. ReapLayoutData(LayoutData), + /// Requests that the layout task measure its memory usage. The resulting reports are sent back + /// via the supplied channel. + CollectMemoryReports(MemoryReportsChan), + /// Requests that the layout task enter a quiescent state in which no more messages are /// accepted except `ExitMsg`. A response message will be sent on the supplied channel when /// this happens. @@ -128,6 +133,14 @@ impl LayoutChan { } } +impl MemoryReporter for LayoutChan { + // Just injects an appropriate event into the layout task's queue. + fn collect_reports(&self, reports_chan: MemoryReportsChan) -> bool { + let LayoutChan(ref c) = *self; + c.send(Msg::CollectMemoryReports(reports_chan)).is_ok() + } +} + /// A trait to manage opaque references to script<->layout channels without needing /// to expose the message type to crates that don't need to know about them. pub trait ScriptLayoutChan { diff --git a/components/servo/Cargo.lock b/components/servo/Cargo.lock index 41ab61464dc..f58178f144d 100644 --- a/components/servo/Cargo.lock +++ b/components/servo/Cargo.lock @@ -534,6 +534,7 @@ dependencies = [ "msg 0.0.1", "net 0.0.1", "script_traits 0.0.1", + "url 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", "util 0.0.1", ] diff --git a/components/servo/lib.rs b/components/servo/lib.rs index ef2b1b320c5..c41b5df01af 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -82,6 +82,7 @@ impl<Window> Browser<Window> where Window: WindowMethods + 'static { let opts_clone = opts.clone(); let time_profiler_chan_clone = time_profiler_chan.clone(); + let memory_profiler_chan_clone = memory_profiler_chan.clone(); let (result_chan, result_port) = channel(); let compositor_proxy_for_constellation = compositor_proxy.clone_compositor_proxy(); @@ -109,6 +110,7 @@ impl<Window> Browser<Window> where Window: WindowMethods + 'static { image_cache_task, font_cache_task, time_profiler_chan_clone, + memory_profiler_chan_clone, devtools_chan, storage_task); diff --git a/components/util/memory.rs b/components/util/memory.rs index 6327a6e01f9..e72873872f8 100644 --- a/components/util/memory.rs +++ b/components/util/memory.rs @@ -6,20 +6,175 @@ use libc::{c_char,c_int,c_void,size_t}; use std::borrow::ToOwned; +use std::collections::{DList, HashMap}; use std::ffi::CString; #[cfg(target_os = "linux")] use std::iter::AdditiveIterator; use std::old_io::timer::sleep; #[cfg(target_os="linux")] use std::old_io::File; -use std::mem::size_of; +use std::mem::{size_of, transmute}; use std::ptr::null_mut; +use std::sync::Arc; use std::sync::mpsc::{Sender, channel, Receiver}; use std::time::duration::Duration; use task::spawn_named; #[cfg(target_os="macos")] use task_info::task_basic_info::{virtual_size,resident_size}; +extern { + // Get the size of a heap block. + // + // Ideally Rust would expose a function like this in std::rt::heap, which would avoid the + // jemalloc dependence. + // + // The C prototype is `je_malloc_usable_size(JEMALLOC_USABLE_SIZE_CONST void *ptr)`. On some + // platforms `JEMALLOC_USABLE_SIZE_CONST` is `const` and on some it is empty. But in practice + // this function doesn't modify the contents of the block that `ptr` points to, so we use + // `*const c_void` here. + fn je_malloc_usable_size(ptr: *const c_void) -> size_t; +} + +// A wrapper for je_malloc_usable_size that handles `EMPTY` and returns `usize`. +pub fn heap_size_of(ptr: *const c_void) -> usize { + if ptr == ::std::rt::heap::EMPTY as *const c_void { + 0 + } else { + unsafe { je_malloc_usable_size(ptr) as usize } + } +} + +// The simplest trait for measuring the size of heap data structures. More complex traits that +// return multiple measurements -- e.g. measure text separately from images -- are also possible, +// and should be used when appropriate. +// +// FIXME(njn): it would be nice to be able to derive this trait automatically, given that +// implementations are mostly repetitive and mechanical. +// +pub trait SizeOf { + /// Measure the size of any heap-allocated structures that hang off this value, but not the + /// space taken up by the value itself (i.e. what size_of::<T> measures, more or less); that + /// space is handled by the implementation of SizeOf for Box<T> below. + fn size_of_excluding_self(&self) -> usize; +} + +// There are two possible ways to measure the size of `self` when it's on the heap: compute it +// (with `::std::rt::heap::usable_size(::std::mem::size_of::<T>(), 0)`) or measure it directly +// using the heap allocator (with `heap_size_of`). We do the latter, for the following reasons. +// +// * The heap allocator is the true authority for the sizes of heap blocks; its measurement is +// guaranteed to be correct. In comparison, size computations are error-prone. (For example, the +// `rt::heap::usable_size` function used in some of Rust's non-default allocator implementations +// underestimate the true usable size of heap blocks, which is safe in general but would cause +// under-measurement here.) +// +// * If we measure something that isn't a heap block, we'll get a crash. This keeps us honest, +// which is important because unsafe code is involved and this can be gotten wrong. +// +// However, in the best case, the two approaches should give the same results. +// +impl<T: SizeOf> SizeOf for Box<T> { + fn size_of_excluding_self(&self) -> usize { + // Measure size of `self`. + heap_size_of(&**self as *const T as *const c_void) + (**self).size_of_excluding_self() + } +} + +impl SizeOf for String { + fn size_of_excluding_self(&self) -> usize { + heap_size_of(self.as_ptr() as *const c_void) + } +} + +impl<T: SizeOf> SizeOf for Option<T> { + fn size_of_excluding_self(&self) -> usize { + match *self { + None => 0, + Some(ref x) => x.size_of_excluding_self() + } + } +} + +impl<T: SizeOf> SizeOf for Arc<T> { + fn size_of_excluding_self(&self) -> usize { + (**self).size_of_excluding_self() + } +} + +impl<T: SizeOf> SizeOf for Vec<T> { + fn size_of_excluding_self(&self) -> usize { + heap_size_of(self.as_ptr() as *const c_void) + + self.iter().fold(0, |n, elem| n + elem.size_of_excluding_self()) + } +} + +// FIXME(njn): We can't implement SizeOf accurately for DList because it requires access to the +// private Node type. Eventually we'll want to add SizeOf (or equivalent) to Rust itself. In the +// meantime, we use the dirty hack of transmuting DList into an identical type (DList2) and +// measuring that. +impl<T: SizeOf> SizeOf for DList<T> { + fn size_of_excluding_self(&self) -> usize { + let list2: &DList2<T> = unsafe { transmute(self) }; + list2.size_of_excluding_self() + } +} + +struct DList2<T> { + _length: usize, + list_head: Link<T>, + _list_tail: Rawlink<Node<T>>, +} + +type Link<T> = Option<Box<Node<T>>>; + +struct Rawlink<T> { + _p: *mut T, +} + +struct Node<T> { + next: Link<T>, + _prev: Rawlink<Node<T>>, + value: T, +} + +impl<T: SizeOf> SizeOf for Node<T> { + // Unlike most size_of_excluding_self() functions, this one does *not* measure descendents. + // Instead, DList2<T>::size_of_excluding_self() handles that, so that it can use iteration + // instead of recursion, which avoids potentially blowing the stack. + fn size_of_excluding_self(&self) -> usize { + self.value.size_of_excluding_self() + } +} + +impl<T: SizeOf> SizeOf for DList2<T> { + fn size_of_excluding_self(&self) -> usize { + let mut size = 0; + let mut curr: &Link<T> = &self.list_head; + while curr.is_some() { + size += (*curr).size_of_excluding_self(); + curr = &curr.as_ref().unwrap().next; + } + size + } +} + +// This is a basic sanity check. If the representation of DList changes such that it becomes a +// different size to DList2, this will fail at compile-time. +#[allow(dead_code)] +unsafe fn dlist2_check() { + transmute::<DList<i32>, DList2<i32>>(panic!()); +} + +// Currently, types that implement the Drop type are larger than those that don't. Because DList +// implements Drop, DList2 must also so that dlist2_check() doesn't fail. +#[unsafe_destructor] +impl<T> Drop for DList2<T> { + fn drop(&mut self) {} +} + +//--------------------------------------------------------------------------- + +#[derive(Clone)] pub struct MemoryProfilerChan(pub Sender<MemoryProfilerMsg>); impl MemoryProfilerChan { @@ -29,15 +184,60 @@ impl MemoryProfilerChan { } } +pub struct MemoryReport { + /// The identifying name for this report. + pub name: String, + + /// The size, in bytes. + pub size: u64, +} + +/// A channel through which memory reports can be sent. +#[derive(Clone)] +pub struct MemoryReportsChan(pub Sender<Vec<MemoryReport>>); + +impl MemoryReportsChan { + pub fn send(&self, report: Vec<MemoryReport>) { + let MemoryReportsChan(ref c) = *self; + c.send(report).unwrap(); + } +} + +/// A memory reporter is capable of measuring some data structure of interest. Because it needs +/// to be passed to and registered with the MemoryProfiler, it's typically a "small" (i.e. easily +/// cloneable) value that provides access to a "large" data structure, e.g. a channel that can +/// inject a request for measurements into the event queue associated with the "large" data +/// structure. +pub trait MemoryReporter { + /// Collect one or more memory reports. Returns true on success, and false on failure. + fn collect_reports(&self, reports_chan: MemoryReportsChan) -> bool; +} + +/// Messages that can be sent to the memory profiler thread. pub enum MemoryProfilerMsg { - /// Message used to force print the memory profiling metrics. + /// Register a MemoryReporter with the memory profiler. The String is only used to identify the + /// reporter so it can be unregistered later. The String must be distinct from that used by any + /// other registered reporter otherwise a panic will occur. + RegisterMemoryReporter(String, Box<MemoryReporter + Send>), + + /// Unregister a MemoryReporter with the memory profiler. The String must match the name given + /// when the reporter was registered. If the String does not match the name of a registered + /// reporter a panic will occur. + UnregisterMemoryReporter(String), + + /// Triggers printing of the memory profiling metrics. Print, + /// Tells the memory profiler to shut down. Exit, } pub struct MemoryProfiler { + /// The port through which messages are received. pub port: Receiver<MemoryProfilerMsg>, + + /// Registered memory reporters. + reporters: HashMap<String, Box<MemoryReporter + Send>>, } impl MemoryProfiler { @@ -57,7 +257,7 @@ impl MemoryProfiler { }); // Spawn the memory profiler. spawn_named("Memory profiler".to_owned(), move || { - let memory_profiler = MemoryProfiler::new(port); + let mut memory_profiler = MemoryProfiler::new(port); memory_profiler.start(); }); } @@ -80,11 +280,12 @@ impl MemoryProfiler { pub fn new(port: Receiver<MemoryProfilerMsg>) -> MemoryProfiler { MemoryProfiler { - port: port + port: port, + reporters: HashMap::new(), } } - pub fn start(&self) { + pub fn start(&mut self) { loop { match self.port.recv() { Ok(msg) => { @@ -97,12 +298,33 @@ impl MemoryProfiler { } } - fn handle_msg(&self, msg: MemoryProfilerMsg) -> bool { + fn handle_msg(&mut self, msg: MemoryProfilerMsg) -> bool { match msg { + MemoryProfilerMsg::RegisterMemoryReporter(name, reporter) => { + // Panic if it has already been registered. + let name_clone = name.clone(); + match self.reporters.insert(name, reporter) { + None => true, + Some(_) => + panic!(format!("RegisterMemoryReporter: '{}' name is already in use", + name_clone)), + } + }, + + MemoryProfilerMsg::UnregisterMemoryReporter(name) => { + // Panic if it hasn't previously been registered. + match self.reporters.remove(&name) { + Some(_) => true, + None => + panic!(format!("UnregisterMemoryReporter: '{}' name is unknown", &name)), + } + }, + MemoryProfilerMsg::Print => { self.handle_print_msg(); true }, + MemoryProfilerMsg::Exit => false } } @@ -120,8 +342,11 @@ impl MemoryProfiler { } fn handle_print_msg(&self) { + println!("{:12}: {}", "_size (MiB)_", "_category_"); + // Collect global measurements from the OS and heap allocators. + // Virtual and physical memory usage, as reported by the OS. MemoryProfiler::print_measurement("vsize", get_vsize()); MemoryProfiler::print_measurement("resident", get_resident()); @@ -154,6 +379,24 @@ impl MemoryProfiler { MemoryProfiler::print_measurement("jemalloc-heap-mapped", get_jemalloc_stat("stats.mapped")); + // Collect reports from memory reporters. + + // This serializes the report-gathering. It might be worth creating a new scoped thread for + // each reporter once we have enough of them. + // + // If anything goes wrong with a reporter, we just skip it. + for reporter in self.reporters.values() { + let (chan, port) = channel(); + if reporter.collect_reports(MemoryReportsChan(chan)) { + if let Ok(reports) = port.recv() { + for report in reports { + MemoryProfiler::print_measurement(report.name.as_slice(), + Some(report.size)); + } + } + } + } + println!(""); } } diff --git a/ports/cef/Cargo.lock b/ports/cef/Cargo.lock index 8504a68c52c..b7fb83f53aa 100644 --- a/ports/cef/Cargo.lock +++ b/ports/cef/Cargo.lock @@ -542,6 +542,7 @@ dependencies = [ "msg 0.0.1", "net 0.0.1", "script_traits 0.0.1", + "url 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", "util 0.0.1", ] diff --git a/ports/gonk/Cargo.lock b/ports/gonk/Cargo.lock index 981037eb606..c776730575d 100644 --- a/ports/gonk/Cargo.lock +++ b/ports/gonk/Cargo.lock @@ -453,6 +453,7 @@ dependencies = [ "msg 0.0.1", "net 0.0.1", "script_traits 0.0.1", + "url 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", "util 0.0.1", ] diff --git a/ports/gonk/src/lib.rs b/ports/gonk/src/lib.rs index f74b266133d..ba5cc455563 100644 --- a/ports/gonk/src/lib.rs +++ b/ports/gonk/src/lib.rs @@ -86,6 +86,7 @@ impl<Window> Browser<Window> where Window: WindowMethods + 'static { let opts_clone = opts.clone(); let time_profiler_chan_clone = time_profiler_chan.clone(); + let memory_profiler_chan_clone = memory_profiler_chan.clone(); let (result_chan, result_port) = channel(); let compositor_proxy_for_constellation = compositor_proxy.clone_compositor_proxy(); @@ -113,6 +114,7 @@ impl<Window> Browser<Window> where Window: WindowMethods + 'static { image_cache_task, font_cache_task, time_profiler_chan_clone, + memory_profiler_chan_clone, devtools_chan, storage_task); |