Xdebug Profiling WordPress Actions and Filters
Xdebug is a great profiling tool for PHP code, but it has one little drawback in WordPress that results in a profiling nightmare – WP_Hook::apply_filters. The profiler lumps together all calls to WordPress actions and filters without distinguishing them as different execution paths. This is bad and not useful at all, resulting in a tangled mess of cycles.

diff -u src/base/base.c /tmp/base.c
--- src/base/base.c 2021-04-08 13:34:41.000000000 +0300
+++ patch/base.c 2021-08-27 20:35:46.191578814 +0300
@@ -225,6 +225,29 @@
if (edata->func->common.function_name) {
if (edata->func->common.fn_flags & ZEND_ACC_CLOSURE) {
tmp->function = xdebug_wrap_closure_location_around_function_name(&edata->func->op_array, STR_NAME_VAL(edata->func->common.function_name));
+ } else if (tmp->type == XFUNC_MEMBER && strcmp(edata->func->common.function_name->val, "apply_filters") == 0 && strcmp(edata->func->common.scope->name->val, "WP_Hook") == 0 ) {
+ zval *tag, *current_filter;
+
+ current_filter = zend_hash_str_find(&EG(symbol_table), "wp_current_filter", 17);
+
+ if (NULL == current_filter) {
+ goto normal_after_all;
+ }
+
+ if (Z_TYPE_P(current_filter) == IS_INDIRECT) {
+ current_filter = Z_INDIRECT_P(current_filter);
+ }
+
+ ZVAL_DEREF(current_filter);
+
+ if (Z_TYPE_P(current_filter) != IS_ARRAY || (tag = zend_hash_get_current_data(Z_ARRVAL_P(current_filter))) == NULL || Z_TYPE_P(tag) != IS_STRING) {
+ goto normal_after_all;
+ }
+
+ tmp->function = xdebug_sprintf(
+ "apply_filters{%s}",
+ Z_STRVAL_P(tag)
+ );
} else if (strncmp(edata->func->common.function_name->val, "call_user_func", 14) == 0) {
zend_string *fname = NULL;
int lineno = 0;
The result is a nice breakdown and cycle untangling:

Very similar code can be used with XHProf which helps as well. Too bad Xdebug has no PHP API to augment the current stack on exit, something like xdebug_profile_override_function( "apply_filters{$wp_current_filter}" );.