PluginProbe
Code Profiler – WordPress Performance Profiling and Debugging Made Easy / 1.9.6
Code Profiler – WordPress Performance Profiling and Debugging Made Easy v1.9.6
1.9.6 1.9.5 1.9.4 1.9.3 trunk 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.5 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.6 1.6.1 1.6.10 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 All 41 releases
← All changes | lib/class-report.php +186 -58 1.41.9.6 View file →
@@ -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 class CodeProfiler_Report {
18 18
@@ -21,11 +21,13 @@
21 21 private $last_time = 0;
22 22 private $themes = [];
23 23 private $plugins = [];
24 24 private $summary_list = [];
25 + private $cx_buffer = [];
25 26 private $parsed_data = 0;
26 27 private $plugins_dir;
27 28 private $themes_dir;
29 + private $mu_dir;
28 30 private $total_plugins;
29 31 private $total_io;
30 32 private $profile_name;
31 33 private $microtime;
@@ -31,64 +33,76 @@
31 33 private $microtime;
32 34 private $tmp_iostats;
33 35 private $tmp_summary;
34 36 private $tmp_diskio;
35 - private $tmp_ticks;
37 + private $tmp_connections;
38 + private $tmp_calls;
36 39
37 40
38 - /*
41 + /**
39 42 * Initialize
40 43 */
41 44 public function __construct( $profile_name, $microtime ) {
42 45
43 - $this->tmp_summary = CODE_PROFILER_UPLOAD_DIR ."/$microtime." .
44 - CODE_PROFILER_TMP_SUMMARY_LOG;
45 - $this->tmp_iostats = CODE_PROFILER_UPLOAD_DIR ."/$microtime." .
46 - CODE_PROFILER_TMP_IOSTATS_LOG;
47 - $this->tmp_ticks = CODE_PROFILER_UPLOAD_DIR ."/$microtime." .
48 - CODE_PROFILER_TMP_TICKS_LOG;
49 - $this->tmp_diskio = CODE_PROFILER_UPLOAD_DIR ."/$microtime." .
50 - CODE_PROFILER_TMP_DISKIO_LOG;
46 + $this->tmp_summary = CODE_PROFILER_UPLOAD_DIR ."/$microtime." .
47 + CODE_PROFILER_TMP_SUMMARY_LOG;
48 + $this->tmp_iostats = CODE_PROFILER_UPLOAD_DIR ."/$microtime." .
49 + CODE_PROFILER_TMP_IOSTATS_LOG;
50 + $this->tmp_calls = CODE_PROFILER_UPLOAD_DIR ."/$microtime." .
51 + CODE_PROFILER_TMP_CALLS_LOG;
52 + $this->tmp_diskio = CODE_PROFILER_UPLOAD_DIR ."/$microtime." .
53 + CODE_PROFILER_TMP_DISKIO_LOG;
54 + $this->tmp_connections = CODE_PROFILER_UPLOAD_DIR ."/$microtime." .
55 + CODE_PROFILER_TMP_CONNECTIONS_LOG;
51 56 // Used for naming, not metrics
52 57 $this->microtime = $microtime;
53 58
54 59 // Make sure all files are there
55 - if (! file_exists( $this->tmp_iostats ) ||
56 - ! file_exists( $this->tmp_ticks ) ) {
57 -
60 + if (! file_exists( $this->tmp_calls ) ) {
61 + $cp_error = 'calls';
62 + } elseif (! file_exists( $this->tmp_iostats ) ) {
63 + $cp_error = 'iostats';
64 + }
65 + if (! empty( $cp_error ) ) {
58 66 $error = sprintf(
59 - esc_html__("Cannot create the report: the profiler didn't generate a data file. Make sure the following directory is writable: %s. If you are using a caching plugin or an opcode cache, try to disable it.", 'code-profiler'),
60 - CODE_PROFILER_UPLOAD_DIR .'/'
61 - );
67 + esc_html__('Cannot create the report: the profiler did not generate a data file (%s). '.
68 + 'You may find more details about this error in the "Log" tab or your PHP error log.',
69 + 'code-profiler'),
70 + $cp_error
71 + );
62 72 $this->return_error( $error );
63 73 }
64 74
65 75 // Total data analyzed
66 76 $this->parsed_data += filesize( $this->tmp_iostats );
67 - $this->parsed_data += filesize( $this->tmp_ticks );
77 + $this->parsed_data += filesize( $this->tmp_calls );
68 78
69 - $this->profile_name = $profile_name;
70 - $this->plugins_dir = preg_quote( realpath( WP_PLUGIN_DIR ) );
71 - $this->themes_dir = preg_quote( realpath( WP_CONTENT_DIR .'/themes' ) );
79 + $this->profile_name = $profile_name;
80 + $this->plugins_dir = preg_quote( realpath( WP_PLUGIN_DIR ) );
81 + $this->themes_dir = preg_quote( realpath( WP_CONTENT_DIR .'/themes') );
82 + $this->mu_dir = preg_quote( realpath( WPMU_PLUGIN_DIR ) );
72 83 }
73 84
74 85
75 - /*
86 + /**
76 87 * Filter and save the profiler's data
77 88 */
78 89 public function prepare_report() {
79 90
80 - $this->parse_ticks();
91 + $this->parse_calls();
81 92 $this->get_plugins_theme_name();
82 93 $this->save_iostats();
94 + $this->save_connections();
83 95 $this->save_data();
84 96 $this->save_diskio();
85 97
86 98 code_profiler_log_info( sprintf(
87 - __('Volume of PHP code analyzed and processed: %s MB (%s plugins and 1 theme) - Memory used: %s MB', 'code-profiler'),
99 + __('Volume of code and data analyzed: %1$sMB (%2$s plugins and '.
100 + '1 theme) in %4$ss - Memory used: %3$sMB', 'code-profiler'),
88 101 number_format( $this->parsed_data / 1024 / 1024, 2 ),
89 - $this->total_plugins,
90 - number_format( memory_get_peak_usage( false ) / 1024 / 1024, 2 )
102 + (int) $this->total_plugins,
103 + number_format( memory_get_peak_usage( false ) / 1024 / 1024, 2 ),
104 + number_format( microtime( true ) - $this->microtime, 2 )
91 105 ));
92 106
93 107 return $this->microtime;
94 108 }
@@ -93,9 +107,9 @@
93 107 return $this->microtime;
94 108 }
95 109
96 110
97 - /*
111 + /**
98 112 * Return a json-encoded error for AJAX, write to log and quit.
99 113 */
100 114 private function return_error( $error ) {
101 115
@@ -105,9 +119,9 @@
105 119 wp_send_json( $response );
106 120 }
107 121
108 122
109 - /*
123 + /**
110 124 * Save all data to disk
111 125 */
112 126 private function save_data() {
113 127
@@ -121,30 +135,48 @@
121 135 $summary_json = CODE_PROFILER_UPLOAD_DIR ."/{$this->microtime}.".
122 136 "{$this->profile_name}.summary.profile";
123 137 if ( file_exists( $summary_json ) ) { unlink( $summary_json ); }
124 138
125 -
126 139 foreach( $this->plugins as $content => $content_array ) {
127 140 $this->total_plugins++;
128 141 // Slugs
129 142 $this->summary_list['time'] += $content_array['time'];
130 - $time = $this->convert_2_seconds( $content_array['time'] );
143 + if ( isset( $this->cx_buffer['plugin'][ $content ] ) ) {
144 + $time = $this->convert_2_seconds(
145 + $content_array['time'] + $this->cx_buffer['plugin'][ $content ]
146 + );
147 + $this->summary_list['time'] += $this->cx_buffer['plugin'][ $content ];
148 + } else {
149 + $time = $this->convert_2_seconds( $content_array['time'] );
150 + }
131 151 if ( empty( $content_array['name'] ) ) {
132 152 // E.g., a plugin without a folder
133 153 $content_array['name'] = $content;
134 154 }
135 - $slugs_buffer .= "$content\t$time\t{$content_array['name']}\tplugin\n";
155 + if ( isset( $content_array['mu'] ) ) {
156 + $plugin = 'mu-plugin';
157 + } else {
158 + $plugin = 'plugin';
159 + }
160 + $slugs_buffer .= "$content\t$time\t{$content_array['name']}\t$plugin\n";
136 161 }
137 162 foreach( $this->themes as $content => $content_array ) {
138 163 // Slugs
139 164 $this->summary_list['time'] += $content_array['time'];
140 - $time = $this->convert_2_seconds( $content_array['time'] );
165 + if ( isset( $this->cx_buffer['theme'][ $content ] ) ) {
166 + $time = $this->convert_2_seconds(
167 + $content_array['time'] + $this->cx_buffer['theme'][ $content ]
168 + );
169 + $this->summary_list['time'] += $this->cx_buffer['theme'][ $content ];
170 + } else {
171 + $time = $this->convert_2_seconds( $content_array['time'] );
172 + }
141 173 $slugs_buffer .= "$content\t$time\t{$content_array['name']}\ttheme\n";
142 174 }
143 175
144 176 $error = '';
145 177 if ( empty( $slugs_buffer ) ) {
146 - $error = esc_html__( 'Data is empty: no plugins or themes found', 'code-profiler' );
178 + $error = esc_html__('Data is empty: no plugins or themes found', 'code-profiler');
147 179 }
148 180 if ( $error ) {
149 181 $this->return_error( $error );
150 182 }
@@ -155,36 +187,55 @@
155 187 // Summary
156 188 $s = json_decode( file_get_contents( $this->tmp_summary ), true );
157 189 unlink( $this->tmp_summary );
158 190 if ( empty( $s['memory'] ) ) {
159 - $this->summary_list['memory'] = '-';
191 + $this->summary_list['memory'] = 0;
160 192 } else {
161 193 $this->summary_list['memory'] = $s['memory'] / 1024 / 1024;
162 194 }
163 195 if ( empty( $s['queries'] ) ) {
164 - $this->summary_list['queries']= '-';
196 + $this->summary_list['queries']= 0;
165 197 } else {
166 198 $this->summary_list['queries']= $s['queries'];
167 199 }
168 200 if ( empty( $this->total_io ) ) {
169 - $this->summary_list['io'] = '-';
201 + $this->summary_list['io'] = 0;
170 202 } else {
171 203 $this->summary_list['io'] = $this->total_io;
172 204 }
205 + if (! empty( $s['precision'] ) ) {
206 + $this->summary_list['precision'] = $s['precision'];
207 + }
173 208 $this->summary_list['items'] = $this->total_plugins + 1; // Add the theme
174 209 $this->summary_list['time'] = $this->convert_2_seconds( $this->summary_list['time'] );
175 210 $this->summary_list['time'] = number_format( $this->summary_list['time'], 4 );
211 +
212 + // Re-run options
213 + if (! empty( $s['rerun'] ) ) {
214 + $this->summary_list['rerun'] = $s['rerun'];
215 + $this->summary_list['rerun']['profile'] = $this->profile_name;
216 + }
217 +
218 + // Save Code Profiler, PHP and WordPress' versions
219 + global $wp_version;
220 + $this->summary_list['versions'] = [
221 + 'wp' => $wp_version,
222 + 'cp' => CODE_PROFILER_VERSION,
223 + 'php' => PHP_VERSION
224 + ];
225 +
176 226 file_put_contents( $summary_json, json_encode( $this->summary_list ) );
177 -
178 227 }
179 228
180 229
181 - /*
230 + /**
182 231 * Convert microtime (PHP<7.3) or hrtime (PHP>=7.3) to seconds
183 232 */
184 233 private function convert_2_seconds( $time ) {
185 234
186 - if ( empty( $time ) ) { return '0'; }
235 + if ( $time < 0 || empty( $time ) ) {
236 + return '0';
237 + }
187 238 if ( strpos( $time, '.' ) !== false ) {
188 239 // Looks like microtime
189 240 $time = number_format( $time, 6 );
190 241 } else {
@@ -194,16 +245,20 @@
194 245 return $time;
195 246 }
196 247
197 248
198 - /*
249 + /**
199 250 * Check if a slug is from a plugin or theme.
200 251 */
201 252 private function plugin_or_theme( $script ) {
202 253
203 - $res = [ 'theme' => '', 'plugin' => '', 'script' => '' ];
254 + $res = [
255 + 'theme' => '',
256 + 'plugin' => '',
257 + 'script' => ''
258 + ];
204 259
205 - if ( preg_match( "`^{$this->plugins_dir}[\\\/](?:(.+?)[\\\/]|([^\\\/]+\.php)$)`", $script, $slug ) ) {
260 + if ( preg_match("`^{$this->plugins_dir}[\\\/](?:(.+?)[\\\/]|([^\\\/]+\.php)$)`", $script, $slug ) ) {
206 261 if (! empty( $slug[1] ) ) {
207 262 $res['plugin'] = $slug[1];
208 263 $res['script'] = $script;
209 264 } elseif (! empty( $slug[2] ) ) {
@@ -210,28 +265,32 @@
210 265 $res['plugin'] = $slug[2];
211 266 $res['script'] = $script;
212 267 }
213 268
214 - } elseif ( preg_match( "`^{$this->themes_dir}[\\\/]([^\\\/]+)[\\\/]`", $script, $slug ) ) {
269 + } elseif ( preg_match("`^{$this->themes_dir}[\\\/]([^\\\/]+)[\\\/]`", $script, $slug ) ) {
215 270 $res['theme'] = $slug[1];
216 271 $res['script'] = $script;
272 +
273 + } elseif ( preg_match("`^{$this->mu_dir}[\\\/]([^\\\/]+\.php)$`", $script, $slug ) ) {
274 + $res['plugin'] = $slug[1];
275 + $res['script'] = $script;
217 276 }
218 277 return $res;
219 278 }
220 279
221 280
222 - /*
281 + /**
223 282 * Retrieve the full name for all plugins and themes.
224 283 */
225 284 private function get_plugins_theme_name() {
226 285
227 286 // Get installed plugins
228 - if ( ! function_exists( 'get_plugins' ) ) {
229 - require_once ABSPATH . 'wp-admin/includes/plugin.php';
287 + if ( ! function_exists('get_plugins') ) {
288 + require_once ABSPATH .'wp-admin/includes/plugin.php';
230 289 }
231 290 $installed_plugins = get_plugins();
232 291 foreach( $installed_plugins as $k => $v ) {
233 - if ( preg_match( '`^([^\\\/]+)[\\\/]`', $k, $match ) ) {
292 + if ( preg_match('`^([^\\\/]+)[\\\/]`', $k, $match ) ) {
234 293 if ( isset( $this->plugins[ $match[1] ]['time'] ) ) {
235 294 $this->plugins[ $match[1] ]['name'] = $v['Name'];
236 295 }
237 296 }
@@ -236,10 +295,10 @@
236 295 }
237 296 }
238 297 }
239 298 // Get installed themes
240 - if ( ! function_exists( 'wp_get_themes' ) ) {
241 - require_once ABSPATH . 'wp-includes/theme.php';
299 + if ( ! function_exists('wp_get_themes') ) {
300 + require_once ABSPATH .'wp-includes/theme.php';
242 301 }
243 302 $installed_themes = wp_get_themes();
244 303 foreach( $installed_themes as $k => $v ) {
245 304 if ( isset( $this->themes[ $k ]['time'] ) ) {
@@ -245,21 +304,29 @@
245 304 if ( isset( $this->themes[ $k ]['time'] ) ) {
246 305 $this->themes[ $k ]['name'] = $v->Name;
247 306 }
248 307 }
308 + // MU plugins
309 + $mu_plugins = get_mu_plugins();
310 + foreach( $mu_plugins as $k => $v ) {
311 + if ( isset( $this->plugins[ $k ]['time'] ) ) {
312 + $this->plugins[ $k ]['name'] = $v['Name'];
313 + $this->plugins[ $k ]['mu'] = 1;
314 + }
315 + }
249 316 }
250 317
251 318
252 - /*
319 + /**
253 320 * Parse, filter and sort the data
254 321 */
255 - private function parse_ticks() {
322 + private function parse_calls() {
256 323
257 - $fh = fopen( $this->tmp_ticks, 'rb' );
324 + $fh = fopen( $this->tmp_calls, 'rb');
258 325 if ( $fh === false ) {
259 326 $error = sprintf(
260 327 esc_html__('Cannot open file for reading: %s', 'code-profiler'),
261 - $this->tmp_ticks
328 + $this->tmp_calls
262 329 );
263 330 $this->return_error( $error );
264 331 }
265 332 while(! feof( $fh ) ) {
@@ -354,12 +421,12 @@
354 421 }
355 422 }
356 423
357 424 fclose( $fh );
358 - unlink( $this->tmp_ticks );
425 + unlink( $this->tmp_calls );
359 426 }
360 427
361 - /*
428 + /********************************************************************
362 429 * Save IO stats
363 430 */
364 431 private function save_iostats() {
365 432
@@ -387,17 +454,78 @@
387 454 unlink( $this->tmp_iostats );
388 455 }
389 456
390 457
391 - /*
458 + /**
392 459 * Save Read/Write stats
393 460 */
394 - function save_diskio() {
461 + private function save_diskio() {
395 462
396 463 rename(
397 464 $this->tmp_diskio,
398 465 CODE_PROFILER_UPLOAD_DIR ."/{$this->microtime}.{$this->profile_name}.diskio.profile"
399 466 );
467 + }
468 +
469 +
470 + /**
471 + * External connections.
472 + */
473 + private function save_connections() {
474 +
475 + if (! file_exists( $this->tmp_connections ) ) {
476 + return;
477 + }
478 +
479 + $connections = json_decode( file_get_contents( $this->tmp_connections ), true );
480 + unlink( $this->tmp_connections );
481 +
482 + if ( empty( $connections ) ) {
483 + return;
484 + }
485 +
486 + foreach( $connections as $connection => $array ) {
487 + $found_call = 0;
488 + $found_caller = 0;
489 +
490 + foreach( $array['bt'] as $k =>$v ) {
491 + if ( isset( $v['file'] ) && isset( $v['function'] ) && isset( $v['line'] ) ) {
492 +
493 + if (! $found_caller && ( $found_call ||
494 + in_array( $v['function'], [
495 + 'wp_remote_get',
496 + 'wp_safe_remote_get',
497 + 'wp_remote_post',
498 + 'wp_safe_remote_post'
499 + ] )
500 + ) ) {
501 +
502 + $found_call = 1;
503 +
504 + $type = $this->plugin_or_theme( $v['file'] );
505 +
506 + if (! empty( $type['theme'] ) ) {
507 + // Save it for later use
508 + if ( isset( $this->cx_buffer['theme'][ $type['theme'] ] ) ) {
509 + $this->cx_buffer['theme'][ $type['theme'] ] += $array['stop'] - $connection;
510 + } else {
511 + $this->cx_buffer['theme'][ $type['theme'] ] = $array['stop'] - $connection;
512 + }
513 + $found_caller = 1;
514 +
515 + } elseif (! empty( $type['plugin'] ) ) {
516 + // Save it for later use
517 + if ( isset( $this->cx_buffer['plugin'][ $type['plugin'] ] ) ) {
518 + $this->cx_buffer['plugin'][ $type['plugin'] ] += $array['stop'] - $connection;
519 + } else {
520 + $this->cx_buffer['plugin'][ $type['plugin'] ] = $array['stop'] - $connection;
521 + }
522 + $found_caller = 1;
523 + }
524 + }
525 + }
526 + }
527 + }
400 528 }
401 529
402 530 }
403 531