aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbors-servo <lbergstrom+bors@mozilla.com>2019-03-29 23:12:40 -0400
committerGitHub <noreply@github.com>2019-03-29 23:12:40 -0400
commit41feb83c10dd8c37b5e3e45b82f670b18ee23243 (patch)
tree9512deabfa946e075368d3f1fd02b04a4d16df8f
parent4cf39a696d1c98f1b88a65c1bd955baa52f1997e (diff)
parent6c1bf6a591f554a87b91a3d91f7a1c20536f0d71 (diff)
downloadservo-41feb83c10dd8c37b5e3e45b82f670b18ee23243.tar.gz
servo-41feb83c10dd8c37b5e3e45b82f670b18ee23243.zip
Auto merge of #22355 - gterzian:add_linux_sampler, r=asajeffrey,gterzian
Add linux sampler <!-- Please describe your changes on the following line: --> --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [x] These changes fix #22203 (github issue number if applicable). <!-- Either: --> - [x] There are tests for these changes - [ ] These changes do not require tests because _____ <!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.--> <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> <!-- 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/22355) <!-- Reviewable:end -->
-rw-r--r--.travis.yml2
-rw-r--r--Cargo.lock13
-rw-r--r--README.md2
-rw-r--r--components/background_hang_monitor/Cargo.toml7
-rw-r--r--components/background_hang_monitor/background_hang_monitor.rs7
-rw-r--r--components/background_hang_monitor/lib.rs5
-rw-r--r--components/background_hang_monitor/sampler.rs16
-rw-r--r--components/background_hang_monitor/sampler_linux.rs273
-rw-r--r--components/background_hang_monitor/tests/hang_monitor_tests.rs16
-rw-r--r--etc/taskcluster/docker/build.dockerfile3
-rw-r--r--python/servo/bootstrap.py2
11 files changed, 336 insertions, 10 deletions
diff --git a/.travis.yml b/.travis.yml
index ce52b227f9a..50fe71dc85c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -19,7 +19,7 @@ matrix:
- sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
- sudo add-apt-repository 'deb http://apt.llvm.org/precise/ llvm-toolchain-precise-3.9 main' -y
- sudo apt-get update -q
- - sudo apt-get install clang-3.9 llvm-3.9 llvm-3.9-runtime -y
+ - sudo apt-get install clang-3.9 llvm-3.9 llvm-3.9-runtime libunwind8-dev -y
- curl -L http://servo-deps.s3.amazonaws.com/gstreamer/gstreamer-1.14-x86_64-linux-gnu.20190213.tar.gz | tar xz
- sed -i "s;prefix=/opt/gst;prefix=$PWD/gst;g" $PWD/gst/lib/pkgconfig/*.pc
- export PKG_CONFIG_PATH=$PWD/gst/lib/pkgconfig
diff --git a/Cargo.lock b/Cargo.lock
index 81684ca88e4..04239dd472f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -152,12 +152,15 @@ 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.6 (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",
+ "nix 0.11.0 (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)",
+ "unwind-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -4694,6 +4697,15 @@ dependencies = [
]
[[package]]
+name = "unwind-sys"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "url"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5549,6 +5561,7 @@ dependencies = [
"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
+"checksum unwind-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bd1c4a6d1cfe0072924d1b1d4ca6faa211c95056666979d7ef1bab4cd206057f"
"checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a"
"checksum url_serde 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "74e7d099f1ee52f823d4bdd60c93c3602043c728f5db3b97bdb548467f7bddea"
"checksum utf-8 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b6f923c601c7ac48ef1d66f7d5b5b2d9a7ba9c51333ab75a3ddf8d0309185a56"
diff --git a/README.md b/README.md
index b6648d28895..3b6245ce995 100644
--- a/README.md
+++ b/README.md
@@ -87,7 +87,7 @@ sudo apt install git curl autoconf libx11-dev \
gperf g++ build-essential cmake virtualenv python-pip \
libssl1.0-dev libbz2-dev libosmesa6-dev libxmu6 libxmu-dev \
libglu1-mesa-dev libgles2-mesa-dev libegl1-mesa-dev libdbus-1-dev \
- libharfbuzz-dev ccache clang \
+ libharfbuzz-dev ccache clang libunwind-dev \
libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-bad1.0-dev autoconf2.13
```
diff --git a/components/background_hang_monitor/Cargo.toml b/components/background_hang_monitor/Cargo.toml
index 4a46d849a09..cab0d712444 100644
--- a/components/background_hang_monitor/Cargo.toml
+++ b/components/background_hang_monitor/Cargo.toml
@@ -23,5 +23,12 @@ serde = "1.0.60"
serde_json = "1.0"
crossbeam-channel = "0.3"
+[dev-dependencies]
+lazy_static = "1.0"
+
[target.'cfg(target_os = "macos")'.dependencies]
mach = "0.2.3"
+
+[target.'cfg(all(target_os = "linux", not(any(target_arch = "arm", target_arch = "aarch64"))))'.dependencies]
+nix = "~0.11.0"
+unwind-sys = "0.1.1"
diff --git a/components/background_hang_monitor/background_hang_monitor.rs b/components/background_hang_monitor/background_hang_monitor.rs
index 1ed55e463b6..904e9a7ebd6 100644
--- a/components/background_hang_monitor/background_hang_monitor.rs
+++ b/components/background_hang_monitor/background_hang_monitor.rs
@@ -55,8 +55,13 @@ impl BackgroundHangMonitorRegister for HangMonitorRegister {
let sampler = crate::sampler_windows::WindowsSampler::new();
#[cfg(target_os = "macos")]
let sampler = crate::sampler_mac::MacOsSampler::new();
- #[cfg(any(target_os = "android", target_os = "linux"))]
+ #[cfg(all(
+ target_os = "linux",
+ not(any(target_arch = "arm", target_arch = "aarch64"))
+ ))]
let sampler = crate::sampler_linux::LinuxSampler::new();
+ #[cfg(any(target_os = "android", target_arch = "arm", target_arch = "aarch64"))]
+ let sampler = crate::sampler::DummySampler::new();
bhm_chan.send(MonitoredComponentMsg::Register(
sampler,
diff --git a/components/background_hang_monitor/lib.rs b/components/background_hang_monitor/lib.rs
index 6b0b4de6a0a..1e7d05cc167 100644
--- a/components/background_hang_monitor/lib.rs
+++ b/components/background_hang_monitor/lib.rs
@@ -11,7 +11,10 @@ extern crate log;
pub mod background_hang_monitor;
mod sampler;
-#[cfg(any(target_os = "android", target_os = "linux"))]
+#[cfg(all(
+ target_os = "linux",
+ not(any(target_arch = "arm", target_arch = "aarch64"))
+))]
mod sampler_linux;
#[cfg(target_os = "macos")]
mod sampler_mac;
diff --git a/components/background_hang_monitor/sampler.rs b/components/background_hang_monitor/sampler.rs
index d4030445d51..7a01bc51f20 100644
--- a/components/background_hang_monitor/sampler.rs
+++ b/components/background_hang_monitor/sampler.rs
@@ -12,6 +12,22 @@ pub trait Sampler: Send {
fn suspend_and_sample_thread(&self) -> Result<NativeStack, ()>;
}
+#[allow(dead_code)]
+pub struct DummySampler;
+
+impl DummySampler {
+ #[allow(dead_code)]
+ pub fn new() -> Box<Sampler> {
+ Box::new(DummySampler)
+ }
+}
+
+impl Sampler for DummySampler {
+ fn suspend_and_sample_thread(&self) -> Result<NativeStack, ()> {
+ Err(())
+ }
+}
+
// Several types in this file are currently not used in a Linux or Windows build.
#[allow(dead_code)]
pub type Address = *const libc::uint8_t;
diff --git a/components/background_hang_monitor/sampler_linux.rs b/components/background_hang_monitor/sampler_linux.rs
index 86297e5ba36..5d981570a1d 100644
--- a/components/background_hang_monitor/sampler_linux.rs
+++ b/components/background_hang_monitor/sampler_linux.rs
@@ -2,21 +2,182 @@
* 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/. */
+#![allow(unsafe_code)]
+
use crate::sampler::{NativeStack, Sampler};
use libc;
+use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal};
+use std::cell::UnsafeCell;
+use std::io;
+use std::mem;
+use std::process;
+use std::ptr;
+use std::sync::atomic::{AtomicPtr, Ordering};
+use std::thread;
+use unwind_sys::{
+ unw_cursor_t, unw_get_reg, unw_init_local, unw_step, UNW_ESUCCESS, UNW_REG_IP, UNW_REG_SP,
+};
+
+// Hack to workaround broken libunwind pkg-config contents for <1.1-3ubuntu.1.
+// https://bugs.launchpad.net/ubuntu/+source/libunwind/+bug/1336912
+#[link(name = "lzma")]
+extern "C" {}
+
+static mut SHARED_STATE: SharedState = SharedState {
+ msg2: None,
+ msg3: None,
+ msg4: None,
+ context: AtomicPtr::new(ptr::null_mut()),
+};
+
+type MonitoredThreadId = libc::pid_t;
+
+struct SharedState {
+ // "msg1" is the signal.
+ msg2: Option<PosixSemaphore>,
+ msg3: Option<PosixSemaphore>,
+ msg4: Option<PosixSemaphore>,
+ context: AtomicPtr<libc::ucontext_t>,
+}
+
+fn clear_shared_state() {
+ unsafe {
+ SHARED_STATE.msg2 = None;
+ SHARED_STATE.msg3 = None;
+ SHARED_STATE.msg4 = None;
+ SHARED_STATE.context = AtomicPtr::new(ptr::null_mut());
+ }
+}
+
+fn reset_shared_state() {
+ unsafe {
+ SHARED_STATE.msg2 = Some(PosixSemaphore::new(0).expect("valid semaphore"));
+ SHARED_STATE.msg3 = Some(PosixSemaphore::new(0).expect("valid semaphore"));
+ SHARED_STATE.msg4 = Some(PosixSemaphore::new(0).expect("valid semaphore"));
+ SHARED_STATE.context = AtomicPtr::new(ptr::null_mut());
+ }
+}
+
+struct PosixSemaphore {
+ sem: UnsafeCell<libc::sem_t>,
+}
+
+impl PosixSemaphore {
+ pub fn new(value: u32) -> io::Result<Self> {
+ let mut sem: libc::sem_t = unsafe { mem::uninitialized() };
+ let r = unsafe {
+ libc::sem_init(&mut sem, 0 /* not shared */, value)
+ };
+ if r == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ Ok(PosixSemaphore {
+ sem: UnsafeCell::new(sem),
+ })
+ }
+
+ pub fn post(&self) -> io::Result<()> {
+ if unsafe { libc::sem_post(self.sem.get()) } == 0 {
+ Ok(())
+ } else {
+ Err(io::Error::last_os_error())
+ }
+ }
+
+ pub fn wait(&self) -> io::Result<()> {
+ if unsafe { libc::sem_wait(self.sem.get()) } == 0 {
+ Ok(())
+ } else {
+ Err(io::Error::last_os_error())
+ }
+ }
-type MonitoredThreadId = libc::pthread_t;
+ /// Retries the wait if it returned due to EINTR.
+ /// Returns Ok on success and the error on any other return value.
+ pub fn wait_through_intr(&self) -> io::Result<()> {
+ loop {
+ match self.wait() {
+ Err(os_error) => {
+ let err = os_error.raw_os_error().expect("no os error");
+ if err == libc::EINTR {
+ thread::yield_now();
+ continue;
+ }
+ return Err(os_error);
+ },
+ _ => return Ok(()),
+ }
+ }
+ }
+}
+
+unsafe impl Sync for PosixSemaphore {}
+
+impl Drop for PosixSemaphore {
+ /// Destroys the semaphore.
+ fn drop(&mut self) {
+ unsafe { libc::sem_destroy(self.sem.get()) };
+ }
+}
#[allow(dead_code)]
pub struct LinuxSampler {
thread_id: MonitoredThreadId,
+ old_handler: SigAction,
}
impl LinuxSampler {
#[allow(unsafe_code, dead_code)]
pub fn new() -> Box<Sampler> {
- let thread_id = unsafe { libc::pthread_self() };
- Box::new(LinuxSampler { thread_id })
+ let thread_id = unsafe { libc::syscall(libc::SYS_gettid) as libc::pid_t };
+ let handler = SigHandler::SigAction(sigprof_handler);
+ let action = SigAction::new(
+ handler,
+ SaFlags::SA_RESTART | SaFlags::SA_SIGINFO,
+ SigSet::empty(),
+ );
+ let old_handler =
+ unsafe { sigaction(Signal::SIGPROF, &action).expect("signal handler set") };
+ Box::new(LinuxSampler {
+ thread_id,
+ old_handler,
+ })
+ }
+}
+
+enum RegNum {
+ Ip = UNW_REG_IP as isize,
+ Sp = UNW_REG_SP as isize,
+}
+
+fn get_register(cursor: &mut unw_cursor_t, num: RegNum) -> Result<u64, i32> {
+ unsafe {
+ let mut val = 0;
+ let ret = unw_get_reg(cursor, num as i32, &mut val);
+ if ret == UNW_ESUCCESS {
+ Ok(val)
+ } else {
+ Err(ret)
+ }
+ }
+}
+
+fn step(cursor: &mut unw_cursor_t) -> Result<bool, i32> {
+ unsafe {
+ // libunwind 1.1 seems to get confused and walks off the end of the stack. The last IP
+ // it reports is 0, so we'll stop if we're there.
+ if get_register(cursor, RegNum::Ip).unwrap_or(1) == 0 {
+ return Ok(false);
+ }
+
+ let ret = unw_step(cursor);
+ if ret > 0 {
+ Ok(true)
+ } else if ret == 0 {
+ Ok(false)
+ } else {
+ Err(ret)
+ }
}
}
@@ -28,8 +189,112 @@ impl Sampler for LinuxSampler {
// we must not do any dynamic memory allocation,
// nor try to acquire any lock
// or any other unshareable resource.
+ // first we reinitialize the semaphores
+ reset_shared_state();
+
+ // signal the thread, wait for it to tell us state was copied.
+ send_sigprof(self.thread_id);
+ unsafe {
+ SHARED_STATE
+ .msg2
+ .as_ref()
+ .unwrap()
+ .wait_through_intr()
+ .expect("msg2 failed");
+ }
+
+ let context = unsafe { SHARED_STATE.context.load(Ordering::SeqCst) };
+ let mut cursor = unsafe { mem::uninitialized() };
+ let ret = unsafe { unw_init_local(&mut cursor, context) };
+ let result = if ret == UNW_ESUCCESS {
+ let mut native_stack = NativeStack::new();
+ loop {
+ let ip = match get_register(&mut cursor, RegNum::Ip) {
+ Ok(ip) => ip,
+ Err(_) => break,
+ };
+ let sp = match get_register(&mut cursor, RegNum::Sp) {
+ Ok(sp) => sp,
+ Err(_) => break,
+ };
+ if native_stack
+ .process_register(ip as *mut _, sp as *mut _)
+ .is_err() ||
+ !step(&mut cursor).unwrap_or(false)
+ {
+ break;
+ }
+ }
+ Ok(native_stack)
+ } else {
+ Err(())
+ };
+
+ // signal the thread to continue.
+ unsafe {
+ SHARED_STATE
+ .msg3
+ .as_ref()
+ .unwrap()
+ .post()
+ .expect("msg3 failed");
+ }
+
+ // wait for thread to continue.
+ unsafe {
+ SHARED_STATE
+ .msg4
+ .as_ref()
+ .unwrap()
+ .wait_through_intr()
+ .expect("msg4 failed");
+ }
+
+ clear_shared_state();
// NOTE: End of "critical section".
- Err(())
+ result
+ }
+}
+
+impl Drop for LinuxSampler {
+ fn drop(&mut self) {
+ unsafe {
+ sigaction(Signal::SIGPROF, &self.old_handler).expect("previous signal handler restored")
+ };
+ }
+}
+
+extern "C" fn sigprof_handler(
+ sig: libc::c_int,
+ _info: *mut libc::siginfo_t,
+ ctx: *mut libc::c_void,
+) {
+ assert_eq!(sig, libc::SIGPROF);
+ unsafe {
+ // copy the context.
+ SHARED_STATE
+ .context
+ .store(ctx as *mut libc::ucontext_t, Ordering::SeqCst);
+ // Tell the sampler we copied the context.
+ SHARED_STATE.msg2.as_ref().unwrap().post().expect("posted");
+
+ // Wait for sampling to finish.
+ SHARED_STATE
+ .msg3
+ .as_ref()
+ .unwrap()
+ .wait_through_intr()
+ .expect("msg3 wait succeeded");
+
+ // OK we are done!
+ SHARED_STATE.msg4.as_ref().unwrap().post().expect("posted");
+ // DO NOT TOUCH shared state here onwards.
+ }
+}
+
+fn send_sigprof(to: libc::pid_t) {
+ unsafe {
+ libc::syscall(libc::SYS_tgkill, process::id(), to, libc::SIGPROF);
}
}
diff --git a/components/background_hang_monitor/tests/hang_monitor_tests.rs b/components/background_hang_monitor/tests/hang_monitor_tests.rs
index 2ecb526e152..8bc0700fa2e 100644
--- a/components/background_hang_monitor/tests/hang_monitor_tests.rs
+++ b/components/background_hang_monitor/tests/hang_monitor_tests.rs
@@ -2,18 +2,28 @@
* 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/. */
+#[macro_use]
+extern crate lazy_static;
+
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, HangMonitorAlert};
use msg::constellation_msg::{MonitoredComponentId, MonitoredComponentType};
+use std::sync::Mutex;
use std::thread;
use std::time::Duration;
+lazy_static! {
+ static ref SERIAL: Mutex<()> = Mutex::new(());
+}
+
#[test]
-#[cfg(target_os = "macos")]
+#[cfg(any(target_os = "macos", target_os = "linux"))]
fn test_sampler() {
+ let _lock = SERIAL.lock().unwrap();
+
use msg::constellation_msg::SamplerControlMsg;
use serde_json::Value;
@@ -57,6 +67,8 @@ fn test_sampler() {
#[test]
fn test_hang_monitoring() {
+ let _lock = SERIAL.lock().unwrap();
+
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");
@@ -153,6 +165,8 @@ fn test_hang_monitoring() {
#[test]
fn test_hang_monitoring_unregister() {
+ let _lock = SERIAL.lock().unwrap();
+
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");
diff --git a/etc/taskcluster/docker/build.dockerfile b/etc/taskcluster/docker/build.dockerfile
index 294adb44796..e0c1c23e56e 100644
--- a/etc/taskcluster/docker/build.dockerfile
+++ b/etc/taskcluster/docker/build.dockerfile
@@ -33,6 +33,9 @@ RUN \
libglu1-mesa-dev \
libbz2-dev \
#
+ # sampling profiler
+ libunwind-dev \
+ #
#
&& \
#
diff --git a/python/servo/bootstrap.py b/python/servo/bootstrap.py
index a3367f2e225..9fefd495008 100644
--- a/python/servo/bootstrap.py
+++ b/python/servo/bootstrap.py
@@ -113,7 +113,7 @@ def linux(context, force=False):
'build-essential', 'cmake', 'python-pip',
'libbz2-dev', 'libosmesa6-dev', 'libxmu6', 'libxmu-dev', 'libglu1-mesa-dev',
'libgles2-mesa-dev', 'libegl1-mesa-dev', 'libdbus-1-dev', 'libharfbuzz-dev',
- 'ccache', 'clang', 'autoconf2.13']
+ 'ccache', 'clang', 'autoconf2.13', "libunwind-dev"]
pkgs_dnf = ['libtool', 'gcc-c++', 'libXi-devel', 'freetype-devel',
'mesa-libGL-devel', 'mesa-libEGL-devel', 'glib2-devel', 'libX11-devel',
'libXrandr-devel', 'gperf', 'fontconfig-devel', 'cabextract', 'ttmkfdir',