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