1use std::convert::TryInto;
4use std::os::raw::c_int;
5use std::time::SystemTime;
6
7use nix::sys::signal;
8use once_cell::sync::Lazy;
9use parking_lot::RwLock;
10use smallvec::SmallVec;
11
12#[cfg(any(
13 target_arch = "x86_64",
14 target_arch = "aarch64",
15 target_arch = "riscv64",
16 target_arch = "loongarch64"
17))]
18use findshlibs::{Segment, SharedLibrary, TargetSharedLibrary};
19
20use crate::backtrace::{Trace, TraceImpl};
21use crate::collector::Collector;
22use crate::error::{Error, Result};
23use crate::frames::UnresolvedFrames;
24use crate::report::ReportBuilder;
25use crate::timer::Timer;
26use crate::{MAX_DEPTH, MAX_THREAD_NAME};
27
28pub(crate) static PROFILER: Lazy<RwLock<Result<Profiler>>> =
29 Lazy::new(|| RwLock::new(Profiler::new()));
30
31pub struct Profiler {
32 pub(crate) data: Collector<UnresolvedFrames>,
33 sample_counter: i32,
34
35 running: bool,
36
37 #[cfg(any(
38 target_arch = "x86_64",
39 target_arch = "aarch64",
40 target_arch = "riscv64",
41 target_arch = "loongarch64"
42 ))]
43 blocklist_segments: Vec<(usize, usize)>,
44}
45
46#[derive(Clone)]
47pub struct ProfilerGuardBuilder {
48 frequency: c_int,
49 #[cfg(any(
50 target_arch = "x86_64",
51 target_arch = "aarch64",
52 target_arch = "riscv64",
53 target_arch = "loongarch64"
54 ))]
55 blocklist_segments: Vec<(usize, usize)>,
56}
57
58impl Default for ProfilerGuardBuilder {
59 fn default() -> ProfilerGuardBuilder {
60 ProfilerGuardBuilder {
61 frequency: 99,
62
63 #[cfg(any(
64 target_arch = "x86_64",
65 target_arch = "aarch64",
66 target_arch = "riscv64",
67 target_arch = "loongarch64"
68 ))]
69 blocklist_segments: Vec::new(),
70 }
71 }
72}
73
74impl ProfilerGuardBuilder {
75 pub fn frequency(self, frequency: c_int) -> Self {
76 Self { frequency, ..self }
77 }
78
79 #[cfg(any(
80 target_arch = "x86_64",
81 target_arch = "aarch64",
82 target_arch = "riscv64",
83 target_arch = "loongarch64"
84 ))]
85 pub fn blocklist<T: AsRef<str>>(self, blocklist: &[T]) -> Self {
86 let blocklist_segments = {
87 let mut segments = Vec::new();
88 TargetSharedLibrary::each(|shlib| {
89 let in_blocklist = match shlib.name().to_str() {
90 Some(name) => {
91 let mut in_blocklist = false;
92 for blocked_name in blocklist.iter() {
93 if name.contains(blocked_name.as_ref()) {
94 in_blocklist = true;
95 }
96 }
97
98 in_blocklist
99 }
100
101 None => false,
102 };
103 if in_blocklist {
104 for seg in shlib.segments() {
105 let avam = seg.actual_virtual_memory_address(shlib);
106 let start = avam.0;
107 let end = start + seg.len();
108 segments.push((start, end));
109 }
110 }
111 });
112 segments
113 };
114
115 Self {
116 blocklist_segments,
117 ..self
118 }
119 }
120 pub fn build(self) -> Result<ProfilerGuard<'static>> {
121 trigger_lazy();
122
123 match PROFILER.write().as_mut() {
124 Err(err) => {
125 log::error!("Error in creating profiler: {}", err);
126 Err(Error::CreatingError)
127 }
128 Ok(profiler) => {
129 #[cfg(any(
130 target_arch = "x86_64",
131 target_arch = "aarch64",
132 target_arch = "riscv64",
133 target_arch = "loongarch64"
134 ))]
135 {
136 profiler.blocklist_segments = self.blocklist_segments;
137 }
138
139 match profiler.start() {
140 Ok(()) => Ok(ProfilerGuard::<'static> {
141 profiler: &PROFILER,
142 timer: Some(Timer::new(self.frequency)),
143 }),
144 Err(err) => Err(err),
145 }
146 }
147 }
148 }
149}
150
151pub struct ProfilerGuard<'a> {
153 profiler: &'a RwLock<Result<Profiler>>,
154 timer: Option<Timer>,
155}
156
157fn trigger_lazy() {
158 let _ = backtrace::Backtrace::new();
159 let _profiler = PROFILER.read();
160}
161
162impl ProfilerGuard<'_> {
163 pub fn new(frequency: c_int) -> Result<ProfilerGuard<'static>> {
165 ProfilerGuardBuilder::default().frequency(frequency).build()
166 }
167
168 pub fn report(&self) -> ReportBuilder {
170 ReportBuilder::new(
171 self.profiler,
172 self.timer.as_ref().map(Timer::timing).unwrap_or_default(),
173 )
174 }
175}
176
177impl<'a> Drop for ProfilerGuard<'a> {
178 fn drop(&mut self) {
179 drop(self.timer.take());
180
181 match self.profiler.write().as_mut() {
182 Err(_) => {}
183 Ok(profiler) => match profiler.stop() {
184 Ok(()) => {}
185 Err(err) => log::error!("error while stopping profiler {}", err),
186 },
187 }
188 }
189}
190
191fn write_thread_name_fallback(current_thread: libc::pthread_t, name: &mut [libc::c_char]) {
192 let mut len = 0;
193 let mut base = 1;
194
195 while current_thread as u128 > base && len < MAX_THREAD_NAME {
196 base *= 10;
197 len += 1;
198 }
199
200 let mut index = 0;
201 while index < len && base > 1 {
202 base /= 10;
203
204 name[index] = match (48 + (current_thread as u128 / base) % 10).try_into() {
205 Ok(digit) => digit,
206 Err(_) => {
207 log::error!("fail to convert thread_id to string");
208 0
209 }
210 };
211
212 index += 1;
213 }
214}
215
216#[cfg(not(all(any(target_os = "linux", target_os = "macos"), target_env = "gnu")))]
217fn write_thread_name(current_thread: libc::pthread_t, name: &mut [libc::c_char]) {
218 write_thread_name_fallback(current_thread, name);
219}
220
221#[cfg(all(any(target_os = "linux", target_os = "macos"), target_env = "gnu"))]
222fn write_thread_name(current_thread: libc::pthread_t, name: &mut [libc::c_char]) {
223 let name_ptr = name as *mut [libc::c_char] as *mut libc::c_char;
224 let ret = unsafe { libc::pthread_getname_np(current_thread, name_ptr, MAX_THREAD_NAME) };
225
226 if ret != 0 {
227 write_thread_name_fallback(current_thread, name);
228 }
229}
230
231struct ErrnoProtector(libc::c_int);
232
233impl ErrnoProtector {
234 fn new() -> Self {
235 unsafe {
236 #[cfg(target_os = "android")]
237 {
238 let errno = *libc::__errno();
239 Self(errno)
240 }
241 #[cfg(target_os = "linux")]
242 {
243 let errno = *libc::__errno_location();
244 Self(errno)
245 }
246 #[cfg(any(target_os = "macos", target_os = "freebsd"))]
247 {
248 let errno = *libc::__error();
249 Self(errno)
250 }
251 }
252 }
253}
254
255impl Drop for ErrnoProtector {
256 fn drop(&mut self) {
257 unsafe {
258 #[cfg(target_os = "android")]
259 {
260 *libc::__errno() = self.0;
261 }
262 #[cfg(target_os = "linux")]
263 {
264 *libc::__errno_location() = self.0;
265 }
266 #[cfg(any(target_os = "macos", target_os = "freebsd"))]
267 {
268 *libc::__error() = self.0;
269 }
270 }
271 }
272}
273
274#[no_mangle]
275#[cfg_attr(
276 not(all(any(
277 target_arch = "x86_64",
278 target_arch = "aarch64",
279 target_arch = "riscv64",
280 target_arch = "loongarch64"
281 ))),
282 allow(unused_variables)
283)]
284#[allow(clippy::unnecessary_cast)]
285extern "C" fn perf_signal_handler(
286 _signal: c_int,
287 _siginfo: *mut libc::siginfo_t,
288 ucontext: *mut libc::c_void,
289) {
290 let _errno = ErrnoProtector::new();
291
292 if let Some(mut guard) = PROFILER.try_write() {
293 if let Ok(profiler) = guard.as_mut() {
294 #[cfg(any(
295 target_arch = "x86_64",
296 target_arch = "aarch64",
297 target_arch = "riscv64",
298 target_arch = "loongarch64"
299 ))]
300 if !ucontext.is_null() {
301 let ucontext: *mut libc::ucontext_t = ucontext as *mut libc::ucontext_t;
302
303 #[cfg(all(target_arch = "x86_64", target_os = "linux"))]
304 let addr =
305 unsafe { (*ucontext).uc_mcontext.gregs[libc::REG_RIP as usize] as usize };
306
307 #[cfg(all(target_arch = "x86_64", target_os = "freebsd"))]
308 let addr = unsafe { (*ucontext).uc_mcontext.mc_rip as usize };
309
310 #[cfg(all(target_arch = "x86_64", target_os = "macos"))]
311 let addr = unsafe {
312 let mcontext = (*ucontext).uc_mcontext;
313 if mcontext.is_null() {
314 0
315 } else {
316 (*mcontext).__ss.__rip as usize
317 }
318 };
319
320 #[cfg(all(
321 target_arch = "aarch64",
322 any(target_os = "android", target_os = "linux")
323 ))]
324 let addr = unsafe { (*ucontext).uc_mcontext.pc as usize };
325
326 #[cfg(all(target_arch = "aarch64", target_os = "freebsd"))]
327 let addr = unsafe { (*ucontext).mc_gpregs.gp_elr as usize };
328
329 #[cfg(all(target_arch = "aarch64", target_os = "macos"))]
330 let addr = unsafe {
331 let mcontext = (*ucontext).uc_mcontext;
332 if mcontext.is_null() {
333 0
334 } else {
335 (*mcontext).__ss.__pc as usize
336 }
337 };
338
339 #[cfg(all(target_arch = "riscv64", target_os = "linux"))]
340 let addr = unsafe { (*ucontext).uc_mcontext.__gregs[libc::REG_PC] as usize };
341
342 #[cfg(all(target_arch = "loongarch64", target_os = "linux"))]
343 let addr = unsafe { (*ucontext).uc_mcontext.__pc as usize };
344
345 if profiler.is_blocklisted(addr) {
346 return;
347 }
348 }
349
350 let mut bt: SmallVec<[<TraceImpl as Trace>::Frame; MAX_DEPTH]> =
351 SmallVec::with_capacity(MAX_DEPTH);
352 let mut index = 0;
353
354 let sample_timestamp: SystemTime = SystemTime::now();
355 TraceImpl::trace(ucontext, |frame| {
356 #[cfg(feature = "frame-pointer")]
357 {
358 let ip = crate::backtrace::Frame::ip(frame);
359 if profiler.is_blocklisted(ip) {
360 return false;
361 }
362 }
363
364 if index < MAX_DEPTH {
365 bt.push(frame.clone());
366 index += 1;
367 true
368 } else {
369 false
370 }
371 });
372
373 let current_thread = unsafe { libc::pthread_self() };
374 let mut name = [0; MAX_THREAD_NAME];
375 let name_ptr = &mut name as *mut [libc::c_char] as *mut libc::c_char;
376
377 write_thread_name(current_thread, &mut name);
378
379 let name = unsafe { std::ffi::CStr::from_ptr(name_ptr) };
380 profiler.sample(bt, name.to_bytes(), current_thread as u64, sample_timestamp);
381 }
382 }
383}
384
385impl Profiler {
386 fn new() -> Result<Self> {
387 Ok(Profiler {
388 data: Collector::new()?,
389 sample_counter: 0,
390 running: false,
391
392 #[cfg(any(
393 target_arch = "x86_64",
394 target_arch = "aarch64",
395 target_arch = "riscv64",
396 target_arch = "loongarch64"
397 ))]
398 blocklist_segments: Vec::new(),
399 })
400 }
401
402 #[cfg(any(
403 target_arch = "x86_64",
404 target_arch = "aarch64",
405 target_arch = "riscv64",
406 target_arch = "loongarch64"
407 ))]
408 fn is_blocklisted(&self, addr: usize) -> bool {
409 for libs in &self.blocklist_segments {
410 if addr > libs.0 && addr < libs.1 {
411 return true;
412 }
413 }
414 false
415 }
416}
417
418impl Profiler {
419 pub fn start(&mut self) -> Result<()> {
420 log::info!("starting cpu profiler");
421 if self.running {
422 Err(Error::Running)
423 } else {
424 self.register_signal_handler()?;
425 self.running = true;
426
427 Ok(())
428 }
429 }
430
431 fn init(&mut self) -> Result<()> {
432 self.sample_counter = 0;
433 self.data = Collector::new()?;
434 self.running = false;
435
436 Ok(())
437 }
438
439 pub fn stop(&mut self) -> Result<()> {
440 log::info!("stopping cpu profiler");
441 if self.running {
442 self.unregister_signal_handler()?;
443 self.init()?;
444
445 Ok(())
446 } else {
447 Err(Error::NotRunning)
448 }
449 }
450
451 fn register_signal_handler(&self) -> Result<()> {
452 let handler = signal::SigHandler::SigAction(perf_signal_handler);
453 let sigaction = signal::SigAction::new(
454 handler,
455 signal::SaFlags::SA_SIGINFO | signal::SaFlags::SA_RESTART,
458 signal::SigSet::empty(),
459 );
460 unsafe { signal::sigaction(signal::SIGPROF, &sigaction) }?;
461
462 Ok(())
463 }
464
465 fn unregister_signal_handler(&self) -> Result<()> {
466 let handler = signal::SigHandler::SigIgn;
467 unsafe { signal::signal(signal::SIGPROF, handler) }?;
468
469 Ok(())
470 }
471
472 pub fn sample(
474 &mut self,
475 backtrace: SmallVec<[<TraceImpl as Trace>::Frame; MAX_DEPTH]>,
476 thread_name: &[u8],
477 thread_id: u64,
478 sample_timestamp: SystemTime,
479 ) {
480 let frames = UnresolvedFrames::new(backtrace, thread_name, thread_id, sample_timestamp);
481 self.sample_counter += 1;
482
483 if let Ok(()) = self.data.add(frames, 1) {}
484 }
485}