aboutsummaryrefslogtreecommitdiffstats
path: root/components/profile/time.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/profile/time.rs')
-rw-r--r--components/profile/time.rs303
1 files changed, 303 insertions, 0 deletions
diff --git a/components/profile/time.rs b/components/profile/time.rs
new file mode 100644
index 00000000000..fcd83d8093a
--- /dev/null
+++ b/components/profile/time.rs
@@ -0,0 +1,303 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Timing functions.
+
+use collections::BTreeMap;
+use std::borrow::ToOwned;
+use std::cmp::Ordering;
+use std::f64;
+use std::old_io::timer::sleep;
+use std::iter::AdditiveIterator;
+use std::num::Float;
+use std::sync::mpsc::{Sender, channel, Receiver};
+use std::time::duration::Duration;
+use std_time::precise_time_ns;
+use url::Url;
+use util::task::spawn_named;
+
+// front-end representation of the profiler used to communicate with the profiler
+#[derive(Clone)]
+pub struct TimeProfilerChan(pub Sender<TimeProfilerMsg>);
+
+impl TimeProfilerChan {
+ pub fn send(&self, msg: TimeProfilerMsg) {
+ let TimeProfilerChan(ref c) = *self;
+ c.send(msg).unwrap();
+ }
+}
+
+#[derive(PartialEq, Clone, PartialOrd, Eq, Ord)]
+pub struct TimerMetadata {
+ url: String,
+ iframe: bool,
+ incremental: bool,
+}
+
+pub trait Formatable {
+ fn format(&self) -> String;
+}
+
+impl Formatable for Option<TimerMetadata> {
+ fn format(&self) -> String {
+ match self {
+ // TODO(cgaebel): Center-align in the format strings as soon as rustc supports it.
+ &Some(ref meta) => {
+ let url = &*meta.url;
+ let url = if url.len() > 30 {
+ &url[..30]
+ } else {
+ url
+ };
+ let incremental = if meta.incremental { " yes" } else { " no " };
+ let iframe = if meta.iframe { " yes" } else { " no " };
+ format!(" {:14} {:9} {:30}", incremental, iframe, url)
+ },
+ &None =>
+ format!(" {:14} {:9} {:30}", " N/A", " N/A", " N/A")
+ }
+ }
+}
+
+#[derive(Clone)]
+pub enum TimeProfilerMsg {
+ /// Normal message used for reporting time
+ Time((TimeProfilerCategory, Option<TimerMetadata>), f64),
+ /// Message used to force print the profiling metrics
+ Print,
+ /// Tells the profiler to shut down.
+ Exit,
+}
+
+#[repr(u32)]
+#[derive(PartialEq, Clone, PartialOrd, Eq, Ord)]
+pub enum TimeProfilerCategory {
+ Compositing,
+ LayoutPerform,
+ LayoutStyleRecalc,
+ LayoutRestyleDamagePropagation,
+ LayoutNonIncrementalReset,
+ LayoutSelectorMatch,
+ LayoutTreeBuilder,
+ LayoutDamagePropagate,
+ LayoutGeneratedContent,
+ LayoutMain,
+ LayoutParallelWarmup,
+ LayoutShaping,
+ LayoutDispListBuild,
+ PaintingPerTile,
+ PaintingPrepBuff,
+ Painting,
+ ImageDecoding,
+}
+
+impl Formatable for TimeProfilerCategory {
+ // some categories are subcategories of LayoutPerformCategory
+ // and should be printed to indicate this
+ fn format(&self) -> String {
+ let padding = match *self {
+ TimeProfilerCategory::LayoutStyleRecalc |
+ TimeProfilerCategory::LayoutRestyleDamagePropagation |
+ TimeProfilerCategory::LayoutNonIncrementalReset |
+ TimeProfilerCategory::LayoutGeneratedContent |
+ TimeProfilerCategory::LayoutMain |
+ TimeProfilerCategory::LayoutDispListBuild |
+ TimeProfilerCategory::LayoutShaping |
+ TimeProfilerCategory::LayoutDamagePropagate |
+ TimeProfilerCategory::PaintingPerTile |
+ TimeProfilerCategory::PaintingPrepBuff => "+ ",
+ TimeProfilerCategory::LayoutParallelWarmup |
+ TimeProfilerCategory::LayoutSelectorMatch |
+ TimeProfilerCategory::LayoutTreeBuilder => "| + ",
+ _ => ""
+ };
+ let name = match *self {
+ TimeProfilerCategory::Compositing => "Compositing",
+ TimeProfilerCategory::LayoutPerform => "Layout",
+ TimeProfilerCategory::LayoutStyleRecalc => "Style Recalc",
+ TimeProfilerCategory::LayoutRestyleDamagePropagation => "Restyle Damage Propagation",
+ TimeProfilerCategory::LayoutNonIncrementalReset => "Non-incremental reset (temporary)",
+ TimeProfilerCategory::LayoutSelectorMatch => "Selector Matching",
+ TimeProfilerCategory::LayoutTreeBuilder => "Tree Building",
+ TimeProfilerCategory::LayoutDamagePropagate => "Damage Propagation",
+ TimeProfilerCategory::LayoutGeneratedContent => "Generated Content Resolution",
+ TimeProfilerCategory::LayoutMain => "Primary Layout Pass",
+ TimeProfilerCategory::LayoutParallelWarmup => "Parallel Warmup",
+ TimeProfilerCategory::LayoutShaping => "Shaping",
+ TimeProfilerCategory::LayoutDispListBuild => "Display List Construction",
+ TimeProfilerCategory::PaintingPerTile => "Painting Per Tile",
+ TimeProfilerCategory::PaintingPrepBuff => "Buffer Prep",
+ TimeProfilerCategory::Painting => "Painting",
+ TimeProfilerCategory::ImageDecoding => "Image Decoding",
+ };
+ format!("{}{}", padding, name)
+ }
+}
+
+type TimeProfilerBuckets = BTreeMap<(TimeProfilerCategory, Option<TimerMetadata>), Vec<f64>>;
+
+// back end of the profiler that handles data aggregation and performance metrics
+pub struct TimeProfiler {
+ pub port: Receiver<TimeProfilerMsg>,
+ buckets: TimeProfilerBuckets,
+ pub last_msg: Option<TimeProfilerMsg>,
+}
+
+impl TimeProfiler {
+ pub fn create(period: Option<f64>) -> TimeProfilerChan {
+ let (chan, port) = channel();
+ match period {
+ Some(period) => {
+ let period = Duration::milliseconds((period * 1000f64) as i64);
+ let chan = chan.clone();
+ spawn_named("Time profiler timer".to_owned(), move || {
+ loop {
+ sleep(period);
+ if chan.send(TimeProfilerMsg::Print).is_err() {
+ break;
+ }
+ }
+ });
+ // Spawn the time profiler.
+ spawn_named("Time profiler".to_owned(), move || {
+ let mut profiler = TimeProfiler::new(port);
+ profiler.start();
+ });
+ }
+ None => {
+ // No-op to handle messages when the time profiler is inactive.
+ spawn_named("Time profiler".to_owned(), move || {
+ loop {
+ match port.recv() {
+ Err(_) | Ok(TimeProfilerMsg::Exit) => break,
+ _ => {}
+ }
+ }
+ });
+ }
+ }
+
+ TimeProfilerChan(chan)
+ }
+
+ pub fn new(port: Receiver<TimeProfilerMsg>) -> TimeProfiler {
+ TimeProfiler {
+ port: port,
+ buckets: BTreeMap::new(),
+ last_msg: None,
+ }
+ }
+
+ pub fn start(&mut self) {
+ loop {
+ let msg = self.port.recv();
+ match msg {
+ Ok(msg) => {
+ if !self.handle_msg(msg) {
+ break
+ }
+ }
+ _ => break
+ }
+ }
+ }
+
+ fn find_or_insert(&mut self, k: (TimeProfilerCategory, Option<TimerMetadata>), t: f64) {
+ match self.buckets.get_mut(&k) {
+ None => {},
+ Some(v) => { v.push(t); return; },
+ }
+
+ self.buckets.insert(k, vec!(t));
+ }
+
+ fn handle_msg(&mut self, msg: TimeProfilerMsg) -> bool {
+ match msg.clone() {
+ TimeProfilerMsg::Time(k, t) => self.find_or_insert(k, t),
+ TimeProfilerMsg::Print => match self.last_msg {
+ // only print if more data has arrived since the last printout
+ Some(TimeProfilerMsg::Time(..)) => self.print_buckets(),
+ _ => ()
+ },
+ TimeProfilerMsg::Exit => return false,
+ };
+ self.last_msg = Some(msg);
+ true
+ }
+
+ fn print_buckets(&mut self) {
+ println!("{:35} {:14} {:9} {:30} {:15} {:15} {:-15} {:-15} {:-15}",
+ "_category_", "_incremental?_", "_iframe?_",
+ " _url_", " _mean (ms)_", " _median (ms)_",
+ " _min (ms)_", " _max (ms)_", " _events_");
+ for (&(ref category, ref meta), ref mut data) in self.buckets.iter_mut() {
+ data.sort_by(|a, b| {
+ if a < b {
+ Ordering::Less
+ } else {
+ Ordering::Greater
+ }
+ });
+ let data_len = data.len();
+ if data_len > 0 {
+ let (mean, median, min, max) =
+ (data.iter().map(|&x|x).sum() / (data_len as f64),
+ data[data_len / 2],
+ data.iter().fold(f64::INFINITY, |a, &b| a.min(b)),
+ data.iter().fold(-f64::INFINITY, |a, &b| a.max(b)));
+ println!("{:-35}{} {:15.4} {:15.4} {:15.4} {:15.4} {:15}",
+ category.format(), meta.format(), mean, median, min, max, data_len);
+ }
+ }
+ println!("");
+ }
+}
+
+#[derive(Eq, PartialEq)]
+pub enum TimerMetadataFrameType {
+ RootWindow,
+ IFrame,
+}
+
+#[derive(Eq, PartialEq)]
+pub enum TimerMetadataReflowType {
+ Incremental,
+ FirstReflow,
+}
+
+pub type ProfilerMetadata<'a> = Option<(&'a Url, TimerMetadataFrameType, TimerMetadataReflowType)>;
+
+pub fn profile<T, F>(category: TimeProfilerCategory,
+ meta: ProfilerMetadata,
+ time_profiler_chan: TimeProfilerChan,
+ callback: F)
+ -> T
+ where F: FnOnce() -> T
+{
+ let start_time = precise_time_ns();
+ let val = callback();
+ let end_time = precise_time_ns();
+ let ms = (end_time - start_time) as f64 / 1000000f64;
+ let meta = meta.map(|(url, iframe, reflow_type)|
+ TimerMetadata {
+ url: url.serialize(),
+ iframe: iframe == TimerMetadataFrameType::IFrame,
+ incremental: reflow_type == TimerMetadataReflowType::Incremental,
+ });
+ time_profiler_chan.send(TimeProfilerMsg::Time((category, meta), ms));
+ return val;
+}
+
+pub fn time<T, F>(msg: &str, callback: F) -> T
+ where F: Fn() -> T
+{
+ let start_time = precise_time_ns();
+ let val = callback();
+ let end_time = precise_time_ns();
+ let ms = (end_time - start_time) as f64 / 1000000f64;
+ if ms >= 5f64 {
+ debug!("{} took {} ms", msg, ms);
+ }
+ return val;
+}