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

View file

@ -124,7 +124,7 @@ use keyboard_types::webdriver::Event as WebDriverInputEvent;
use keyboard_types::KeyboardEvent;
use layout_traits::LayoutThreadFactory;
use log::{Level, LevelFilter, Log, Metadata, Record};
use msg::constellation_msg::{BackgroundHangMonitorRegister, HangAlert};
use msg::constellation_msg::{BackgroundHangMonitorRegister, HangMonitorAlert, SamplerControlMsg};
use msg::constellation_msg::{
BrowsingContextId, HistoryStateId, PipelineId, TopLevelBrowsingContextId,
};
@ -207,13 +207,16 @@ pub struct Constellation<Message, LTF, STF> {
/// None when in multiprocess mode.
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
/// to the constellation.
background_hang_monitor_sender: IpcSender<HangAlert>,
background_hang_monitor_sender: IpcSender<HangMonitorAlert>,
/// A channel for the constellation to receiver messages
/// 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.
/// This is the layout threads' view of `layout_receiver`.
@ -613,12 +616,19 @@ where
// If we are in multiprocess mode,
// a dedicated per-process hang monitor will be initialized later inside the content process.
// See run_content_process in servo/lib.rs
let background_monitor_register = if opts::multiprocess() {
None
let (background_monitor_register, sampler_chan) = if opts::multiprocess() {
(None, vec![])
} else {
Some(HangMonitorRegister::init(
background_hang_monitor_sender.clone(),
))
let (sampling_profiler_control, sampling_profiler_port) =
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) =
@ -639,6 +649,7 @@ where
background_hang_monitor_sender,
background_hang_monitor_receiver,
background_monitor_register,
sampling_profiler_control: sampler_chan,
layout_sender: ipc_layout_sender,
script_receiver: script_receiver,
compositor_receiver: compositor_receiver,
@ -840,6 +851,10 @@ where
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 {
debug!(
"Adding new host entry {} for top-level browsing context {}.",
@ -847,11 +862,11 @@ where
);
let _ = self
.event_loops
.insert(host, Rc::downgrade(&pipeline.event_loop));
.insert(host, Rc::downgrade(&pipeline.pipeline.event_loop));
}
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.
@ -941,7 +956,7 @@ where
#[derive(Debug)]
enum Request {
Script((PipelineId, FromScriptMsg)),
BackgroundHangMonitor(HangAlert),
BackgroundHangMonitor(HangMonitorAlert),
Compositor(FromCompositorMsg),
Layout(FromLayoutMsg),
NetworkListener((PipelineId, FetchResponseMsg)),
@ -1007,10 +1022,17 @@ where
}
}
fn handle_request_from_background_hang_monitor(&self, message: HangAlert) {
// TODO: In case of a permanent hang being reported, add a "kill script" workflow,
// via the embedder?
warn!("Component hang alert: {:?}", message);
fn handle_request_from_background_hang_monitor(&self, message: HangMonitorAlert) {
match message {
HangMonitorAlert::Profile(bytes) => self
.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)) {
@ -1194,6 +1216,20 @@ where
self.forward_event(destination_pipeline_id, event);
},
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);
}
}
},
}
}