Skip to content

Commit

Permalink
add linux sampler
Browse files Browse the repository at this point in the history
  • Loading branch information
gterzian authored and jdm committed Mar 30, 2019
1 parent 0cb87cc commit 7bc2920
Show file tree
Hide file tree
Showing 11 changed files with 263 additions and 9 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -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
```

Expand Down
7 changes: 7 additions & 0 deletions components/background_hang_monitor/Cargo.toml
Expand Up @@ -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"
Expand Up @@ -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,
Expand Down
5 changes: 4 additions & 1 deletion components/background_hang_monitor/lib.rs
Expand Up @@ -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;
Expand Down
16 changes: 16 additions & 0 deletions components/background_hang_monitor/sampler.rs
Expand Up @@ -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;
Expand Down
199 changes: 196 additions & 3 deletions components/background_hang_monitor/sampler_linux.rs
Expand Up @@ -2,21 +2,136 @@
* 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::thread;

static mut SHARED_STATE: SharedState = SharedState {
msg2: None,
msg3: None,
msg4: None,
context: None,
};

type MonitoredThreadId = libc::pid_t;

struct SharedState {
// "msg1" is the signal.
msg2: Option<PosixSemaphore>,
msg3: Option<PosixSemaphore>,
msg4: Option<PosixSemaphore>,
context: Option<libc::ucontext_t>,
}

fn clear_shared_state() {
unsafe {
SHARED_STATE.msg2 = None;
SHARED_STATE.msg3 = None;
SHARED_STATE.msg4 = None;
SHARED_STATE.context = None;
}
}

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 = None;
}
}

type MonitoredThreadId = libc::pthread_t;
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())
}
}

/// 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,
})
}
}

Expand All @@ -28,8 +143,86 @@ 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 results = unsafe { callback(&mut SHARED_STATE.context.expect("valid context")) };

// 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(())
}
}

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.
let context: libc::ucontext_t = *(ctx as *mut libc::ucontext_t);
SHARED_STATE.context = Some(context);
// 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);
}
}
16 changes: 15 additions & 1 deletion components/background_hang_monitor/tests/hang_monitor_tests.rs
Expand Up @@ -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;

Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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");
Expand Down

0 comments on commit 7bc2920

Please sign in to comment.