Skip to content

Instantly share code, notes, and snippets.

@kurtpayne
Created March 9, 2012 22:43
Show Gist options
  • Save kurtpayne/2009105 to your computer and use it in GitHub Desktop.
Save kurtpayne/2009105 to your computer and use it in GitHub Desktop.
P3 Profiler Reader
<?php
if ( !defined('P3_PATH') )
die( 'Forbidden ');
/**
* Performance Profile Reader
*
* @author GoDaddy.com
* @version 1.0
* @package P3_Profiler
*/
class P3_Profile_Reader {
/**
* Total site load time (profile + theme + core + plugins)
* @var float
*/
public $total_time = 0;
/**
* Total site load time (theme + core + plugins - profile)
* @var float
*/
public $site_time = 0;
/**
* Time spent in themes
* Calls which spend time in multiple areas are prioritized prioritized
* first as plugins, second as themes, lastly as core.
* @var float
*/
public $theme_time = 0;
/**
* Time spent in plugins.
* Calls which spend time in multiple areas are prioritized prioritized
* first as plugins, second as themes, lastly as core.
* @var float
*/
public $plugin_time = 0;
/**
* Time spent in the profiler code.
* @var float
*/
public $profile_time = 0;
/**
* Time spent in themes
* Calls which spend time in multiple areas are prioritized prioritized
* first as plugins, second as themes, lastly as core.
* @var float
*/
public $core_time = 0;
/**
* Memory usage per visit as reported by memory_get_peak_usage(true)
* @var float
*/
public $memory = 0;
/**
* Number of plugin related function calls (does not include php internal
* calls due to a limitation of how the tick handler works)
* @var int
*/
public $plugin_calls = 0;
/**
* Extracted URL from the profile
* @var string
*/
public $report_url = '';
/**
* Extracted date from the first visit in the profile
* @var int
*/
public $report_date = '';
/**
* Total number of mysql queries as reported by get_num_queries()
* @var int
*/
public $queries = 0;
/**
* Total number of visits recorded in the profile
* @var int
*/
public $visits = 0;
/**
* List of detected plugins
* @var array
*/
public $detected_plugins = array();
/**
* Array of total time spent in each plugin
* key = plugin name
* value = seconds (float)
* @var array
*/
public $plugin_times = array();
/**
* Theme name, as determined from the file path
* @var string
*/
public $theme_name = '';
/**
* Averaged values for the report
* @var array
*/
public $averages = array(
'total' => 0,
'site' => 0,
'core' => 0,
'plugins' => 0,
'profile' => 0,
'theme' => 0,
'memory' => 0,
'plugin_calls' => 0,
'queries' => 0,
'observed' => 0,
'expected' => 0,
'drift' => 0,
'plugin_impact' => 0,
);
/**
* Internal profile data
* @var array
*/
private $_data = array();
/**
* Scan name, correlates to a file name, with .json stripped off
* @var string
*/
public $profile_name = '';
/**
* Constructor
* @param string $file Full path to the profile json file
* @return P3_Profile_Reader
*/
public function __construct( $file ) {
// Open the file
$fp = fopen( $file, 'r' );
if ( FALSE === $fp ) {
throw new Exception( 'Cannot open ' . $file );
}
// Decode each line. Each line is a separate json object. Whenever a
// a visit is recorded, a new line is added to the file.
while ( !feof( $fp ) ) {
$line = fgets( $fp );
if ( empty( $line) ) {
continue;
}
$tmp = json_decode( $line );
if ( null === $tmp ) {
throw new Exception( 'Cannot parse ' . $file );
fclose( $fp );
}
$this->_data[] = $tmp;
}
// Close the file
fclose( $fp );
// Set the profile name
$this->profile_name = preg_replace( '/\.json$/', '', basename ( $file ) );
// Parse the data
$this->_parse();
}
/**
* Parse from $this->_data and fill in the rest of the member vars
* @return void
*/
private function _parse() {
// Check for empty data
if ( empty( $this->_data ) ) {
throw new P3_Profile_No_Data_Exception('No visits recorded during this profiling session.');
}
foreach ( $this->_data as $o ) {
// Set report meta-data
if ( empty( $this->report_date ) ) {
$this->report_date = strtotime( $o->date );
$scheme = parse_url( $o->url, PHP_URL_SCHEME );
$host = parse_url( $o->url, PHP_URL_HOST );
$path = parse_url( $o->url, PHP_URL_PATH );
$this->report_url = sprintf( '%s://%s%s', $scheme, $host, $path );
$this->visits = count( $this->_data );
}
// Set total times / queries / function calls
$this->total_time += $o->runtime->total;
$this->site_time += ( $o->runtime->total - $o->runtime->profile );
$this->theme_time += $o->runtime->theme;
$this->plugin_time += $o->runtime->plugins;
$this->profile_time += $o->runtime->profile;
$this->core_time += $o->runtime->wordpress;
$this->memory += $o->memory;
$this->plugin_calls += $o->stacksize;
$this->queries += $o->queries;
// Loop through the plugin data
foreach ( $o->runtime->breakdown as $k => $v ) {
if ( !array_key_exists( $k, $this->plugin_times ) ) {
$this->plugin_times[$k] = 0;
}
$this->plugin_times[$k] += $v;
}
}
// Fix plugin names and average out plugin times
$tmp = $this->plugin_times;
$this->plugin_times = array();
foreach ( $tmp as $k => $v ) {
$k = $this->_get_plugin_name( $k );
$this->plugin_times[$k] = $v / $this->visits;
}
// Get a list of the plugins we detected
$this->detected_plugins = array_keys( $this->plugin_times );
sort( $this->detected_plugins );
// Calculate the averages
$this->_get_averages();
// Get theme name
if ( property_exists( $this->_data[0], 'theme_name') ) {
$this->theme_name = str_replace( realpath( WP_CONTENT_DIR . '/themes/' ), '', realpath( $this->_data[0]->theme_name ) );
$this->theme_name = preg_replace('|^[\\\/]+([^\\\/]+)[\\\/]+.*|', '$1', $this->theme_name);
$this->theme_name = $this->_get_theme_name( $this->theme_name );
} else {
$this->theme_name = 'unknown';
}
}
/**
* Calculate the average values
* @return void
*/
private function _get_averages() {
if ( $this->visits <= 0 ) {
return;
}
$this->averages = array(
'total' => $this->total_time / $this->visits,
'site' => ( $this->total_time - $this->profile_time ) / $this->visits,
'core' => $this->core_time / $this->visits,
'plugins' => $this->plugin_time / $this->visits,
'profile' => $this->profile_time / $this->visits,
'theme' => $this->theme_time / $this->visits,
'memory' => $this->memory / $this->visits,
'plugin_calls' => $this->plugin_calls / $this->visits,
'queries' => $this->queries / $this->visits,
'observed' => $this->total_time / $this->visits,
'expected' => ( $this->theme_time + $this->core_time + $this->profile_time + $this->plugin_time) / $this->visits,
);
$this->averages['drift'] = $this->averages['observed'] - $this->averages['expected'];
$this->averages['plugin_impact'] = $this->averages['plugins'] / $this->averages['site'] * 100;
}
/**
* Return a list of runtimes times by url
* Where the key is the url and the value is an array of runtime values
* in seconds (float)
* @return array
*/
public function get_stats_by_url() {
$ret = array();
foreach ( $this->_data as $o ) {
$tmp = array(
'url' => $o->url,
'core' => $o->runtime->wordpress,
'plugins' => $o->runtime->plugins,
'profile' => $o->runtime->profile,
'theme' => $o->runtime->theme,
'queries' => $o->queries,
'breakdown' => array()
);
foreach ( $o->runtime->breakdown as $k => $v ) {
$name = $this->_get_plugin_name( $k );
if ( !array_key_exists( $name, $tmp['breakdown'] ) ) {
$tmp['breakdown'][$name] = 0;
}
$tmp['breakdown'][$name] += $v;
}
$ret[] = $tmp;
}
return $ret;
}
/**
* Translate a plugin name
* Uses get_plugin_data if available.
* @param string $plugin Plugin name (possible paths will be guessed)
* @return string
*/
private function _get_plugin_name( $plugin ) {
if ( function_exists( 'get_plugin_data' ) ) {
$plugin_info = array();
$possible_paths = array(
WP_PLUGIN_DIR . "/$plugin.php",
WP_PLUGIN_DIR . "/$plugin/$plugin.php",
WPMU_PLUGIN_DIR . "/$plugin.php"
);
foreach ( $possible_paths as $path ) {
if ( file_exists( $path ) ) {
$plugin_info = get_plugin_data( $path );
if ( !empty( $plugin_info ) && !empty( $plugin_info['Name'] ) ) {
return $plugin_info['Name'];
}
}
}
}
return $this->_format_name( $plugin );
}
/**
* Translate a theme name
* Uses get_theme_data if available.
* @param string $plugin Theme name (possible path will be guessed)
* @return string
*/
private function _get_theme_name( $theme ) {
if ( function_exists( 'get_theme_data' ) && file_exists( WP_CONTENT_DIR . '/themes/' . $theme . '/style.css' ) ) {
$theme_info = get_theme_data( WP_CONTENT_DIR . '/themes/' . $theme . '/style.css' );
if ( !empty( $theme_info ) && !empty( $theme_info['Name'] ) ) {
return $theme_info['Name'];
}
}
return $this->_format_name( $theme );
}
/**
* Format plugin / theme name. This is only to be used if
* get_plugin_data() / get_theme_data() aren't available or if the
* original files are missing
* @param string $name
* @return string
*/
private function _format_name( $name ) {
return ucwords( str_replace( array( '-', '_' ), ' ', $name ) );
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment