pprof/
profiler.rs

1// Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0.
2
3use 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
151/// RAII structure used to stop profiling when dropped. It is the only interface to access profiler.
152pub 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    /// Start profiling with given sample frequency.
164    pub fn new(frequency: c_int) -> Result<ProfilerGuard<'static>> {
165        ProfilerGuardBuilder::default().frequency(frequency).build()
166    }
167
168    /// Generate a report
169    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            // SA_RESTART will only restart a syscall when it's safe to do so,
456            // e.g. when it's a blocking read(2) or write(2). See man 7 signal.
457            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    // This function has to be AS-safe
473    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}