diff options
author | bors-servo <lbergstrom+bors@mozilla.com> | 2019-03-29 23:12:40 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-03-29 23:12:40 -0400 |
commit | 41feb83c10dd8c37b5e3e45b82f670b18ee23243 (patch) | |
tree | 9512deabfa946e075368d3f1fd02b04a4d16df8f | |
parent | 4cf39a696d1c98f1b88a65c1bd955baa52f1997e (diff) | |
parent | 6c1bf6a591f554a87b91a3d91f7a1c20536f0d71 (diff) | |
download | servo-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.yml | 2 | ||||
-rw-r--r-- | Cargo.lock | 13 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | components/background_hang_monitor/Cargo.toml | 7 | ||||
-rw-r--r-- | components/background_hang_monitor/background_hang_monitor.rs | 7 | ||||
-rw-r--r-- | components/background_hang_monitor/lib.rs | 5 | ||||
-rw-r--r-- | components/background_hang_monitor/sampler.rs | 16 | ||||
-rw-r--r-- | components/background_hang_monitor/sampler_linux.rs | 273 | ||||
-rw-r--r-- | components/background_hang_monitor/tests/hang_monitor_tests.rs | 16 | ||||
-rw-r--r-- | etc/taskcluster/docker/build.dockerfile | 3 | ||||
-rw-r--r-- | python/servo/bootstrap.py | 2 |
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', |