diff --git a/Cargo.toml b/Cargo.toml index 1c113036..ab625380 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ backtrace = { version = "0.3" } once_cell = "1.9" libc = "^0.2.66" log = "0.4" -nix = { version = "0.24", default-features = false, features = ["signal", "fs"] } + parking_lot = "0.12" tempfile = "3.1" thiserror = "1.0" @@ -37,7 +37,15 @@ inferno = { version = "0.11", default-features = false, features = ["nameattr"], prost = { version = "0.10", optional = true } prost-derive = { version = "0.10", optional = true } protobuf = { version = "2.0", optional = true } -criterion = {version = "0.3", optional = true} +criterion = { version = "0.3", optional = true } + +[target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies] +nix = { version = "0.24", default-features = false, features = [ + "signal", + "fs", +] } + +[target.'cfg(target_os = "windows")'.dependencies] [dependencies.symbolic-demangle] version = "9.0" diff --git a/benches/addr_validate.rs b/benches/addr_validate.rs index 4154f33f..4c4c1b1a 100644 --- a/benches/addr_validate.rs +++ b/benches/addr_validate.rs @@ -1,7 +1,7 @@ // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. use criterion::{criterion_group, criterion_main, Criterion}; -use pprof::validate; +use pprof::addr_validate; fn bench_validate_addr(c: &mut Criterion) { c.bench_function("validate stack addr", |b| { @@ -9,7 +9,7 @@ fn bench_validate_addr(c: &mut Criterion) { b.iter(|| { stack_addrs.iter().for_each(|item| { - validate(item as *const _ as *const libc::c_void); + addr_validate(item as *const _ as *const libc::c_void); }) }) }); @@ -19,7 +19,7 @@ fn bench_validate_addr(c: &mut Criterion) { b.iter(|| { heap_addrs.iter().for_each(|item| { - validate(item as *const _ as *const libc::c_void); + addr_validate(item as *const _ as *const libc::c_void); }) }) }); diff --git a/src/backtrace/mod.rs b/src/backtrace.rs similarity index 65% rename from src/backtrace/mod.rs rename to src/backtrace.rs index 44cd3214..59a569b7 100644 --- a/src/backtrace/mod.rs +++ b/src/backtrace.rs @@ -3,6 +3,8 @@ use libc::c_void; use std::path::PathBuf; +pub use crate::platform::TraceImpl; + pub trait Symbol: Sized { fn name(&self) -> Option>; fn addr(&self) -> Option<*mut c_void>; @@ -43,25 +45,3 @@ pub trait Trace { where Self: Sized; } - -#[cfg(not(all( - any(target_arch = "x86_64", target_arch = "aarch64"), - feature = "frame-pointer" -)))] -mod backtrace_rs; -#[cfg(not(all( - any(target_arch = "x86_64", target_arch = "aarch64"), - feature = "frame-pointer" -)))] -pub use backtrace_rs::Trace as TraceImpl; - -#[cfg(all( - any(target_arch = "x86_64", target_arch = "aarch64"), - feature = "frame-pointer" -))] -pub mod frame_pointer; -#[cfg(all( - any(target_arch = "x86_64", target_arch = "aarch64"), - feature = "frame-pointer" -))] -pub use frame_pointer::Trace as TraceImpl; diff --git a/src/error.rs b/src/error.rs index 6d5747ea..d487cf4d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,9 +1,16 @@ // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. +// TODO Windows error is not finished #[derive(Debug, thiserror::Error)] pub enum Error { + #[cfg(target_os = "windows")] #[error("{0}")] - NixError(#[from] nix::Error), + OsError(i32), + + #[cfg(any(target_os = "linux", target_os = "macos"))] + #[error("{0}")] + OsError(#[from] nix::Error), + #[error("{0}")] IoError(#[from] std::io::Error), #[error("create profiler error")] diff --git a/src/lib.rs b/src/lib.rs index ed87e566..6be09597 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,7 +45,7 @@ pub const MAX_DEPTH: usize = 32; /// Define the MAX supported thread name length. TODO: make this variable mutable. pub const MAX_THREAD_NAME: usize = 16; -mod addr_validate; +mod platform; mod backtrace; mod collector; @@ -54,13 +54,14 @@ mod frames; mod profiler; mod report; mod timer; +mod validator; -pub use self::addr_validate::validate; pub use self::collector::{Collector, HashCounter}; pub use self::error::{Error, Result}; pub use self::frames::{Frames, Symbol}; pub use self::profiler::{ProfilerGuard, ProfilerGuardBuilder}; pub use self::report::{Report, ReportBuilder, UnresolvedReport}; +pub use self::validator::addr_validate; #[cfg(feature = "flamegraph")] pub use inferno::flamegraph; diff --git a/src/backtrace/backtrace_rs.rs b/src/platform/backtrace_rs.rs similarity index 85% rename from src/backtrace/backtrace_rs.rs rename to src/platform/backtrace_rs.rs index 1b7e3663..b2f18049 100644 --- a/src/backtrace/backtrace_rs.rs +++ b/src/platform/backtrace_rs.rs @@ -1,4 +1,4 @@ -impl super::Frame for backtrace::Frame { +impl crate::backtrace::Frame for backtrace::Frame { type S = backtrace::Symbol; fn ip(&self) -> usize { @@ -16,7 +16,7 @@ impl super::Frame for backtrace::Frame { pub struct Trace {} -impl super::Trace for Trace { +impl crate::backtrace::Trace for Trace { type Frame = backtrace::Frame; fn trace bool>(_: *mut libc::c_void, cb: F) { diff --git a/src/platform/mod.rs b/src/platform/mod.rs new file mode 100644 index 00000000..380b7fba --- /dev/null +++ b/src/platform/mod.rs @@ -0,0 +1,49 @@ +#[cfg(any(target_os = "linux", target_os = "macos"))] +mod nix_impl { + mod addr_validate; + mod profiler; + mod timer; + + #[cfg(all( + any(target_arch = "x86_64", target_arch = "aarch64"), + feature = "frame-pointer", + ))] + mod frame_pointer; + #[cfg(all( + any(target_arch = "x86_64", target_arch = "aarch64"), + feature = "frame-pointer", + ))] + pub use frame_pointer::Trace as TraceImpl; + + #[cfg(not(all( + any(target_arch = "x86_64", target_arch = "aarch64"), + feature = "frame-pointer", + )))] + #[path = "../backtrace_rs.rs"] + mod backtrace_rs; + #[cfg(not(all( + any(target_arch = "x86_64", target_arch = "aarch64"), + feature = "frame-pointer", + )))] + pub use backtrace_rs::Trace as TraceImpl; +} + +#[cfg(target_os = "windows")] +mod windows_impl { + mod addr_validate; + mod profiler; + mod timer; + + #[cfg(feature = "frame-pointer")] + std::compile_error!("frame-pointer feature is currently not supported on windows."); + + #[path = "../backtrace_rs.rs"] + mod backtrace_rs; + pub use backtrace_rs::Trace as TraceImpl; +} + +#[cfg(any(target_os = "linux", target_os = "macos"))] +pub use nix_impl::*; + +#[cfg(target_os = "windows")] +pub use windows_impl::*; diff --git a/src/addr_validate.rs b/src/platform/nix_impl/addr_validate.rs similarity index 53% rename from src/addr_validate.rs rename to src/platform/nix_impl/addr_validate.rs index 9e9a34c6..60f66d11 100644 --- a/src/addr_validate.rs +++ b/src/platform/nix_impl/addr_validate.rs @@ -5,10 +5,50 @@ use nix::{ unistd::{close, read, write}, }; +use crate::validator::{Validator, ValidatorImpl}; + thread_local! { static MEM_VALIDATE_PIPE: RefCell<[i32; 2]> = RefCell::new([-1, -1]); } +impl ValidatorImpl for Validator { + fn addr_validate(addr: *const libc::c_void) -> bool { + const CHECK_LENGTH: usize = 2 * size_of::<*const libc::c_void>() / size_of::(); + + // read data in the pipe + let valid_read = MEM_VALIDATE_PIPE.with(|pipes| { + let pipes = pipes.borrow(); + loop { + let mut buf = [0u8; CHECK_LENGTH]; + + match read(pipes[0], &mut buf) { + Ok(bytes) => break bytes > 0, + Err(_err @ Errno::EINTR) => continue, + Err(_err @ Errno::EAGAIN) => break true, + Err(_) => break false, + } + } + }); + + if !valid_read && open_pipe().is_err() { + return false; + } + + MEM_VALIDATE_PIPE.with(|pipes| { + let pipes = pipes.borrow(); + loop { + let buf = unsafe { std::slice::from_raw_parts(addr as *const u8, CHECK_LENGTH) }; + + match write(pipes[1], buf) { + Ok(bytes) => break bytes > 0, + Err(_err @ Errno::EINTR) => continue, + Err(_) => break false, + } + } + }) + } +} + #[inline] #[cfg(target_os = "linux")] fn create_pipe() -> nix::Result<(i32, i32)> { @@ -58,51 +98,15 @@ fn open_pipe() -> nix::Result<()> { }) } -pub fn validate(addr: *const libc::c_void) -> bool { - const CHECK_LENGTH: usize = 2 * size_of::<*const libc::c_void>() / size_of::(); - - // read data in the pipe - let valid_read = MEM_VALIDATE_PIPE.with(|pipes| { - let pipes = pipes.borrow(); - loop { - let mut buf = [0u8; CHECK_LENGTH]; - - match read(pipes[0], &mut buf) { - Ok(bytes) => break bytes > 0, - Err(_err @ Errno::EINTR) => continue, - Err(_err @ Errno::EAGAIN) => break true, - Err(_) => break false, - } - } - }); - - if !valid_read && open_pipe().is_err() { - return false; - } - - MEM_VALIDATE_PIPE.with(|pipes| { - let pipes = pipes.borrow(); - loop { - let buf = unsafe { std::slice::from_raw_parts(addr as *const u8, CHECK_LENGTH) }; - - match write(pipes[1], buf) { - Ok(bytes) => break bytes > 0, - Err(_err @ Errno::EINTR) => continue, - Err(_) => break false, - } - } - }) -} - #[cfg(test)] mod test { - use super::*; + use crate::addr_validate; #[test] fn validate_stack() { let i = 0; - assert!(validate(&i as *const _ as *const libc::c_void)); + assert!(addr_validate(&i as *const _ as *const libc::c_void)); } #[test] @@ -110,13 +114,13 @@ mod test { let vec = vec![0; 1000]; for i in vec.iter() { - assert!(validate(i as *const _ as *const libc::c_void)); + assert!(addr_validate(i as *const _ as *const libc::c_void)); } } #[test] fn failed_validate() { - assert!(!validate(std::ptr::null::())); - assert!(!validate(-1_i32 as usize as *const libc::c_void)) + assert!(!addr_validate(std::ptr::null::())); + assert!(!addr_validate(-1_i32 as usize as *const libc::c_void)) } } diff --git a/src/backtrace/frame_pointer.rs b/src/platform/nix_impl/frame_pointer.rs similarity index 94% rename from src/backtrace/frame_pointer.rs rename to src/platform/nix_impl/frame_pointer.rs index 1810e4a0..4da575ad 100644 --- a/src/backtrace/frame_pointer.rs +++ b/src/platform/nix_impl/frame_pointer.rs @@ -4,7 +4,7 @@ use std::ptr::null_mut; use libc::c_void; -use crate::addr_validate::validate; +use crate::addr_validate; #[derive(Clone, Debug)] pub struct Frame { @@ -16,7 +16,7 @@ extern "C" { } -impl super::Frame for Frame { +impl crate::backtrace::Frame for Frame { type S = backtrace::Symbol; fn ip(&self) -> usize { @@ -37,7 +37,7 @@ impl super::Frame for Frame { } pub struct Trace {} -impl super::Trace for Trace { +impl crate::backtrace::Trace for Trace { type Frame = Frame; fn trace bool>(ucontext: *mut libc::c_void, mut cb: F) { @@ -91,7 +91,7 @@ impl super::Trace for Trace { break; } - if !validate(frame_pointer as *const libc::c_void) { + if !addr_validate(frame_pointer as *const libc::c_void) { break; } last_frame_pointer = frame_pointer; diff --git a/src/platform/nix_impl/profiler.rs b/src/platform/nix_impl/profiler.rs new file mode 100644 index 00000000..0c5df5e4 --- /dev/null +++ b/src/platform/nix_impl/profiler.rs @@ -0,0 +1,272 @@ +use std::os::raw::c_int; +use std::time::SystemTime; + +use smallvec::SmallVec; + +use nix::sys::signal; + +use crate::backtrace::{Frame, Trace, TraceImpl}; +use crate::error::Result; +use crate::profiler::{write_thread_name_fallback, Profiler, ProfilerImpl, PROFILER}; +use crate::{MAX_DEPTH, MAX_THREAD_NAME}; + +impl ProfilerImpl for Profiler { + fn register(&mut self) -> Result<()> { + let handler = signal::SigHandler::SigAction(perf_signal_handler); + let sigaction = signal::SigAction::new( + handler, + signal::SaFlags::SA_SIGINFO, + signal::SigSet::empty(), + ); + unsafe { signal::sigaction(signal::SIGPROF, &sigaction) }?; + + Ok(()) + } + fn unregister(&mut self) -> Result<()> { + let handler = signal::SigHandler::SigIgn; + unsafe { signal::signal(signal::SIGPROF, handler) }?; + + Ok(()) + } +} + +#[cfg(not(all(any(target_os = "linux", target_os = "macos"), target_env = "gnu")))] +fn write_thread_name(current_thread: libc::pthread_t, name: &mut [libc::c_char]) { + write_thread_name_fallback(current_thread as usize as u128, name); +} + +#[cfg(all(any(target_os = "linux", target_os = "macos"), target_env = "gnu"))] +fn write_thread_name(current_thread: libc::pthread_t, name: &mut [libc::c_char]) { + let name_ptr = name as *mut [libc::c_char] as *mut libc::c_char; + let ret = unsafe { libc::pthread_getname_np(current_thread, name_ptr, MAX_THREAD_NAME) }; + + if ret != 0 { + write_thread_name_fallback(current_thread as usize as u128, name); + } +} + +struct ErrnoProtector(libc::c_int); + +impl ErrnoProtector { + fn new() -> Self { + unsafe { + #[cfg(target_os = "linux")] + { + let errno = *libc::__errno_location(); + Self(errno) + } + #[cfg(target_os = "macos")] + { + let errno = *libc::__error(); + Self(errno) + } + } + } +} + +impl Drop for ErrnoProtector { + fn drop(&mut self) { + unsafe { + #[cfg(target_os = "linux")] + { + *libc::__errno_location() = self.0; + } + #[cfg(target_os = "macos")] + { + *libc::__error() = self.0; + } + } + } +} + +#[no_mangle] +#[cfg_attr( + not(all(any(target_arch = "x86_64", target_arch = "aarch64"))), + allow(unused_variables) +)] +extern "C" fn perf_signal_handler( + _signal: c_int, + _siginfo: *mut libc::siginfo_t, + ucontext: *mut libc::c_void, +) { + let _errno = ErrnoProtector::new(); + + if let Some(mut guard) = PROFILER.try_write() { + if let Ok(profiler) = guard.as_mut() { + #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] + if !ucontext.is_null() { + let ucontext: *mut libc::ucontext_t = ucontext as *mut libc::ucontext_t; + + #[cfg(all(target_arch = "x86_64", target_os = "linux"))] + let addr = + unsafe { (*ucontext).uc_mcontext.gregs[libc::REG_RIP as usize] as usize }; + + #[cfg(all(target_arch = "x86_64", target_os = "macos"))] + let addr = unsafe { + let mcontext = (*ucontext).uc_mcontext; + if mcontext.is_null() { + 0 + } else { + (*mcontext).__ss.__rip as usize + } + }; + + #[cfg(all(target_arch = "aarch64", target_os = "linux"))] + let addr = unsafe { (*ucontext).uc_mcontext.pc as usize }; + + #[cfg(all(target_arch = "aarch64", target_os = "macos"))] + let addr = unsafe { + let mcontext = (*ucontext).uc_mcontext; + if mcontext.is_null() { + 0 + } else { + (*mcontext).__ss.__pc as usize + } + }; + + if profiler.is_blocklisted(addr) { + return; + } + } + + let mut bt: SmallVec<[::Frame; MAX_DEPTH]> = + SmallVec::with_capacity(MAX_DEPTH); + let mut index = 0; + + let sample_timestamp: SystemTime = SystemTime::now(); + TraceImpl::trace(ucontext, |frame| { + let ip = Frame::ip(frame); + if profiler.is_blocklisted(ip) { + return false; + } + + if index < MAX_DEPTH { + bt.push(frame.clone()); + index += 1; + true + } else { + false + } + }); + + let current_thread = unsafe { libc::pthread_self() }; + let mut name = [0; MAX_THREAD_NAME]; + let name_ptr = &mut name as *mut [libc::c_char] as *mut libc::c_char; + + write_thread_name(current_thread, &mut name); + + let name = unsafe { std::ffi::CStr::from_ptr(name_ptr) }; + profiler.sample(bt, name.to_bytes(), current_thread as u64, sample_timestamp); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::profiler::trigger_lazy; + + use std::cell::RefCell; + use std::ffi::c_void; + use std::ptr::null_mut; + + #[cfg(not(target_env = "gnu"))] + #[allow(clippy::wrong_self_convention)] + #[allow(non_upper_case_globals)] + static mut __malloc_hook: Option *mut c_void> = None; + + extern "C" { + #[cfg(target_env = "gnu")] + static mut __malloc_hook: Option *mut c_void>; + + fn malloc(size: usize) -> *mut c_void; + } + + thread_local! { + static FLAG: RefCell = RefCell::new(false); + } + + extern "C" fn malloc_hook(size: usize) -> *mut c_void { + unsafe { + __malloc_hook = None; + } + + FLAG.with(|flag| { + flag.replace(true); + }); + let p = unsafe { malloc(size) }; + + unsafe { + __malloc_hook = Some(malloc_hook); + } + + p + } + + #[inline(never)] + fn is_prime_number(v: usize, prime_numbers: &[usize]) -> bool { + if v < 10000 { + let r = prime_numbers.binary_search(&v); + return r.is_ok(); + } + + for n in prime_numbers { + if v % n == 0 { + return false; + } + } + + true + } + + #[inline(never)] + fn prepare_prime_numbers() -> Vec { + // bootstrap: Generate a prime table of 0..10000 + let mut prime_number_table: [bool; 10000] = [true; 10000]; + prime_number_table[0] = false; + prime_number_table[1] = false; + for i in 2..10000 { + if prime_number_table[i] { + let mut v = i * 2; + while v < 10000 { + prime_number_table[v] = false; + v += i; + } + } + } + let mut prime_numbers = vec![]; + for (i, item) in prime_number_table.iter().enumerate().skip(2) { + if *item { + prime_numbers.push(i); + } + } + prime_numbers + } + + #[cfg(target_os = "linux")] + #[test] + fn malloc_free() { + trigger_lazy(); + + let prime_numbers = prepare_prime_numbers(); + + let mut _v = 0; + + unsafe { + __malloc_hook = Some(malloc_hook); + } + for i in 2..50000 { + if is_prime_number(i, &prime_numbers) { + _v += 1; + perf_signal_handler(27, null_mut(), null_mut()); + } + } + unsafe { + __malloc_hook = None; + } + + FLAG.with(|flag| { + assert!(!*flag.borrow()); + }); + } +} diff --git a/src/platform/nix_impl/timer.rs b/src/platform/nix_impl/timer.rs new file mode 100644 index 00000000..14e0e42a --- /dev/null +++ b/src/platform/nix_impl/timer.rs @@ -0,0 +1,64 @@ +// Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. + +use crate::timer::{Timer, TimerImpl}; + +use std::os::raw::c_int; +use std::ptr::null_mut; + +extern "C" { + fn setitimer(which: c_int, new_value: *mut Itimerval, old_value: *mut Itimerval) -> c_int; +} + +const ITIMER_PROF: c_int = 2; + +#[repr(C)] +#[derive(Clone)] +struct Timeval { + pub tv_sec: i64, + pub tv_usec: i64, +} +#[repr(C)] +#[derive(Clone)] +struct Itimerval { + pub it_interval: Timeval, + pub it_value: Timeval, +} + +impl TimerImpl for Timer { + fn start(&mut self) { + let interval = 1e6 as i64 / i64::from(self.frequency); + let it_interval = Timeval { + tv_sec: interval / 1e6 as i64, + tv_usec: interval % 1e6 as i64, + }; + let it_value = it_interval.clone(); + + unsafe { + setitimer( + ITIMER_PROF, + &mut Itimerval { + it_interval, + it_value, + }, + null_mut(), + ) + }; + } + fn stop(&mut self) { + let it_interval = Timeval { + tv_sec: 0, + tv_usec: 0, + }; + let it_value = it_interval.clone(); + unsafe { + setitimer( + ITIMER_PROF, + &mut Itimerval { + it_interval, + it_value, + }, + null_mut(), + ) + }; + } +} diff --git a/src/platform/windows_impl/addr_validate.rs b/src/platform/windows_impl/addr_validate.rs new file mode 100644 index 00000000..45edb1c4 --- /dev/null +++ b/src/platform/windows_impl/addr_validate.rs @@ -0,0 +1,7 @@ +use crate::validator::{Validator, ValidatorImpl}; + +impl ValidatorImpl for Validator { + fn addr_validate(_: *const libc::c_void) -> bool { + unimplemented!() + } +} diff --git a/src/platform/windows_impl/profiler.rs b/src/platform/windows_impl/profiler.rs new file mode 100644 index 00000000..35aed48f --- /dev/null +++ b/src/platform/windows_impl/profiler.rs @@ -0,0 +1,11 @@ +use crate::error::Result; +use crate::profiler::{Profiler, ProfilerImpl}; + +impl ProfilerImpl for Profiler { + fn register(&mut self) -> Result<()> { + unimplemented!() + } + fn unregister(&mut self) -> Result<()> { + unimplemented!() + } +} diff --git a/src/platform/windows_impl/timer.rs b/src/platform/windows_impl/timer.rs new file mode 100644 index 00000000..3c6a852c --- /dev/null +++ b/src/platform/windows_impl/timer.rs @@ -0,0 +1,6 @@ +use crate::timer::{Timer, TimerImpl}; + +impl TimerImpl for Timer { + fn start(&mut self) {} + fn stop(&mut self) {} +} diff --git a/src/profiler.rs b/src/profiler.rs index 4bc72ad2..5669d854 100644 --- a/src/profiler.rs +++ b/src/profiler.rs @@ -1,10 +1,8 @@ // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. -use std::convert::TryInto; use std::os::raw::c_int; use std::time::SystemTime; -use nix::sys::signal; use once_cell::sync::Lazy; use parking_lot::RwLock; use smallvec::SmallVec; @@ -12,7 +10,7 @@ use smallvec::SmallVec; #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] use findshlibs::{Segment, SharedLibrary, TargetSharedLibrary}; -use crate::backtrace::{Frame, Trace, TraceImpl}; +use crate::backtrace::{Trace, TraceImpl}; use crate::collector::Collector; use crate::error::{Error, Result}; use crate::frames::UnresolvedFrames; @@ -23,6 +21,35 @@ use crate::{MAX_DEPTH, MAX_THREAD_NAME}; pub(crate) static PROFILER: Lazy>> = Lazy::new(|| RwLock::new(Profiler::new())); +pub fn write_thread_name_fallback(thread: u128, name: &mut [libc::c_char]) { + let mut len = 0; + let mut base = 1; + + while thread > base && len < MAX_THREAD_NAME { + base *= 10; + len += 1; + } + + let mut index = 0; + while index < len && base > 1 { + base /= 10; + + name[index] = match (48 + (thread / base) % 10).try_into() { + Ok(digit) => digit, + Err(_) => { + log::error!("fail to convert thread_id to string"); + 0 + } + }; + + index += 1; + } +} + +pub trait ProfilerImpl { + fn register(&mut self) -> Result<()>; + fn unregister(&mut self) -> Result<()>; +} pub struct Profiler { pub(crate) data: Collector, sample_counter: i32, @@ -124,7 +151,7 @@ pub struct ProfilerGuard<'a> { timer: Option, } -fn trigger_lazy() { +pub(crate) fn trigger_lazy() { let _ = backtrace::Backtrace::new(); let _profiler = PROFILER.read(); } @@ -158,162 +185,6 @@ impl<'a> Drop for ProfilerGuard<'a> { } } -fn write_thread_name_fallback(current_thread: libc::pthread_t, name: &mut [libc::c_char]) { - let mut len = 0; - let mut base = 1; - - while current_thread as u128 > base && len < MAX_THREAD_NAME { - base *= 10; - len += 1; - } - - let mut index = 0; - while index < len && base > 1 { - base /= 10; - - name[index] = match (48 + (current_thread as u128 / base) % 10).try_into() { - Ok(digit) => digit, - Err(_) => { - log::error!("fail to convert thread_id to string"); - 0 - } - }; - - index += 1; - } -} - -#[cfg(not(all(any(target_os = "linux", target_os = "macos"), target_env = "gnu")))] -fn write_thread_name(current_thread: libc::pthread_t, name: &mut [libc::c_char]) { - write_thread_name_fallback(current_thread, name); -} - -#[cfg(all(any(target_os = "linux", target_os = "macos"), target_env = "gnu"))] -fn write_thread_name(current_thread: libc::pthread_t, name: &mut [libc::c_char]) { - let name_ptr = name as *mut [libc::c_char] as *mut libc::c_char; - let ret = unsafe { libc::pthread_getname_np(current_thread, name_ptr, MAX_THREAD_NAME) }; - - if ret != 0 { - write_thread_name_fallback(current_thread, name); - } -} - -struct ErrnoProtector(libc::c_int); - -impl ErrnoProtector { - fn new() -> Self { - unsafe { - #[cfg(target_os = "linux")] - { - let errno = *libc::__errno_location(); - Self(errno) - } - #[cfg(target_os = "macos")] - { - let errno = *libc::__error(); - Self(errno) - } - } - } -} - -impl Drop for ErrnoProtector { - fn drop(&mut self) { - unsafe { - #[cfg(target_os = "linux")] - { - *libc::__errno_location() = self.0; - } - #[cfg(target_os = "macos")] - { - *libc::__error() = self.0; - } - } - } -} - -#[no_mangle] -#[cfg_attr( - not(all(any(target_arch = "x86_64", target_arch = "aarch64"))), - allow(unused_variables) -)] -extern "C" fn perf_signal_handler( - _signal: c_int, - _siginfo: *mut libc::siginfo_t, - ucontext: *mut libc::c_void, -) { - let _errno = ErrnoProtector::new(); - - if let Some(mut guard) = PROFILER.try_write() { - if let Ok(profiler) = guard.as_mut() { - #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] - if !ucontext.is_null() { - let ucontext: *mut libc::ucontext_t = ucontext as *mut libc::ucontext_t; - - #[cfg(all(target_arch = "x86_64", target_os = "linux"))] - let addr = - unsafe { (*ucontext).uc_mcontext.gregs[libc::REG_RIP as usize] as usize }; - - #[cfg(all(target_arch = "x86_64", target_os = "macos"))] - let addr = unsafe { - let mcontext = (*ucontext).uc_mcontext; - if mcontext.is_null() { - 0 - } else { - (*mcontext).__ss.__rip as usize - } - }; - - #[cfg(all(target_arch = "aarch64", target_os = "linux"))] - let addr = unsafe { (*ucontext).uc_mcontext.pc as usize }; - - #[cfg(all(target_arch = "aarch64", target_os = "macos"))] - let addr = unsafe { - let mcontext = (*ucontext).uc_mcontext; - if mcontext.is_null() { - 0 - } else { - (*mcontext).__ss.__pc as usize - } - }; - - if profiler.is_blocklisted(addr) { - return; - } - } - - let mut bt: SmallVec<[::Frame; MAX_DEPTH]> = - SmallVec::with_capacity(MAX_DEPTH); - let mut index = 0; - - let sample_timestamp: SystemTime = SystemTime::now(); - TraceImpl::trace(ucontext, |frame| { - let ip = Frame::ip(frame); - if profiler.is_blocklisted(ip) { - return false; - } - - if index < MAX_DEPTH { - bt.push(frame.clone()); - index += 1; - true - } else { - false - } - }); - - let current_thread = unsafe { libc::pthread_self() }; - let mut name = [0; MAX_THREAD_NAME]; - let name_ptr = &mut name as *mut [libc::c_char] as *mut libc::c_char; - - write_thread_name(current_thread, &mut name); - - let name = unsafe { std::ffi::CStr::from_ptr(name_ptr) }; - profiler.sample(bt, name.to_bytes(), current_thread as u64, sample_timestamp); - } - } -} - impl Profiler { fn new() -> Result { Ok(Profiler { @@ -327,7 +198,7 @@ impl Profiler { } #[cfg(all(any(target_arch = "x86_64", target_arch = "aarch64")))] - fn is_blocklisted(&self, addr: usize) -> bool { + pub(crate) fn is_blocklisted(&self, addr: usize) -> bool { for libs in &self.blocklist_segments { if addr > libs.0 && addr < libs.1 { return true; @@ -343,7 +214,7 @@ impl Profiler { if self.running { Err(Error::Running) } else { - self.register_signal_handler()?; + Self::register(self)?; self.running = true; Ok(()) @@ -361,7 +232,7 @@ impl Profiler { pub fn stop(&mut self) -> Result<()> { log::info!("stopping cpu profiler"); if self.running { - self.unregister_signal_handler()?; + Self::unregister(self)?; self.init()?; Ok(()) @@ -370,25 +241,6 @@ impl Profiler { } } - fn register_signal_handler(&self) -> Result<()> { - let handler = signal::SigHandler::SigAction(perf_signal_handler); - let sigaction = signal::SigAction::new( - handler, - signal::SaFlags::SA_SIGINFO, - signal::SigSet::empty(), - ); - unsafe { signal::sigaction(signal::SIGPROF, &sigaction) }?; - - Ok(()) - } - - fn unregister_signal_handler(&self) -> Result<()> { - let handler = signal::SigHandler::SigIgn; - unsafe { signal::signal(signal::SIGPROF, handler) }?; - - Ok(()) - } - // This function has to be AS-safe pub fn sample( &mut self, @@ -403,113 +255,3 @@ impl Profiler { if let Ok(()) = self.data.add(frames, 1) {} } } - -#[cfg(test)] -#[cfg(target_os = "linux")] -mod tests { - use super::*; - - use std::cell::RefCell; - use std::ffi::c_void; - use std::ptr::null_mut; - - #[cfg(not(target_env = "gnu"))] - #[allow(clippy::wrong_self_convention)] - #[allow(non_upper_case_globals)] - static mut __malloc_hook: Option *mut c_void> = None; - - extern "C" { - #[cfg(target_env = "gnu")] - static mut __malloc_hook: Option *mut c_void>; - - fn malloc(size: usize) -> *mut c_void; - } - - thread_local! { - static FLAG: RefCell = RefCell::new(false); - } - - extern "C" fn malloc_hook(size: usize) -> *mut c_void { - unsafe { - __malloc_hook = None; - } - - FLAG.with(|flag| { - flag.replace(true); - }); - let p = unsafe { malloc(size) }; - - unsafe { - __malloc_hook = Some(malloc_hook); - } - - p - } - - #[inline(never)] - fn is_prime_number(v: usize, prime_numbers: &[usize]) -> bool { - if v < 10000 { - let r = prime_numbers.binary_search(&v); - return r.is_ok(); - } - - for n in prime_numbers { - if v % n == 0 { - return false; - } - } - - true - } - - #[inline(never)] - fn prepare_prime_numbers() -> Vec { - // bootstrap: Generate a prime table of 0..10000 - let mut prime_number_table: [bool; 10000] = [true; 10000]; - prime_number_table[0] = false; - prime_number_table[1] = false; - for i in 2..10000 { - if prime_number_table[i] { - let mut v = i * 2; - while v < 10000 { - prime_number_table[v] = false; - v += i; - } - } - } - let mut prime_numbers = vec![]; - for (i, item) in prime_number_table.iter().enumerate().skip(2) { - if *item { - prime_numbers.push(i); - } - } - prime_numbers - } - - #[cfg(target_os = "linux")] - #[test] - fn malloc_free() { - trigger_lazy(); - - let prime_numbers = prepare_prime_numbers(); - - let mut _v = 0; - - unsafe { - __malloc_hook = Some(malloc_hook); - } - for i in 2..50000 { - if is_prime_number(i, &prime_numbers) { - _v += 1; - perf_signal_handler(27, null_mut(), null_mut()); - } - } - unsafe { - __malloc_hook = None; - } - - FLAG.with(|flag| { - assert!(!*flag.borrow()); - }); - } -} diff --git a/src/report.rs b/src/report.rs index a19b940e..7f479353 100644 --- a/src/report.rs +++ b/src/report.rs @@ -159,7 +159,7 @@ impl Debug for Report { mod flamegraph { use super::*; use inferno::flamegraph; - use std::io::Write; + use std::{fmt::Write as _, io::Write}; impl Report { /// `flamegraph` will write an svg flamegraph into `writer` **only available with `flamegraph` feature** @@ -188,13 +188,13 @@ mod flamegraph { for frame in key.frames.iter().rev() { for symbol in frame.iter().rev() { - line.push_str(&format!("{}", symbol)); + write!(line, "{}", symbol).unwrap(); line.push(';'); } } line.pop().unwrap_or_default(); - line.push_str(&format!(" {}", value)); + write!(line, " {}", value).unwrap(); line }) diff --git a/src/timer.rs b/src/timer.rs index a6bfa1fe..c92f38e9 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -1,29 +1,13 @@ // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. use std::os::raw::c_int; -use std::ptr::null_mut; use std::time::{Duration, Instant, SystemTime}; -#[repr(C)] -#[derive(Clone)] -struct Timeval { - pub tv_sec: i64, - pub tv_usec: i64, -} - -#[repr(C)] -#[derive(Clone)] -struct Itimerval { - pub it_interval: Timeval, - pub it_value: Timeval, +pub trait TimerImpl { + fn start(&mut self); + fn stop(&mut self); } -extern "C" { - fn setitimer(which: c_int, new_value: *mut Itimerval, old_value: *mut Itimerval) -> c_int; -} - -const ITIMER_PROF: c_int = 2; - pub struct Timer { pub frequency: c_int, pub start_time: SystemTime, @@ -32,29 +16,13 @@ pub struct Timer { impl Timer { pub fn new(frequency: c_int) -> Timer { - let interval = 1e6 as i64 / i64::from(frequency); - let it_interval = Timeval { - tv_sec: interval / 1e6 as i64, - tv_usec: interval % 1e6 as i64, - }; - let it_value = it_interval.clone(); - - unsafe { - setitimer( - ITIMER_PROF, - &mut Itimerval { - it_interval, - it_value, - }, - null_mut(), - ) - }; - - Timer { + let mut timer = Timer { frequency, start_time: SystemTime::now(), start_instant: Instant::now(), - } + }; + timer.start(); + timer } /// Returns a `ReportTiming` struct having this timer's frequency and start @@ -67,24 +35,9 @@ impl Timer { } } } - impl Drop for Timer { fn drop(&mut self) { - let it_interval = Timeval { - tv_sec: 0, - tv_usec: 0, - }; - let it_value = it_interval.clone(); - unsafe { - setitimer( - ITIMER_PROF, - &mut Itimerval { - it_interval, - it_value, - }, - null_mut(), - ) - }; + self.stop() } } diff --git a/src/validator.rs b/src/validator.rs new file mode 100644 index 00000000..9b8a196a --- /dev/null +++ b/src/validator.rs @@ -0,0 +1,8 @@ +pub struct Validator {} +pub trait ValidatorImpl { + fn addr_validate(addr: *const libc::c_void) -> bool; +} + +pub fn addr_validate(addr: *const libc::c_void) -> bool { + Validator::addr_validate(addr) +}