PluginProbe
Code Profiler – WordPress Performance Profiling and Debugging Made Easy / 1.6.1
Code Profiler – WordPress Performance Profiling and Debugging Made Easy v1.6.1
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
code-profiler / lib / class-report.php

class-report.php in Code Profiler – WordPress Performance Profiling and Debugging Made Easy 1.6.1, at lib/class-report.php

470 lines 14.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 +=====================================================================+
4 | ____ _ ____ __ _ _ |
5 | / ___|___ __| | ___ | _ \ _ __ ___ / _(_) | ___ _ __ |
6 | | | / _ \ / _` |/ _ \ | |_) | '__/ _ \| |_| | |/ _ \ '__| |
7 | | |__| (_) | (_| | __/ | __/| | | (_) | _| | | __/ | |
8 | \____\___/ \__,_|\___| |_| |_| \___/|_| |_|_|\___|_| |
9 | |
10 | (c) Jerome Bruandet ~ https://code-profiler.com/ |
11 +=====================================================================+
12 */
13
14 if (! defined('ABSPATH') ) { die('Forbidden'); }
15
16
17 class CodeProfiler_Report {
18
19 private $last_slug;
20 private $last_type;
21 private $last_time = 0;
22 private $themes = [];
23 private $plugins = [];
24 private $composer = [];
25 private $summary_list = [];
26 private $parsed_data = 0;
27 private $plugins_dir;
28 private $themes_dir;
29 private $mu_dir;
30 private $total_plugins;
31 private $total_io;
32 private $profile_name;
33 private $microtime;
34 private $tmp_iostats;
35 private $tmp_summary;
36 private $tmp_diskio;
37 private $tmp_ticks;
38
39
40 /********************************************************************
41 * Initialize
42 */
43 public function __construct( $profile_name, $microtime ) {
44
45 $this->tmp_summary = CODE_PROFILER_UPLOAD_DIR ."/$microtime." .
46 CODE_PROFILER_TMP_SUMMARY_LOG;
47 $this->tmp_iostats = CODE_PROFILER_UPLOAD_DIR ."/$microtime." .
48 CODE_PROFILER_TMP_IOSTATS_LOG;
49 $this->tmp_ticks = CODE_PROFILER_UPLOAD_DIR ."/$microtime." .
50 CODE_PROFILER_TMP_TICKS_LOG;
51 $this->tmp_diskio = CODE_PROFILER_UPLOAD_DIR ."/$microtime." .
52 CODE_PROFILER_TMP_DISKIO_LOG;
53 // Used for naming, not metrics
54 $this->microtime = $microtime;
55
56 // Make sure all files are there
57 if (! file_exists( $this->tmp_ticks ) ) {
58 $cp_error = 1;
59 } elseif (! file_exists( $this->tmp_iostats ) ) {
60 $cp_error = 2;
61 }
62 if (! empty( $cp_error ) ) {
63 $error = sprintf(
64 esc_html__('Cannot create the report: the profiler did not generate a data file (#%s). Make sure the following directory is writable: %s. If you are using a caching plugin or an opcode cache, try to disable it. You may also find more details about the error in the "Log" tab.', 'code-profiler'),
65 $cp_error,
66 CODE_PROFILER_UPLOAD_DIR .'/'
67 );
68 $this->return_error( $error );
69 }
70
71 // Total data analyzed
72 $this->parsed_data += filesize( $this->tmp_iostats );
73 $this->parsed_data += filesize( $this->tmp_ticks );
74
75 $this->profile_name = $profile_name;
76 $this->plugins_dir = preg_quote( realpath( WP_PLUGIN_DIR ) );
77 $this->themes_dir = preg_quote( realpath( WP_CONTENT_DIR .'/themes') );
78 $this->mu_dir = preg_quote( realpath( WPMU_PLUGIN_DIR ) );
79 }
80
81
82 /********************************************************************
83 * Filter and save the profiler's data
84 */
85 public function prepare_report() {
86
87 $this->parse_ticks();
88 $this->get_plugins_theme_name();
89 $this->save_iostats();
90 $this->save_data();
91 $this->save_diskio();
92 $this->save_composer();
93
94 code_profiler_log_info( sprintf(
95 __('Volume of code and data analyzed: %1$sMB (%2$s plugins and 1 theme) in %4$ss - Memory used: %3$sMB', 'code-profiler'),
96 number_format( $this->parsed_data / 1024 / 1024, 2 ),
97 (int) $this->total_plugins,
98 number_format( memory_get_peak_usage( false ) / 1024 / 1024, 2 ),
99 number_format( microtime( true ) - $this->microtime, 2 )
100 ));
101
102 return $this->microtime;
103 }
104
105
106 /********************************************************************
107 * Return a json-encoded error for AJAX, write to log and quit.
108 */
109 private function return_error( $error ) {
110
111 $response['message'] = $error;
112 $response['status'] = 'error';
113 code_profiler_log_error( $error );
114 wp_send_json( $response );
115 }
116
117
118 /********************************************************************
119 * Save all data to disk
120 */
121 private function save_data() {
122
123 $slugs_buffer = '';
124 $this->summary_list['time'] = 0;
125
126 $slugs_tsv = CODE_PROFILER_UPLOAD_DIR ."/{$this->microtime}.".
127 "{$this->profile_name}.slugs.profile";
128 if ( file_exists( $slugs_tsv ) ) { unlink( $slugs_tsv ); }
129
130 $summary_json = CODE_PROFILER_UPLOAD_DIR ."/{$this->microtime}.".
131 "{$this->profile_name}.summary.profile";
132 if ( file_exists( $summary_json ) ) { unlink( $summary_json ); }
133
134 foreach( $this->plugins as $content => $content_array ) {
135 $this->total_plugins++;
136 // Slugs
137 $this->summary_list['time'] += $content_array['time'];
138 $time = $this->convert_2_seconds( $content_array['time'] );
139 if ( empty( $content_array['name'] ) ) {
140 // E.g., a plugin without a folder
141 $content_array['name'] = $content;
142 }
143 if ( isset( $content_array['mu'] ) ) {
144 $plugin = 'mu-plugin';
145 } else {
146 $plugin = 'plugin';
147 }
148 $slugs_buffer .= "$content\t$time\t{$content_array['name']}\t$plugin\n";
149 }
150 foreach( $this->themes as $content => $content_array ) {
151 // Slugs
152 $this->summary_list['time'] += $content_array['time'];
153 $time = $this->convert_2_seconds( $content_array['time'] );
154 $slugs_buffer .= "$content\t$time\t{$content_array['name']}\ttheme\n";
155 }
156
157 $error = '';
158 if ( empty( $slugs_buffer ) ) {
159 $error = esc_html__('Data is empty: no plugins or themes found', 'code-profiler');
160 }
161 if ( $error ) {
162 $this->return_error( $error );
163 }
164
165 // Save data
166 file_put_contents( $slugs_tsv, $slugs_buffer );
167
168 // Summary
169 $s = json_decode( file_get_contents( $this->tmp_summary ), true );
170 unlink( $this->tmp_summary );
171 if ( empty( $s['memory'] ) ) {
172 $this->summary_list['memory'] = '-';
173 } else {
174 $this->summary_list['memory'] = $s['memory'] / 1024 / 1024;
175 }
176 if ( empty( $s['queries'] ) ) {
177 $this->summary_list['queries']= '-';
178 } else {
179 $this->summary_list['queries']= $s['queries'];
180 }
181 if ( empty( $this->total_io ) ) {
182 $this->summary_list['io'] = '-';
183 } else {
184 $this->summary_list['io'] = $this->total_io;
185 }
186 $this->summary_list['items'] = $this->total_plugins + 1; // Add the theme
187 $this->summary_list['time'] = $this->convert_2_seconds( $this->summary_list['time'] );
188 $this->summary_list['time'] = number_format( $this->summary_list['time'], 4 );
189
190 // Save Code Profiler, PHP and WordPress' versions
191 global $wp_version;
192 $this->summary_list['versions'] = [
193 'wp' => $wp_version,
194 'cp' => CODE_PROFILER_VERSION,
195 'php' => PHP_VERSION
196 ];
197
198 file_put_contents( $summary_json, json_encode( $this->summary_list ) );
199
200 }
201
202
203 /********************************************************************
204 * Convert microtime (PHP<7.3) or hrtime (PHP>=7.3) to seconds
205 */
206 private function convert_2_seconds( $time ) {
207
208 if ( $time < 0 || empty( $time ) ) {
209 return '0';
210 }
211 if ( strpos( $time, '.' ) !== false ) {
212 // Looks like microtime
213 $time = number_format( $time, 6 );
214 } else {
215 // hrtime
216 $time = number_format( $time / 1000000000, 6 );
217 }
218 return $time;
219 }
220
221
222 /********************************************************************
223 * Check if a slug is from a plugin or theme.
224 */
225 private function plugin_or_theme( $script ) {
226
227 $res = ['theme' => '', 'plugin' => '', 'script' => ''];
228
229 if ( preg_match("`^{$this->plugins_dir}[\\\/](?:(.+?)[\\\/]|([^\\\/]+\.php)$)`", $script, $slug ) ) {
230 if (! empty( $slug[1] ) ) {
231 $res['plugin'] = $slug[1];
232 $res['script'] = $script;
233 } elseif (! empty( $slug[2] ) ) {
234 $res['plugin'] = $slug[2];
235 $res['script'] = $script;
236 }
237
238 } elseif ( preg_match("`^{$this->themes_dir}[\\\/]([^\\\/]+)[\\\/]`", $script, $slug ) ) {
239 $res['theme'] = $slug[1];
240 $res['script'] = $script;
241
242 } elseif ( preg_match("`^{$this->mu_dir}[\\\/]([^\\\/]+\.php)$`", $script, $slug ) ) {
243 $res['plugin'] = $slug[1];
244 $res['script'] = $script;
245 }
246 return $res;
247 }
248
249
250 /********************************************************************
251 * Retrieve the full name for all plugins and themes.
252 */
253 private function get_plugins_theme_name() {
254
255 // Get installed plugins
256 if ( ! function_exists('get_plugins') ) {
257 require_once ABSPATH .'wp-admin/includes/plugin.php';
258 }
259 $installed_plugins = get_plugins();
260 foreach( $installed_plugins as $k => $v ) {
261 if ( preg_match('`^([^\\\/]+)[\\\/]`', $k, $match ) ) {
262 if ( isset( $this->plugins[ $match[1] ]['time'] ) ) {
263 $this->plugins[ $match[1] ]['name'] = $v['Name'];
264 }
265 }
266 }
267 // Get installed themes
268 if ( ! function_exists('wp_get_themes') ) {
269 require_once ABSPATH .'wp-includes/theme.php';
270 }
271 $installed_themes = wp_get_themes();
272 foreach( $installed_themes as $k => $v ) {
273 if ( isset( $this->themes[ $k ]['time'] ) ) {
274 $this->themes[ $k ]['name'] = $v->Name;
275 }
276 }
277 // MU plugins
278 $mu_plugins = get_mu_plugins();
279 foreach( $mu_plugins as $k => $v ) {
280 if ( isset( $this->plugins[ $k ]['time'] ) ) {
281 $this->plugins[ $k ]['name'] = $v['Name'];
282 $this->plugins[ $k ]['mu'] = 1;
283 }
284 }
285 }
286
287
288 /********************************************************************
289 * Parse, filter and sort the data
290 */
291 private function parse_ticks() {
292
293 $fh = fopen( $this->tmp_ticks, 'rb');
294 if ( $fh === false ) {
295 $error = sprintf(
296 esc_html__('Cannot open file for reading: %s', 'code-profiler'),
297 $this->tmp_ticks
298 );
299 $this->return_error( $error );
300 }
301 while(! feof( $fh ) ) {
302 $line = fgets( $fh );
303 if ( preg_match( '/^(.+?)\t(.+?)\t(.+?)\t(.+)$/', $line, $match ) ) {
304
305 $caller = $match[1]; $callee = $match[2];
306 $start = $match[3]; $stop = $match[4];
307
308 // Check if we have a plugin or theme
309 $slug = $this->plugin_or_theme( $callee );
310 if ( empty( $slug['theme'] ) && empty( $slug['plugin'] ) ) {
311 $slug = $this->plugin_or_theme( $caller );
312 }
313
314 // Elapsed time since last tick
315 if (! empty( $this->last_time ) && ! empty( $start ) ) {
316 $elapse = $start - $this->last_time;
317 } else {
318 $elapse = 0;
319 }
320
321 // Plugin: update/create stats (time)
322
323 // We have a slug
324 if (! empty( $slug['theme'] ) || ! empty( $slug['plugin'] ) ) {
325 // It's a theme
326 if (! empty( $slug['theme'] ) ) {
327 // Same theme as previous record, update its stats
328 if ( $this->last_slug == $slug['theme'] ) {
329 if ( isset( $this->themes[ $this->last_slug ]['time'] ) ) {
330 $this->themes[ $this->last_slug ]['time'] += $elapse;
331 }
332 // Update the old record and create the new one if it doesn't exist
333 } else {
334 if (! empty( $this->last_slug ) ) {
335 if ( isset( $this->themes[ $this->last_slug ]['time'] ) ) {
336 $this->themes[ $this->last_slug ]['time'] += $elapse;
337 }
338 }
339 if (! isset( $this->themes[ $slug['theme'] ]['time'] ) ) {
340 $this->themes[ $slug['theme'] ]['time'] = 0;
341 }
342 }
343 // It's a plugin
344 } elseif (! empty( $slug['plugin'] ) ) {
345 // Same plugin as previous record, update its stats
346 if ( $this->last_slug == $slug['plugin'] ) {
347 if ( isset( $this->plugins[ $this->last_slug ]['time'] ) ) {
348 $this->plugins[ $this->last_slug ]['time'] += $elapse;
349 }
350 // Update the old one and create the new one if it doesn't exist
351 } else {
352 if (! empty( $this->last_slug ) ) {
353 if ( isset( $this->plugins[ $this->last_slug ]['time'] ) ) {
354 $this->plugins[ $this->last_slug ]['time'] += $elapse;
355 }
356 }
357 if (! isset( $this->plugins[ $slug['plugin'] ]['time'] ) ) {
358 $this->plugins[ $slug['plugin'] ]['time'] = 0;
359 }
360 }
361
362 // Look for multiple copies of composer and warn the user
363 if ( preg_match("`^{$this->plugins_dir}[\\\/](?:[^\\\/]+)[\\\/].+?[\\\/]composer[\\\/]autoload_real\.php`", $caller ) ) {
364 // Save the slug first, we'll fetch the name later
365 if (! isset( $this->composer[ $slug['plugin'] ] ) ) {
366 $this->composer[ $slug['plugin'] ] = '';
367 }
368 }
369 }
370
371 // We don't have a slug: if there's an old record, update it
372 } else {
373 if (! empty( $this->last_type ) && ! empty( $this->last_slug ) ) {
374 if ( $this->last_type == 'plugin' ) {
375 if ( isset( $this->plugins[ $this->last_slug ]['time'] ) ) {
376 $this->plugins[ $this->last_slug ]['time'] += $elapse;
377 }
378 } elseif ( $this->last_type == 'theme' ) {
379 if ( isset( $this->themes[ $this->last_slug ]['time'] ) ) {
380 $this->themes[ $this->last_slug ]['time'] += $elapse;
381 }
382 }
383 }
384 }
385
386 // Update all last records
387 if (! empty( $slug['theme'] ) ) {
388 $this->last_type = 'theme';
389 $this->last_slug = $slug['theme'];
390 } elseif (! empty( $slug['plugin'] ) ) {
391 $this->last_type = 'plugin';
392 $this->last_slug = $slug['plugin'];
393 } else {
394 $this->last_type = '';
395 $this->last_slug = '';
396 }
397 $this->last_time = $stop;
398 }
399 }
400
401 fclose( $fh );
402 unlink( $this->tmp_ticks );
403 }
404
405 /********************************************************************
406 * Save IO stats
407 */
408 private function save_iostats() {
409
410 $buffer = json_decode ( file_get_contents( $this->tmp_iostats ) , true );
411
412 if ( empty( $buffer ) ) {
413 $error = sprintf(
414 esc_html__('Cannot decode JSON-encode file (%s)', 'code-profiler'),
415 'CODE_PROFILER_TMP_IOSTATS_LOG'
416 );
417 $this->return_error( $error );
418 }
419
420 $lines = '';
421 foreach( $buffer as $key => $value ) {
422 $lines .= "$key\t$value\n";
423 $this->total_io += $value;
424 }
425
426 file_put_contents(
427 CODE_PROFILER_UPLOAD_DIR ."/{$this->microtime}.{$this->profile_name}.iostats.profile",
428 $lines
429 );
430
431 unlink( $this->tmp_iostats );
432 }
433
434
435 /********************************************************************
436 * Save Read/Write stats
437 */
438 private function save_diskio() {
439
440 rename(
441 $this->tmp_diskio,
442 CODE_PROFILER_UPLOAD_DIR ."/{$this->microtime}.{$this->profile_name}.diskio.profile"
443 );
444 }
445
446
447 /********************************************************************
448 * Save list of plugins using composer
449 */
450 private function save_composer() {
451
452 if (! empty( $this->composer ) ) {
453 // Try to get the plugin's name
454 foreach( $this->composer as $slug => $v ) {
455 if ( isset( $this->plugins[ $slug ]['name'] ) ) {
456 $this->composer[ $slug ] = $this->plugins[ $slug ]['name'];
457 }
458 }
459 file_put_contents(
460 CODE_PROFILER_UPLOAD_DIR ."/{$this->microtime}.{$this->profile_name}.composer.profile",
461 json_encode( $this->composer )
462 );
463 }
464 }
465
466 }
467
468 // =====================================================================
469 // EOF
470