PluginProbe
Code Profiler – WordPress Performance Profiling and Debugging Made Easy / 1.5
Code Profiler – WordPress Performance Profiling and Debugging Made Easy v1.5
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.5, at lib/class-report.php

458 lines 13.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 page.', '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 PHP code analyzed and processed: %s MB (%s plugins and 1 theme) - Memory used: %s MB', 'code-profiler'),
96 number_format( $this->parsed_data / 1024 / 1024, 2 ),
97 $this->total_plugins,
98 number_format( memory_get_peak_usage( false ) / 1024 / 1024, 2 )
99 ));
100
101 return $this->microtime;
102 }
103
104
105 /**
106 * Return a json-encoded error for AJAX, write to log and quit.
107 */
108 private function return_error( $error ) {
109
110 $response['message'] = $error;
111 $response['status'] = 'error';
112 code_profiler_log_error( $error );
113 wp_send_json( $response );
114 }
115
116
117 /**
118 * Save all data to disk
119 */
120 private function save_data() {
121
122 $slugs_buffer = '';
123 $this->summary_list['time'] = 0;
124
125 $slugs_tsv = CODE_PROFILER_UPLOAD_DIR ."/{$this->microtime}.".
126 "{$this->profile_name}.slugs.profile";
127 if ( file_exists( $slugs_tsv ) ) { unlink( $slugs_tsv ); }
128
129 $summary_json = CODE_PROFILER_UPLOAD_DIR ."/{$this->microtime}.".
130 "{$this->profile_name}.summary.profile";
131 if ( file_exists( $summary_json ) ) { unlink( $summary_json ); }
132
133 foreach( $this->plugins as $content => $content_array ) {
134 $this->total_plugins++;
135 // Slugs
136 $this->summary_list['time'] += $content_array['time'];
137 $time = $this->convert_2_seconds( $content_array['time'] );
138 if ( empty( $content_array['name'] ) ) {
139 // E.g., a plugin without a folder
140 $content_array['name'] = $content;
141 }
142 if ( isset( $content_array['mu'] ) ) {
143 $plugin = 'mu-plugin';
144 } else {
145 $plugin = 'plugin';
146 }
147 $slugs_buffer .= "$content\t$time\t{$content_array['name']}\t$plugin\n";
148 }
149 foreach( $this->themes as $content => $content_array ) {
150 // Slugs
151 $this->summary_list['time'] += $content_array['time'];
152 $time = $this->convert_2_seconds( $content_array['time'] );
153 $slugs_buffer .= "$content\t$time\t{$content_array['name']}\ttheme\n";
154 }
155
156 $error = '';
157 if ( empty( $slugs_buffer ) ) {
158 $error = esc_html__( 'Data is empty: no plugins or themes found', 'code-profiler' );
159 }
160 if ( $error ) {
161 $this->return_error( $error );
162 }
163
164 // Save data
165 file_put_contents( $slugs_tsv, $slugs_buffer );
166
167 // Summary
168 $s = json_decode( file_get_contents( $this->tmp_summary ), true );
169 unlink( $this->tmp_summary );
170 if ( empty( $s['memory'] ) ) {
171 $this->summary_list['memory'] = '-';
172 } else {
173 $this->summary_list['memory'] = $s['memory'] / 1024 / 1024;
174 }
175 if ( empty( $s['queries'] ) ) {
176 $this->summary_list['queries']= '-';
177 } else {
178 $this->summary_list['queries']= $s['queries'];
179 }
180 if ( empty( $this->total_io ) ) {
181 $this->summary_list['io'] = '-';
182 } else {
183 $this->summary_list['io'] = $this->total_io;
184 }
185 $this->summary_list['items'] = $this->total_plugins + 1; // Add the theme
186 $this->summary_list['time'] = $this->convert_2_seconds( $this->summary_list['time'] );
187 $this->summary_list['time'] = number_format( $this->summary_list['time'], 4 );
188 file_put_contents( $summary_json, json_encode( $this->summary_list ) );
189
190 }
191
192
193 /**
194 * Convert microtime (PHP<7.3) or hrtime (PHP>=7.3) to seconds
195 */
196 private function convert_2_seconds( $time ) {
197
198 if ( empty( $time ) ) { return '0'; }
199 if ( strpos( $time, '.' ) !== false ) {
200 // Looks like microtime
201 $time = number_format( $time, 6 );
202 } else {
203 // hrtime
204 $time = number_format( $time / 1000000000, 6 );
205 }
206 return $time;
207 }
208
209
210 /**
211 * Check if a slug is from a plugin or theme.
212 */
213 private function plugin_or_theme( $script ) {
214
215 $res = [ 'theme' => '', 'plugin' => '', 'script' => '' ];
216
217 if ( preg_match("`^{$this->plugins_dir}[\\\/](?:(.+?)[\\\/]|([^\\\/]+\.php)$)`", $script, $slug ) ) {
218 if (! empty( $slug[1] ) ) {
219 $res['plugin'] = $slug[1];
220 $res['script'] = $script;
221 } elseif (! empty( $slug[2] ) ) {
222 $res['plugin'] = $slug[2];
223 $res['script'] = $script;
224 }
225
226 } elseif ( preg_match("`^{$this->themes_dir}[\\\/]([^\\\/]+)[\\\/]`", $script, $slug ) ) {
227 $res['theme'] = $slug[1];
228 $res['script'] = $script;
229
230 } elseif ( preg_match("`^{$this->mu_dir}[\\\/]([^\\\/]+\.php)$`", $script, $slug ) ) {
231 $res['plugin'] = $slug[1];
232 $res['script'] = $script;
233 }
234 return $res;
235 }
236
237
238 /**
239 * Retrieve the full name for all plugins and themes.
240 */
241 private function get_plugins_theme_name() {
242
243 // Get installed plugins
244 if ( ! function_exists('get_plugins') ) {
245 require_once ABSPATH .'wp-admin/includes/plugin.php';
246 }
247 $installed_plugins = get_plugins();
248 foreach( $installed_plugins as $k => $v ) {
249 if ( preg_match('`^([^\\\/]+)[\\\/]`', $k, $match ) ) {
250 if ( isset( $this->plugins[ $match[1] ]['time'] ) ) {
251 $this->plugins[ $match[1] ]['name'] = $v['Name'];
252 }
253 }
254 }
255 // Get installed themes
256 if ( ! function_exists('wp_get_themes') ) {
257 require_once ABSPATH .'wp-includes/theme.php';
258 }
259 $installed_themes = wp_get_themes();
260 foreach( $installed_themes as $k => $v ) {
261 if ( isset( $this->themes[ $k ]['time'] ) ) {
262 $this->themes[ $k ]['name'] = $v->Name;
263 }
264 }
265 // MU plugins
266 $mu_plugins = get_mu_plugins();
267 foreach( $mu_plugins as $k => $v ) {
268 if ( isset( $this->plugins[ $k ]['time'] ) ) {
269 $this->plugins[ $k ]['name'] = $v['Name'];
270 $this->plugins[ $k ]['mu'] = 1;
271 }
272 }
273 }
274
275
276 /**
277 * Parse, filter and sort the data
278 */
279 private function parse_ticks() {
280
281 $fh = fopen( $this->tmp_ticks, 'rb' );
282 if ( $fh === false ) {
283 $error = sprintf(
284 esc_html__('Cannot open file for reading: %s', 'code-profiler'),
285 $this->tmp_ticks
286 );
287 $this->return_error( $error );
288 }
289 while(! feof( $fh ) ) {
290 $line = fgets( $fh );
291 if ( preg_match( '/^(.+?)\t(.+?)\t(.+?)\t(.+)$/', $line, $match ) ) {
292
293 $caller = $match[1]; $callee = $match[2];
294 $start = $match[3]; $stop = $match[4];
295
296 // Check if we have a plugin or theme
297 $slug = $this->plugin_or_theme( $callee );
298 if ( empty( $slug['theme'] ) && empty( $slug['plugin'] ) ) {
299 $slug = $this->plugin_or_theme( $caller );
300 }
301
302 // Elapsed time since last tick
303 if (! empty( $this->last_time ) && ! empty( $start ) ) {
304 $elapse = $start - $this->last_time;
305 } else {
306 $elapse = 0;
307 }
308
309 // Plugin: update/create stats (time)
310
311 // We have a slug
312 if (! empty( $slug['theme'] ) || ! empty( $slug['plugin'] ) ) {
313 // It's a theme
314 if (! empty( $slug['theme'] ) ) {
315 // Same theme as previous record, update its stats
316 if ( $this->last_slug == $slug['theme'] ) {
317 if ( isset( $this->themes[ $this->last_slug ]['time'] ) ) {
318 $this->themes[ $this->last_slug ]['time'] += $elapse;
319 }
320 // Update the old record and create the new one if it doesn't exist
321 } else {
322 if (! empty( $this->last_slug ) ) {
323 if ( isset( $this->themes[ $this->last_slug ]['time'] ) ) {
324 $this->themes[ $this->last_slug ]['time'] += $elapse;
325 }
326 }
327 if (! isset( $this->themes[ $slug['theme'] ]['time'] ) ) {
328 $this->themes[ $slug['theme'] ]['time'] = 0;
329 }
330 }
331 // It's a plugin
332 } elseif (! empty( $slug['plugin'] ) ) {
333 // Same plugin as previous record, update its stats
334 if ( $this->last_slug == $slug['plugin'] ) {
335 if ( isset( $this->plugins[ $this->last_slug ]['time'] ) ) {
336 $this->plugins[ $this->last_slug ]['time'] += $elapse;
337 }
338 // Update the old one and create the new one if it doesn't exist
339 } else {
340 if (! empty( $this->last_slug ) ) {
341 if ( isset( $this->plugins[ $this->last_slug ]['time'] ) ) {
342 $this->plugins[ $this->last_slug ]['time'] += $elapse;
343 }
344 }
345 if (! isset( $this->plugins[ $slug['plugin'] ]['time'] ) ) {
346 $this->plugins[ $slug['plugin'] ]['time'] = 0;
347 }
348 }
349
350 // Look for multiple copies of composer and warn the user
351 if ( preg_match("`^{$this->plugins_dir}[\\\/](?:[^\\\/]+)[\\\/].+?[\\\/]composer[\\\/]autoload_real\.php`", $caller ) ) {
352 // Save the slug first, we'll fetch the name later
353 if (! isset( $this->composer[ $slug['plugin'] ] ) ) {
354 $this->composer[ $slug['plugin'] ] = '';
355 }
356 }
357 }
358
359 // We don't have a slug: if there's an old record, update it
360 } else {
361 if (! empty( $this->last_type ) && ! empty( $this->last_slug ) ) {
362 if ( $this->last_type == 'plugin' ) {
363 if ( isset( $this->plugins[ $this->last_slug ]['time'] ) ) {
364 $this->plugins[ $this->last_slug ]['time'] += $elapse;
365 }
366 } elseif ( $this->last_type == 'theme' ) {
367 if ( isset( $this->themes[ $this->last_slug ]['time'] ) ) {
368 $this->themes[ $this->last_slug ]['time'] += $elapse;
369 }
370 }
371 }
372 }
373
374 // Update all last records
375 if (! empty( $slug['theme'] ) ) {
376 $this->last_type = 'theme';
377 $this->last_slug = $slug['theme'];
378 } elseif (! empty( $slug['plugin'] ) ) {
379 $this->last_type = 'plugin';
380 $this->last_slug = $slug['plugin'];
381 } else {
382 $this->last_type = '';
383 $this->last_slug = '';
384 }
385 $this->last_time = $stop;
386 }
387 }
388
389 fclose( $fh );
390 unlink( $this->tmp_ticks );
391 }
392
393 /**
394 * Save IO stats
395 */
396 private function save_iostats() {
397
398 $buffer = json_decode ( file_get_contents( $this->tmp_iostats ) , true );
399
400 if ( empty( $buffer ) ) {
401 $error = sprintf(
402 esc_html__('Cannot decode JSON-encode file (%s)', 'code-profiler'),
403 'CODE_PROFILER_TMP_IOSTATS_LOG'
404 );
405 $this->return_error( $error );
406 }
407
408 $lines = '';
409 foreach( $buffer as $key => $value ) {
410 $lines .= "$key\t$value\n";
411 $this->total_io += $value;
412 }
413
414 file_put_contents(
415 CODE_PROFILER_UPLOAD_DIR ."/{$this->microtime}.{$this->profile_name}.iostats.profile",
416 $lines
417 );
418
419 unlink( $this->tmp_iostats );
420 }
421
422
423 /**
424 * Save Read/Write stats
425 */
426 function save_diskio() {
427
428 rename(
429 $this->tmp_diskio,
430 CODE_PROFILER_UPLOAD_DIR ."/{$this->microtime}.{$this->profile_name}.diskio.profile"
431 );
432 }
433
434
435 /**
436 * Save list of plugins using composer
437 */
438 function save_composer() {
439
440 if (! empty( $this->composer ) ) {
441 // Try to get the plugin's name
442 foreach( $this->composer as $slug => $v ) {
443 if ( isset( $this->plugins[ $slug ]['name'] ) ) {
444 $this->composer[ $slug ] = $this->plugins[ $slug ]['name'];
445 }
446 }
447 file_put_contents(
448 CODE_PROFILER_UPLOAD_DIR ."/{$this->microtime}.{$this->profile_name}.composer.profile",
449 json_encode( $this->composer )
450 );
451 }
452 }
453
454 }
455
456 // =====================================================================
457 // EOF
458