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