From 07e795aa574085319c9292a3646717d442628783 Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Tue, 19 May 2026 16:55:42 -0600 Subject: [PATCH 1/6] fix(profiling): disable on tailcall VM on 8.5.{0..6} --- profiling/src/config.rs | 25 +++++++++++++++++++++++++ profiling/src/php_ffi.c | 13 +++++++++++++ profiling/src/php_ffi.h | 5 +++++ 3 files changed, 43 insertions(+) diff --git a/profiling/src/config.rs b/profiling/src/config.rs index bb0a44b107e..ef2fe985f95 100644 --- a/profiling/src/config.rs +++ b/profiling/src/config.rs @@ -110,6 +110,21 @@ impl SystemSettings { uri, } } + + fn disable_profiling(&mut self) { + self.profiling_enabled = false; + self.profiling_experimental_features_enabled = false; + self.profiling_endpoint_collection_enabled = false; + self.profiling_experimental_cpu_time_enabled = false; + self.profiling_allocation_enabled = false; + self.profiling_timeline_enabled = false; + self.profiling_exception_enabled = false; + self.profiling_exception_message_enabled = false; + self.profiling_wall_time_enabled = false; + self.profiling_io_enabled = false; + self.output_pprof = None; + self.profiling_log_level = LevelFilter::Off; + } } static mut SYSTEM_SETTINGS: SystemSettings = SystemSettings::initial(); @@ -132,6 +147,16 @@ impl SystemSettings { let mut system_settings = SystemSettings::new(); // Work around version-specific issues. + let tailcall_check = unsafe { bindings::ddog_php_prof_check_tailcall_vm_interrupt() }; + if system_settings.profiling_enabled && tailcall_check != bindings::ZendResult::Success { + error!(concat!( + "Profiling is disabled because PHP 8.5.0-8.5.6 can crash on the tailcall VM kind with profiling enabled.", + " To enable profiling, upgrade PHP to 8.5.7 or newer, or use a PHP build without the tailcall VM.", + " See https://github.com/php/php-src/pull/21922" + )); + system_settings.disable_profiling(); + } + #[cfg(not(php_zend_mm_set_custom_handlers_ex))] if allocation::allocation_le83::first_rinit_should_disable_due_to_jit() { if bindings::PHP_VERSION_ID >= 80400 { diff --git a/profiling/src/php_ffi.c b/profiling/src/php_ffi.c index 00f684e9534..5d6782978e1 100644 --- a/profiling/src/php_ffi.c +++ b/profiling/src/php_ffi.c @@ -5,6 +5,7 @@ #include #include #include +#include #include "SAPI.h" #if CFG_STACK_WALKING_TESTS @@ -88,6 +89,18 @@ sapi_request_info datadog_sapi_globals_request_info() { */ uint32_t ddog_php_prof_php_version_id(void) { return php_version_id(); } +zend_result ddog_php_prof_check_tailcall_vm_interrupt(void) { +#if PHP_VERSION_ID >= 80500 && PHP_VERSION_ID < 80600 + uint32_t version_id = php_version_id(); + // See this PR for more information on the tailcall VM crash: + // https://github.com/php/php-src/pull/21922 + if (zend_vm_kind() == ZEND_VM_KIND_TAILCALL && version_id < 80507) { + return FAILURE; + } +#endif + return SUCCESS; +} + /** * Returns the PHP_VERSION of the engine at run-time, not the version the * extension was built against at compile-time. diff --git a/profiling/src/php_ffi.h b/profiling/src/php_ffi.h index f9aef1a21ec..ca6d5616182 100644 --- a/profiling/src/php_ffi.h +++ b/profiling/src/php_ffi.h @@ -55,6 +55,11 @@ const char *datadog_extension_build_id(void); */ const char *datadog_module_build_id(void); +/** + * Detects tailcall VM interrupt bugs where the profiler must be disabled. + */ +zend_result ddog_php_prof_check_tailcall_vm_interrupt(void); + /** * Returns the `sapi_request_info` from the SAPI_GLOBALS */ From 8e0406cce4b37648c62e6fdd160e5d8bd109c426 Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Tue, 19 May 2026 17:47:38 -0600 Subject: [PATCH 2/6] test(profiling): skipif for tailvm kind issues --- profiling/tests/phpt/allocation_sampling_distance.phpt | 1 + profiling/tests/phpt/exceptions_01.phpt | 1 + profiling/tests/phpt/gc_collect_cycles_01.phpt | 1 + profiling/tests/phpt/phpinfo_03.phpt | 3 ++- profiling/tests/phpt/phpinfo_05.phpt | 1 + profiling/tests/phpt/phpinfo_07.phpt | 3 ++- profiling/tests/phpt/phpinfo_08.phpt | 3 ++- profiling/tests/phpt/phpinfo_ini_02.phpt | 1 + profiling/tests/phpt/skipif_tailcall_vm_interrupt.inc | 9 +++++++++ 9 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 profiling/tests/phpt/skipif_tailcall_vm_interrupt.inc diff --git a/profiling/tests/phpt/allocation_sampling_distance.phpt b/profiling/tests/phpt/allocation_sampling_distance.phpt index 46c0c94c66d..b84f89928d2 100644 --- a/profiling/tests/phpt/allocation_sampling_distance.phpt +++ b/profiling/tests/phpt/allocation_sampling_distance.phpt @@ -7,6 +7,7 @@ cannot regress again. --INI-- datadog.profiling.enabled=1 diff --git a/profiling/tests/phpt/exceptions_01.phpt b/profiling/tests/phpt/exceptions_01.phpt index db537861ee5..e54a6a4bffb 100644 --- a/profiling/tests/phpt/exceptions_01.phpt +++ b/profiling/tests/phpt/exceptions_01.phpt @@ -7,6 +7,7 @@ sampling rate will actually be used. --ENV-- DD_PROFILING_ENABLED=yes @@ -70,4 +71,4 @@ echo "Done.", PHP_EOL; ?> --EXPECT-- -Done. \ No newline at end of file +Done. diff --git a/profiling/tests/phpt/phpinfo_05.phpt b/profiling/tests/phpt/phpinfo_05.phpt index d3ec73c23fe..84732278d42 100644 --- a/profiling/tests/phpt/phpinfo_05.phpt +++ b/profiling/tests/phpt/phpinfo_05.phpt @@ -7,6 +7,7 @@ overwritten by the new ones without EXPERIMENTAL in them. --ENV-- DD_PROFILING_ENABLED=yes diff --git a/profiling/tests/phpt/phpinfo_07.phpt b/profiling/tests/phpt/phpinfo_07.phpt index 526777e724b..56fc4ec8e60 100644 --- a/profiling/tests/phpt/phpinfo_07.phpt +++ b/profiling/tests/phpt/phpinfo_07.phpt @@ -7,6 +7,7 @@ test verifies that certain information is present. --ENV-- DD_PROFILING_ENABLED=yes @@ -62,4 +63,4 @@ echo "Done."; ?> --EXPECT-- -Done. \ No newline at end of file +Done. diff --git a/profiling/tests/phpt/phpinfo_08.phpt b/profiling/tests/phpt/phpinfo_08.phpt index a4bac30d89b..754393bacba 100644 --- a/profiling/tests/phpt/phpinfo_08.phpt +++ b/profiling/tests/phpt/phpinfo_08.phpt @@ -7,6 +7,7 @@ test verifies that certain information is present. --ENV-- DD_PROFILING_ENABLED=auto @@ -49,4 +50,4 @@ echo "Done."; ?> --EXPECT-- -Done. \ No newline at end of file +Done. diff --git a/profiling/tests/phpt/phpinfo_ini_02.phpt b/profiling/tests/phpt/phpinfo_ini_02.phpt index e16076e518c..026676c56c4 100644 --- a/profiling/tests/phpt/phpinfo_ini_02.phpt +++ b/profiling/tests/phpt/phpinfo_ini_02.phpt @@ -7,6 +7,7 @@ test verifies that certain information is present when configured by .ini. --INI-- assert.exception=1 diff --git a/profiling/tests/phpt/skipif_tailcall_vm_interrupt.inc b/profiling/tests/phpt/skipif_tailcall_vm_interrupt.inc new file mode 100644 index 00000000000..708eb265797 --- /dev/null +++ b/profiling/tests/phpt/skipif_tailcall_vm_interrupt.inc @@ -0,0 +1,9 @@ += 80500 && + PHP_VERSION_ID < 80507 && + defined('ZEND_VM_KIND') && + ZEND_VM_KIND === 'ZEND_VM_KIND_TAILCALL' +) { + die("skip: profiler is disabled on PHP 8.5.0-8.5.6 with the tailcall VM kind\n"); +} From 3eff82228d96fe36e3a5966055bfa59a04fdc665 Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Thu, 21 May 2026 11:48:07 -0600 Subject: [PATCH 3/6] Revert "test(profiling): skipif for tailvm kind issues" This reverts commit 8e0406cce4b37648c62e6fdd160e5d8bd109c426. --- profiling/tests/phpt/allocation_sampling_distance.phpt | 1 - profiling/tests/phpt/exceptions_01.phpt | 1 - profiling/tests/phpt/gc_collect_cycles_01.phpt | 1 - profiling/tests/phpt/phpinfo_03.phpt | 3 +-- profiling/tests/phpt/phpinfo_05.phpt | 1 - profiling/tests/phpt/phpinfo_07.phpt | 3 +-- profiling/tests/phpt/phpinfo_08.phpt | 3 +-- profiling/tests/phpt/phpinfo_ini_02.phpt | 1 - profiling/tests/phpt/skipif_tailcall_vm_interrupt.inc | 9 --------- 9 files changed, 3 insertions(+), 20 deletions(-) delete mode 100644 profiling/tests/phpt/skipif_tailcall_vm_interrupt.inc diff --git a/profiling/tests/phpt/allocation_sampling_distance.phpt b/profiling/tests/phpt/allocation_sampling_distance.phpt index b84f89928d2..46c0c94c66d 100644 --- a/profiling/tests/phpt/allocation_sampling_distance.phpt +++ b/profiling/tests/phpt/allocation_sampling_distance.phpt @@ -7,7 +7,6 @@ cannot regress again. --INI-- datadog.profiling.enabled=1 diff --git a/profiling/tests/phpt/exceptions_01.phpt b/profiling/tests/phpt/exceptions_01.phpt index e54a6a4bffb..db537861ee5 100644 --- a/profiling/tests/phpt/exceptions_01.phpt +++ b/profiling/tests/phpt/exceptions_01.phpt @@ -7,7 +7,6 @@ sampling rate will actually be used. --ENV-- DD_PROFILING_ENABLED=yes @@ -71,4 +70,4 @@ echo "Done.", PHP_EOL; ?> --EXPECT-- -Done. +Done. \ No newline at end of file diff --git a/profiling/tests/phpt/phpinfo_05.phpt b/profiling/tests/phpt/phpinfo_05.phpt index 84732278d42..d3ec73c23fe 100644 --- a/profiling/tests/phpt/phpinfo_05.phpt +++ b/profiling/tests/phpt/phpinfo_05.phpt @@ -7,7 +7,6 @@ overwritten by the new ones without EXPERIMENTAL in them. --ENV-- DD_PROFILING_ENABLED=yes diff --git a/profiling/tests/phpt/phpinfo_07.phpt b/profiling/tests/phpt/phpinfo_07.phpt index 56fc4ec8e60..526777e724b 100644 --- a/profiling/tests/phpt/phpinfo_07.phpt +++ b/profiling/tests/phpt/phpinfo_07.phpt @@ -7,7 +7,6 @@ test verifies that certain information is present. --ENV-- DD_PROFILING_ENABLED=yes @@ -63,4 +62,4 @@ echo "Done."; ?> --EXPECT-- -Done. +Done. \ No newline at end of file diff --git a/profiling/tests/phpt/phpinfo_08.phpt b/profiling/tests/phpt/phpinfo_08.phpt index 754393bacba..a4bac30d89b 100644 --- a/profiling/tests/phpt/phpinfo_08.phpt +++ b/profiling/tests/phpt/phpinfo_08.phpt @@ -7,7 +7,6 @@ test verifies that certain information is present. --ENV-- DD_PROFILING_ENABLED=auto @@ -50,4 +49,4 @@ echo "Done."; ?> --EXPECT-- -Done. +Done. \ No newline at end of file diff --git a/profiling/tests/phpt/phpinfo_ini_02.phpt b/profiling/tests/phpt/phpinfo_ini_02.phpt index 026676c56c4..e16076e518c 100644 --- a/profiling/tests/phpt/phpinfo_ini_02.phpt +++ b/profiling/tests/phpt/phpinfo_ini_02.phpt @@ -7,7 +7,6 @@ test verifies that certain information is present when configured by .ini. --INI-- assert.exception=1 diff --git a/profiling/tests/phpt/skipif_tailcall_vm_interrupt.inc b/profiling/tests/phpt/skipif_tailcall_vm_interrupt.inc deleted file mode 100644 index 708eb265797..00000000000 --- a/profiling/tests/phpt/skipif_tailcall_vm_interrupt.inc +++ /dev/null @@ -1,9 +0,0 @@ -= 80500 && - PHP_VERSION_ID < 80507 && - defined('ZEND_VM_KIND') && - ZEND_VM_KIND === 'ZEND_VM_KIND_TAILCALL' -) { - die("skip: profiler is disabled on PHP 8.5.0-8.5.6 with the tailcall VM kind\n"); -} From 1398ccc2ac9dc7bb182c7fbbfaff0de29b80e890 Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Thu, 21 May 2026 11:48:11 -0600 Subject: [PATCH 4/6] Revert "fix(profiling): disable on tailcall VM on 8.5.{0..6}" This reverts commit 07e795aa574085319c9292a3646717d442628783. --- profiling/src/config.rs | 25 ------------------------- profiling/src/php_ffi.c | 13 ------------- profiling/src/php_ffi.h | 5 ----- 3 files changed, 43 deletions(-) diff --git a/profiling/src/config.rs b/profiling/src/config.rs index ef2fe985f95..bb0a44b107e 100644 --- a/profiling/src/config.rs +++ b/profiling/src/config.rs @@ -110,21 +110,6 @@ impl SystemSettings { uri, } } - - fn disable_profiling(&mut self) { - self.profiling_enabled = false; - self.profiling_experimental_features_enabled = false; - self.profiling_endpoint_collection_enabled = false; - self.profiling_experimental_cpu_time_enabled = false; - self.profiling_allocation_enabled = false; - self.profiling_timeline_enabled = false; - self.profiling_exception_enabled = false; - self.profiling_exception_message_enabled = false; - self.profiling_wall_time_enabled = false; - self.profiling_io_enabled = false; - self.output_pprof = None; - self.profiling_log_level = LevelFilter::Off; - } } static mut SYSTEM_SETTINGS: SystemSettings = SystemSettings::initial(); @@ -147,16 +132,6 @@ impl SystemSettings { let mut system_settings = SystemSettings::new(); // Work around version-specific issues. - let tailcall_check = unsafe { bindings::ddog_php_prof_check_tailcall_vm_interrupt() }; - if system_settings.profiling_enabled && tailcall_check != bindings::ZendResult::Success { - error!(concat!( - "Profiling is disabled because PHP 8.5.0-8.5.6 can crash on the tailcall VM kind with profiling enabled.", - " To enable profiling, upgrade PHP to 8.5.7 or newer, or use a PHP build without the tailcall VM.", - " See https://github.com/php/php-src/pull/21922" - )); - system_settings.disable_profiling(); - } - #[cfg(not(php_zend_mm_set_custom_handlers_ex))] if allocation::allocation_le83::first_rinit_should_disable_due_to_jit() { if bindings::PHP_VERSION_ID >= 80400 { diff --git a/profiling/src/php_ffi.c b/profiling/src/php_ffi.c index 5d6782978e1..00f684e9534 100644 --- a/profiling/src/php_ffi.c +++ b/profiling/src/php_ffi.c @@ -5,7 +5,6 @@ #include #include #include -#include #include "SAPI.h" #if CFG_STACK_WALKING_TESTS @@ -89,18 +88,6 @@ sapi_request_info datadog_sapi_globals_request_info() { */ uint32_t ddog_php_prof_php_version_id(void) { return php_version_id(); } -zend_result ddog_php_prof_check_tailcall_vm_interrupt(void) { -#if PHP_VERSION_ID >= 80500 && PHP_VERSION_ID < 80600 - uint32_t version_id = php_version_id(); - // See this PR for more information on the tailcall VM crash: - // https://github.com/php/php-src/pull/21922 - if (zend_vm_kind() == ZEND_VM_KIND_TAILCALL && version_id < 80507) { - return FAILURE; - } -#endif - return SUCCESS; -} - /** * Returns the PHP_VERSION of the engine at run-time, not the version the * extension was built against at compile-time. diff --git a/profiling/src/php_ffi.h b/profiling/src/php_ffi.h index ca6d5616182..f9aef1a21ec 100644 --- a/profiling/src/php_ffi.h +++ b/profiling/src/php_ffi.h @@ -55,11 +55,6 @@ const char *datadog_extension_build_id(void); */ const char *datadog_module_build_id(void); -/** - * Detects tailcall VM interrupt bugs where the profiler must be disabled. - */ -zend_result ddog_php_prof_check_tailcall_vm_interrupt(void); - /** * Returns the `sapi_request_info` from the SAPI_GLOBALS */ From 80530b190df8705c258c99cc47f6f85fc0acfe83 Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Thu, 21 May 2026 11:48:44 -0600 Subject: [PATCH 5/6] fix(profiling): disable wall/cpu on some PHP 8.5 builds --- profiling/src/bindings/mod.rs | 4 ++ profiling/src/capi.rs | 5 +- profiling/src/config.rs | 22 +++++- profiling/src/lib.rs | 12 ++-- profiling/src/php_ffi.c | 13 ++++ profiling/src/php_ffi.h | 6 ++ profiling/src/profiling/sample_type_filter.rs | 69 +++++++++++++++++-- profiling/src/wall_time.rs | 5 +- profiling/tests/phpt/phpinfo_07.phpt | 21 ++++-- 9 files changed, 134 insertions(+), 23 deletions(-) diff --git a/profiling/src/bindings/mod.rs b/profiling/src/bindings/mod.rs index ac60a6bdbc2..4de29958f5f 100644 --- a/profiling/src/bindings/mod.rs +++ b/profiling/src/bindings/mod.rs @@ -322,6 +322,10 @@ extern "C" { /// Must be called from a PHP thread during a request. pub fn datadog_php_profiling_vm_interrupt_addr() -> *const AtomicBool; + /// Returns Failure when this PHP build/runtime has the tailcall VM + /// interrupt crash that requires time sample collection to be disabled. + pub fn ddog_php_prof_check_tailcall_vm_interrupt() -> ZendResult; + /// Registers the extension. Note that it's kept in a zend_llist and gets /// pemalloc'd + memcpy'd into place. The engine says this is a mutable /// pointer, but in practice it's const. diff --git a/profiling/src/capi.rs b/profiling/src/capi.rs index 0ad6588fb3b..acae5a7daec 100644 --- a/profiling/src/capi.rs +++ b/profiling/src/capi.rs @@ -43,7 +43,10 @@ extern "C" fn ddog_php_prof_trigger_time_sample() { use std::sync::atomic::Ordering; let result = super::REQUEST_LOCALS.try_with_borrow(|locals| { - if locals.system_settings().profiling_enabled { + let system_settings = locals.system_settings(); + if system_settings.profiling_wall_time_enabled + | system_settings.profiling_experimental_cpu_time_enabled + { // Safety: only vm interrupts are stored there, or possibly null (edges only). if let Some(vm_interrupt) = unsafe { locals.vm_interrupt_addr.as_ref() } { locals.interrupt_count.fetch_add(1, Ordering::SeqCst); diff --git a/profiling/src/config.rs b/profiling/src/config.rs index bb0a44b107e..c3660d90104 100644 --- a/profiling/src/config.rs +++ b/profiling/src/config.rs @@ -132,6 +132,23 @@ impl SystemSettings { let mut system_settings = SystemSettings::new(); // Work around version-specific issues. + let tailcall_check = unsafe { bindings::ddog_php_prof_check_tailcall_vm_interrupt() }; + if system_settings.profiling_enabled + && (system_settings.profiling_wall_time_enabled + | system_settings.profiling_experimental_cpu_time_enabled) + && tailcall_check != bindings::ZendResult::Success + { + error!(concat!( + "Wall-time and CPU-time sample collection is disabled because PHP 8.5.0-8.5.6 can crash ", + "on the tailcall VM kind with VM interrupt based sample collection enabled.", + " Other profiling sample types remain enabled.", + " To enable time sample collection, upgrade PHP to 8.5.7 or newer, or use a PHP build without the tailcall VM.", + " See https://github.com/php/php-src/pull/21922" + )); + system_settings.profiling_wall_time_enabled = false; + system_settings.profiling_experimental_cpu_time_enabled = false; + } + #[cfg(not(php_zend_mm_set_custom_handlers_ex))] if allocation::allocation_le83::first_rinit_should_disable_due_to_jit() { if bindings::PHP_VERSION_ID >= 80400 { @@ -1106,9 +1123,8 @@ pub(crate) fn minit(module_number: libc::c_int) { displayer: None, env_config_fallback: None, }, - // At the moment, wall-time cannot be fully disabled. This only - // controls automatic collection (manual collection is still - // possible). + // Controls wall-time collection and the wall-time related + // sample types. zai_config_entry { id: transmute::(ProfilingWallTimeEnabled), name: ProfilingWallTimeEnabled.env_var_name(), diff --git a/profiling/src/lib.rs b/profiling/src/lib.rs index ec0bd6da9f7..43aa9a941f4 100644 --- a/profiling/src/lib.rs +++ b/profiling/src/lib.rs @@ -693,7 +693,6 @@ extern "C" fn rinit(_type: c_int, _module_number: c_int) -> ZendResult { // Not logging, rinit could be quite spammy. _ = REQUEST_LOCALS.try_with_borrow(|locals| { let cpu_time_enabled = system_settings.profiling_experimental_cpu_time_enabled; - let wall_time_enabled = system_settings.profiling_wall_time_enabled; CLOCKS.with_borrow_mut(|clocks| clocks.initialize(cpu_time_enabled)); TAGS.set({ @@ -717,8 +716,8 @@ extern "C" fn rinit(_type: c_int, _module_number: c_int) -> ZendResult { Arc::new(tags) }); - // Only add interrupt if cpu- or wall-time is enabled. - if !(cpu_time_enabled | wall_time_enabled) { + // Only add interrupt if cpu- or wall-time sample collection is enabled. + if !(cpu_time_enabled | system_settings.profiling_wall_time_enabled) { return; } @@ -775,10 +774,9 @@ extern "C" fn rshutdown(_type: c_int, _module_number: c_int) -> ZendResult { _ = REQUEST_LOCALS.try_with_borrow(|locals| { let system_settings = locals.system_settings(); - // The interrupt is only added if CPU- or wall-time are enabled BUT - // wall-time is not expected to ever be disabled, except in testing, - // and we don't need to optimize for that. - if system_settings.profiling_enabled { + if system_settings.profiling_wall_time_enabled + | system_settings.profiling_experimental_cpu_time_enabled + { if let Some(profiler) = Profiler::get() { let interrupt = VmInterrupt { interrupt_count_ptr: &locals.interrupt_count, diff --git a/profiling/src/php_ffi.c b/profiling/src/php_ffi.c index 00f684e9534..5d6782978e1 100644 --- a/profiling/src/php_ffi.c +++ b/profiling/src/php_ffi.c @@ -5,6 +5,7 @@ #include #include #include +#include #include "SAPI.h" #if CFG_STACK_WALKING_TESTS @@ -88,6 +89,18 @@ sapi_request_info datadog_sapi_globals_request_info() { */ uint32_t ddog_php_prof_php_version_id(void) { return php_version_id(); } +zend_result ddog_php_prof_check_tailcall_vm_interrupt(void) { +#if PHP_VERSION_ID >= 80500 && PHP_VERSION_ID < 80600 + uint32_t version_id = php_version_id(); + // See this PR for more information on the tailcall VM crash: + // https://github.com/php/php-src/pull/21922 + if (zend_vm_kind() == ZEND_VM_KIND_TAILCALL && version_id < 80507) { + return FAILURE; + } +#endif + return SUCCESS; +} + /** * Returns the PHP_VERSION of the engine at run-time, not the version the * extension was built against at compile-time. diff --git a/profiling/src/php_ffi.h b/profiling/src/php_ffi.h index f9aef1a21ec..1c2fd00be27 100644 --- a/profiling/src/php_ffi.h +++ b/profiling/src/php_ffi.h @@ -72,6 +72,12 @@ zend_module_entry *datadog_get_module_entry(const char *str, uintptr_t len); */ void *datadog_php_profiling_vm_interrupt_addr(void); +/** + * Detects tailcall VM interrupt bugs where time sample collection must be + * disabled. + */ +zend_result ddog_php_prof_check_tailcall_vm_interrupt(void); + /** * For Code Hotspots, we need the tracer's local root span id and the current * span id. This is a cross-product struct, so keep it in sync with tracer's diff --git a/profiling/src/profiling/sample_type_filter.rs b/profiling/src/profiling/sample_type_filter.rs index 7d6f609d576..22f242ba800 100644 --- a/profiling/src/profiling/sample_type_filter.rs +++ b/profiling/src/profiling/sample_type_filter.rs @@ -41,12 +41,17 @@ impl SampleTypeFilter { let mut sample_types_mask = [false; MAX_SAMPLE_TYPES]; if system_settings.profiling_enabled { - // sample, wall-time, cpu-time - let len = 2 + system_settings.profiling_experimental_cpu_time_enabled as usize; - sample_types.extend_from_slice(&SAMPLE_TYPES[0..len]); - sample_types_mask[0] = true; - sample_types_mask[1] = true; - sample_types_mask[2] = system_settings.profiling_experimental_cpu_time_enabled; + if system_settings.profiling_wall_time_enabled { + // sample, wall-time + sample_types.extend_from_slice(&SAMPLE_TYPES[0..2]); + sample_types_mask[0] = true; + sample_types_mask[1] = true; + } + + if system_settings.profiling_experimental_cpu_time_enabled { + sample_types.push(SAMPLE_TYPES[2]); + sample_types_mask[2] = true; + } // alloc-samples, alloc-size if system_settings.profiling_allocation_enabled { @@ -219,6 +224,36 @@ mod tests { assert_eq!(values, vec![10, 20, 30]); } + #[test] + fn filter_without_wall_or_cpu_time() { + let mut settings = get_system_settings(); + settings.profiling_enabled = true; + settings.profiling_wall_time_enabled = false; + settings.profiling_experimental_cpu_time_enabled = false; + + let sample_type_filter = SampleTypeFilter::new(&settings); + let values = sample_type_filter.filter(get_samples()); + let types = sample_type_filter.sample_types(); + + assert_eq!(types, Vec::::new()); + assert_eq!(values, Vec::::new()); + } + + #[test] + fn filter_with_cpu_time_and_without_wall_time() { + let mut settings = get_system_settings(); + settings.profiling_enabled = true; + settings.profiling_wall_time_enabled = false; + settings.profiling_experimental_cpu_time_enabled = true; + + let sample_type_filter = SampleTypeFilter::new(&settings); + let values = sample_type_filter.filter(get_samples()); + let types = sample_type_filter.sample_types(); + + assert_eq!(types, vec![ValueType::new("cpu-time", "nanoseconds")]); + assert_eq!(values, vec![30]); + } + #[test] fn filter_with_allocations() { let mut settings = get_system_settings(); @@ -242,6 +277,28 @@ mod tests { assert_eq!(values, vec![10, 20, 40, 50]); } + #[test] + fn filter_with_allocations_without_wall_or_cpu_time() { + let mut settings = get_system_settings(); + settings.profiling_enabled = true; + settings.profiling_allocation_enabled = true; + settings.profiling_wall_time_enabled = false; + settings.profiling_experimental_cpu_time_enabled = false; + + let sample_type_filter = SampleTypeFilter::new(&settings); + let values = sample_type_filter.filter(get_samples()); + let types = sample_type_filter.sample_types(); + + assert_eq!( + types, + vec![ + ValueType::new("alloc-samples", "count"), + ValueType::new("alloc-size", "bytes"), + ] + ); + assert_eq!(values, vec![40, 50]); + } + #[test] fn filter_with_allocations_and_cpu_time() { let mut settings = get_system_settings(); diff --git a/profiling/src/wall_time.rs b/profiling/src/wall_time.rs index bfd9390c90d..b2cf01dbe3d 100644 --- a/profiling/src/wall_time.rs +++ b/profiling/src/wall_time.rs @@ -110,7 +110,10 @@ static mut PREV_INTERRUPT_FUNCTION: Option = None; #[inline(never)] pub extern "C" fn ddog_php_prof_interrupt_function(execute_data: *mut zend_execute_data) { let result = REQUEST_LOCALS.try_with_borrow(|locals| { - if !locals.system_settings().profiling_enabled { + let system_settings = locals.system_settings(); + if !(system_settings.profiling_wall_time_enabled + | system_settings.profiling_experimental_cpu_time_enabled) + { return; } diff --git a/profiling/tests/phpt/phpinfo_07.phpt b/profiling/tests/phpt/phpinfo_07.phpt index 526777e724b..6810097fe8d 100644 --- a/profiling/tests/phpt/phpinfo_07.phpt +++ b/profiling/tests/phpt/phpinfo_07.phpt @@ -40,11 +40,21 @@ foreach ($lines as $line) { // Check that Version exists, but not its value assert(isset($values["Version"])); -// Check exact values for this set +$tailcall_vm_interrupt_workaround_applies = + PHP_VERSION_ID >= 80500 && + PHP_VERSION_ID < 80507 && + defined('ZEND_VM_KIND') && + ZEND_VM_KIND === 'ZEND_VM_KIND_TAILCALL'; + +$cpu_time_expected = $tailcall_vm_interrupt_workaround_applies + ? "false" + : "true (all experimental features enabled)"; + +// Check exact values for this set. $sections = [ ["Profiling Enabled", "true"], ["Profiling Experimental Features Enabled", "true"], - ["Experimental CPU Time Profiling Enabled", "true (all experimental features enabled)"], + ["Experimental CPU Time Profiling Enabled", $cpu_time_expected], ["Allocation Profiling Enabled", "false"], ["Exception Profiling Enabled", "false"], ["Timeline Enabled", "false"], @@ -52,9 +62,10 @@ $sections = [ ]; foreach ($sections as [$key, $expected]) { + $actual = $values[$key] ?? null; assert( - $values[$key] === $expected, - "Expected '{$expected}', found '{$values[$key]}', for key '{$key}'" + $actual === $expected, + "Expected '{$expected}', found '{$actual}', for key '{$key}'" ); } @@ -62,4 +73,4 @@ echo "Done."; ?> --EXPECT-- -Done. \ No newline at end of file +Done. From 5a44a851e2c29ffbefcbf03e8884c8e6b0cea0ff Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Thu, 21 May 2026 12:07:37 -0600 Subject: [PATCH 6/6] Improve wording of error message when disabled --- profiling/src/config.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/profiling/src/config.rs b/profiling/src/config.rs index c3660d90104..2544dfd3a6e 100644 --- a/profiling/src/config.rs +++ b/profiling/src/config.rs @@ -139,10 +139,8 @@ impl SystemSettings { && tailcall_check != bindings::ZendResult::Success { error!(concat!( - "Wall-time and CPU-time sample collection is disabled because PHP 8.5.0-8.5.6 can crash ", - "on the tailcall VM kind with VM interrupt based sample collection enabled.", - " Other profiling sample types remain enabled.", - " To enable time sample collection, upgrade PHP to 8.5.7 or newer, or use a PHP build without the tailcall VM.", + "Wall- and CPU-time sample types are disabled because their VM interrupts can crash PHP 8.5.0-8.5.6 builds that have the tailcall VM.", + " To re-enable them, upgrade PHP to 8.5.7 or newer, or use a PHP build without the tailcall VM.", " See https://github.com/php/php-src/pull/21922" )); system_settings.profiling_wall_time_enabled = false;