| @@ -6,13 +6,13 @@ | ||
| 6 | 6 | | | | / _ \ / _` |/ _ \ | |_) | '__/ _ \| |_| | |/ _ \ '__| | |
| 7 | 7 | | | |__| (_) | (_| | __/ | __/| | | (_) | _| | | __/ | | |
| 8 | 8 | | \____\___/ \__,_|\___| |_| |_| \___/|_| |_|_|\___|_| | |
| 9 | 9 | | | |
| 10 | - | (c) Jerome Bruandet ~ https://code-profiler.com/ | | |
| 10 | + | (c) Jerome Bruandet ~ https://nintechnet.com/codeprofiler/ | | |
| 11 | 11 | +=====================================================================+ |
| 12 | 12 | */ |
| 13 | 13 | |
| 14 | -if (! defined( 'ABSPATH' ) ) { die( 'Forbidden' ); } | |
| 14 | +if (! defined('ABSPATH') ) { die('Forbidden'); } | |
| 15 | 15 | |
| 16 | 16 | // ===================================================================== |
| 17 | 17 | |
| 18 | 18 | class CodeProfiler_Profiler { |
| @@ -17,17 +17,22 @@ | ||
| 17 | 17 | |
| 18 | 18 | class CodeProfiler_Profiler { |
| 19 | 19 | |
| 20 | 20 | static $tick_list; |
| 21 | - static $buffer = 10000000; | |
| 21 | + static $buffer; | |
| 22 | 22 | static $metrics; |
| 23 | - | |
| 23 | + static $fh; | |
| 24 | + static $connections_list = []; | |
| 25 | + static $connections_start; | |
| 26 | + static $exclusions = []; | |
| 24 | 27 | private $tmp_iostats; |
| 25 | 28 | private $tmp_summary; |
| 26 | 29 | private $tmp_diskio; |
| 27 | - private $tmp_ticks; | |
| 30 | + private $tmp_calls; | |
| 31 | + private $tmp_connections; | |
| 32 | + private $tmp_rerun; | |
| 28 | 33 | |
| 29 | - /* | |
| 34 | + /** | |
| 30 | 35 | * Initialize |
| 31 | 36 | */ |
| 32 | 37 | public function __construct() { |
| 33 | 38 | |
| @@ -32,37 +37,114 @@ | ||
| 32 | 37 | public function __construct() { |
| 33 | 38 | |
| 34 | 39 | $microtime = sanitize_file_name( $_REQUEST['CODE_PROFILER_ON'] ); |
| 35 | 40 | |
| 36 | - $this->tmp_summary = CODE_PROFILER_UPLOAD_DIR ."/$microtime." . | |
| 37 | - CODE_PROFILER_TMP_SUMMARY_LOG; | |
| 38 | - $this->tmp_iostats = CODE_PROFILER_UPLOAD_DIR ."/$microtime." . | |
| 39 | - CODE_PROFILER_TMP_IOSTATS_LOG; | |
| 40 | - $this->tmp_ticks = CODE_PROFILER_UPLOAD_DIR ."/$microtime." . | |
| 41 | - CODE_PROFILER_TMP_TICKS_LOG; | |
| 42 | - $this->tmp_diskio = CODE_PROFILER_UPLOAD_DIR ."/$microtime." . | |
| 43 | - CODE_PROFILER_TMP_DISKIO_LOG; | |
| 41 | + $this->tmp_summary = CODE_PROFILER_UPLOAD_DIR ."/$microtime." . | |
| 42 | + CODE_PROFILER_TMP_SUMMARY_LOG; | |
| 43 | + $this->tmp_iostats = CODE_PROFILER_UPLOAD_DIR ."/$microtime." . | |
| 44 | + CODE_PROFILER_TMP_IOSTATS_LOG; | |
| 45 | + $this->tmp_calls = CODE_PROFILER_UPLOAD_DIR ."/$microtime." . | |
| 46 | + CODE_PROFILER_TMP_CALLS_LOG; | |
| 47 | + $this->tmp_diskio = CODE_PROFILER_UPLOAD_DIR ."/$microtime." . | |
| 48 | + CODE_PROFILER_TMP_DISKIO_LOG; | |
| 49 | + $this->tmp_connections = CODE_PROFILER_UPLOAD_DIR ."/$microtime." . | |
| 50 | + CODE_PROFILER_TMP_CONNECTIONS_LOG; | |
| 51 | + $this->tmp_rerun = CODE_PROFILER_UPLOAD_DIR ."/$microtime." . | |
| 52 | + CODE_PROFILER_TMP_RERUN_LOG; | |
| 44 | 53 | |
| 45 | - // Clear the temporary log because the buffer will be written | |
| 46 | - // with the FILE_APPEND flag | |
| 47 | - if ( file_exists( $this->tmp_ticks ) ) { | |
| 48 | - unlink( $this->tmp_ticks ); | |
| 54 | + if ( function_exists('hrtime') ) { | |
| 55 | + // PHP >=7.3 | |
| 56 | + self::$metrics = 'hrtime'; | |
| 57 | + } else { | |
| 58 | + self::$metrics = 'microtime'; | |
| 49 | 59 | } |
| 50 | 60 | |
| 51 | - if ( version_compare( PHP_VERSION, '7.3', '<' ) ) { | |
| 52 | - self::$metrics = 'microtime'; | |
| 61 | + // Select theme (stylesheet::template) | |
| 62 | + if (! empty( $_SERVER['HTTP_THEME'] ) ) { | |
| 63 | + | |
| 64 | + $theme = explode('::', $_SERVER['HTTP_THEME'] ); | |
| 65 | + if (! empty( $theme[1] ) ) { | |
| 66 | + define('CODE_PROFILER_STYLESHEET', sanitize_file_name( $theme[0] ) ); | |
| 67 | + define('CODE_PROFILER_TEMPLATE', sanitize_file_name( $theme[1] ) ); | |
| 68 | + | |
| 69 | + if ( is_file( WP_CONTENT_DIR .'/themes/'. CODE_PROFILER_STYLESHEET .'/style.css' ) ) { | |
| 70 | + | |
| 71 | + add_filter('pre_option_template', function () { | |
| 72 | + return CODE_PROFILER_TEMPLATE; | |
| 73 | + }, 1000 ); | |
| 74 | + | |
| 75 | + add_filter('pre_option_stylesheet', function () { | |
| 76 | + return CODE_PROFILER_STYLESHEET; | |
| 77 | + }, 1000 ); | |
| 78 | + | |
| 79 | + code_profiler_log_debug( | |
| 80 | + sprintf( | |
| 81 | + // We cannot load translation here. | |
| 82 | + 'Setting theme to %s', | |
| 83 | + CODE_PROFILER_STYLESHEET | |
| 84 | + ) | |
| 85 | + ); | |
| 86 | + } else { | |
| 87 | + code_profiler_log_error( | |
| 88 | + sprintf( | |
| 89 | + // We cannot load translation here. | |
| 90 | + 'Unable to switch theme, %s not found', | |
| 91 | + CODE_PROFILER_STYLESHEET | |
| 92 | + ) | |
| 93 | + ); | |
| 94 | + } | |
| 95 | + } | |
| 96 | + } | |
| 97 | + | |
| 98 | + $cp_options = get_option('code-profiler'); | |
| 99 | + if ( empty( $cp_options['accuracy'] ) ) { | |
| 100 | + define('CODE_PROFILER_TICKS', 1 ); | |
| 53 | 101 | } else { |
| 54 | - self::$metrics = 'hrtime'; | |
| 102 | + define('CODE_PROFILER_TICKS', (int) $cp_options['accuracy'] ); | |
| 55 | 103 | } |
| 104 | + define('CODE_PROFILER_LENGTH', 17 + strlen( (string) CODE_PROFILER_TICKS ) ); | |
| 56 | 105 | |
| 57 | - register_shutdown_function( array( $this, 'code_profiler_shutdown' ) ); | |
| 106 | + if ( empty( $cp_options['buffer'] ) || | |
| 107 | + ! preg_match('/^(?:[1-9]|10)$/', $cp_options['buffer'] ) ) { | |
| 58 | 108 | |
| 109 | + $recommended = code_profiler_suggested_memory(); | |
| 110 | + self::$buffer = (int) $recommended * 1000000; | |
| 111 | + } else { | |
| 112 | + self::$buffer = $cp_options['buffer'] * 1000000; | |
| 113 | + } | |
| 114 | + code_profiler_log_debug( | |
| 115 | + sprintf( | |
| 116 | + // We cannot load translation here. | |
| 117 | + 'Setting size of memory buffer to %sMB', | |
| 118 | + self::$buffer / 1000000 | |
| 119 | + ) | |
| 120 | + ); | |
| 121 | + | |
| 122 | + // File & folder exclusions | |
| 123 | + if ( isset( $cp_options['exclusions'] ) ) { | |
| 124 | + self::$exclusions = json_decode( $cp_options['exclusions'] ); | |
| 125 | + } | |
| 126 | + | |
| 127 | + add_filter('pre_http_request', [ $this, 'pre_http_request'], 10000, 3 ); | |
| 128 | + add_action('http_api_debug', [ $this, 'http_api_debug'], 10000, 5 ); | |
| 129 | + register_shutdown_function( [ $this, 'code_profiler_shutdown'] ); | |
| 130 | + code_profiler_log_debug( | |
| 131 | + // We cannot load translation here. | |
| 132 | + 'Starting profiler' | |
| 133 | + ); | |
| 59 | 134 | require 'class-stream.php'; |
| 135 | + | |
| 136 | + // Clear the temporary log because the buffer will be appended to it | |
| 137 | + if ( file_exists( $this->tmp_calls ) ) { | |
| 138 | + unlink( $this->tmp_calls ); | |
| 139 | + } | |
| 140 | + self::$fh = fopen( $this->tmp_calls, 'wb'); | |
| 141 | + | |
| 60 | 142 | CodeProfiler_Stream::start(); |
| 61 | - register_tick_function( array( $this, 'code_profiler_tick_handler' ) ); | |
| 143 | + register_tick_function( array( $this, 'code_profiler_tick_handler') ); | |
| 62 | 144 | } |
| 63 | 145 | |
| 64 | - /* | |
| 146 | + /** | |
| 65 | 147 | * Save data and check for potential PHP errors. |
| 66 | 148 | */ |
| 67 | 149 | public function code_profiler_shutdown() { |
| 68 | 150 | |
| @@ -67,10 +149,19 @@ | ||
| 67 | 149 | public function code_profiler_shutdown() { |
| 68 | 150 | |
| 69 | 151 | CodeProfiler_Stream::stop(); |
| 70 | 152 | |
| 71 | - $summary['memory'] = memory_get_peak_usage(); | |
| 72 | - $summary['queries'] = get_num_queries() - 2; | |
| 153 | + fclose( self::$fh ); | |
| 154 | + | |
| 155 | + // Summary file (metadata) | |
| 156 | + $summary = []; | |
| 157 | + if ( is_file( $this->tmp_rerun ) ) { | |
| 158 | + $summary['rerun'] = json_decode( file_get_contents( $this->tmp_rerun ), true ); | |
| 159 | + unlink( $this->tmp_rerun ); | |
| 160 | + } | |
| 161 | + $summary['memory'] = memory_get_peak_usage(); | |
| 162 | + $summary['queries'] = get_num_queries() - 2; | |
| 163 | + $summary['precision'] = CODE_PROFILER_TICKS; | |
| 73 | 164 | file_put_contents( $this->tmp_summary, json_encode( $summary ) ); |
| 74 | 165 | |
| 75 | 166 | // Catch potential error |
| 76 | 167 | $e = error_get_last(); |
| @@ -90,11 +181,11 @@ | ||
| 90 | 181 | if ( $res === false ) { |
| 91 | 182 | $msg = sprintf( $err_msg, $this->tmp_iostats ); |
| 92 | 183 | code_profiler_log_error( $msg ); |
| 93 | 184 | } |
| 94 | - $res = file_put_contents( $this->tmp_ticks, self::$tick_list, FILE_APPEND ); | |
| 185 | + $res = file_put_contents( $this->tmp_calls, self::$tick_list, FILE_APPEND ); | |
| 95 | 186 | if ( $res === false ) { |
| 96 | - $msg = sprintf( $err_msg, $this->tmp_ticks ); | |
| 187 | + $msg = sprintf( $err_msg, $this->tmp_calls ); | |
| 97 | 188 | code_profiler_log_error( $msg ); |
| 98 | 189 | } |
| 99 | 190 | $res = file_put_contents( |
| 100 | 191 | $this->tmp_diskio, "read\t". CodeProfiler_Stream::$io_read ."\nwrite\t". |
| @@ -99,21 +190,19 @@ | ||
| 99 | 190 | $res = file_put_contents( |
| 100 | 191 | $this->tmp_diskio, "read\t". CodeProfiler_Stream::$io_read ."\nwrite\t". |
| 101 | 192 | CodeProfiler_Stream::$io_write |
| 102 | 193 | ); |
| 103 | - | |
| 194 | + if (! empty( self::$connections_list ) ) { | |
| 195 | + file_put_contents( $this->tmp_connections, json_encode( self::$connections_list) ); | |
| 196 | + } | |
| 104 | 197 | } |
| 105 | 198 | |
| 106 | - /* | |
| 199 | + /** | |
| 107 | 200 | * Our tick handler. |
| 108 | 201 | */ |
| 109 | 202 | public function code_profiler_tick_handler() { |
| 110 | 203 | |
| 111 | - if ( self::$metrics == 'hrtime' ) { | |
| 112 | - $start = hrtime( true ); | |
| 113 | - } else { | |
| 114 | - $start = microtime( true ); | |
| 115 | - } | |
| 204 | + $start = $this->time(); | |
| 116 | 205 | |
| 117 | 206 | $backtrace = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 2 ); |
| 118 | 207 | if ( isset( $backtrace[1] ) ) { |
| 119 | 208 | $el = $backtrace[1]; |
| @@ -121,9 +210,13 @@ | ||
| 121 | 210 | $el = $backtrace[0]; |
| 122 | 211 | } |
| 123 | 212 | |
| 124 | 213 | if ( empty( $el['file'] ) ) { |
| 125 | - return; | |
| 214 | + if ( isset( $backtrace[0]['file'] ) ) { | |
| 215 | + $el['file'] = $backtrace[0]['file']; | |
| 216 | + } else { | |
| 217 | + return; | |
| 218 | + } | |
| 126 | 219 | } |
| 127 | 220 | |
| 128 | 221 | self::$tick_list .= "{$el['file']}\t"; |
| 129 | 222 | if ( isset( $backtrace[0]['file'][0] ) ) { |
| @@ -130,21 +223,55 @@ | ||
| 130 | 223 | self::$tick_list .= $backtrace[0]['file']; |
| 131 | 224 | } else { |
| 132 | 225 | self::$tick_list .= '-'; |
| 133 | 226 | } |
| 227 | + $backtrace = null; | |
| 134 | 228 | |
| 135 | - if ( self::$metrics == 'hrtime' ) { | |
| 136 | - self::$tick_list .="\t{$start}\t". hrtime(true) ."\n"; | |
| 137 | - } else { | |
| 138 | - self::$tick_list .="\t{$start}\t". microtime(true) ."\n"; | |
| 139 | - } | |
| 140 | - | |
| 141 | 229 | // Buffer can grow *very* big hence we flush it every 10MB by default |
| 142 | 230 | if ( strlen( self::$tick_list ) > self::$buffer ) { |
| 143 | 231 | CodeProfiler_Stream::stop(); |
| 144 | - file_put_contents( $this->tmp_ticks, self::$tick_list, FILE_APPEND ); | |
| 232 | + fwrite( self::$fh, self::$tick_list ."\t{$start}\t". $this->time() ."\n" ); | |
| 145 | 233 | CodeProfiler_Stream::start(); |
| 146 | 234 | self::$tick_list = ''; |
| 235 | + | |
| 236 | + } else { | |
| 237 | + self::$tick_list .="\t{$start}\t". $this->time() ."\n"; | |
| 238 | + } | |
| 239 | + } | |
| 240 | + | |
| 241 | + | |
| 242 | + /** | |
| 243 | + * HTTP API request. | |
| 244 | + */ | |
| 245 | + public function pre_http_request( $preempt, $r, $url ) { | |
| 246 | + | |
| 247 | + if ( empty( $url ) ) { | |
| 248 | + return false; | |
| 249 | + } | |
| 250 | + $backtrace = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); | |
| 251 | + self::$connections_start = $this->time(); | |
| 252 | + self::$connections_list[ self::$connections_start ]['bt'] = $backtrace; | |
| 253 | + return false; | |
| 254 | + } | |
| 255 | + | |
| 256 | + | |
| 257 | + /** | |
| 258 | + * HTTP API response. | |
| 259 | + */ | |
| 260 | + public function http_api_debug( $response, $context, $class, $parsed_args, $url ) { | |
| 261 | + | |
| 262 | + self::$connections_list[ self::$connections_start ]['stop'] = $this->time(); | |
| 263 | + } | |
| 264 | + | |
| 265 | + /** | |
| 266 | + * Return time | |
| 267 | + */ | |
| 268 | + private function time() { | |
| 269 | + | |
| 270 | + if ( self::$metrics == 'hrtime') { | |
| 271 | + return hrtime( true ); | |
| 272 | + } else { | |
| 273 | + return microtime( true ); | |
| 147 | 274 | } |
| 148 | 275 | } |
| 149 | 276 | } |
| 150 | 277 | |