aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/compositing/compositor.rs8
-rw-r--r--components/compositing/constellation.rs7
-rw-r--r--components/compositing/pipeline.rs4
-rw-r--r--components/gfx/display_list/mod.rs111
-rw-r--r--components/layout/layout_task.rs54
-rw-r--r--components/layout_traits/Cargo.toml3
-rw-r--r--components/layout_traits/lib.rs5
-rw-r--r--components/script/layout_interface.rs13
-rw-r--r--components/servo/Cargo.lock1
-rw-r--r--components/servo/lib.rs2
-rw-r--r--components/util/memory.rs255
-rw-r--r--ports/cef/Cargo.lock1
-rw-r--r--ports/gonk/Cargo.lock1
-rw-r--r--ports/gonk/src/lib.rs2
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);