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.

Xdebug WordPress WP_Hook->apply_filters” width=”959″ height=”457″ class=”aligncenter size-full wp-image-2160″ /></p>
<p>Xdebug can be patched, however (patch below is for version 3.0.4), to differentiate calls to <code>P_Hook::apply_filters</code> and tag them with the actual filter name:</p>
<p><span id=

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:

Xdebug untangled WP_Hook::apply_filters

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}" );.