Skip to content

Instantly share code, notes, and snippets.

@figureone
Created October 16, 2018 01:44
Show Gist options
  • Save figureone/f83108f6c6fc4ad61bc9f5d3d88e56c3 to your computer and use it in GitHub Desktop.
Save figureone/f83108f6c6fc4ad61bc9f5d3d88e56c3 to your computer and use it in GitHub Desktop.
Profile WordPress hooks (action or filter) and log any taking more than 0.5 seconds
<?php
/**
* This little class records how long it takes each WordPress action or filter
* to execute which gives a good indicator of what hooks are being slow.
* You can then debug those hooks to see what hooked functions are causing problems.
*
* This class does NOT time the core WordPress code that is being run between hooks.
* You could use similar code to this that doesn't have an end processor to do that.
*
* @version 0.4
* @author Alex Mills (Viper007Bond)
*
* This code is released under the same license as WordPress:
* http://wordpress.org/about/license/
*
* https://gist.github.com/Viper007Bond/5192117
*/
/*
INSTALLATION:
Easiest way to integrate is to place this file in the active theme and add
the following to functions.php:
// Get execution times for all filter and action hooks running.
function whatissoslow() {
// Restrict to just AJAX calls for now.
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
require_once( dirname( __FILE__ ) . '/whatissoslow.php' );
}
}
add_action( 'after_setup_theme', 'whatissoslow' );
EXTRA STEPS:
To time individual callbacks hooked into a specific action/filter, do the
following core monkeypatches:
* Add to class variables in /wp-includes/class-wp-hook.php:
public $debug = false;
* Add to apply_filters() function in /wp-includes/class-wp-hook.php, at the
beginning of the foreach loop:
// ### DEBUG
if ( $this->debug ) {
$start = microtime( true );
}// ### DEBUG END
* And at the end of the foreach loop:
// ### DEBUG
if ( $this->debug ) {
$func = '(closure)';
if ( isset( $the_['function'] ) && is_string( $the_['function'] ) ) {
$func = $the_['function'];
} elseif ( isset( $the_['function'] ) && is_array( $the_['function'] ) ) {
$func = '';
foreach ( $the_['function'] as $f ) {
if ( is_string( $f ) ) {
$func .= ' (' . $f . ')';
} elseif ( is_object( $f ) ) {
$func .= get_class( $f );
}
}
}
error_log( ' # ' . sprintf( '%0.3f seconds', microtime( true ) - $start ) . ': ' . $func );
}// ### DEBUG END
* Enable reporting on a specific hook by adding this to do_action() in
/wp-includes/plugin.php, right before the $wp_filter[$tag]->do_action() call:
// ### DEBUG
if ( $tag === 'init' && defined( 'DOING_AJAX' ) && DOING_AJAX ) {
$wp_filter[$tag]->debug = true;
}// ### DEBUG END
Note that this example only fires on 'init' action fired in an AJAX request.
That's it!
*/
// Here's a test to make sure it's working. wp_footer action should be logged as taking 2 seconds.
add_action( 'wp_footer', function() { sleep( 2 ); } );
class WhatIsSoSlow {
public $data = array();
function __construct() {
add_action( 'all', array( $this, 'filter_start' ) );
add_action( 'shutdown', array( $this, 'results' ) );
}
// This runs first for all actions and filters.
// It starts a timer for this hook.
public function filter_start() {
$current_filter = current_filter();
$this->data[ $current_filter ][]['start'] = microtime( true );
add_filter( $current_filter, array( $this, 'filter_end' ), 99999 );
}
// This runs last (hopefully) for each hook and records the end time.
// This has problems if a hook fires inside of itself since it assumes
// the last entry in the data key for this hook is the matching pair.
public function filter_end( $filter_data = null ) {
$current_filter = current_filter();
remove_filter( $current_filter, array( $this, 'filter_end' ), 99999 );
end( $this->data[ $current_filter ] );
$last_key = key( $this->data[ $current_filter ] );
$this->data[ $current_filter ][ $last_key ]['stop'] = microtime( true );
return $filter_data;
}
// Processes the results and var_dump()'s them. TODO: Debug bar panel?
public function results() {
$results = array();
foreach ( $this->data as $filter => $calls ) {
foreach ( $calls as $call ) {
// Skip filters with no end point (i.e. the hook this function is hooked into)
if ( ! isset( $call['stop'] ) ) {
continue;
}
if ( ! isset( $results[ $filter ] ) ) {
$results[ $filter ] = 0;
}
$results[ $filter ] = $results[ $filter ] + ( $call['stop'] - $call['start'] );
}
}
asort( $results, SORT_NUMERIC );
$results = array_reverse( $results );
// error_log(print_r($_SERVER,true));
// error_log(print_r($results,true));
// var_dump( $results );
foreach ( $results as $hook => $time ) {
if ( $time >= 0.5 ) {
$time = sprintf( '%0.2f', $time );
error_log( "### Slow hook: $hook ($time seconds) (${_SERVER['PHP_SELF']})." );
}
}
}
}
new WhatIsSoSlow();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment