Skip to content
Permalink
Browse files

Add a sampling profiler and a script to generate profiles for use wit…

…h Gecko tooling.
  • Loading branch information...
jdm committed Mar 22, 2019
1 parent db7bb2a commit 90f67c11e5de39341b8b212a022ce997f9382eb3

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
@@ -21,6 +21,7 @@ libc = "0.2"
log = "0.4"
msg = {path = "../msg"}
serde = "1.0.60"
serde_json = "1.0"
crossbeam-channel = "0.3"

[target.'cfg(target_os = "macos")'.dependencies]
@@ -2,16 +2,17 @@
* 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/. */

use crate::sampler::Sampler;
use crate::sampler::{NativeStack, Sampler};
use crossbeam_channel::{after, unbounded, Receiver, Sender};
use ipc_channel::ipc::IpcSender;
use msg::constellation_msg::MonitoredComponentId;
use msg::constellation_msg::{
BackgroundHangMonitor, BackgroundHangMonitorClone, BackgroundHangMonitorRegister,
};
use msg::constellation_msg::{HangAlert, HangAnnotation};
use msg::constellation_msg::{HangAlert, HangAnnotation, HangMonitorAlert};
use std::cell::Cell;
use std::collections::HashMap;
use std::sync::Mutex;
use std::thread;
use std::time::{Duration, Instant};

@@ -20,18 +21,46 @@ pub struct HangMonitorRegister {
sender: Sender<(MonitoredComponentId, MonitoredComponentMsg)>,
}

#[derive(Copy, Clone, PartialEq)]
enum SamplerState {
NotSampling,
StartSampling(Duration),
Sampling,
Resolving,
}

lazy_static! {
static ref SAMPLING_STATE: Mutex<SamplerState> = Mutex::new(SamplerState::NotSampling);
}
impl HangMonitorRegister {
/// 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>,
) -> Box<BackgroundHangMonitorRegister> {
let (sender, port) = unbounded();
let _ = thread::Builder::new().spawn(move || {
let mut monitor = { BackgroundHangMonitorWorker::new(constellation_chan, port) };
let mut monitor = BackgroundHangMonitorWorker::new(constellation_chan, port);
while monitor.run() {
// Monitoring until all senders have been dropped...
}
});
Box::new(HangMonitorRegister { sender })
}

pub fn toggle(rate: Duration) {
let state = *SAMPLING_STATE.lock().unwrap();
match state {
SamplerState::NotSampling => {
println!("Starting profiler.");
*SAMPLING_STATE.lock().unwrap() = SamplerState::StartSampling(rate);
},
SamplerState::Sampling => {
println!("Stopping profiler.");
*SAMPLING_STATE.lock().unwrap() = SamplerState::Resolving;
},
_ => (),
}
}
}

impl BackgroundHangMonitorRegister for HangMonitorRegister {
@@ -55,6 +84,7 @@ impl BackgroundHangMonitorRegister for HangMonitorRegister {

bhm_chan.send(MonitoredComponentMsg::Register(
sampler,
thread::current().name().map(str::to_owned),
transient_hang_timeout,
permanent_hang_timeout,
));
@@ -71,7 +101,7 @@ impl BackgroundHangMonitorClone for HangMonitorRegister {
/// Messages sent from monitored components to the monitor.
pub enum MonitoredComponentMsg {
/// Register component for monitoring,
Register(Box<Sampler>, Duration, Duration),
Register(Box<Sampler>, Option<String>, Duration, Duration),
/// Unregister component for monitoring.
Unregister,
/// Notify start of new activity for a given component,
@@ -138,25 +168,103 @@ struct MonitoredComponent {
is_waiting: bool,
}

struct Sample(MonitoredComponentId, Instant, NativeStack);

pub struct BackgroundHangMonitorWorker {
component_names: HashMap<MonitoredComponentId, String>,
monitored_components: HashMap<MonitoredComponentId, MonitoredComponent>,
constellation_chan: IpcSender<HangAlert>,
constellation_chan: IpcSender<HangMonitorAlert>,
port: Receiver<(MonitoredComponentId, MonitoredComponentMsg)>,
sampling_duration: Option<Duration>,
last_sample: Instant,
creation: Instant,
sampling_baseline: Instant,
samples: Vec<Sample>,
}

impl BackgroundHangMonitorWorker {
pub fn new(
constellation_chan: IpcSender<HangAlert>,
constellation_chan: IpcSender<HangMonitorAlert>,
port: Receiver<(MonitoredComponentId, MonitoredComponentMsg)>,
) -> Self {
Self {
component_names: Default::default(),
monitored_components: Default::default(),
constellation_chan,
port,
sampling_duration: None,
last_sample: Instant::now(),
sampling_baseline: Instant::now(),
creation: Instant::now(),
samples: vec![],
}
}

fn handle_sampling(&mut self) {
let state = *SAMPLING_STATE.lock().unwrap();
match state {
SamplerState::StartSampling(rate) => {
*SAMPLING_STATE.lock().unwrap() = SamplerState::Sampling;
self.sampling_duration = Some(rate);
self.sampling_baseline = Instant::now();
},
SamplerState::Resolving => {
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));

*SAMPLING_STATE.lock().unwrap() = SamplerState::NotSampling;
self.sampling_duration = None;
},
_ => (),
}
}

pub fn run(&mut self) -> bool {
self.handle_sampling();

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! {
recv(self.port) -> event => {
match event {
@@ -165,7 +273,7 @@ impl BackgroundHangMonitorWorker {
Err(_) => return false,
}
},
recv(after(Duration::from_millis(100))) -> _ => None,
recv(after(timeout)) -> _ => None,
};
if let Some(msg) = received {
self.handle_msg(msg);
@@ -175,7 +283,15 @@ impl BackgroundHangMonitorWorker {
self.handle_msg(another_msg);
}
}
self.perform_a_hang_monitor_checkpoint();

if let Some(duration) = self.sampling_duration {
if Instant::now() - self.last_sample > duration {
self.sample();
self.last_sample = Instant::now();
}
} else {
self.perform_a_hang_monitor_checkpoint();
}
true
}

@@ -185,6 +301,7 @@ impl BackgroundHangMonitorWorker {
component_id,
MonitoredComponentMsg::Register(
sampler,
name,
transient_hang_timeout,
permanent_hang_timeout,
),
@@ -199,6 +316,9 @@ impl BackgroundHangMonitorWorker {
sent_permanent_alert: false,
is_waiting: true,
};
if let Some(name) = name {
self.component_names.insert(component_id.clone(), name);
}
assert!(
self.monitored_components
.insert(component_id, component)
@@ -250,11 +370,13 @@ impl BackgroundHangMonitorWorker {
Ok(native_stack) => Some(native_stack.to_hangprofile()),
Err(()) => None,
};
let _ = self.constellation_chan.send(HangAlert::Permanent(
component_id.clone(),
last_annotation,
profile,
));
let _ = self
.constellation_chan
.send(HangMonitorAlert::Hang(HangAlert::Permanent(
component_id.clone(),
last_annotation,
profile,
)));
monitored.sent_permanent_alert = true;
continue;
}
@@ -264,9 +386,23 @@ impl BackgroundHangMonitorWorker {
}
let _ = self
.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;
}
}
}

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));
}
}
}
}
@@ -7,6 +7,8 @@
#[macro_use]
extern crate crossbeam_channel;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;

pub mod background_hang_monitor;
@@ -16,6 +16,7 @@ use servo_url::ServoUrl;
use std::fmt::{Debug, Error, Formatter};
#[cfg(feature = "gl")]
use std::rc::Rc;
use std::time::Duration;
use style_traits::DevicePixel;
use webrender_api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePoint, ScrollLocation};
use webvr::VRServiceManager;
@@ -91,6 +92,8 @@ pub enum WindowEvent {
ToggleWebRenderDebug(WebRenderDebugOption),
/// Capture current WebRender
CaptureWebRender,
/// Toggle sampling profiler with the given sampling rate
ToggleSamplingProfiler(Duration),
}

impl Debug for WindowEvent {
@@ -118,6 +121,7 @@ impl Debug for WindowEvent {
WindowEvent::SelectBrowser(..) => write!(f, "SelectBrowser"),
WindowEvent::ToggleWebRenderDebug(..) => write!(f, "ToggleWebRenderDebug"),
WindowEvent::CaptureWebRender => write!(f, "CaptureWebRender"),
WindowEvent::ToggleSamplingProfiler(..) => write!(f, "ToggleSamplingProfiler"),
}
}
}
@@ -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};
use msg::constellation_msg::{
BrowsingContextId, HistoryStateId, PipelineId, TopLevelBrowsingContextId,
};
@@ -210,11 +210,11 @@ pub struct Constellation<Message, LTF, STF> {

/// 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`.
@@ -942,7 +942,7 @@ where
#[derive(Debug)]
enum Request {
Script((PipelineId, FromScriptMsg)),
BackgroundHangMonitor(HangAlert),
BackgroundHangMonitor(HangMonitorAlert),
Compositor(FromCompositorMsg),
Layout(FromLayoutMsg),
NetworkListener((PipelineId, FetchResponseMsg)),
@@ -1008,10 +1008,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)) {
Oops, something went wrong.

0 comments on commit 90f67c1

Please sign in to comment.
You can’t perform that action at this time.