mirror of
https://hk.gh-proxy.com/https://github.com/wp-cli/profile-command.git
synced 2025-08-20 06:30:52 +08:00
*Proof of concept to enable intermediate hook profiling on PHP7
This commit is contained in:
parent
343063dcc8
commit
ce8eb8987e
3 changed files with 179 additions and 9 deletions
12
command.php
Normal file
12
command.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
if ( class_exists( 'WP_CLI' ) ) {
|
||||
require_once dirname( __FILE__ ) . '/inc/class-command.php';
|
||||
require_once dirname( __FILE__ ) . '/inc/class-formatter.php';
|
||||
require_once dirname( __FILE__ ) . '/inc/class-logger.php';
|
||||
require_once dirname( __FILE__ ) . '/inc/class-profiler.php';
|
||||
if ( version_compare( PHP_VERSION, '7.0.0' ) >= 0 ) {
|
||||
require_once dirname( __FILE__ ) . '/inc/class-filestreamwrapper.php';
|
||||
}
|
||||
WP_CLI::add_command( 'profile', 'runcommand\Profile\Command' );
|
||||
}
|
150
inc/class-filestreamwrapper.php
Normal file
150
inc/class-filestreamwrapper.php
Normal file
|
@ -0,0 +1,150 @@
|
|||
<?php
|
||||
|
||||
namespace runcommand\Profile;
|
||||
|
||||
/**
|
||||
* Stream Wrapper Class to create a temporary file with ticks enabled
|
||||
* Props to https://github.com/hakre for the original P.O.C.
|
||||
*
|
||||
* Class FileStreamWrapper
|
||||
*
|
||||
* @package runcommand\Profile
|
||||
* @author Derrick Hammer
|
||||
*/
|
||||
class FileStreamWrapper {
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
const PROTOCOL = 'file';
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
const PHP_TICK = "\ndeclare(ticks=1);\n";
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
public $context;
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
private $handle;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $file;
|
||||
|
||||
public function stream_open( $path, $mode, $options, &$opened_path ) {
|
||||
|
||||
if ( isset( $this->handle ) ) {
|
||||
throw new \UnexpectedValueException( 'Handle congruency' );
|
||||
}
|
||||
|
||||
$use_include_path = true;
|
||||
|
||||
$context = $this->context;
|
||||
if ( null === $context ) {
|
||||
$context = stream_context_get_default();
|
||||
}
|
||||
self::restore();
|
||||
$data = @file_get_contents( $path );
|
||||
|
||||
if ( false !== $data && preg_match( '~^(<\?php\s*)$~m', $data ) ) {
|
||||
$result = preg_replace(
|
||||
'~^(<\?php\s*)$~m',
|
||||
'\\0' . self::PHP_TICK,
|
||||
$data,
|
||||
1
|
||||
);
|
||||
$pathinfo = pathinfo( $path );
|
||||
$this->file = $pathinfo['dirname'] . DIRECTORY_SEPARATOR . $pathinfo['filename'] . '_profile.' . $pathinfo['extension'];
|
||||
file_put_contents( $this->file, $result );
|
||||
$handle = @fopen( $this->file, $mode, $use_include_path, $context );
|
||||
|
||||
} else {
|
||||
$handle = @fopen( $path, $mode, $use_include_path, $context );
|
||||
}
|
||||
self::init();
|
||||
if ( false === $handle ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
$meta = stream_get_meta_data( $handle );
|
||||
if ( ! isset( $meta['uri'] ) ) {
|
||||
throw new \UnexpectedValueException( 'Uri not in meta data' );
|
||||
}
|
||||
|
||||
$opened_path = $meta['uri'];
|
||||
|
||||
$this->handle = $handle;
|
||||
|
||||
if ( $this->file ) {
|
||||
register_shutdown_function( [ $this, 'cleanup' ] );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function restore() {
|
||||
$result = stream_wrapper_restore( self::PROTOCOL );
|
||||
if ( false === $result ) {
|
||||
throw new \UnexpectedValueException( 'Failed to restore' );
|
||||
}
|
||||
}
|
||||
|
||||
public static function init() {
|
||||
$result = stream_wrapper_unregister( self::PROTOCOL );
|
||||
if ( false === $result ) {
|
||||
throw new \UnexpectedValueException( 'Failed to unregister' );
|
||||
}
|
||||
stream_wrapper_register( self::PROTOCOL, '\runcommand\Profile\FileStreamWrapper', 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function stream_stat() {
|
||||
self::restore();
|
||||
$array = @fstat( $this->handle );
|
||||
self::init();
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $count
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function stream_read( $count ) {
|
||||
self::restore();
|
||||
$result = fread( $this->handle, $count );
|
||||
self::init();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function stream_eof() {
|
||||
self::restore();
|
||||
$result = @feof( $this->handle );
|
||||
self::init();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function stream_set_option( $option, $arg1, $arg2 ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat( $path, $flags ) {
|
||||
self::restore();
|
||||
$array = @stat( $path );
|
||||
self::init();
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
public function cleanup() {
|
||||
@unlink( $this->file );
|
||||
}
|
||||
}
|
|
@ -47,6 +47,7 @@ class Profiler {
|
|||
private $tick_query_offset = null;
|
||||
private $tick_cache_hit_offset = null;
|
||||
private $tick_cache_miss_offset = null;
|
||||
private $is_php7 = false;
|
||||
|
||||
public function __construct( $type, $focus ) {
|
||||
$this->type = $type;
|
||||
|
@ -99,6 +100,9 @@ class Profiler {
|
|||
}
|
||||
}
|
||||
);
|
||||
|
||||
$this->is_php7 = version_compare( PHP_VERSION, '7.0.0' ) >= 0;
|
||||
|
||||
if ( 'hook' === $this->type
|
||||
&& ':before' === substr( $this->focus, -7, 7 ) ) {
|
||||
$stage_hooks = array();
|
||||
|
@ -131,10 +135,6 @@ class Profiler {
|
|||
*/
|
||||
public function wp_tick_profile_begin( $value = null ) {
|
||||
|
||||
if ( version_compare( PHP_VERSION, '7.0.0' ) >= 0 ) {
|
||||
WP_CLI::error( 'Profiling intermediate hooks is broken in PHP 7, see https://bugs.php.net/bug.php?id=72966' );
|
||||
}
|
||||
|
||||
// Disable opcode optimizers. These "optimize" calls out of the stack
|
||||
// and hide calls from the tick handler and backtraces.
|
||||
// Copied from P3 Profiler
|
||||
|
@ -157,8 +157,13 @@ class Profiler {
|
|||
// WordPress.PHP.NoSilencedErrors.Discouraged -- ini_set can be disabled on server.
|
||||
}
|
||||
|
||||
register_tick_function( array( $this, 'handle_function_tick' ) );
|
||||
declare( ticks = 1 );
|
||||
declare( ticks=1 );
|
||||
|
||||
if ( $this->is_php7 ) {
|
||||
register_tick_function( array( $this, 'handle_function_tick' ) );
|
||||
FileStreamWrapper::init();
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
@ -167,6 +172,7 @@ class Profiler {
|
|||
*/
|
||||
public function wp_tick_profile_end( $value = null ) {
|
||||
unregister_tick_function( array( $this, 'handle_function_tick' ) );
|
||||
FileStreamWrapper::restore();
|
||||
$this->tick_callback = null;
|
||||
return $value;
|
||||
}
|
||||
|
@ -340,8 +346,9 @@ class Profiler {
|
|||
|
||||
$location = '';
|
||||
$callback = '';
|
||||
if ( in_array( strtolower( $frame['function'] ), array( 'include', 'require', 'include_once', 'require_once' ), true ) ) {
|
||||
$callback = $frame['function'] . " '" . $frame['args'][0] . "'";
|
||||
if ( in_array( strtolower( $frame['function'] ), [ 'include', 'require', 'include_once', 'require_once' ] ) ) {
|
||||
$ext = pathinfo( $frame['args'][0] , PATHINFO_EXTENSION);
|
||||
$callback = $frame['function'] . " '" . str_replace( "_profile.{$ext}" , ".{$ext}", $frame['args'][0]) . "'";
|
||||
} elseif ( isset( $frame['object'] ) && method_exists( $frame['object'], $frame['function'] ) ) {
|
||||
$callback = get_class( $frame['object'] ) . '->' . $frame['function'] . '()';
|
||||
} elseif ( isset( $frame['class'] ) && method_exists( $frame['class'], $frame['function'] ) ) {
|
||||
|
@ -358,7 +365,8 @@ class Profiler {
|
|||
}
|
||||
|
||||
if ( isset( $frame['file'] ) ) {
|
||||
$location = $frame['file'];
|
||||
$ext = pathinfo( $frame['file'], PATHINFO_EXTENSION );
|
||||
$location = str_replace( "_profile.{$ext}", ".{$ext}", $frame['args'][0] );
|
||||
if ( isset( $frame['line'] ) ) {
|
||||
$location .= ':' . $frame['line'];
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue