Add memory reporting infrastructure and use it to measure the display list.

This changeset implements the beginnings of fine-grained measurement of
Servo's data structures.

- It adds a new `SizeOf` trait, which is used to measure the memory used
  by heap data structures, and implements it for some std types: Box,
  String, Option, Arc, Vec, and DList.

- It adds a new `MemoryReporter` trait which is used to report memory
  measurements from other threads to the memory profiler. Reporters are
  registered and unregistered with the memory profiler, and the memory
  profiler makes measurement requests of reporters when necessary.

- It plumbs a MemoryProfilerChan through to the layout task so it can
  register a memory reporter.

- It implements the `SizeOf` trait for `DisplayList` and associated
  types, and adds a memory reporter that uses it.

The display list hits 14.77 MiB when viewing
tests/html/perf-rainbow.html, and 2.51 MiB when viewing the Guardians of
the Galaxy Wikipedia page from servo-static-suite. Example output:

  0.29: display-list::http://www.reddit.com/
  0.00: display-list::http://static.adzerk.net/reddit/ads.html?sr=-reddit.com,loggedout&bust2#http://www.reddit.com
  0.00: display-list::http://www.reddit.com/static/createadframe.html

There are a number of FIXME comments indicating sub-optimal things. This
is a big enough change for now that doing them as follow-ups seems best.
This commit is contained in:
Nicholas Nethercote 2015-03-10 21:01:05 -07:00
parent 5865d5f717
commit ece2711185
14 changed files with 453 additions and 14 deletions

View file

@ -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();
}

View file

@ -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);

View file

@ -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,

View file

@ -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(),
}
}
}

View file

@ -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()
}

View file

@ -21,3 +21,6 @@ path = "../net"
[dependencies.util]
path = "../util"
[dependencies]
url = "0.2.16"

View file

@ -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<()>);
}

View file

@ -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 {

View file

@ -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",
]

View file

@ -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);

View file

@ -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!("");
}
}

1
ports/cef/Cargo.lock generated
View file

@ -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",
]

1
ports/gonk/Cargo.lock generated
View file

@ -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",
]

View file

@ -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);