Skip to content

Commit 48498a4

Browse files
committed
refactor(profiling): extract SystemProfiler
The global profiler (e.g. PROFILER) and the Profiler::* functions that worked on it were moved into a new struct SystemProfiler.
1 parent b8f3260 commit 48498a4

File tree

8 files changed

+115
-102
lines changed

8 files changed

+115
-102
lines changed

Diff for: profiling/src/allocation.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::bindings::{
22
self as zend, datadog_php_install_handler, datadog_php_zif_handler,
33
ddog_php_prof_copy_long_into_zval,
44
};
5-
use crate::profiling::Profiler;
5+
use crate::profiling::SystemProfiler;
66
use crate::{PROFILER_NAME, REQUEST_LOCALS};
77
use lazy_static::lazy_static;
88
use libc::{c_char, c_int, c_void, size_t};
@@ -95,7 +95,7 @@ impl AllocationProfilingStats {
9595

9696
self.next_sampling_interval();
9797

98-
if let Some(profiler) = Profiler::get() {
98+
if let Some(profiler) = SystemProfiler::get() {
9999
// Safety: execute_data was provided by the engine, and the profiler doesn't mutate it.
100100
unsafe {
101101
profiler.collect_allocations(

Diff for: profiling/src/exception.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::profiling::Profiler;
1+
use crate::profiling::SystemProfiler;
22
use crate::zend::{self, zend_execute_data, zend_generator, zval, InternalFunctionHandler};
33
use crate::REQUEST_LOCALS;
44
use log::{error, info};
@@ -83,7 +83,7 @@ impl ExceptionProfilingStats {
8383

8484
self.next_sampling_interval();
8585

86-
if let Some(profiler) = Profiler::get() {
86+
if let Some(profiler) = SystemProfiler::get() {
8787
// Safety: execute_data was provided by the engine, and the profiler doesn't mutate it.
8888
unsafe {
8989
profiler.collect_exception(

Diff for: profiling/src/lib.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ use lazy_static::lazy_static;
3333
use libc::c_char;
3434
use log::{debug, error, info, trace, warn};
3535
use once_cell::sync::{Lazy, OnceCell};
36-
use profiling::{LocalRootSpanResourceMessage, Profiler, VmInterrupt};
36+
use profiling::{LocalRootSpanResourceMessage, SystemProfiler, VmInterrupt};
3737
use sapi::Sapi;
3838
use std::borrow::Cow;
3939
use std::cell::RefCell;
@@ -504,7 +504,7 @@ extern "C" fn rinit(_type: c_int, _module_number: c_int) -> ZendResult {
504504
return ZendResult::Success;
505505
}
506506

507-
Profiler::init(system_settings);
507+
SystemProfiler::init(system_settings);
508508

509509
if system_settings.profiling_enabled {
510510
REQUEST_LOCALS.with(|cell| {
@@ -541,7 +541,7 @@ extern "C" fn rinit(_type: c_int, _module_number: c_int) -> ZendResult {
541541
return;
542542
}
543543

544-
if let Some(profiler) = Profiler::get() {
544+
if let Some(profiler) = SystemProfiler::get() {
545545
let interrupt = VmInterrupt {
546546
interrupt_count_ptr: &locals.interrupt_count as *const AtomicU32,
547547
engine_ptr: locals.vm_interrupt_addr,
@@ -597,7 +597,7 @@ extern "C" fn rshutdown(_type: c_int, _module_number: c_int) -> ZendResult {
597597
// wall-time is not expected to ever be disabled, except in testing,
598598
// and we don't need to optimize for that.
599599
if system_settings.profiling_enabled {
600-
if let Some(profiler) = Profiler::get() {
600+
if let Some(profiler) = SystemProfiler::get() {
601601
let interrupt = VmInterrupt {
602602
interrupt_count_ptr: &locals.interrupt_count,
603603
engine_ptr: locals.vm_interrupt_addr,
@@ -807,7 +807,7 @@ extern "C" fn mshutdown(_type: c_int, _module_number: c_int) -> ZendResult {
807807
#[cfg(feature = "exception_profiling")]
808808
exception::exception_profiling_mshutdown();
809809

810-
Profiler::stop(Duration::from_secs(1));
810+
SystemProfiler::stop(Duration::from_secs(1));
811811

812812
ZendResult::Success
813813
}
@@ -841,7 +841,7 @@ extern "C" fn shutdown(_extension: *mut ZendExtension) {
841841
#[cfg(debug_assertions)]
842842
trace!("shutdown({:p})", _extension);
843843

844-
Profiler::shutdown(Duration::from_secs(2));
844+
SystemProfiler::shutdown(Duration::from_secs(2));
845845

846846
// SAFETY: calling in shutdown before zai config is shutdown, and after
847847
// all configuration is done being accessed. Well... in the happy-path,
@@ -870,7 +870,7 @@ fn notify_trace_finished(local_root_span_id: u64, span_type: Cow<str>, resource:
870870
return;
871871
}
872872

873-
if let Some(profiler) = Profiler::get() {
873+
if let Some(profiler) = SystemProfiler::get() {
874874
let message = LocalRootSpanResourceMessage {
875875
local_root_span_id,
876876
resource: resource.into_owned(),

Diff for: profiling/src/pcntl.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::bindings::{
33
datadog_php_install_handler, datadog_php_zif_handler, zend_execute_data, zend_long, zval,
44
InternalFunctionHandler,
55
};
6-
use crate::{config, Profiler};
6+
use crate::{config, SystemProfiler};
77
use ddcommon::cstr;
88
use log::{error, warn};
99
use std::ffi::CStr;
@@ -61,21 +61,21 @@ unsafe fn handle_fork(
6161
// Hold mutexes across the handler. If there are any spurious wakeups by
6262
// the threads while the fork is occurring, they cannot acquire locks
6363
// since this thread holds them, preventing a deadlock situation.
64-
if let Some(profiler) = Profiler::get() {
64+
if let Some(profiler) = SystemProfiler::get() {
6565
let _ = profiler.fork_prepare();
6666
}
6767

6868
match dispatch(handler, execute_data, return_value) {
6969
Ok(ForkId::Parent) => {
70-
if let Some(profiler) = Profiler::get() {
70+
if let Some(profiler) = SystemProfiler::get() {
7171
profiler.post_fork_parent();
7272
}
7373
return;
7474
}
7575
Ok(ForkId::Child) => { /* fallthrough */ }
7676
Err(ForkError::NullRetval) => {
7777
// Skip error message if no profiler.
78-
if Profiler::get().is_some() {
78+
if SystemProfiler::get().is_some() {
7979
error!(
8080
"Failed to read return value of forking function. A crash or deadlock may occur."
8181
);
@@ -85,7 +85,7 @@ unsafe fn handle_fork(
8585

8686
Err(ForkError::ZvalType(r#type)) => {
8787
// Skip error message if no profiler.
88-
if Profiler::get().is_some() {
88+
if SystemProfiler::get().is_some() {
8989
warn!(
9090
"Return type of forking function was unexpected: {type}. A crash or deadlock may occur."
9191
);
@@ -99,7 +99,7 @@ unsafe fn handle_fork(
9999
// 2. Something went wrong, and disable it to be safe.
100100
// And then leak the old profiler. Its drop method is not safe to run in
101101
// these situations.
102-
Profiler::kill();
102+
SystemProfiler::kill();
103103

104104
alloc_prof_rshutdown();
105105

Diff for: profiling/src/profiling/mod.rs

+2-76
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
mod interrupts;
22
mod sample_type_filter;
33
pub mod stack_walking;
4+
mod system_profiler;
45
mod thread_utils;
56
mod uploader;
67

78
pub use interrupts::*;
89
pub use sample_type_filter::*;
910
pub use stack_walking::*;
11+
pub use system_profiler::*;
1012
use uploader::*;
1113

1214
#[cfg(all(php_has_fibers, not(test)))]
@@ -26,12 +28,10 @@ use datadog_profiling::api::{
2628
use datadog_profiling::exporter::Tag;
2729
use datadog_profiling::internal::Profile as InternalProfile;
2830
use log::{debug, info, trace, warn};
29-
use once_cell::sync::OnceCell;
3031
use std::borrow::Cow;
3132
use std::collections::HashMap;
3233
use std::hash::Hash;
3334
use std::intrinsics::transmute;
34-
use std::mem::forget;
3535
use std::num::NonZeroI64;
3636
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering};
3737
use std::sync::{Arc, Barrier};
@@ -58,10 +58,6 @@ pub const NO_TIMESTAMP: i64 = 0;
5858
// magnitude for the capacity.
5959
const UPLOAD_CHANNEL_CAPACITY: usize = 8;
6060

61-
/// The global profiler. Profiler gets made during the first rinit after an
62-
/// minit, and is destroyed on mshutdown.
63-
static mut PROFILER: OnceCell<Profiler> = OnceCell::new();
64-
6561
/// Order this array this way:
6662
/// 1. Always enabled types.
6763
/// 2. On by default types.
@@ -520,21 +516,6 @@ const DDPROF_TIME: &str = "ddprof_time";
520516
const DDPROF_UPLOAD: &str = "ddprof_upload";
521517

522518
impl Profiler {
523-
/// Will initialize the `PROFILER` OnceCell and makes sure that only one thread will do so.
524-
pub fn init(system_settings: &mut SystemSettings) {
525-
// SAFETY: the `get_or_init` access is a thread-safe API, and the
526-
// PROFILER is not being mutated outside single-threaded phases such
527-
// as minit/mshutdown.
528-
unsafe { PROFILER.get_or_init(|| Profiler::new(system_settings)) };
529-
}
530-
531-
pub fn get() -> Option<&'static Profiler> {
532-
// SAFETY: the `get` access is a thread-safe API, and the PROFILER is
533-
// not being mutated outside single-threaded phases such as minit and
534-
// mshutdown.
535-
unsafe { PROFILER.get() }
536-
}
537-
538519
pub fn new(system_settings: &mut SystemSettings) -> Self {
539520
let fork_barrier = Arc::new(Barrier::new(3));
540521
let interrupt_manager = Arc::new(InterruptManager::new());
@@ -644,19 +625,6 @@ impl Profiler {
644625
.map_err(Box::new)
645626
}
646627

647-
/// Begins the shutdown process. To complete it, call [Profiler::shutdown].
648-
/// Note that you must call [Profiler::shutdown] afterwards; it's two
649-
/// parts of the same operation. It's split so you (or other extensions)
650-
/// can do something while the other threads finish up.
651-
pub fn stop(timeout: Duration) {
652-
// SAFETY: the `get_mut` access is a thread-safe API, and the PROFILER
653-
// is not being mutated outside single-threaded phases such as minit
654-
// and mshutdown.
655-
if let Some(profiler) = unsafe { PROFILER.get_mut() } {
656-
profiler.join_and_drop_sender(timeout);
657-
}
658-
}
659-
660628
pub fn join_and_drop_sender(&mut self, timeout: Duration) {
661629
debug!("Stopping profiler.");
662630

@@ -685,20 +653,6 @@ impl Profiler {
685653
std::mem::swap(&mut self.upload_sender, &mut empty_sender);
686654
}
687655

688-
/// Completes the shutdown process; to start it, call [Profiler::stop]
689-
/// before calling [Profiler::shutdown].
690-
/// Note the timeout is per thread, and there may be multiple threads.
691-
///
692-
/// Safety: only safe to be called in `SHUTDOWN`/`MSHUTDOWN` phase
693-
pub fn shutdown(timeout: Duration) {
694-
// SAFETY: the `take` access is a thread-safe API, and the PROFILER is
695-
// not being mutated outside single-threaded phases such as minit and
696-
// mshutdown.
697-
if let Some(profiler) = unsafe { PROFILER.take() } {
698-
profiler.join_collector_and_uploader(timeout);
699-
}
700-
}
701-
702656
pub fn join_collector_and_uploader(self, timeout: Duration) {
703657
if self.should_join.load(Ordering::SeqCst) {
704658
thread_utils::join_timeout(
@@ -718,34 +672,6 @@ impl Profiler {
718672
}
719673
}
720674

721-
/// Throws away the profiler and moves it to uninitialized.
722-
///
723-
/// In a forking situation, the currently active profiler may not be valid
724-
/// because it has join handles and other state shared by other threads,
725-
/// and threads are not copied when the process is forked.
726-
/// Additionally, if we've hit certain other issues like not being able to
727-
/// determine the return type of the pcntl_fork function, we don't know if
728-
/// we're the parent or child.
729-
/// So, we throw away the current profiler and forget it, which avoids
730-
/// running the destructor. Yes, this will leak some memory.
731-
///
732-
/// # Safety
733-
/// Must be called when no other thread is using the PROFILER object. That
734-
/// includes this thread in some kind of recursive manner.
735-
pub unsafe fn kill() {
736-
// SAFETY: see this function's safety conditions.
737-
if let Some(mut profiler) = PROFILER.take() {
738-
// Drop some things to reduce memory.
739-
profiler.interrupt_manager = Arc::new(InterruptManager::new());
740-
profiler.message_sender = crossbeam_channel::bounded(0).0;
741-
profiler.upload_sender = crossbeam_channel::bounded(0).0;
742-
743-
// But we're not 100% sure everything is safe to drop, notably the
744-
// join handles, so we leak the rest.
745-
forget(profiler)
746-
}
747-
}
748-
749675
/// Collect a stack sample with elapsed wall time. Collects CPU time if
750676
/// it's enabled and available.
751677
pub fn collect_time(&self, execute_data: *mut zend_execute_data, interrupt_count: u32) {

Diff for: profiling/src/profiling/system_profiler.rs

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use super::{InterruptManager, Profiler, SystemSettings};
2+
use once_cell::sync::OnceCell;
3+
use std::mem::forget;
4+
use std::sync::Arc;
5+
use std::time::Duration;
6+
7+
/// The global profiler. Profiler gets made during the first rinit after an
8+
/// minit, and is destroyed on mshutdown.
9+
static mut PROFILER: SystemProfiler = SystemProfiler::new();
10+
11+
pub struct SystemProfiler(OnceCell<Profiler>);
12+
13+
impl SystemProfiler {
14+
pub const fn new() -> Self {
15+
SystemProfiler(OnceCell::new())
16+
}
17+
18+
/// Initializes the system profiler safely by one thread.
19+
pub fn init(system_settings: &mut SystemSettings) {
20+
// SAFETY: the `get_or_init` access is a thread-safe API, and the
21+
// PROFILER is not being mutated outside single-threaded phases such
22+
// as minit and mshutdown.
23+
unsafe { PROFILER.0.get_or_init(|| Profiler::new(system_settings)) };
24+
}
25+
26+
pub fn get() -> Option<&'static Profiler> {
27+
// SAFETY: the `get` access is a thread-safe API, and the PROFILER is
28+
// not being mutated outside single-threaded phases such as minit and
29+
// mshutdown.
30+
unsafe { PROFILER.0.get() }
31+
}
32+
33+
/// Begins the shutdown process. To complete it, call [Profiler::shutdown].
34+
/// Note that you must call [Profiler::shutdown] afterward; it's two
35+
/// parts of the same operation. It's split so you (or other extensions)
36+
/// can do something while the other threads finish up.
37+
pub fn stop(timeout: Duration) {
38+
// SAFETY: the `get_mut` access is a thread-safe API, and the PROFILER
39+
// is not being mutated outside single-threaded phases such as minit
40+
// and mshutdown.
41+
if let Some(profiler) = unsafe { PROFILER.0.get_mut() } {
42+
profiler.join_and_drop_sender(timeout);
43+
}
44+
}
45+
46+
/// Completes the shutdown process; to start it, call [Profiler::stop]
47+
/// before calling [Profiler::shutdown].
48+
/// Note the timeout is per thread, and there may be multiple threads.
49+
///
50+
/// Safety: only safe to be called in `SHUTDOWN`/`MSHUTDOWN` phase
51+
pub fn shutdown(timeout: Duration) {
52+
// SAFETY: the `take` access is a thread-safe API, and the PROFILER is
53+
// not being mutated outside single-threaded phases such as minit and
54+
// mshutdown.
55+
if let Some(profiler) = unsafe { PROFILER.0.take() } {
56+
profiler.join_collector_and_uploader(timeout);
57+
}
58+
}
59+
60+
/// Throws away the profiler and moves it to uninitialized.
61+
///
62+
/// In a forking situation, the currently active profiler may not be valid
63+
/// because it has join handles and other state shared by other threads,
64+
/// and threads are not copied when the process is forked.
65+
/// Additionally, if we've hit certain other issues like not being able to
66+
/// determine the return type of the pcntl_fork function, we don't know if
67+
/// we're the parent or child.
68+
/// So, we throw away the current profiler and forget it, which avoids
69+
/// running the destructor. Yes, this will leak some memory.
70+
///
71+
/// # Safety
72+
/// Must be called when no other thread is using the PROFILER object. That
73+
/// includes this thread in some kind of recursive manner.
74+
pub unsafe fn kill() {
75+
// SAFETY: see this function's safety conditions.
76+
if let Some(mut profiler) = PROFILER.0.take() {
77+
// Drop some things to reduce memory.
78+
profiler.interrupt_manager = Arc::new(InterruptManager::new());
79+
profiler.message_sender = crossbeam_channel::bounded(0).0;
80+
profiler.upload_sender = crossbeam_channel::bounded(0).0;
81+
82+
// But we're not 100% sure everything is safe to drop, notably the
83+
// join handles, so we leak the rest.
84+
forget(profiler)
85+
}
86+
}
87+
}

0 commit comments

Comments
 (0)