diff --git a/components/profile/Cargo.toml b/components/profile/Cargo.toml index 4ec2abfcb87..c71569c3177 100644 --- a/components/profile/Cargo.toml +++ b/components/profile/Cargo.toml @@ -13,6 +13,9 @@ path = "../profile_traits" [dependencies.util] path = "../util" +[dependencies.hbs-pow] +git = "https://github.com/libheartbeats/heartbeats-simple-rust.git" + [dependencies.ipc-channel] git = "https://github.com/pcwalton/ipc-channel" diff --git a/components/profile/heartbeats.rs b/components/profile/heartbeats.rs new file mode 100644 index 00000000000..0d7f9511d4c --- /dev/null +++ b/components/profile/heartbeats.rs @@ -0,0 +1,120 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +use hbs_pow::HeartbeatPow as Heartbeat; +use hbs_pow::HeartbeatPowContext as HeartbeatContext; +use profile_traits::time::ProfilerCategory; +use std::collections::HashMap; +use std::env::var_os; +use std::error::Error; +use std::fs::File; +use std::mem; + + +static mut HBS: Option<*mut HashMap> = None; + +/// Initialize heartbeats +pub fn init() { + let mut hbs: HashMap = HashMap::new(); + maybe_create_heartbeat(&mut hbs, ProfilerCategory::Compositing); + maybe_create_heartbeat(&mut hbs, ProfilerCategory::LayoutPerform); + maybe_create_heartbeat(&mut hbs, ProfilerCategory::LayoutStyleRecalc); + maybe_create_heartbeat(&mut hbs, ProfilerCategory::LayoutRestyleDamagePropagation); + maybe_create_heartbeat(&mut hbs, ProfilerCategory::LayoutNonIncrementalReset); + maybe_create_heartbeat(&mut hbs, ProfilerCategory::LayoutSelectorMatch); + maybe_create_heartbeat(&mut hbs, ProfilerCategory::LayoutTreeBuilder); + maybe_create_heartbeat(&mut hbs, ProfilerCategory::LayoutDamagePropagate); + maybe_create_heartbeat(&mut hbs, ProfilerCategory::LayoutGeneratedContent); + maybe_create_heartbeat(&mut hbs, ProfilerCategory::LayoutMain); + maybe_create_heartbeat(&mut hbs, ProfilerCategory::LayoutParallelWarmup); + maybe_create_heartbeat(&mut hbs, ProfilerCategory::LayoutShaping); + maybe_create_heartbeat(&mut hbs, ProfilerCategory::LayoutDispListBuild); + maybe_create_heartbeat(&mut hbs, ProfilerCategory::PaintingPerTile); + maybe_create_heartbeat(&mut hbs, ProfilerCategory::PaintingPrepBuff); + maybe_create_heartbeat(&mut hbs, ProfilerCategory::Painting); + maybe_create_heartbeat(&mut hbs, ProfilerCategory::ImageDecoding); + unsafe { + HBS = Some(mem::transmute(Box::new(hbs))); + } +} + +/// Log regmaining buffer data and cleanup heartbeats +pub fn cleanup() { + unsafe { + if let Some(hbs) = HBS { + let mut h: Box> = mem::transmute(hbs); + for (_, mut v) in h.iter_mut() { + // log any remaining heartbeat records before dropping + log_heartbeat_records(v); + } + h.clear(); + } + HBS = None; + } +} + +/// Issue a heartbeat (if one exists) for the given category +pub fn maybe_heartbeat(category: &ProfilerCategory, + start_time: u64, + end_time: u64, + start_energy: u64, + end_energy: u64) { + unsafe { + if let Some(map) = HBS { + if let Some(mut h) = (*map).get_mut(category) { + (*h).heartbeat(0, 1, start_time, end_time, start_energy, end_energy); + } + } + } +} + +/// Create a heartbeat if the correct environment variable is set +fn maybe_create_heartbeat(hbs: &mut HashMap, + category: ProfilerCategory) { + static WINDOW_SIZE_DEFAULT: usize = 20; + if let Some(_) = var_os(format!("SERVO_HEARTBEAT_ENABLE_{:?}", category)) { + // get optional log file + let logfile: Option = var_os(format!("SERVO_HEARTBEAT_LOG_{:?}", category)) + .and_then(|name| File::create(name).ok()); + // get window size + let window_size: usize = match var_os(format!("SERVO_HEARTBEAT_WINDOW_{:?}", category)) { + Some(w) => match w.into_string() { + Ok(s) => s.parse::().unwrap_or(WINDOW_SIZE_DEFAULT), + _ => WINDOW_SIZE_DEFAULT, + }, + None => WINDOW_SIZE_DEFAULT, + }; + // create the heartbeat + match Heartbeat::new(window_size, Some(heartbeat_window_callback), logfile) { + Ok(hb) => { + debug!("Created heartbeat for {:?}", category); + hbs.insert(category, hb); + }, + Err(e) => warn!("Failed to create heartbeat for {:?}: {}", category, e), + } + }; +} + +/// Log heartbeat records up to the buffer index +fn log_heartbeat_records(hb: &mut Heartbeat) { + match hb.log_to_buffer_index() { + Ok(_) => (), + Err(e) => warn!("Failed to write heartbeat log: {}", Error::description(&e)), + } +} + +/// Callback function used to log the window buffer. +/// When this is called from native C, the heartbeat is safely locked +extern fn heartbeat_window_callback(hb: *const HeartbeatContext) { + unsafe { + if let Some(map) = HBS { + for (_, v) in (*map).iter_mut() { + if &v.hb as *const HeartbeatContext == hb { + log_heartbeat_records(v); + } + } + } + } +} diff --git a/components/profile/lib.rs b/components/profile/lib.rs index 1577df154f2..9bf2018f4be 100644 --- a/components/profile/lib.rs +++ b/components/profile/lib.rs @@ -8,6 +8,7 @@ #[macro_use] extern crate log; +extern crate hbs_pow; extern crate ipc_channel; extern crate libc; #[macro_use] @@ -21,3 +22,5 @@ extern crate util; pub mod mem; pub mod time; + +mod heartbeats; diff --git a/components/profile/time.rs b/components/profile/time.rs index 484cb882bf9..f1c8527366c 100644 --- a/components/profile/time.rs +++ b/components/profile/time.rs @@ -4,6 +4,7 @@ //! Timing functions. +use heartbeats; use ipc_channel::ipc::{self, IpcReceiver}; use profile_traits::time::{ProfilerCategory, ProfilerChan, ProfilerMsg, TimerMetadata}; use std::borrow::ToOwned; @@ -124,6 +125,7 @@ impl Profiler { }); } } + heartbeats::init(); ProfilerChan(chan) } @@ -161,13 +163,20 @@ impl Profiler { fn handle_msg(&mut self, msg: ProfilerMsg) -> bool { match msg.clone() { - ProfilerMsg::Time(k, t) => self.find_or_insert(k, t), + ProfilerMsg::Time(k, t) => { + heartbeats::maybe_heartbeat(&k.0, t.0, t.1, 0, 0); + let ms = (t.1 - t.0) as f64 / 1000000f64; + self.find_or_insert(k, ms); + }, ProfilerMsg::Print => match self.last_msg { // only print if more data has arrived since the last printout Some(ProfilerMsg::Time(..)) => self.print_buckets(), _ => () }, - ProfilerMsg::Exit => return false, + ProfilerMsg::Exit => { + heartbeats::cleanup(); + return false; + }, }; self.last_msg = Some(msg); true diff --git a/components/profile_traits/time.rs b/components/profile_traits/time.rs index ef38acc6c5f..14fccae7969 100644 --- a/components/profile_traits/time.rs +++ b/components/profile_traits/time.rs @@ -29,7 +29,7 @@ impl ProfilerChan { #[derive(Clone, Deserialize, Serialize)] pub enum ProfilerMsg { /// Normal message used for reporting time - Time((ProfilerCategory, Option), f64), + Time((ProfilerCategory, Option), (u64, u64)), /// Message used to force print the profiling metrics Print, /// Tells the profiler to shut down. @@ -37,7 +37,7 @@ pub enum ProfilerMsg { } #[repr(u32)] -#[derive(PartialEq, Clone, PartialOrd, Eq, Ord, Deserialize, Serialize)] +#[derive(PartialEq, Clone, PartialOrd, Eq, Ord, Deserialize, Serialize, Debug, Hash)] pub enum ProfilerCategory { Compositing, LayoutPerform, @@ -83,13 +83,12 @@ pub fn profile(category: ProfilerCategory, 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, }); - profiler_chan.send(ProfilerMsg::Time((category, meta), ms)); + profiler_chan.send(ProfilerMsg::Time((category, meta), (start_time, end_time))); return val; } diff --git a/components/servo/Cargo.lock b/components/servo/Cargo.lock index 02a96a64acd..e698818dc36 100644 --- a/components/servo/Cargo.lock +++ b/components/servo/Cargo.lock @@ -630,6 +630,33 @@ dependencies = [ "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "hbs-common-sys" +version = "0.1.0" +source = "git+https://github.com/libheartbeats/heartbeats-simple-sys.git#4476aa6ad4e2dfded87251d6069d9e747a54d9d8" +dependencies = [ + "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hbs-pow" +version = "0.1.0" +source = "git+https://github.com/libheartbeats/heartbeats-simple-rust.git#70ad49c810da3842e12eef2075d58552f1f6c707" +dependencies = [ + "hbs-pow-sys 0.1.0 (git+https://github.com/libheartbeats/heartbeats-simple-sys.git)", + "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hbs-pow-sys" +version = "0.1.0" +source = "git+https://github.com/libheartbeats/heartbeats-simple-sys.git#4476aa6ad4e2dfded87251d6069d9e747a54d9d8" +dependencies = [ + "hbs-common-sys 0.1.0 (git+https://github.com/libheartbeats/heartbeats-simple-sys.git)", + "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "heapsize" version = "0.1.2" @@ -1136,6 +1163,7 @@ source = "git+https://github.com/servo/rust-png#a3569ca11ea54e5d6152ee80d7d39b27 name = "profile" version = "0.0.1" dependencies = [ + "hbs-pow 0.1.0 (git+https://github.com/libheartbeats/heartbeats-simple-rust.git)", "ipc-channel 0.1.0 (git+https://github.com/pcwalton/ipc-channel)", "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/ports/cef/Cargo.lock b/ports/cef/Cargo.lock index 04470eea902..bf4ec26537f 100644 --- a/ports/cef/Cargo.lock +++ b/ports/cef/Cargo.lock @@ -622,6 +622,33 @@ dependencies = [ "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "hbs-common-sys" +version = "0.1.0" +source = "git+https://github.com/libheartbeats/heartbeats-simple-sys.git#4476aa6ad4e2dfded87251d6069d9e747a54d9d8" +dependencies = [ + "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hbs-pow" +version = "0.1.0" +source = "git+https://github.com/libheartbeats/heartbeats-simple-rust.git#70ad49c810da3842e12eef2075d58552f1f6c707" +dependencies = [ + "hbs-pow-sys 0.1.0 (git+https://github.com/libheartbeats/heartbeats-simple-sys.git)", + "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hbs-pow-sys" +version = "0.1.0" +source = "git+https://github.com/libheartbeats/heartbeats-simple-sys.git#4476aa6ad4e2dfded87251d6069d9e747a54d9d8" +dependencies = [ + "hbs-common-sys 0.1.0 (git+https://github.com/libheartbeats/heartbeats-simple-sys.git)", + "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "heapsize" version = "0.1.2" @@ -1114,6 +1141,7 @@ source = "git+https://github.com/servo/rust-png#a3569ca11ea54e5d6152ee80d7d39b27 name = "profile" version = "0.0.1" dependencies = [ + "hbs-pow 0.1.0 (git+https://github.com/libheartbeats/heartbeats-simple-rust.git)", "ipc-channel 0.1.0 (git+https://github.com/pcwalton/ipc-channel)", "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/ports/gonk/Cargo.lock b/ports/gonk/Cargo.lock index 9e5f5c4d1d8..95be0309ff7 100644 --- a/ports/gonk/Cargo.lock +++ b/ports/gonk/Cargo.lock @@ -554,6 +554,33 @@ dependencies = [ "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "hbs-common-sys" +version = "0.1.0" +source = "git+https://github.com/libheartbeats/heartbeats-simple-sys.git#4476aa6ad4e2dfded87251d6069d9e747a54d9d8" +dependencies = [ + "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hbs-pow" +version = "0.1.0" +source = "git+https://github.com/libheartbeats/heartbeats-simple-rust.git#70ad49c810da3842e12eef2075d58552f1f6c707" +dependencies = [ + "hbs-pow-sys 0.1.0 (git+https://github.com/libheartbeats/heartbeats-simple-sys.git)", + "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hbs-pow-sys" +version = "0.1.0" +source = "git+https://github.com/libheartbeats/heartbeats-simple-sys.git#4476aa6ad4e2dfded87251d6069d9e747a54d9d8" +dependencies = [ + "hbs-common-sys 0.1.0 (git+https://github.com/libheartbeats/heartbeats-simple-sys.git)", + "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "heapsize" version = "0.1.2" @@ -1020,6 +1047,7 @@ source = "git+https://github.com/servo/rust-png#a3569ca11ea54e5d6152ee80d7d39b27 name = "profile" version = "0.0.1" dependencies = [ + "hbs-pow 0.1.0 (git+https://github.com/libheartbeats/heartbeats-simple-rust.git)", "ipc-channel 0.1.0 (git+https://github.com/pcwalton/ipc-channel)", "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",