Skip to content

Commit

Permalink
feat(profiling) add stream_select()-type functions to timeline (#2943)
Browse files Browse the repository at this point in the history
* cleanup

* observe `stream_select()`-type functions

* add \parallel\Events::poll

* remove socket_accept()

* cleanup
  • Loading branch information
realFlowControl authored Nov 14, 2024
1 parent 66df35c commit ba07209
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 65 deletions.
8 changes: 7 additions & 1 deletion profiling/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,16 @@ static mut GLOBALS_ID_PTR: i32 = 0;
/// consecutive return value.
#[no_mangle]
pub extern "C" fn get_module() -> &'static mut zend::ModuleEntry {
static DEPS: [zend::ModuleDep; 4] = [
static DEPS: [zend::ModuleDep; 8] = [
zend::ModuleDep::required(cstr!("standard")),
zend::ModuleDep::required(cstr!("json")),
zend::ModuleDep::optional(cstr!("ddtrace")),
// Optionally, be dependent on these event extensions so that the functions they provide
// are registered in the function table and we can hook into them.
zend::ModuleDep::optional(cstr!("ev")),
zend::ModuleDep::optional(cstr!("event")),
zend::ModuleDep::optional(cstr!("libevent")),
zend::ModuleDep::optional(cstr!("uv")),
zend::ModuleDep::end(),
];

Expand Down
160 changes: 96 additions & 64 deletions profiling/src/timeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ use crate::zend::{
InternalFunctionHandler,
};
use crate::REQUEST_LOCALS;
use ddcommon::cstr;
use libc::c_char;
use log::{error, trace};
#[cfg(php_zts)]
use std::cell::Cell;
use std::cell::RefCell;
use std::ffi::CStr;
use std::ptr;
use std::time::Instant;
use std::time::SystemTime;
Expand All @@ -24,12 +24,6 @@ static mut PREV_ZEND_COMPILE_STRING: Option<zend::VmZendCompileString> = None;
/// The engine's original (or neighbouring extensions) `zend_compile_file()` function
static mut PREV_ZEND_COMPILE_FILE: Option<zend::VmZendCompileFile> = None;

static mut SLEEP_HANDLER: InternalFunctionHandler = None;
static mut USLEEP_HANDLER: InternalFunctionHandler = None;
static mut TIME_NANOSLEEP_HANDLER: InternalFunctionHandler = None;
static mut TIME_SLEEP_UNTIL_HANDLER: InternalFunctionHandler = None;
static mut FRANKENPHP_HANDLE_REQUEST_HANDLER: InternalFunctionHandler = None;

thread_local! {
static IDLE_SINCE: RefCell<Instant> = RefCell::new(Instant::now());
#[cfg(php_zts)]
Expand All @@ -39,6 +33,7 @@ thread_local! {
enum State {
Idle,
Sleeping,
Select,
#[cfg(php_zts)]
ThreadStart,
#[cfg(php_zts)]
Expand All @@ -50,6 +45,7 @@ impl State {
match self {
State::Idle => "idle",
State::Sleeping => "sleeping",
State::Select => "select",
#[cfg(php_zts)]
State::ThreadStart => "thread start",
#[cfg(php_zts)]
Expand Down Expand Up @@ -101,60 +97,40 @@ fn sleeping_fn(
}
}

/// Wrapping the PHP `sleep()` function to take the time it is blocking the current thread
#[no_mangle]
unsafe extern "C" fn ddog_php_prof_sleep(
execute_data: *mut zend_execute_data,
return_value: *mut zval,
) {
if let Some(func) = SLEEP_HANDLER {
sleeping_fn(func, execute_data, return_value, State::Sleeping)
}
}

/// Wrapping the PHP `usleep()` function to take the time it is blocking the current thread
#[no_mangle]
unsafe extern "C" fn ddog_php_prof_usleep(
execute_data: *mut zend_execute_data,
return_value: *mut zval,
) {
if let Some(func) = USLEEP_HANDLER {
sleeping_fn(func, execute_data, return_value, State::Sleeping)
}
}

/// Wrapping the PHP `time_nanosleep()` function to take the time it is blocking the current thread
#[no_mangle]
unsafe extern "C" fn ddog_php_prof_time_nanosleep(
execute_data: *mut zend_execute_data,
return_value: *mut zval,
) {
if let Some(func) = TIME_NANOSLEEP_HANDLER {
sleeping_fn(func, execute_data, return_value, State::Sleeping)
}
}

/// Wrapping the PHP `time_sleep_until()` function to take the time it is blocking the current thread
#[no_mangle]
unsafe extern "C" fn ddog_php_prof_time_sleep_until(
execute_data: *mut zend_execute_data,
return_value: *mut zval,
) {
if let Some(func) = TIME_SLEEP_UNTIL_HANDLER {
sleeping_fn(func, execute_data, return_value, State::Sleeping)
}
macro_rules! create_sleeping_fn {
($fn_name:ident, $handler:ident, $state:expr) => {
static mut $handler: InternalFunctionHandler = None;

#[no_mangle]
unsafe extern "C" fn $fn_name(
execute_data: *mut zend_execute_data,
return_value: *mut zval,
) {
if let Some(func) = $handler {
sleeping_fn(func, execute_data, return_value, $state)
}
}
};
}

/// Wrapping the FrankenPHP `frankenphp_handle_request()` function to take the time it is blocking the current thread
#[no_mangle]
unsafe extern "C" fn ddog_php_prof_frankenphp_handle_request(
execute_data: *mut zend_execute_data,
return_value: *mut zval,
) {
if let Some(func) = FRANKENPHP_HANDLE_REQUEST_HANDLER {
sleeping_fn(func, execute_data, return_value, State::Idle)
}
}
// Functions that are sleeping
create_sleeping_fn!(ddog_php_prof_sleep, SLEEP_HANDLER, State::Sleeping);
create_sleeping_fn!(ddog_php_prof_usleep, USLEEP_HANDLER, State::Sleeping);
create_sleeping_fn!(ddog_php_prof_time_nanosleep, TIME_NANOSLEEP_HANDLER, State::Sleeping);
create_sleeping_fn!(ddog_php_prof_time_sleep_until, TIME_SLEEP_UNTIL_HANDLER, State::Sleeping);

// Idle functions: these are functions which are like RSHUTDOWN -> RINIT
create_sleeping_fn!(ddog_php_prof_frankenphp_handle_request, FRANKENPHP_HANDLE_REQUEST_HANDLER, State::Idle);

// Functions that are blocking on I/O
create_sleeping_fn!(ddog_php_prof_stream_select, STREAM_SELECT_HANDLER, State::Select);
create_sleeping_fn!(ddog_php_prof_socket_select, SOCKET_SELECT_HANDLER, State::Select);
create_sleeping_fn!(ddog_php_prof_curl_multi_select, CURL_MULTI_SELECT_HANDLER, State::Select);
create_sleeping_fn!(ddog_php_prof_uv_run, UV_RUN_HANDLER, State::Select);
create_sleeping_fn!(ddog_php_prof_event_base_loop, EVENT_BASE_LOOP_HANDLER, State::Select);
create_sleeping_fn!(ddog_php_prof_eventbase_loop, EVENTBASE_LOOP_HANDLER, State::Select);
create_sleeping_fn!(ddog_php_prof_ev_loop_run, EV_LOOP_RUN_HANDLER, State::Select);
create_sleeping_fn!(ddog_php_prof_parallel_events_poll, PARALLEL_EVENTS_POLL_HANDLER, State::Select);

/// Will be called by the ZendEngine on all errors happening. This is a PHP 8 API
#[cfg(zend_error_observer)]
Expand Down Expand Up @@ -227,36 +203,92 @@ pub fn timeline_minit() {
pub unsafe fn timeline_startup() {
let handlers = [
zend::datadog_php_zif_handler::new(
CStr::from_bytes_with_nul_unchecked(b"sleep\0"),
cstr!("sleep"),
ptr::addr_of_mut!(SLEEP_HANDLER),
Some(ddog_php_prof_sleep),
),
zend::datadog_php_zif_handler::new(
CStr::from_bytes_with_nul_unchecked(b"usleep\0"),
cstr!("usleep"),
ptr::addr_of_mut!(USLEEP_HANDLER),
Some(ddog_php_prof_usleep),
),
zend::datadog_php_zif_handler::new(
CStr::from_bytes_with_nul_unchecked(b"time_nanosleep\0"),
cstr!("time_nanosleep"),
ptr::addr_of_mut!(TIME_NANOSLEEP_HANDLER),
Some(ddog_php_prof_time_nanosleep),
),
zend::datadog_php_zif_handler::new(
CStr::from_bytes_with_nul_unchecked(b"time_sleep_until\0"),
cstr!("time_sleep_until"),
ptr::addr_of_mut!(TIME_SLEEP_UNTIL_HANDLER),
Some(ddog_php_prof_time_sleep_until),
),
zend::datadog_php_zif_handler::new(
CStr::from_bytes_with_nul_unchecked(b"frankenphp_handle_request\0"),
cstr!("frankenphp_handle_request"),
ptr::addr_of_mut!(FRANKENPHP_HANDLE_REQUEST_HANDLER),
Some(ddog_php_prof_frankenphp_handle_request),
),
zend::datadog_php_zif_handler::new(
cstr!("stream_select"),
ptr::addr_of_mut!(STREAM_SELECT_HANDLER),
Some(ddog_php_prof_stream_select),
),
zend::datadog_php_zif_handler::new(
cstr!("socket_select"),
ptr::addr_of_mut!(SOCKET_SELECT_HANDLER),
Some(ddog_php_prof_socket_select),
),
zend::datadog_php_zif_handler::new(
cstr!("curl_multi_select"),
ptr::addr_of_mut!(CURL_MULTI_SELECT_HANDLER),
Some(ddog_php_prof_curl_multi_select),
),
// provided by `ext-uv` from https://pecl.php.net/package/uv
zend::datadog_php_zif_handler::new(
cstr!("uv_run"),
ptr::addr_of_mut!(UV_RUN_HANDLER),
Some(ddog_php_prof_uv_run),
),
// provided by `ext-libevent` from https://pecl.php.net/package/libevent
zend::datadog_php_zif_handler::new(
cstr!("event_base_loop"),
ptr::addr_of_mut!(EVENT_BASE_LOOP_HANDLER),
Some(ddog_php_prof_event_base_loop),
),
];

for handler in handlers.into_iter() {
// Safety: we've set all the parameters correctly for this C call.
zend::datadog_php_install_handler(handler);
}

let handlers = [
// provided by `ext-ev` from https://pecl.php.net/package/ev
zend::datadog_php_zim_handler::new(
cstr!("evloop"),
cstr!("run"),
ptr::addr_of_mut!(EV_LOOP_RUN_HANDLER),
Some(ddog_php_prof_ev_loop_run),
),
// provided by `ext-event` from https://pecl.php.net/package/event
zend::datadog_php_zim_handler::new(
cstr!("eventbase"),
cstr!("loop"),
ptr::addr_of_mut!(EVENTBASE_LOOP_HANDLER),
Some(ddog_php_prof_eventbase_loop),
),
// provided by `ext-parallel` from https://pecl.php.net/package/parallel
zend::datadog_php_zim_handler::new(
cstr!("parallel\\events"),
cstr!("poll"),
ptr::addr_of_mut!(PARALLEL_EVENTS_POLL_HANDLER),
Some(ddog_php_prof_parallel_events_poll),
),
];

for handler in handlers.into_iter() {
// Safety: we've set all the parameters correctly for this C call.
zend::datadog_php_install_method_handler(handler);
}
}

/// This function is run during the RINIT phase and reports any `IDLE_SINCE` duration as an idle
Expand Down

0 comments on commit ba07209

Please sign in to comment.