From 90f67c11e5de39341b8b212a022ce997f9382eb3 Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Fri, 22 Mar 2019 13:15:50 -0400 Subject: [PATCH 1/3] Add a sampling profiler and a script to generate profiles for use with Gecko tooling. --- Cargo.lock | 1 + components/background_hang_monitor/Cargo.toml | 1 + .../background_hang_monitor.rs | 166 ++++++++++++++++-- components/background_hang_monitor/lib.rs | 2 + components/compositing/windowing.rs | 4 + components/constellation/constellation.rs | 23 ++- components/constellation/pipeline.rs | 8 +- components/embedder_traits/lib.rs | 3 + components/msg/constellation_msg.rs | 18 ++ components/servo/lib.rs | 4 + etc/profilicate.py | 153 ++++++++++++++++ ports/libsimpleservo/api/src/lib.rs | 3 +- ports/servo/browser.rs | 21 +++ 13 files changed, 379 insertions(+), 28 deletions(-) create mode 100644 etc/profilicate.py diff --git a/Cargo.lock b/Cargo.lock index c0df2ab8bc7..30e5835a08e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -158,6 +158,7 @@ dependencies = [ "mach 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "msg 0.0.1", "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]] diff --git a/components/background_hang_monitor/Cargo.toml b/components/background_hang_monitor/Cargo.toml index 35795a87951..474fad51ea6 100644 --- a/components/background_hang_monitor/Cargo.toml +++ b/components/background_hang_monitor/Cargo.toml @@ -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] diff --git a/components/background_hang_monitor/background_hang_monitor.rs b/components/background_hang_monitor/background_hang_monitor.rs index 3511b725f5a..297a518d9ca 100644 --- a/components/background_hang_monitor/background_hang_monitor.rs +++ b/components/background_hang_monitor/background_hang_monitor.rs @@ -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 = 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) -> Box { + pub fn init( + constellation_chan: IpcSender, + ) -> Box { 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, Duration, Duration), + Register(Box, Option, 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, monitored_components: HashMap, - constellation_chan: IpcSender, + constellation_chan: IpcSender, port: Receiver<(MonitoredComponentId, MonitoredComponentMsg)>, + sampling_duration: Option, + last_sample: Instant, + creation: Instant, + sampling_baseline: Instant, + samples: Vec, } impl BackgroundHangMonitorWorker { pub fn new( - constellation_chan: IpcSender, + constellation_chan: IpcSender, 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)); + } + } + } } diff --git a/components/background_hang_monitor/lib.rs b/components/background_hang_monitor/lib.rs index 6b0b4de6a0a..24c34b910c8 100644 --- a/components/background_hang_monitor/lib.rs +++ b/components/background_hang_monitor/lib.rs @@ -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; diff --git a/components/compositing/windowing.rs b/components/compositing/windowing.rs index 6c4ba4b3cee..385cde3c242 100644 --- a/components/compositing/windowing.rs +++ b/components/compositing/windowing.rs @@ -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"), } } } diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index e4120579912..7dc9074eb2f 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -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 { /// A channel for the background hang monitor to send messages /// to the constellation. - background_hang_monitor_sender: IpcSender, + background_hang_monitor_sender: IpcSender, /// A channel for the constellation to receiver messages /// from the background hang monitor. - background_hang_monitor_receiver: Receiver>, + background_hang_monitor_receiver: Receiver>, /// 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)) { diff --git a/components/constellation/pipeline.rs b/components/constellation/pipeline.rs index be369c0c08e..4e9d795d459 100644 --- a/components/constellation/pipeline.rs +++ b/components/constellation/pipeline.rs @@ -18,7 +18,7 @@ use ipc_channel::Error; use layout_traits::LayoutThreadFactory; use metrics::PaintTimeMetrics; use msg::constellation_msg::TopLevelBrowsingContextId; -use msg::constellation_msg::{BackgroundHangMonitorRegister, HangAlert}; +use msg::constellation_msg::{BackgroundHangMonitorRegister, HangMonitorAlert}; use msg::constellation_msg::{BrowsingContextId, HistoryStateId, PipelineId, PipelineNamespaceId}; use net::image_cache::ImageCacheImpl; use net_traits::image_cache::ImageCache; @@ -122,7 +122,7 @@ pub struct InitialPipelineState { pub background_monitor_register: Option>, /// A channel for the background hang monitor to send messages to the constellation. - pub background_hang_monitor_to_constellation_chan: IpcSender, + pub background_hang_monitor_to_constellation_chan: IpcSender, /// A channel for the layout thread to send messages to the constellation. pub layout_to_constellation_chan: IpcSender, @@ -467,7 +467,7 @@ pub struct UnprivilegedPipelineContent { parent_pipeline_id: Option, opener: Option, script_to_constellation_chan: ScriptToConstellationChan, - background_hang_monitor_to_constellation_chan: IpcSender, + background_hang_monitor_to_constellation_chan: IpcSender, layout_to_constellation_chan: IpcSender, scheduler_chan: IpcSender, devtools_chan: Option>, @@ -669,7 +669,7 @@ impl UnprivilegedPipelineContent { } } - pub fn background_hang_monitor_to_constellation_chan(&self) -> &IpcSender { + pub fn background_hang_monitor_to_constellation_chan(&self) -> &IpcSender { &self.background_hang_monitor_to_constellation_chan } diff --git a/components/embedder_traits/lib.rs b/components/embedder_traits/lib.rs index bfb27235af1..29431ebbb63 100644 --- a/components/embedder_traits/lib.rs +++ b/components/embedder_traits/lib.rs @@ -160,6 +160,8 @@ pub enum EmbedderMsg { HideIME, /// Servo has shut down Shutdown, + /// Report a complete sampled profile + ReportProfile(Vec), } impl Debug for EmbedderMsg { @@ -189,6 +191,7 @@ impl Debug for EmbedderMsg { EmbedderMsg::Shutdown => write!(f, "Shutdown"), EmbedderMsg::AllowOpeningBrowser(..) => write!(f, "AllowOpeningBrowser"), EmbedderMsg::BrowserCreated(..) => write!(f, "BrowserCreated"), + EmbedderMsg::ReportProfile(..) => write!(f, "ReportProfile"), } } } diff --git a/components/msg/constellation_msg.rs b/components/msg/constellation_msg.rs index f0d9efc4782..0f15b79f382 100644 --- a/components/msg/constellation_msg.rs +++ b/components/msg/constellation_msg.rs @@ -350,6 +350,24 @@ pub enum HangAnnotation { 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), +} + +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. #[derive(Deserialize, Serialize)] pub enum HangAlert { diff --git a/components/servo/lib.rs b/components/servo/lib.rs index cc3a7c849c2..cf510695f5c 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -406,6 +406,10 @@ where } }, + WindowEvent::ToggleSamplingProfiler(rate) => { + HangMonitorRegister::toggle(rate); + }, + WindowEvent::ToggleWebRenderDebug(option) => { self.compositor.toggle_webrender_debug(option); }, diff --git a/etc/profilicate.py b/etc/profilicate.py new file mode 100644 index 00000000000..44c734aef99 --- /dev/null +++ b/etc/profilicate.py @@ -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 or the MIT license +# , 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)) diff --git a/ports/libsimpleservo/api/src/lib.rs b/ports/libsimpleservo/api/src/lib.rs index 8622f832af7..4b3c56ad378 100644 --- a/ports/libsimpleservo/api/src/lib.rs +++ b/ports/libsimpleservo/api/src/lib.rs @@ -519,7 +519,8 @@ impl ServoGlue { EmbedderMsg::SetFullscreenState(..) | EmbedderMsg::ShowIME(..) | EmbedderMsg::HideIME | - EmbedderMsg::Panic(..) => {}, + EmbedderMsg::Panic(..) | + EmbedderMsg::ReportProfile(..) => {}, } } Ok(()) diff --git a/ports/servo/browser.rs b/ports/servo/browser.rs index 78417305401..86a810fed5e 100644 --- a/ports/servo/browser.rs +++ b/ports/servo/browser.rs @@ -16,9 +16,13 @@ use servo::servo_config::opts; use servo::servo_config::prefs::PREFS; use servo::servo_url::ServoUrl; use servo::webrender_api::ScrollLocation; +use std::env; +use std::fs::File; +use std::io::Write; use std::mem; use std::rc::Rc; use std::thread; +use std::time::Duration; use tinyfiledialogs::{self, MessageBoxIcon}; pub struct Browser { @@ -112,6 +116,14 @@ impl Browser { .shortcut(CMD_OR_CONTROL, 'Q', || { 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, || { self.event_queue.push(WindowEvent::CaptureWebRender) }) @@ -378,6 +390,15 @@ impl Browser { EmbedderMsg::HideIME => { 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); + } + } } } } From 8b7244f0d1d259971547e76aa299f404b5baedfd Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Mon, 25 Mar 2019 14:10:44 -0400 Subject: [PATCH 2/3] Support multiprocess in sampling profiler. --- Cargo.lock | 1 - components/background_hang_monitor/Cargo.toml | 1 - .../background_hang_monitor.rs | 152 ++++++++---------- components/background_hang_monitor/lib.rs | 2 - components/constellation/constellation.rs | 45 +++++- components/constellation/pipeline.rs | 45 ++++-- components/msg/constellation_msg.rs | 7 + components/script_traits/lib.rs | 7 + components/servo/lib.rs | 22 ++- 9 files changed, 165 insertions(+), 117 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 30e5835a08e..db84409d852 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -152,7 +152,6 @@ dependencies = [ "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)", "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)", "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "mach 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/components/background_hang_monitor/Cargo.toml b/components/background_hang_monitor/Cargo.toml index 474fad51ea6..4a46d849a09 100644 --- a/components/background_hang_monitor/Cargo.toml +++ b/components/background_hang_monitor/Cargo.toml @@ -16,7 +16,6 @@ doctest = false backtrace = "0.3" bitflags = "1.0" ipc-channel = "0.11" -lazy_static = "1" libc = "0.2" log = "0.4" msg = {path = "../msg"} diff --git a/components/background_hang_monitor/background_hang_monitor.rs b/components/background_hang_monitor/background_hang_monitor.rs index 297a518d9ca..1ed55e463b6 100644 --- a/components/background_hang_monitor/background_hang_monitor.rs +++ b/components/background_hang_monitor/background_hang_monitor.rs @@ -4,15 +4,15 @@ use crate::sampler::{NativeStack, Sampler}; 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::{ BackgroundHangMonitor, BackgroundHangMonitorClone, BackgroundHangMonitorRegister, }; -use msg::constellation_msg::{HangAlert, HangAnnotation, HangMonitorAlert}; +use msg::constellation_msg::{HangAlert, HangAnnotation, HangMonitorAlert, SamplerControlMsg}; use std::cell::Cell; use std::collections::HashMap; -use std::sync::Mutex; use std::thread; use std::time::{Duration, Instant}; @@ -21,46 +21,22 @@ 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 = 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, + control_port: IpcReceiver, ) -> Box { 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, control_port, 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 { @@ -175,6 +151,7 @@ pub struct BackgroundHangMonitorWorker { monitored_components: HashMap, constellation_chan: IpcSender, port: Receiver<(MonitoredComponentId, MonitoredComponentMsg)>, + control_port: Receiver, sampling_duration: Option, last_sample: Instant, creation: Instant, @@ -185,13 +162,16 @@ pub struct BackgroundHangMonitorWorker { impl BackgroundHangMonitorWorker { pub fn new( constellation_chan: IpcSender, + control_port: IpcReceiver, port: Receiver<(MonitoredComponentId, MonitoredComponentMsg)>, ) -> Self { + let control_port = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(control_port); Self { component_names: Default::default(), monitored_components: Default::default(), constellation_chan, port, + control_port, sampling_duration: None, last_sample: Instant::now(), sampling_baseline: Instant::now(), @@ -200,64 +180,48 @@ impl BackgroundHangMonitorWorker { } } - 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(), - ); + 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)); - - *SAMPLING_STATE.lock().unwrap() = SamplerState::NotSampling; - self.sampling_duration = None; - }, - _ => (), + 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 { - self.handle_sampling(); - let timeout = if let Some(duration) = self.sampling_duration { duration .checked_sub(Instant::now() - self.last_sample) @@ -273,6 +237,23 @@ impl BackgroundHangMonitorWorker { Err(_) => return false, } }, + 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 { @@ -285,9 +266,10 @@ impl BackgroundHangMonitorWorker { } if let Some(duration) = self.sampling_duration { - if Instant::now() - self.last_sample > duration { + let now = Instant::now(); + if now - self.last_sample > duration { self.sample(); - self.last_sample = Instant::now(); + self.last_sample = now; } } else { self.perform_a_hang_monitor_checkpoint(); diff --git a/components/background_hang_monitor/lib.rs b/components/background_hang_monitor/lib.rs index 24c34b910c8..6b0b4de6a0a 100644 --- a/components/background_hang_monitor/lib.rs +++ b/components/background_hang_monitor/lib.rs @@ -7,8 +7,6 @@ #[macro_use] extern crate crossbeam_channel; #[macro_use] -extern crate lazy_static; -#[macro_use] extern crate log; pub mod background_hang_monitor; diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index 7dc9074eb2f..188f2ed7c3b 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -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, HangMonitorAlert}; +use msg::constellation_msg::{BackgroundHangMonitorRegister, HangMonitorAlert, SamplerControlMsg}; use msg::constellation_msg::{ BrowsingContextId, HistoryStateId, PipelineId, TopLevelBrowsingContextId, }; @@ -208,6 +208,9 @@ pub struct Constellation { /// None when in multiprocess mode. background_monitor_register: Option>, + /// Channels to control all sampling profilers. + sampling_profiler_control: Vec>, + /// A channel for the background hang monitor to send messages /// to the constellation. background_hang_monitor_sender: IpcSender, @@ -614,12 +617,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) = @@ -640,6 +650,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, @@ -841,6 +852,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 {}.", @@ -848,11 +863,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. @@ -1202,6 +1217,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); + } + } + }, } } diff --git a/components/constellation/pipeline.rs b/components/constellation/pipeline.rs index 4e9d795d459..f518f48f295 100644 --- a/components/constellation/pipeline.rs +++ b/components/constellation/pipeline.rs @@ -3,6 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use crate::event_loop::EventLoop; +use background_hang_monitor::HangMonitorRegister; use bluetooth_traits::BluetoothRequest; use canvas_traits::webgl::WebGLPipeline; use compositing::compositor_thread::Msg as CompositorMsg; @@ -18,7 +19,7 @@ use ipc_channel::Error; use layout_traits::LayoutThreadFactory; use metrics::PaintTimeMetrics; use msg::constellation_msg::TopLevelBrowsingContextId; -use msg::constellation_msg::{BackgroundHangMonitorRegister, HangMonitorAlert}; +use msg::constellation_msg::{BackgroundHangMonitorRegister, HangMonitorAlert, SamplerControlMsg}; use msg::constellation_msg::{BrowsingContextId, HistoryStateId, PipelineId, PipelineNamespaceId}; use net::image_cache::ImageCacheImpl; use net_traits::image_cache::ImageCache; @@ -188,10 +189,15 @@ pub struct InitialPipelineState { pub webvr_chan: Option>, } +pub struct NewPipeline { + pub pipeline: Pipeline, + pub sampler_control_chan: Option>, +} + impl Pipeline { /// Starts a layout thread, and possibly a script thread, in /// a new process if requested. - pub fn spawn(state: InitialPipelineState) -> Result + pub fn spawn(state: InitialPipelineState) -> Result where LTF: LayoutThreadFactory, STF: ScriptThreadFactory, @@ -210,7 +216,7 @@ impl Pipeline { 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) => { let new_layout_info = NewLayoutInfo { parent_info: state.parent_pipeline_id, @@ -229,7 +235,7 @@ impl Pipeline { { warn!("Sending to script during pipeline creation failed ({})", e); } - script_chan + (script_chan, None) }, None => { 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) = ipc::channel().expect("Pipeline script content process shutdown chan"); - let unprivileged_pipeline_content = UnprivilegedPipelineContent { + let mut unprivileged_pipeline_content = UnprivilegedPipelineContent { id: state.id, browsing_context_id: state.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 .clone(), + sampling_profiler_port: None, scheduler_chan: state.scheduler_chan, devtools_chan: script_to_devtools_chan, bluetooth_thread: state.bluetooth_thread, @@ -302,21 +309,25 @@ impl Pipeline { // Spawn the child process. // // 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()?; + Some(sampler_chan) } else { // Should not be None in single-process mode. let register = state .background_monitor_register .expect("Couldn't start content, no background monitor has been initiated"); unprivileged_pipeline_content.start_all::(false, register); - } + None + }; - EventLoop::new(script_chan) + (EventLoop::new(script_chan), sampler_chan) }, }; - Ok(Pipeline::new( + let pipeline = Pipeline::new( state.id, state.browsing_context_id, state.top_level_browsing_context_id, @@ -327,7 +338,11 @@ impl Pipeline { url, state.prev_visibility, state.load_data, - )) + ); + Ok(NewPipeline { + pipeline, + sampler_control_chan: sampler_chan, + }) } /// Creates a new `Pipeline`, after the script and layout threads have been @@ -468,6 +483,7 @@ pub struct UnprivilegedPipelineContent { opener: Option, script_to_constellation_chan: ScriptToConstellationChan, background_hang_monitor_to_constellation_chan: IpcSender, + sampling_profiler_port: Option>, layout_to_constellation_chan: IpcSender, scheduler_chan: IpcSender, devtools_chan: Option>, @@ -669,8 +685,13 @@ impl UnprivilegedPipelineContent { } } - pub fn background_hang_monitor_to_constellation_chan(&self) -> &IpcSender { - &self.background_hang_monitor_to_constellation_chan + pub fn register_with_background_hang_monitor(&mut self) -> Box { + 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 { diff --git a/components/msg/constellation_msg.rs b/components/msg/constellation_msg.rs index 0f15b79f382..04aa13b483f 100644 --- a/components/msg/constellation_msg.rs +++ b/components/msg/constellation_msg.rs @@ -490,3 +490,10 @@ pub trait BackgroundHangMonitor { /// Unregister the component from monitor. fn unregister(&self); } + +/// Messages to control the sampling profiler. +#[derive(Deserialize, Serialize)] +pub enum SamplerControlMsg { + Enable(Duration), + Disable, +} diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index 724d76daede..49f4f055e63 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -51,6 +51,7 @@ use servo_url::ServoUrl; use std::collections::HashMap; use std::fmt; use std::sync::Arc; +use std::time::Duration; use style_traits::CSSPixel; use style_traits::SpeculativePainter; use webrender_api::{ @@ -777,6 +778,10 @@ pub enum ConstellationMsg { ForwardEvent(PipelineId, CompositorEvent), /// Requesting a change to the onscreen cursor. SetCursor(Cursor), + /// Enable the sampling profiler. + EnableProfiler(Duration), + /// Disable the sampling profiler. + DisableProfiler, } impl fmt::Debug for ConstellationMsg { @@ -804,6 +809,8 @@ impl fmt::Debug for ConstellationMsg { SelectBrowser(..) => "SelectBrowser", ForwardEvent(..) => "ForwardEvent", SetCursor(..) => "SetCursor", + EnableProfiler(..) => "EnableProfiler", + DisableProfiler => "DisableProfiler", }; write!(formatter, "ConstellationMsg::{}", variant) } diff --git a/components/servo/lib.rs b/components/servo/lib.rs index cf510695f5c..e70219cba50 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -60,7 +60,6 @@ fn webdriver(port: u16, constellation: Sender) { #[cfg(not(feature = "webdriver"))] fn webdriver(_port: u16, _constellation: Sender) {} -use background_hang_monitor::HangMonitorRegister; use bluetooth::BluetoothThreadFactory; use bluetooth_traits::BluetoothRequest; use canvas::gl_context::GLContextFactory; @@ -133,6 +132,7 @@ pub struct Servo { constellation_chan: Sender, embedder_receiver: EmbedderReceiver, embedder_events: Vec<(Option, EmbedderMsg)>, + profiler_enabled: bool, } #[derive(Clone)] @@ -318,6 +318,7 @@ where constellation_chan: constellation_chan, embedder_receiver: embedder_receiver, embedder_events: Vec::new(), + profiler_enabled: false, } } @@ -407,7 +408,15 @@ where }, WindowEvent::ToggleSamplingProfiler(rate) => { - HangMonitorRegister::toggle(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) => { @@ -714,7 +723,7 @@ pub fn run_content_process(token: String) { .send(unprivileged_content_sender) .unwrap(); - let unprivileged_content = unprivileged_content_receiver.recv().unwrap(); + let mut unprivileged_content = unprivileged_content_receiver.recv().unwrap(); opts::set_options(unprivileged_content.opts()); PREFS.extend(unprivileged_content.prefs()); set_logger(unprivileged_content.script_to_constellation_chan().clone()); @@ -724,11 +733,8 @@ pub fn run_content_process(token: String) { create_sandbox(); } - let background_hang_monitor_register = HangMonitorRegister::init( - unprivileged_content - .background_hang_monitor_to_constellation_chan() - .clone(), - ); + let background_hang_monitor_register = + unprivileged_content.register_with_background_hang_monitor(); // send the required channels to the service worker manager let sw_senders = unprivileged_content.swmanager_senders(); From aee2974c330ed955958e90764f9a985c32fbc863 Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Tue, 26 Mar 2019 14:55:56 -0400 Subject: [PATCH 3/3] Add unit test for sampling profiler. --- .../tests/hang_monitor_tests.rs | 68 ++++++++++++++++--- 1 file changed, 57 insertions(+), 11 deletions(-) diff --git a/components/background_hang_monitor/tests/hang_monitor_tests.rs b/components/background_hang_monitor/tests/hang_monitor_tests.rs index d7e6dcf268d..2ecb526e152 100644 --- a/components/background_hang_monitor/tests/hang_monitor_tests.rs +++ b/components/background_hang_monitor/tests/hang_monitor_tests.rs @@ -6,18 +6,63 @@ use background_hang_monitor::HangMonitorRegister; use ipc_channel::ipc; use msg::constellation_msg::ScriptHangAnnotation; 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 std::thread; 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] fn test_hang_monitoring() { 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()); + 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), @@ -33,11 +78,11 @@ fn test_hang_monitoring() { // Check for a transient hang alert. 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); assert_eq!(expected, component_id); }, - HangAlert::Permanent(..) => unreachable!(), + _ => unreachable!(), } // Sleep until the "permanent" timeout has been reached. @@ -45,11 +90,11 @@ fn test_hang_monitoring() { // Check for a permanent hang alert. 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); assert_eq!(expected, component_id); }, - HangAlert::Transient(..) => unreachable!(), + _ => unreachable!(), } // Now the component is not hanging anymore. @@ -61,11 +106,11 @@ fn test_hang_monitoring() { // Check for a transient hang alert. 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); assert_eq!(expected, component_id); }, - HangAlert::Permanent(..) => unreachable!(), + _ => unreachable!(), } // 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. 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); assert_eq!(expected, component_id); }, - HangAlert::Permanent(..) => unreachable!(), + _ => unreachable!(), } // No new alert yet @@ -110,9 +155,10 @@ fn test_hang_monitoring() { fn test_hang_monitoring_unregister() { 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()); + 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),