Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(profiling): Add fatals and thread start/end events to timeline #2820

Merged
merged 18 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
27 changes: 27 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,11 @@ extern "C" {
pub static ddtrace_runtime_id: *const Uuid;
}

/// We do not need this, as we do not have any globals, but as we want PHP to call GINIT and
/// GSHUTDOWN, we need to have a valid pointer here.
#[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 +167,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
Loading