Auto merge of #23080 - jdm:sampling-profiler, r=gterzian

Add a sampling profiler

This uses the code already built for the background hang monitor and adds the ability to repeatedly sample all monitored threads. This sampling allows us to generate profiles that we can translate into the format used by https://perf-html.io/, allowing us to benefit from modern Gecko performance tooling.

You can run Servo with `PROFILE_OUTPUT=foo.json` and `SAMPLING_RATE=50` (for example), otherwise these values will default to `samples.json` and 10ms, respectively. To activate the profiler, press cmd+p, and to stop profiling, press cmd+p again. This will the captured samples to be symbolicated, which will take a very long time, and eventually there will be a new JSON profile in the output location.

To create a profile for use by Gecko's tools, run `python etc/profilicate.py path/to/profile.json >gecko_profile.json`, and load `gecko_profile.json` in the https://perf-html.io/ to see something like [this](https://profiler.firefox.com/public/8137e2b11fbb92afb80090bc534fd83015c87ee6/calltree/?globalTrackOrder=0-1&thread=1&v=3);

---
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors
- [x] These changes fix #13103
- [x] These changes do not require tests because way too many pieces to automate

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/23080)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2019-03-26 16:36:19 -04:00 committed by GitHub
commit 2e0191b839
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 511 additions and 66 deletions

2
Cargo.lock generated
View file

@ -152,12 +152,12 @@ dependencies = [
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-channel 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-channel 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ipc-channel 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", "ipc-channel 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"mach 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "mach 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"msg 0.0.1", "msg 0.0.1",
"serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]

View file

@ -16,11 +16,11 @@ doctest = false
backtrace = "0.3" backtrace = "0.3"
bitflags = "1.0" bitflags = "1.0"
ipc-channel = "0.11" ipc-channel = "0.11"
lazy_static = "1"
libc = "0.2" libc = "0.2"
log = "0.4" log = "0.4"
msg = {path = "../msg"} msg = {path = "../msg"}
serde = "1.0.60" serde = "1.0.60"
serde_json = "1.0"
crossbeam-channel = "0.3" crossbeam-channel = "0.3"
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]

View file

@ -2,14 +2,15 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::sampler::Sampler; use crate::sampler::{NativeStack, Sampler};
use crossbeam_channel::{after, unbounded, Receiver, Sender}; use crossbeam_channel::{after, unbounded, Receiver, Sender};
use ipc_channel::ipc::IpcSender; use ipc_channel::ipc::{IpcReceiver, IpcSender};
use ipc_channel::router::ROUTER;
use msg::constellation_msg::MonitoredComponentId; use msg::constellation_msg::MonitoredComponentId;
use msg::constellation_msg::{ use msg::constellation_msg::{
BackgroundHangMonitor, BackgroundHangMonitorClone, BackgroundHangMonitorRegister, BackgroundHangMonitor, BackgroundHangMonitorClone, BackgroundHangMonitorRegister,
}; };
use msg::constellation_msg::{HangAlert, HangAnnotation}; use msg::constellation_msg::{HangAlert, HangAnnotation, HangMonitorAlert, SamplerControlMsg};
use std::cell::Cell; use std::cell::Cell;
use std::collections::HashMap; use std::collections::HashMap;
use std::thread; use std::thread;
@ -22,10 +23,14 @@ pub struct HangMonitorRegister {
impl HangMonitorRegister { impl HangMonitorRegister {
/// Start a new hang monitor worker, and return a handle to register components for monitoring. /// Start a new hang monitor worker, and return a handle to register components for monitoring.
pub fn init(constellation_chan: IpcSender<HangAlert>) -> Box<BackgroundHangMonitorRegister> { pub fn init(
constellation_chan: IpcSender<HangMonitorAlert>,
control_port: IpcReceiver<SamplerControlMsg>,
) -> Box<BackgroundHangMonitorRegister> {
let (sender, port) = unbounded(); let (sender, port) = unbounded();
let _ = thread::Builder::new().spawn(move || { let _ = thread::Builder::new().spawn(move || {
let mut monitor = { BackgroundHangMonitorWorker::new(constellation_chan, port) }; let mut monitor =
BackgroundHangMonitorWorker::new(constellation_chan, control_port, port);
while monitor.run() { while monitor.run() {
// Monitoring until all senders have been dropped... // Monitoring until all senders have been dropped...
} }
@ -55,6 +60,7 @@ impl BackgroundHangMonitorRegister for HangMonitorRegister {
bhm_chan.send(MonitoredComponentMsg::Register( bhm_chan.send(MonitoredComponentMsg::Register(
sampler, sampler,
thread::current().name().map(str::to_owned),
transient_hang_timeout, transient_hang_timeout,
permanent_hang_timeout, permanent_hang_timeout,
)); ));
@ -71,7 +77,7 @@ impl BackgroundHangMonitorClone for HangMonitorRegister {
/// Messages sent from monitored components to the monitor. /// Messages sent from monitored components to the monitor.
pub enum MonitoredComponentMsg { pub enum MonitoredComponentMsg {
/// Register component for monitoring, /// Register component for monitoring,
Register(Box<Sampler>, Duration, Duration), Register(Box<Sampler>, Option<String>, Duration, Duration),
/// Unregister component for monitoring. /// Unregister component for monitoring.
Unregister, Unregister,
/// Notify start of new activity for a given component, /// Notify start of new activity for a given component,
@ -138,25 +144,91 @@ struct MonitoredComponent {
is_waiting: bool, is_waiting: bool,
} }
struct Sample(MonitoredComponentId, Instant, NativeStack);
pub struct BackgroundHangMonitorWorker { pub struct BackgroundHangMonitorWorker {
component_names: HashMap<MonitoredComponentId, String>,
monitored_components: HashMap<MonitoredComponentId, MonitoredComponent>, monitored_components: HashMap<MonitoredComponentId, MonitoredComponent>,
constellation_chan: IpcSender<HangAlert>, constellation_chan: IpcSender<HangMonitorAlert>,
port: Receiver<(MonitoredComponentId, MonitoredComponentMsg)>, port: Receiver<(MonitoredComponentId, MonitoredComponentMsg)>,
control_port: Receiver<SamplerControlMsg>,
sampling_duration: Option<Duration>,
last_sample: Instant,
creation: Instant,
sampling_baseline: Instant,
samples: Vec<Sample>,
} }
impl BackgroundHangMonitorWorker { impl BackgroundHangMonitorWorker {
pub fn new( pub fn new(
constellation_chan: IpcSender<HangAlert>, constellation_chan: IpcSender<HangMonitorAlert>,
control_port: IpcReceiver<SamplerControlMsg>,
port: Receiver<(MonitoredComponentId, MonitoredComponentMsg)>, port: Receiver<(MonitoredComponentId, MonitoredComponentMsg)>,
) -> Self { ) -> Self {
let control_port = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(control_port);
Self { Self {
component_names: Default::default(),
monitored_components: Default::default(), monitored_components: Default::default(),
constellation_chan, constellation_chan,
port, port,
control_port,
sampling_duration: None,
last_sample: Instant::now(),
sampling_baseline: Instant::now(),
creation: Instant::now(),
samples: vec![],
} }
} }
fn finish_sampled_profile(&mut self) {
let mut bytes = vec![];
bytes.extend(
format!(
"{{ \"rate\": {}, \"start\": {}, \"data\": [\n",
self.sampling_duration.unwrap().as_millis(),
(self.sampling_baseline - self.creation).as_millis(),
)
.as_bytes(),
);
let mut first = true;
let to_resolve = self.samples.len();
for (i, Sample(id, instant, stack)) in self.samples.drain(..).enumerate() {
println!("Resolving {}/{}", i + 1, to_resolve);
let profile = stack.to_hangprofile();
let name = match self.component_names.get(&id) {
Some(ref s) => format!("\"{}\"", s),
None => format!("null"),
};
let json = format!(
"{}{{ \"name\": {}, \"namespace\": {}, \"index\": {}, \"type\": \"{:?}\", \
\"time\": {}, \"frames\": {} }}",
if !first { ",\n" } else { "" },
name,
id.0.namespace_id.0,
id.0.index.0.get(),
id.1,
(instant - self.sampling_baseline).as_millis(),
serde_json::to_string(&profile.backtrace).unwrap(),
);
bytes.extend(json.as_bytes());
first = false;
}
bytes.extend(b"\n] }");
let _ = self
.constellation_chan
.send(HangMonitorAlert::Profile(bytes));
}
pub fn run(&mut self) -> bool { pub fn run(&mut self) -> bool {
let timeout = if let Some(duration) = self.sampling_duration {
duration
.checked_sub(Instant::now() - self.last_sample)
.unwrap_or_else(|| Duration::from_millis(0))
} else {
Duration::from_millis(100)
};
let received = select! { let received = select! {
recv(self.port) -> event => { recv(self.port) -> event => {
match event { match event {
@ -165,7 +237,24 @@ impl BackgroundHangMonitorWorker {
Err(_) => return false, Err(_) => return false,
} }
}, },
recv(after(Duration::from_millis(100))) -> _ => None, recv(self.control_port) -> event => {
match event {
Ok(SamplerControlMsg::Enable(rate)) => {
println!("Enabling profiler.");
self.sampling_duration = Some(rate);
self.sampling_baseline = Instant::now();
None
}
Ok(SamplerControlMsg::Disable) => {
println!("Disabling profiler.");
self.finish_sampled_profile();
self.sampling_duration = None;
None
}
Err(_) => return false,
}
}
recv(after(timeout)) -> _ => None,
}; };
if let Some(msg) = received { if let Some(msg) = received {
self.handle_msg(msg); self.handle_msg(msg);
@ -175,7 +264,16 @@ impl BackgroundHangMonitorWorker {
self.handle_msg(another_msg); self.handle_msg(another_msg);
} }
} }
self.perform_a_hang_monitor_checkpoint();
if let Some(duration) = self.sampling_duration {
let now = Instant::now();
if now - self.last_sample > duration {
self.sample();
self.last_sample = now;
}
} else {
self.perform_a_hang_monitor_checkpoint();
}
true true
} }
@ -185,6 +283,7 @@ impl BackgroundHangMonitorWorker {
component_id, component_id,
MonitoredComponentMsg::Register( MonitoredComponentMsg::Register(
sampler, sampler,
name,
transient_hang_timeout, transient_hang_timeout,
permanent_hang_timeout, permanent_hang_timeout,
), ),
@ -199,6 +298,9 @@ impl BackgroundHangMonitorWorker {
sent_permanent_alert: false, sent_permanent_alert: false,
is_waiting: true, is_waiting: true,
}; };
if let Some(name) = name {
self.component_names.insert(component_id.clone(), name);
}
assert!( assert!(
self.monitored_components self.monitored_components
.insert(component_id, component) .insert(component_id, component)
@ -250,11 +352,13 @@ impl BackgroundHangMonitorWorker {
Ok(native_stack) => Some(native_stack.to_hangprofile()), Ok(native_stack) => Some(native_stack.to_hangprofile()),
Err(()) => None, Err(()) => None,
}; };
let _ = self.constellation_chan.send(HangAlert::Permanent( let _ = self
component_id.clone(), .constellation_chan
last_annotation, .send(HangMonitorAlert::Hang(HangAlert::Permanent(
profile, component_id.clone(),
)); last_annotation,
profile,
)));
monitored.sent_permanent_alert = true; monitored.sent_permanent_alert = true;
continue; continue;
} }
@ -264,9 +368,23 @@ impl BackgroundHangMonitorWorker {
} }
let _ = self let _ = self
.constellation_chan .constellation_chan
.send(HangAlert::Transient(component_id.clone(), last_annotation)); .send(HangMonitorAlert::Hang(HangAlert::Transient(
component_id.clone(),
last_annotation,
)));
monitored.sent_transient_alert = true; monitored.sent_transient_alert = true;
} }
} }
} }
fn sample(&mut self) {
for (component_id, monitored) in self.monitored_components.iter_mut() {
let instant = Instant::now();
if let Ok(stack) = monitored.sampler.suspend_and_sample_thread() {
// TODO: support a bounded buffer that discards older samples.
self.samples
.push(Sample(component_id.clone(), instant, stack));
}
}
}
} }

View file

@ -6,18 +6,63 @@ use background_hang_monitor::HangMonitorRegister;
use ipc_channel::ipc; use ipc_channel::ipc;
use msg::constellation_msg::ScriptHangAnnotation; use msg::constellation_msg::ScriptHangAnnotation;
use msg::constellation_msg::TEST_PIPELINE_ID; use msg::constellation_msg::TEST_PIPELINE_ID;
use msg::constellation_msg::{HangAlert, HangAnnotation}; use msg::constellation_msg::{HangAlert, HangAnnotation, HangMonitorAlert};
use msg::constellation_msg::{MonitoredComponentId, MonitoredComponentType}; use msg::constellation_msg::{MonitoredComponentId, MonitoredComponentType};
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;
#[test]
#[cfg(target_os = "macos")]
fn test_sampler() {
use msg::constellation_msg::SamplerControlMsg;
use serde_json::Value;
let (background_hang_monitor_ipc_sender, background_hang_monitor_receiver) =
ipc::channel().expect("ipc channel failure");
let (sampler_sender, sampler_receiver) = ipc::channel().expect("ipc channel failure");
let background_hang_monitor_register =
HangMonitorRegister::init(background_hang_monitor_ipc_sender.clone(), sampler_receiver);
let _background_hang_monitor = background_hang_monitor_register.register_component(
MonitoredComponentId(TEST_PIPELINE_ID, MonitoredComponentType::Script),
Duration::from_millis(10),
Duration::from_millis(1000),
);
const RATE: u64 = 10;
sampler_sender
.send(SamplerControlMsg::Enable(Duration::from_millis(RATE)))
.unwrap();
thread::sleep(Duration::from_millis(30));
sampler_sender.send(SamplerControlMsg::Disable).unwrap();
loop {
match background_hang_monitor_receiver.recv().unwrap() {
HangMonitorAlert::Hang(_) => continue,
HangMonitorAlert::Profile(ref bytes) => {
let json: Value = serde_json::from_slice(bytes).unwrap();
let rate = json["rate"].as_u64().unwrap();
assert_eq!(rate, RATE);
let data = json["data"].as_array().unwrap();
assert!(data.len() > 1);
assert_eq!(data[0]["name"].as_str().unwrap(), "test_sampler");
assert!(data[0]["frames"].as_array().unwrap().len() > 0);
break;
},
}
}
}
#[test] #[test]
fn test_hang_monitoring() { fn test_hang_monitoring() {
let (background_hang_monitor_ipc_sender, background_hang_monitor_receiver) = let (background_hang_monitor_ipc_sender, background_hang_monitor_receiver) =
ipc::channel().expect("ipc channel failure"); ipc::channel().expect("ipc channel failure");
let (_sampler_sender, sampler_receiver) = ipc::channel().expect("ipc channel failure");
let background_hang_monitor_register = let background_hang_monitor_register =
HangMonitorRegister::init(background_hang_monitor_ipc_sender.clone()); HangMonitorRegister::init(background_hang_monitor_ipc_sender.clone(), sampler_receiver);
let background_hang_monitor = background_hang_monitor_register.register_component( let background_hang_monitor = background_hang_monitor_register.register_component(
MonitoredComponentId(TEST_PIPELINE_ID, MonitoredComponentType::Script), MonitoredComponentId(TEST_PIPELINE_ID, MonitoredComponentType::Script),
Duration::from_millis(10), Duration::from_millis(10),
@ -33,11 +78,11 @@ fn test_hang_monitoring() {
// Check for a transient hang alert. // Check for a transient hang alert.
match background_hang_monitor_receiver.recv().unwrap() { match background_hang_monitor_receiver.recv().unwrap() {
HangAlert::Transient(component_id, _annotation) => { HangMonitorAlert::Hang(HangAlert::Transient(component_id, _annotation)) => {
let expected = MonitoredComponentId(TEST_PIPELINE_ID, MonitoredComponentType::Script); let expected = MonitoredComponentId(TEST_PIPELINE_ID, MonitoredComponentType::Script);
assert_eq!(expected, component_id); assert_eq!(expected, component_id);
}, },
HangAlert::Permanent(..) => unreachable!(), _ => unreachable!(),
} }
// Sleep until the "permanent" timeout has been reached. // Sleep until the "permanent" timeout has been reached.
@ -45,11 +90,11 @@ fn test_hang_monitoring() {
// Check for a permanent hang alert. // Check for a permanent hang alert.
match background_hang_monitor_receiver.recv().unwrap() { match background_hang_monitor_receiver.recv().unwrap() {
HangAlert::Permanent(component_id, _annotation, _profile) => { HangMonitorAlert::Hang(HangAlert::Permanent(component_id, _annotation, _profile)) => {
let expected = MonitoredComponentId(TEST_PIPELINE_ID, MonitoredComponentType::Script); let expected = MonitoredComponentId(TEST_PIPELINE_ID, MonitoredComponentType::Script);
assert_eq!(expected, component_id); assert_eq!(expected, component_id);
}, },
HangAlert::Transient(..) => unreachable!(), _ => unreachable!(),
} }
// Now the component is not hanging anymore. // Now the component is not hanging anymore.
@ -61,11 +106,11 @@ fn test_hang_monitoring() {
// Check for a transient hang alert. // Check for a transient hang alert.
match background_hang_monitor_receiver.recv().unwrap() { match background_hang_monitor_receiver.recv().unwrap() {
HangAlert::Transient(component_id, _annotation) => { HangMonitorAlert::Hang(HangAlert::Transient(component_id, _annotation)) => {
let expected = MonitoredComponentId(TEST_PIPELINE_ID, MonitoredComponentType::Script); let expected = MonitoredComponentId(TEST_PIPELINE_ID, MonitoredComponentType::Script);
assert_eq!(expected, component_id); assert_eq!(expected, component_id);
}, },
HangAlert::Permanent(..) => unreachable!(), _ => unreachable!(),
} }
// Now the component is waiting for a new task. // Now the component is waiting for a new task.
@ -85,11 +130,11 @@ fn test_hang_monitoring() {
// We're getting new hang alerts for the latest task. // We're getting new hang alerts for the latest task.
match background_hang_monitor_receiver.recv().unwrap() { match background_hang_monitor_receiver.recv().unwrap() {
HangAlert::Transient(component_id, _annotation) => { HangMonitorAlert::Hang(HangAlert::Transient(component_id, _annotation)) => {
let expected = MonitoredComponentId(TEST_PIPELINE_ID, MonitoredComponentType::Script); let expected = MonitoredComponentId(TEST_PIPELINE_ID, MonitoredComponentType::Script);
assert_eq!(expected, component_id); assert_eq!(expected, component_id);
}, },
HangAlert::Permanent(..) => unreachable!(), _ => unreachable!(),
} }
// No new alert yet // No new alert yet
@ -110,9 +155,10 @@ fn test_hang_monitoring() {
fn test_hang_monitoring_unregister() { fn test_hang_monitoring_unregister() {
let (background_hang_monitor_ipc_sender, background_hang_monitor_receiver) = let (background_hang_monitor_ipc_sender, background_hang_monitor_receiver) =
ipc::channel().expect("ipc channel failure"); ipc::channel().expect("ipc channel failure");
let (_sampler_sender, sampler_receiver) = ipc::channel().expect("ipc channel failure");
let background_hang_monitor_register = let background_hang_monitor_register =
HangMonitorRegister::init(background_hang_monitor_ipc_sender.clone()); HangMonitorRegister::init(background_hang_monitor_ipc_sender.clone(), sampler_receiver);
let background_hang_monitor = background_hang_monitor_register.register_component( let background_hang_monitor = background_hang_monitor_register.register_component(
MonitoredComponentId(TEST_PIPELINE_ID, MonitoredComponentType::Script), MonitoredComponentId(TEST_PIPELINE_ID, MonitoredComponentType::Script),
Duration::from_millis(10), Duration::from_millis(10),

View file

@ -16,6 +16,7 @@ use servo_url::ServoUrl;
use std::fmt::{Debug, Error, Formatter}; use std::fmt::{Debug, Error, Formatter};
#[cfg(feature = "gl")] #[cfg(feature = "gl")]
use std::rc::Rc; use std::rc::Rc;
use std::time::Duration;
use style_traits::DevicePixel; use style_traits::DevicePixel;
use webrender_api::{ use webrender_api::{
DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePoint, FramebufferIntRect, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePoint, FramebufferIntRect,
@ -94,6 +95,8 @@ pub enum WindowEvent {
ToggleWebRenderDebug(WebRenderDebugOption), ToggleWebRenderDebug(WebRenderDebugOption),
/// Capture current WebRender /// Capture current WebRender
CaptureWebRender, CaptureWebRender,
/// Toggle sampling profiler with the given sampling rate
ToggleSamplingProfiler(Duration),
} }
impl Debug for WindowEvent { impl Debug for WindowEvent {
@ -121,6 +124,7 @@ impl Debug for WindowEvent {
WindowEvent::SelectBrowser(..) => write!(f, "SelectBrowser"), WindowEvent::SelectBrowser(..) => write!(f, "SelectBrowser"),
WindowEvent::ToggleWebRenderDebug(..) => write!(f, "ToggleWebRenderDebug"), WindowEvent::ToggleWebRenderDebug(..) => write!(f, "ToggleWebRenderDebug"),
WindowEvent::CaptureWebRender => write!(f, "CaptureWebRender"), WindowEvent::CaptureWebRender => write!(f, "CaptureWebRender"),
WindowEvent::ToggleSamplingProfiler(..) => write!(f, "ToggleSamplingProfiler"),
} }
} }
} }

View file

@ -124,7 +124,7 @@ use keyboard_types::webdriver::Event as WebDriverInputEvent;
use keyboard_types::KeyboardEvent; use keyboard_types::KeyboardEvent;
use layout_traits::LayoutThreadFactory; use layout_traits::LayoutThreadFactory;
use log::{Level, LevelFilter, Log, Metadata, Record}; use log::{Level, LevelFilter, Log, Metadata, Record};
use msg::constellation_msg::{BackgroundHangMonitorRegister, HangAlert}; use msg::constellation_msg::{BackgroundHangMonitorRegister, HangMonitorAlert, SamplerControlMsg};
use msg::constellation_msg::{ use msg::constellation_msg::{
BrowsingContextId, HistoryStateId, PipelineId, TopLevelBrowsingContextId, BrowsingContextId, HistoryStateId, PipelineId, TopLevelBrowsingContextId,
}; };
@ -207,13 +207,16 @@ pub struct Constellation<Message, LTF, STF> {
/// None when in multiprocess mode. /// None when in multiprocess mode.
background_monitor_register: Option<Box<BackgroundHangMonitorRegister>>, background_monitor_register: Option<Box<BackgroundHangMonitorRegister>>,
/// Channels to control all sampling profilers.
sampling_profiler_control: Vec<IpcSender<SamplerControlMsg>>,
/// A channel for the background hang monitor to send messages /// A channel for the background hang monitor to send messages
/// to the constellation. /// to the constellation.
background_hang_monitor_sender: IpcSender<HangAlert>, background_hang_monitor_sender: IpcSender<HangMonitorAlert>,
/// A channel for the constellation to receiver messages /// A channel for the constellation to receiver messages
/// from the background hang monitor. /// from the background hang monitor.
background_hang_monitor_receiver: Receiver<Result<HangAlert, IpcError>>, background_hang_monitor_receiver: Receiver<Result<HangMonitorAlert, IpcError>>,
/// An IPC channel for layout threads to send messages to the constellation. /// An IPC channel for layout threads to send messages to the constellation.
/// This is the layout threads' view of `layout_receiver`. /// This is the layout threads' view of `layout_receiver`.
@ -613,12 +616,19 @@ where
// If we are in multiprocess mode, // If we are in multiprocess mode,
// a dedicated per-process hang monitor will be initialized later inside the content process. // a dedicated per-process hang monitor will be initialized later inside the content process.
// See run_content_process in servo/lib.rs // See run_content_process in servo/lib.rs
let background_monitor_register = if opts::multiprocess() { let (background_monitor_register, sampler_chan) = if opts::multiprocess() {
None (None, vec![])
} else { } else {
Some(HangMonitorRegister::init( let (sampling_profiler_control, sampling_profiler_port) =
background_hang_monitor_sender.clone(), ipc::channel().expect("ipc channel failure");
))
(
Some(HangMonitorRegister::init(
background_hang_monitor_sender.clone(),
sampling_profiler_port,
)),
vec![sampling_profiler_control],
)
}; };
let (ipc_layout_sender, ipc_layout_receiver) = let (ipc_layout_sender, ipc_layout_receiver) =
@ -639,6 +649,7 @@ where
background_hang_monitor_sender, background_hang_monitor_sender,
background_hang_monitor_receiver, background_hang_monitor_receiver,
background_monitor_register, background_monitor_register,
sampling_profiler_control: sampler_chan,
layout_sender: ipc_layout_sender, layout_sender: ipc_layout_sender,
script_receiver: script_receiver, script_receiver: script_receiver,
compositor_receiver: compositor_receiver, compositor_receiver: compositor_receiver,
@ -840,6 +851,10 @@ where
Err(e) => return self.handle_send_error(pipeline_id, e), Err(e) => return self.handle_send_error(pipeline_id, e),
}; };
if let Some(sampler_chan) = pipeline.sampler_control_chan {
self.sampling_profiler_control.push(sampler_chan);
}
if let Some(host) = host { if let Some(host) = host {
debug!( debug!(
"Adding new host entry {} for top-level browsing context {}.", "Adding new host entry {} for top-level browsing context {}.",
@ -847,11 +862,11 @@ where
); );
let _ = self let _ = self
.event_loops .event_loops
.insert(host, Rc::downgrade(&pipeline.event_loop)); .insert(host, Rc::downgrade(&pipeline.pipeline.event_loop));
} }
assert!(!self.pipelines.contains_key(&pipeline_id)); assert!(!self.pipelines.contains_key(&pipeline_id));
self.pipelines.insert(pipeline_id, pipeline); self.pipelines.insert(pipeline_id, pipeline.pipeline);
} }
/// Get an iterator for the fully active browsing contexts in a subtree. /// Get an iterator for the fully active browsing contexts in a subtree.
@ -941,7 +956,7 @@ where
#[derive(Debug)] #[derive(Debug)]
enum Request { enum Request {
Script((PipelineId, FromScriptMsg)), Script((PipelineId, FromScriptMsg)),
BackgroundHangMonitor(HangAlert), BackgroundHangMonitor(HangMonitorAlert),
Compositor(FromCompositorMsg), Compositor(FromCompositorMsg),
Layout(FromLayoutMsg), Layout(FromLayoutMsg),
NetworkListener((PipelineId, FetchResponseMsg)), NetworkListener((PipelineId, FetchResponseMsg)),
@ -1007,10 +1022,17 @@ where
} }
} }
fn handle_request_from_background_hang_monitor(&self, message: HangAlert) { fn handle_request_from_background_hang_monitor(&self, message: HangMonitorAlert) {
// TODO: In case of a permanent hang being reported, add a "kill script" workflow, match message {
// via the embedder? HangMonitorAlert::Profile(bytes) => self
warn!("Component hang alert: {:?}", message); .embedder_proxy
.send((None, EmbedderMsg::ReportProfile(bytes))),
HangMonitorAlert::Hang(hang) => {
// TODO: In case of a permanent hang being reported, add a "kill script" workflow,
// via the embedder?
warn!("Component hang alert: {:?}", hang);
},
}
} }
fn handle_request_from_network_listener(&mut self, message: (PipelineId, FetchResponseMsg)) { fn handle_request_from_network_listener(&mut self, message: (PipelineId, FetchResponseMsg)) {
@ -1194,6 +1216,20 @@ where
self.forward_event(destination_pipeline_id, event); self.forward_event(destination_pipeline_id, event);
}, },
FromCompositorMsg::SetCursor(cursor) => self.handle_set_cursor_msg(cursor), FromCompositorMsg::SetCursor(cursor) => self.handle_set_cursor_msg(cursor),
FromCompositorMsg::EnableProfiler(rate) => {
for chan in &self.sampling_profiler_control {
if let Err(e) = chan.send(SamplerControlMsg::Enable(rate)) {
warn!("error communicating with sampling profiler: {}", e);
}
}
},
FromCompositorMsg::DisableProfiler => {
for chan in &self.sampling_profiler_control {
if let Err(e) = chan.send(SamplerControlMsg::Disable) {
warn!("error communicating with sampling profiler: {}", e);
}
}
},
} }
} }

View file

@ -3,6 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::event_loop::EventLoop; use crate::event_loop::EventLoop;
use background_hang_monitor::HangMonitorRegister;
use bluetooth_traits::BluetoothRequest; use bluetooth_traits::BluetoothRequest;
use canvas_traits::webgl::WebGLPipeline; use canvas_traits::webgl::WebGLPipeline;
use compositing::compositor_thread::Msg as CompositorMsg; use compositing::compositor_thread::Msg as CompositorMsg;
@ -18,7 +19,7 @@ use ipc_channel::Error;
use layout_traits::LayoutThreadFactory; use layout_traits::LayoutThreadFactory;
use metrics::PaintTimeMetrics; use metrics::PaintTimeMetrics;
use msg::constellation_msg::TopLevelBrowsingContextId; use msg::constellation_msg::TopLevelBrowsingContextId;
use msg::constellation_msg::{BackgroundHangMonitorRegister, HangAlert}; use msg::constellation_msg::{BackgroundHangMonitorRegister, HangMonitorAlert, SamplerControlMsg};
use msg::constellation_msg::{BrowsingContextId, HistoryStateId, PipelineId, PipelineNamespaceId}; use msg::constellation_msg::{BrowsingContextId, HistoryStateId, PipelineId, PipelineNamespaceId};
use net::image_cache::ImageCacheImpl; use net::image_cache::ImageCacheImpl;
use net_traits::image_cache::ImageCache; use net_traits::image_cache::ImageCache;
@ -122,7 +123,7 @@ pub struct InitialPipelineState {
pub background_monitor_register: Option<Box<BackgroundHangMonitorRegister>>, pub background_monitor_register: Option<Box<BackgroundHangMonitorRegister>>,
/// A channel for the background hang monitor to send messages to the constellation. /// A channel for the background hang monitor to send messages to the constellation.
pub background_hang_monitor_to_constellation_chan: IpcSender<HangAlert>, pub background_hang_monitor_to_constellation_chan: IpcSender<HangMonitorAlert>,
/// A channel for the layout thread to send messages to the constellation. /// A channel for the layout thread to send messages to the constellation.
pub layout_to_constellation_chan: IpcSender<LayoutMsg>, pub layout_to_constellation_chan: IpcSender<LayoutMsg>,
@ -188,10 +189,15 @@ pub struct InitialPipelineState {
pub webvr_chan: Option<IpcSender<WebVRMsg>>, pub webvr_chan: Option<IpcSender<WebVRMsg>>,
} }
pub struct NewPipeline {
pub pipeline: Pipeline,
pub sampler_control_chan: Option<IpcSender<SamplerControlMsg>>,
}
impl Pipeline { impl Pipeline {
/// Starts a layout thread, and possibly a script thread, in /// Starts a layout thread, and possibly a script thread, in
/// a new process if requested. /// a new process if requested.
pub fn spawn<Message, LTF, STF>(state: InitialPipelineState) -> Result<Pipeline, Error> pub fn spawn<Message, LTF, STF>(state: InitialPipelineState) -> Result<NewPipeline, Error>
where where
LTF: LayoutThreadFactory<Message = Message>, LTF: LayoutThreadFactory<Message = Message>,
STF: ScriptThreadFactory<Message = Message>, STF: ScriptThreadFactory<Message = Message>,
@ -210,7 +216,7 @@ impl Pipeline {
let url = state.load_data.url.clone(); let url = state.load_data.url.clone();
let script_chan = match state.event_loop { let (script_chan, sampler_chan) = match state.event_loop {
Some(script_chan) => { Some(script_chan) => {
let new_layout_info = NewLayoutInfo { let new_layout_info = NewLayoutInfo {
parent_info: state.parent_pipeline_id, parent_info: state.parent_pipeline_id,
@ -229,7 +235,7 @@ impl Pipeline {
{ {
warn!("Sending to script during pipeline creation failed ({})", e); warn!("Sending to script during pipeline creation failed ({})", e);
} }
script_chan (script_chan, None)
}, },
None => { None => {
let (script_chan, script_port) = ipc::channel().expect("Pipeline script chan"); let (script_chan, script_port) = ipc::channel().expect("Pipeline script chan");
@ -262,7 +268,7 @@ impl Pipeline {
let (script_content_process_shutdown_chan, script_content_process_shutdown_port) = let (script_content_process_shutdown_chan, script_content_process_shutdown_port) =
ipc::channel().expect("Pipeline script content process shutdown chan"); ipc::channel().expect("Pipeline script content process shutdown chan");
let unprivileged_pipeline_content = UnprivilegedPipelineContent { let mut unprivileged_pipeline_content = UnprivilegedPipelineContent {
id: state.id, id: state.id,
browsing_context_id: state.browsing_context_id, browsing_context_id: state.browsing_context_id,
top_level_browsing_context_id: state.top_level_browsing_context_id, top_level_browsing_context_id: state.top_level_browsing_context_id,
@ -272,6 +278,7 @@ impl Pipeline {
background_hang_monitor_to_constellation_chan: state background_hang_monitor_to_constellation_chan: state
.background_hang_monitor_to_constellation_chan .background_hang_monitor_to_constellation_chan
.clone(), .clone(),
sampling_profiler_port: None,
scheduler_chan: state.scheduler_chan, scheduler_chan: state.scheduler_chan,
devtools_chan: script_to_devtools_chan, devtools_chan: script_to_devtools_chan,
bluetooth_thread: state.bluetooth_thread, bluetooth_thread: state.bluetooth_thread,
@ -302,21 +309,25 @@ impl Pipeline {
// Spawn the child process. // Spawn the child process.
// //
// Yes, that's all there is to it! // Yes, that's all there is to it!
if opts::multiprocess() { let sampler_chan = if opts::multiprocess() {
let (sampler_chan, sampler_port) = ipc::channel().expect("Sampler chan");
unprivileged_pipeline_content.sampling_profiler_port = Some(sampler_port);
let _ = unprivileged_pipeline_content.spawn_multiprocess()?; let _ = unprivileged_pipeline_content.spawn_multiprocess()?;
Some(sampler_chan)
} else { } else {
// Should not be None in single-process mode. // Should not be None in single-process mode.
let register = state let register = state
.background_monitor_register .background_monitor_register
.expect("Couldn't start content, no background monitor has been initiated"); .expect("Couldn't start content, no background monitor has been initiated");
unprivileged_pipeline_content.start_all::<Message, LTF, STF>(false, register); unprivileged_pipeline_content.start_all::<Message, LTF, STF>(false, register);
} None
};
EventLoop::new(script_chan) (EventLoop::new(script_chan), sampler_chan)
}, },
}; };
Ok(Pipeline::new( let pipeline = Pipeline::new(
state.id, state.id,
state.browsing_context_id, state.browsing_context_id,
state.top_level_browsing_context_id, state.top_level_browsing_context_id,
@ -327,7 +338,11 @@ impl Pipeline {
url, url,
state.prev_visibility, state.prev_visibility,
state.load_data, state.load_data,
)) );
Ok(NewPipeline {
pipeline,
sampler_control_chan: sampler_chan,
})
} }
/// Creates a new `Pipeline`, after the script and layout threads have been /// Creates a new `Pipeline`, after the script and layout threads have been
@ -467,7 +482,8 @@ pub struct UnprivilegedPipelineContent {
parent_pipeline_id: Option<PipelineId>, parent_pipeline_id: Option<PipelineId>,
opener: Option<BrowsingContextId>, opener: Option<BrowsingContextId>,
script_to_constellation_chan: ScriptToConstellationChan, script_to_constellation_chan: ScriptToConstellationChan,
background_hang_monitor_to_constellation_chan: IpcSender<HangAlert>, background_hang_monitor_to_constellation_chan: IpcSender<HangMonitorAlert>,
sampling_profiler_port: Option<IpcReceiver<SamplerControlMsg>>,
layout_to_constellation_chan: IpcSender<LayoutMsg>, layout_to_constellation_chan: IpcSender<LayoutMsg>,
scheduler_chan: IpcSender<TimerSchedulerMsg>, scheduler_chan: IpcSender<TimerSchedulerMsg>,
devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>, devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
@ -669,8 +685,13 @@ impl UnprivilegedPipelineContent {
} }
} }
pub fn background_hang_monitor_to_constellation_chan(&self) -> &IpcSender<HangAlert> { pub fn register_with_background_hang_monitor(&mut self) -> Box<BackgroundHangMonitorRegister> {
&self.background_hang_monitor_to_constellation_chan HangMonitorRegister::init(
self.background_hang_monitor_to_constellation_chan.clone(),
self.sampling_profiler_port
.take()
.expect("no sampling profiler?"),
)
} }
pub fn script_to_constellation_chan(&self) -> &ScriptToConstellationChan { pub fn script_to_constellation_chan(&self) -> &ScriptToConstellationChan {

View file

@ -160,6 +160,8 @@ pub enum EmbedderMsg {
HideIME, HideIME,
/// Servo has shut down /// Servo has shut down
Shutdown, Shutdown,
/// Report a complete sampled profile
ReportProfile(Vec<u8>),
} }
impl Debug for EmbedderMsg { impl Debug for EmbedderMsg {
@ -189,6 +191,7 @@ impl Debug for EmbedderMsg {
EmbedderMsg::Shutdown => write!(f, "Shutdown"), EmbedderMsg::Shutdown => write!(f, "Shutdown"),
EmbedderMsg::AllowOpeningBrowser(..) => write!(f, "AllowOpeningBrowser"), EmbedderMsg::AllowOpeningBrowser(..) => write!(f, "AllowOpeningBrowser"),
EmbedderMsg::BrowserCreated(..) => write!(f, "BrowserCreated"), EmbedderMsg::BrowserCreated(..) => write!(f, "BrowserCreated"),
EmbedderMsg::ReportProfile(..) => write!(f, "ReportProfile"),
} }
} }
} }

View file

@ -350,6 +350,24 @@ pub enum HangAnnotation {
Script(ScriptHangAnnotation), Script(ScriptHangAnnotation),
} }
/// Hang-alerts are sent by the monitor to the constellation.
#[derive(Deserialize, Serialize)]
pub enum HangMonitorAlert {
/// A component hang has been detected.
Hang(HangAlert),
/// Report a completed sampled profile.
Profile(Vec<u8>),
}
impl fmt::Debug for HangMonitorAlert {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self {
HangMonitorAlert::Hang(..) => write!(fmt, "Hang"),
HangMonitorAlert::Profile(..) => write!(fmt, "Profile"),
}
}
}
/// Hang-alerts are sent by the monitor to the constellation. /// Hang-alerts are sent by the monitor to the constellation.
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub enum HangAlert { pub enum HangAlert {
@ -472,3 +490,10 @@ pub trait BackgroundHangMonitor {
/// Unregister the component from monitor. /// Unregister the component from monitor.
fn unregister(&self); fn unregister(&self);
} }
/// Messages to control the sampling profiler.
#[derive(Deserialize, Serialize)]
pub enum SamplerControlMsg {
Enable(Duration),
Disable,
}

View file

@ -51,6 +51,7 @@ use servo_url::ServoUrl;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt; use std::fmt;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration;
use style_traits::CSSPixel; use style_traits::CSSPixel;
use style_traits::SpeculativePainter; use style_traits::SpeculativePainter;
use webrender_api::{ use webrender_api::{
@ -777,6 +778,10 @@ pub enum ConstellationMsg {
ForwardEvent(PipelineId, CompositorEvent), ForwardEvent(PipelineId, CompositorEvent),
/// Requesting a change to the onscreen cursor. /// Requesting a change to the onscreen cursor.
SetCursor(Cursor), SetCursor(Cursor),
/// Enable the sampling profiler.
EnableProfiler(Duration),
/// Disable the sampling profiler.
DisableProfiler,
} }
impl fmt::Debug for ConstellationMsg { impl fmt::Debug for ConstellationMsg {
@ -804,6 +809,8 @@ impl fmt::Debug for ConstellationMsg {
SelectBrowser(..) => "SelectBrowser", SelectBrowser(..) => "SelectBrowser",
ForwardEvent(..) => "ForwardEvent", ForwardEvent(..) => "ForwardEvent",
SetCursor(..) => "SetCursor", SetCursor(..) => "SetCursor",
EnableProfiler(..) => "EnableProfiler",
DisableProfiler => "DisableProfiler",
}; };
write!(formatter, "ConstellationMsg::{}", variant) write!(formatter, "ConstellationMsg::{}", variant)
} }

View file

@ -60,7 +60,6 @@ fn webdriver(port: u16, constellation: Sender<ConstellationMsg>) {
#[cfg(not(feature = "webdriver"))] #[cfg(not(feature = "webdriver"))]
fn webdriver(_port: u16, _constellation: Sender<ConstellationMsg>) {} fn webdriver(_port: u16, _constellation: Sender<ConstellationMsg>) {}
use background_hang_monitor::HangMonitorRegister;
use bluetooth::BluetoothThreadFactory; use bluetooth::BluetoothThreadFactory;
use bluetooth_traits::BluetoothRequest; use bluetooth_traits::BluetoothRequest;
use canvas::gl_context::GLContextFactory; use canvas::gl_context::GLContextFactory;
@ -133,6 +132,7 @@ pub struct Servo<Window: WindowMethods + 'static> {
constellation_chan: Sender<ConstellationMsg>, constellation_chan: Sender<ConstellationMsg>,
embedder_receiver: EmbedderReceiver, embedder_receiver: EmbedderReceiver,
embedder_events: Vec<(Option<BrowserId>, EmbedderMsg)>, embedder_events: Vec<(Option<BrowserId>, EmbedderMsg)>,
profiler_enabled: bool,
} }
#[derive(Clone)] #[derive(Clone)]
@ -319,6 +319,7 @@ where
constellation_chan: constellation_chan, constellation_chan: constellation_chan,
embedder_receiver: embedder_receiver, embedder_receiver: embedder_receiver,
embedder_events: Vec::new(), embedder_events: Vec::new(),
profiler_enabled: false,
} }
} }
@ -407,6 +408,18 @@ where
} }
}, },
WindowEvent::ToggleSamplingProfiler(rate) => {
self.profiler_enabled = !self.profiler_enabled;
let msg = if self.profiler_enabled {
ConstellationMsg::EnableProfiler(rate)
} else {
ConstellationMsg::DisableProfiler
};
if let Err(e) = self.constellation_chan.send(msg) {
warn!("Sending profiler toggle to constellation failed ({:?}).", e);
}
},
WindowEvent::ToggleWebRenderDebug(option) => { WindowEvent::ToggleWebRenderDebug(option) => {
self.compositor.toggle_webrender_debug(option); self.compositor.toggle_webrender_debug(option);
}, },
@ -711,7 +724,7 @@ pub fn run_content_process(token: String) {
.send(unprivileged_content_sender) .send(unprivileged_content_sender)
.unwrap(); .unwrap();
let unprivileged_content = unprivileged_content_receiver.recv().unwrap(); let mut unprivileged_content = unprivileged_content_receiver.recv().unwrap();
opts::set_options(unprivileged_content.opts()); opts::set_options(unprivileged_content.opts());
prefs::pref_map() prefs::pref_map()
.set_all(unprivileged_content.prefs()) .set_all(unprivileged_content.prefs())
@ -723,11 +736,8 @@ pub fn run_content_process(token: String) {
create_sandbox(); create_sandbox();
} }
let background_hang_monitor_register = HangMonitorRegister::init( let background_hang_monitor_register =
unprivileged_content unprivileged_content.register_with_background_hang_monitor();
.background_hang_monitor_to_constellation_chan()
.clone(),
);
// send the required channels to the service worker manager // send the required channels to the service worker manager
let sw_senders = unprivileged_content.swmanager_senders(); let sw_senders = unprivileged_content.swmanager_senders();

153
etc/profilicate.py Normal file
View file

@ -0,0 +1,153 @@
#!/usr/bin/env python
# Copyright 2018 The Servo Project Developers. See the COPYRIGHT
# file at the top-level directory of this distribution.
#
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
# option. This file may not be copied, modified, or distributed
# except according to those terms.
# Script to take raw sample output from Servo sampling profiler and
# output a [processed profile]. Based largely on [this script] and
# [this documentation].
#
# [processed profile]: https://github.com/firefox-devtools/profiler/blob/master/docs-developer/processed-profile-format.md
# [this script]: https://github.com/firefox-devtools/profiler/blob/master/src/profile-logic/import/linux-perf.js
# [this documentation]: https://github.com/firefox-devtools/profiler/blob/master/src/types/profile.js
from collections import defaultdict
import json
import sys
class StringTable:
def __init__(self):
self.table = {}
self.idx = 0
def get(self, s):
assert s
if s not in self.table:
self.table[s] = self.idx
self.idx += 1
return self.table[s]
def length(self):
return len(self.table.keys())
def contents(self):
return sorted(self.table.keys(), key=self.table.__getitem__)
with open(sys.argv[1]) as f:
profile = json.load(f)
rate = profile["rate"]
samples = profile["data"]
startTime = profile["start"]
frames = {}
stacks = {}
thread_data = defaultdict(list)
thread_order = {}
for sample in samples:
if sample['name']:
name = sample['name']
else:
name = "%s %d %d" % (sample['type'], sample['namespace'], sample['index'])
thread_data[name].append((sample['time'], sample['frames']))
if name not in thread_order:
thread_order[name] = (sample['namespace'], sample['index'])
tid = 0
threads = []
for (name, raw_samples) in sorted(thread_data.iteritems(), key=lambda x: thread_order[x[0]]):
string_table = StringTable()
tid += 1
stackMap = {}
stacks = []
frameMap = {}
frames = []
samples = []
for sample in raw_samples:
prefix = None
for frame in sample[1]:
if not frame['name']:
continue
if not frame['name'] in frameMap:
frameMap[frame['name']] = len(frames)
frame_index = string_table.get(frame['name'])
frames.append([frame_index])
frame = frameMap[frame['name']]
stack_key = "%d,%d" % (frame, prefix) if prefix else str(frame)
if stack_key not in stackMap:
stackMap[stack_key] = len(stacks)
stacks.append([frame, prefix])
stack = stackMap[stack_key]
prefix = stack
samples.append([stack, sample[0]])
threads.append({
'tid': tid,
'name': name,
'markers': {
'schema': {
'name': 0,
'time': 1,
'data': 2,
},
'data': [],
},
'samples': {
'schema': {
'stack': 0,
'time': 1,
'responsiveness': 2,
'rss': 2,
'uss': 4,
'frameNumber': 5,
},
'data': samples,
},
'frameTable': {
'schema': {
'location': 0,
'implementation': 1,
'optimizations': 2,
'line': 3,
'category': 4,
},
'data': frames,
},
'stackTable': {
'schema': {
'frame': 0,
'prefix': 1,
},
'data': stacks,
},
'stringTable': string_table.contents(),
})
output = {
'meta': {
'interval': rate,
'processType': 0,
'product': 'Servo',
'stackwalk': 1,
'startTime': startTime,
'version': 4,
'presymbolicated': True,
},
'libs': [],
'threads': threads,
}
print(json.dumps(output))

View file

@ -538,7 +538,8 @@ impl ServoGlue {
EmbedderMsg::SetFullscreenState(..) | EmbedderMsg::SetFullscreenState(..) |
EmbedderMsg::ShowIME(..) | EmbedderMsg::ShowIME(..) |
EmbedderMsg::HideIME | EmbedderMsg::HideIME |
EmbedderMsg::Panic(..) => {}, EmbedderMsg::Panic(..) |
EmbedderMsg::ReportProfile(..) => {},
} }
} }
Ok(()) Ok(())

View file

@ -16,9 +16,13 @@ use servo::servo_config::opts;
use servo::servo_config::pref; use servo::servo_config::pref;
use servo::servo_url::ServoUrl; use servo::servo_url::ServoUrl;
use servo::webrender_api::ScrollLocation; use servo::webrender_api::ScrollLocation;
use std::env;
use std::fs::File;
use std::io::Write;
use std::mem; use std::mem;
use std::rc::Rc; use std::rc::Rc;
use std::thread; use std::thread;
use std::time::Duration;
use tinyfiledialogs::{self, MessageBoxIcon}; use tinyfiledialogs::{self, MessageBoxIcon};
pub struct Browser { pub struct Browser {
@ -112,6 +116,14 @@ impl Browser {
.shortcut(CMD_OR_CONTROL, 'Q', || { .shortcut(CMD_OR_CONTROL, 'Q', || {
self.event_queue.push(WindowEvent::Quit); self.event_queue.push(WindowEvent::Quit);
}) })
.shortcut(CMD_OR_CONTROL, 'P', || {
let rate = env::var("SAMPLING_RATE")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(10);
self.event_queue.push(WindowEvent::ToggleSamplingProfiler(
Duration::from_millis(rate)));
})
.shortcut(Modifiers::CONTROL, Key::F9, || { .shortcut(Modifiers::CONTROL, Key::F9, || {
self.event_queue.push(WindowEvent::CaptureWebRender) self.event_queue.push(WindowEvent::CaptureWebRender)
}) })
@ -378,6 +390,15 @@ impl Browser {
EmbedderMsg::HideIME => { EmbedderMsg::HideIME => {
debug!("HideIME received"); debug!("HideIME received");
}, },
EmbedderMsg::ReportProfile(bytes) => {
let filename = env::var("PROFILE_OUTPUT")
.unwrap_or("samples.json".to_string());
let result = File::create(&filename)
.and_then(|mut f| f.write_all(&bytes));
if let Err(e) = result {
error!("Failed to store profile: {}", e);
}
}
} }
} }
} }