Skip to content

Commit

Permalink
feat(profiling): Add fatals and thread start/end events to timeline (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
realFlowControl authored Nov 7, 2024
1 parent db89e26 commit a987853
Show file tree
Hide file tree
Showing 6 changed files with 268 additions and 3 deletions.
32 changes: 29 additions & 3 deletions profiling/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,17 @@ fn main() {
let fibers = cfg_fibers(vernum);
let run_time_cache = cfg_run_time_cache(vernum);
let trigger_time_sample = cfg_trigger_time_sample();
let zend_error_observer = cfg_zend_error_observer(vernum);

generate_bindings(php_config_includes, fibers);
generate_bindings(php_config_includes, fibers, zend_error_observer);
build_zend_php_ffis(
php_config_includes,
post_startup_cb,
preload,
run_time_cache,
fibers,
trigger_time_sample,
zend_error_observer,
vernum,
);

Expand Down Expand Up @@ -83,13 +85,15 @@ const ZAI_H_FILES: &[&str] = &[
"../zend_abstract_interface/json/json.h",
];

#[allow(clippy::too_many_arguments)]
fn build_zend_php_ffis(
php_config_includes: &str,
post_startup_cb: bool,
preload: bool,
run_time_cache: bool,
fibers: bool,
trigger_time_sample: bool,
zend_error_observer: bool,
vernum: u64,
) {
println!("cargo:rerun-if-changed=src/php_ffi.h");
Expand Down Expand Up @@ -135,6 +139,7 @@ fn build_zend_php_ffis(
let fibers = if fibers { "1" } else { "0" };
let run_time_cache = if run_time_cache { "1" } else { "0" };
let trigger_time_sample = if trigger_time_sample { "1" } else { "0" };
let zend_error_observer = if zend_error_observer { "1" } else { "0" };

#[cfg(feature = "stack_walking_tests")]
let stack_walking_tests = "1";
Expand All @@ -150,6 +155,7 @@ fn build_zend_php_ffis(
.define("CFG_RUN_TIME_CACHE", run_time_cache)
.define("CFG_STACK_WALKING_TESTS", stack_walking_tests)
.define("CFG_TRIGGER_TIME_SAMPLE", trigger_time_sample)
.define("CFG_ZEND_ERROR_OBSERVER", zend_error_observer)
.includes([Path::new("../ext")])
.includes(
str::replace(php_config_includes, "-I", "")
Expand Down Expand Up @@ -190,7 +196,7 @@ impl bindgen::callbacks::ParseCallbacks for IgnoreMacros {
}
}

fn generate_bindings(php_config_includes: &str, fibers: bool) {
fn generate_bindings(php_config_includes: &str, fibers: bool, zend_error_observer: bool) {
println!("cargo:rerun-if-changed=src/php_ffi.h");
println!("cargo:rerun-if-changed=../ext/handlers_api.h");
let ignored_macros = IgnoreMacros(
Expand All @@ -206,12 +212,20 @@ fn generate_bindings(php_config_includes: &str, fibers: bool) {
.collect(),
);

let clang_args = if fibers {
let mut clang_args = if fibers {
vec!["-D", "CFG_FIBERS=1"]
} else {
vec!["-D", "CFG_FIBERS=0"]
};

if zend_error_observer {
clang_args.push("-D");
clang_args.push("CFG_ZEND_ERROR_OBSERVER=1");
} else {
clang_args.push("-D");
clang_args.push("CFG_ZEND_ERROR_OBSERVER=0");
}

let bindings = bindgen::Builder::default()
.ctypes_prefix("libc")
.clang_args(clang_args)
Expand Down Expand Up @@ -296,6 +310,18 @@ fn cfg_trigger_time_sample() -> bool {
env::var("CARGO_FEATURE_TRIGGER_TIME_SAMPLE").is_ok()
}

fn cfg_zend_error_observer(vernum: u64) -> bool {
if vernum >= 80000 {
println!("cargo:rustc-cfg=zend_error_observer");
if vernum < 80100 {
println!("cargo:rustc-cfg=zend_error_observer_80");
}
true
} else {
false
}
}

fn cfg_php_major_version(vernum: u64) {
let major_version = match vernum {
70000..=79999 => 7,
Expand Down
13 changes: 13 additions & 0 deletions profiling/src/bindings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,12 +169,25 @@ pub struct ModuleEntry {
Option<unsafe extern "C" fn(type_: c_int, module_number: c_int) -> ZendResult>,
pub info_func: Option<unsafe extern "C" fn(zend_module: *mut ModuleEntry)>,
pub version: *const u8,
/// Size of the module globals in bytes. In ZTS this will be the size TSRM will allocate per
/// thread for module globals. The function pointers in [`ModuleEntry::globals_ctor`] and
/// [`ModuleEntry::globals_dtor`] will only be called if this is a non-zero.
pub globals_size: size_t,
#[cfg(php_zts)]
/// Pointer to a `ts_rsrc_id` (which is a [`i32`]). For C-Extension this is created using the
/// `ZEND_DECLARE_MODULE_GLOBALS(module_name)` macro.
/// See <https://heap.space/xref/PHP-8.3/Zend/zend_API.h?r=a89d22cc#249>
pub globals_id_ptr: *mut ts_rsrc_id,
#[cfg(not(php_zts))]
/// Pointer to the module globals struct in NTS mode
pub globals_ptr: *mut c_void,
/// Constructor for module globals.
/// Be aware this will only be called in case [`ModuleEntry::globals_size`] is non-zero and for
/// ZTS you need to make sure [`ModuleEntry::globals_id_ptr`] is a valid, non-null pointer.
pub globals_ctor: Option<unsafe extern "C" fn(global: *mut c_void)>,
/// Destructor for module globals.
/// Be aware this will only be called in case [`ModuleEntry::globals_size`] is non-zero and for
/// ZTS you need to make sure [`ModuleEntry::globals_id_ptr`] is a valid, non-null pointer.
pub globals_dtor: Option<unsafe extern "C" fn(global: *mut c_void)>,
pub post_deactivate_func: Option<unsafe extern "C" fn() -> ZendResult>,
pub module_started: c_int,
Expand Down
29 changes: 29 additions & 0 deletions profiling/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ use core::ptr;
use ddcommon::{cstr, tag, tag::Tag};
use lazy_static::lazy_static;
use libc::c_char;
#[cfg(php_zts)]
use libc::c_void;
use log::{debug, error, info, trace, warn};
use once_cell::sync::{Lazy, OnceCell};
use profiling::{LocalRootSpanResourceMessage, Profiler, VmInterrupt};
Expand Down Expand Up @@ -128,6 +130,13 @@ extern "C" {
pub static ddtrace_runtime_id: *const Uuid;
}

/// We do not have any globals, but we need TSRM to call into GINIT and GSHUTDOWN to observe
/// spawning and joining threads. This will be pointed to by the [`ModuleEntry::globals_id_ptr`] in
/// the `zend_module_entry` and the TSRM will store it's thread-safe-resource id here.
/// See: <https://heap.space/xref/PHP-8.3/Zend/zend_API.c?r=d41e97ae#2303>
#[cfg(php_zts)]
static mut GLOBALS_ID_PTR: i32 = 0;

/// The function `get_module` is what makes this a PHP module. Please do not
/// call this directly; only let it be called by the engine. Generally it is
/// only called once, but if someone accidentally loads the module twice then
Expand Down Expand Up @@ -160,13 +169,33 @@ pub extern "C" fn get_module() -> &'static mut zend::ModuleEntry {
version: PROFILER_VERSION.as_ptr(),
post_deactivate_func: Some(prshutdown),
deps: DEPS.as_ptr(),
#[cfg(php_zts)]
globals_ctor: Some(ginit),
#[cfg(php_zts)]
globals_dtor: Some(gshutdown),
#[cfg(php_zts)]
globals_size: 1,
#[cfg(php_zts)]
globals_id_ptr: unsafe { &mut GLOBALS_ID_PTR },
..Default::default()
});

// SAFETY: well, it's at least as safe as what every single C extension does.
unsafe { &mut *ptr::addr_of_mut!(MODULE) }
}

#[cfg(php_zts)]
unsafe extern "C" fn ginit(_globals_ptr: *mut c_void) {
#[cfg(all(feature = "timeline", php_zts))]
timeline::timeline_ginit();
}

#[cfg(php_zts)]
unsafe extern "C" fn gshutdown(_globals_ptr: *mut c_void) {
#[cfg(all(feature = "timeline", php_zts))]
timeline::timeline_gshutdown();
}

/* Important note on the PHP lifecycle:
* Based on how some SAPIs work and the documentation, one might expect that
* MINIT is called once per process, but this is only sort-of true. Some SAPIs
Expand Down
6 changes: 6 additions & 0 deletions profiling/src/php_ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@

#include <ext/standard/info.h>

// Needed for `zend_observer_error_register` starting from PHP 8
#if CFG_ZEND_ERROR_OBSERVER // defined by build.rs
#include <Zend/zend_errors.h>
#include <Zend/zend_observer.h>
#endif

// Profiling needs ZAI config for INI support.
#include <config/config.h>
// And json to cleanup json state for graceful restart
Expand Down
74 changes: 74 additions & 0 deletions profiling/src/profiling/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,80 @@ impl Profiler {
}
}

/// This function will collect a thread start or stop timeline event
#[cfg(all(feature = "timeline", php_zts))]
pub fn collect_thread_start_end(&self, now: i64, event: &'static str) {
let mut labels = Profiler::common_labels(1);

labels.push(Label {
key: "event",
value: LabelValue::Str(std::borrow::Cow::Borrowed(event)),
});

let n_labels = labels.len();

match self.prepare_and_send_message(
vec![ZendFrame {
function: format!("[{event}]").into(),
file: None,
line: 0,
}],
SampleValues {
timeline: 1,
..Default::default()
},
labels,
now,
) {
Ok(_) => {
trace!("Sent event '{event}' with {n_labels} labels to profiler.")
}
Err(err) => {
warn!("Failed to send event '{event}' with {n_labels} labels to profiler: {err}")
}
}
}

/// This function can be called to collect any fatal errors
#[cfg(feature = "timeline")]
pub fn collect_fatal(&self, now: i64, file: String, line: u32, message: String) {
let mut labels = Profiler::common_labels(2);

labels.push(Label {
key: "event",
value: LabelValue::Str("fatal".into()),
});
labels.push(Label {
key: "message",
value: LabelValue::Str(message.into()),
});

let n_labels = labels.len();

match self.prepare_and_send_message(
vec![ZendFrame {
function: "[fatal]".into(),
file: Some(file),
line,
}],
SampleValues {
timeline: 1,
..Default::default()
},
labels,
now,
) {
Ok(_) => {
trace!("Sent event 'fatal error' with {n_labels} labels to profiler.")
}
Err(err) => {
warn!(
"Failed to send event 'fatal error' with {n_labels} labels to profiler: {err}"
)
}
}
}

/// This function can be called to collect any kind of inactivity that is happening
#[cfg(feature = "timeline")]
pub fn collect_idle(&self, now: i64, duration: i64, reason: &'static str) {
Expand Down
Loading

0 comments on commit a987853

Please sign in to comment.