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

461 lines 14.0 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 file_put_contents( $summary_json, json_encode( $this->summary_list ) );
190
191 }
192
193
194 /********************************************************************
195 * Convert microtime (PHP<7.3) or hrtime (PHP>=7.3) to seconds
196 */
197 private function convert_2_seconds( $time ) {
198
199 if ( $time < 0 || empty( $time ) ) {
200 return '0';
201 }
202 if ( strpos( $time, '.' ) !== false ) {
203 // Looks like microtime
204 $time = number_format( $time, 6 );
205 } else {
206 // hrtime
207 $time = number_format( $time / 1000000000, 6 );
208 }
209 return $time;
210 }
211
212
213 /********************************************************************
214 * Check if a slug is from a plugin or theme.
215 */
216 private function plugin_or_theme( $script ) {
217
218 $res = ['theme' => '', 'plugin' => '', 'script' => ''];
219
220 if ( preg_match("`^{$this->plugins_dir}[\\\/](?:(.+?)[\\\/]|([^\\\/]+\.php)$)`", $script, $slug ) ) {
221 if (! empty( $slug[1] ) ) {
222 $res['plugin'] = $slug[1];
223 $res['script'] = $script;
224 } elseif (! empty( $slug[2] ) ) {
225 $res['plugin'] = $slug[2];
226 $res['script'] = $script;
227 }
228
229 } elseif ( preg_match("`^{$this->themes_dir}[\\\/]([^\\\/]+)[\\\/]`", $script, $slug ) ) {
230 $res['theme'] = $slug[1];
231 $res['script'] = $script;
232
233 } elseif ( preg_match("`^{$this->mu_dir}[\\\/]([^\\\/]+\.php)$`", $script, $slug ) ) {
234 $res['plugin'] = $slug[1];
235 $res['script'] = $script;
236 }
237 return $res;
238 }
239
240
241 /********************************************************************
242 * Retrieve the full name for all plugins and themes.
243 */
244 private function get_plugins_theme_name() {
245
246 // Get installed plugins
247 if ( ! function_exists('get_plugins') ) {
248 require_once ABSPATH .'wp-admin/includes/plugin.php';
249 }
250 $installed_plugins = get_plugins();
251 foreach( $installed_plugins as $k => $v ) {
252 if ( preg_match('`^([^\\\/]+)[\\\/]`', $k, $match ) ) {
253 if ( isset( $this->plugins[ $match[1] ]['time'] ) ) {
254 $this->plugins[ $match[1] ]['name'] = $v['Name'];
255 }
256 }
257 }
258 // Get installed themes
259 if ( ! function_exists('wp_get_themes') ) {
260 require_once ABSPATH .'wp-includes/theme.php';
261 }
262 $installed_themes = wp_get_themes();
263 foreach( $installed_themes as $k => $v ) {
264 if ( isset( $this->themes[ $k ]['time'] ) ) {
265 $this->themes[ $k ]['name'] = $v->Name;
266 }
267 }
268 // MU plugins
269 $mu_plugins = get_mu_plugins();
270 foreach( $mu_plugins as $k => $v ) {
271 if ( isset( $this->plugins[ $k ]['time'] ) ) {
272 $this->plugins[ $k ]['name'] = $v['Name'];
273 $this->plugins[ $k ]['mu'] = 1;
274 }
275 }
276 }
277
278
279 /********************************************************************
280 * Parse, filter and sort the data
281 */
282 private function parse_ticks() {
283
284 $fh = fopen( $this->tmp_ticks, 'rb');
285 if ( $fh === false ) {
286 $error = sprintf(
287 esc_html__('Cannot open file for reading: %s', 'code-profiler'),
288 $this->tmp_ticks
289 );
290 $this->return_error( $error );
291 }
292 while(! feof( $fh ) ) {
293 $line = fgets( $fh );
294 if ( preg_match( '/^(.+?)\t(.+?)\t(.+?)\t(.+)$/', $line, $match ) ) {
295
296 $caller = $match[1]; $callee = $match[2];
297 $start = $match[3]; $stop = $match[4];
298
299 // Check if we have a plugin or theme
300 $slug = $this->plugin_or_theme( $callee );
301 if ( empty( $slug['theme'] ) && empty( $slug['plugin'] ) ) {
302 $slug = $this->plugin_or_theme( $caller );
303 }
304
305 // Elapsed time since last tick
306 if (! empty( $this->last_time ) && ! empty( $start ) ) {
307 $elapse = $start - $this->last_time;
308 } else {
309 $elapse = 0;
310 }
311
312 // Plugin: update/create stats (time)
313
314 // We have a slug
315 if (! empty( $slug['theme'] ) || ! empty( $slug['plugin'] ) ) {
316 // It's a theme
317 if (! empty( $slug['theme'] ) ) {
318 // Same theme as previous record, update its stats
319 if ( $this->last_slug == $slug['theme'] ) {
320 if ( isset( $this->themes[ $this->last_slug ]['time'] ) ) {
321 $this->themes[ $this->last_slug ]['time'] += $elapse;
322 }
323 // Update the old record and create the new one if it doesn't exist
324 } else {
325 if (! empty( $this->last_slug ) ) {
326 if ( isset( $this->themes[ $this->last_slug ]['time'] ) ) {
327 $this->themes[ $this->last_slug ]['time'] += $elapse;
328 }
329 }
330 if (! isset( $this->themes[ $slug['theme'] ]['time'] ) ) {
331 $this->themes[ $slug['theme'] ]['time'] = 0;
332 }
333 }
334 // It's a plugin
335 } elseif (! empty( $slug['plugin'] ) ) {
336 // Same plugin as previous record, update its stats
337 if ( $this->last_slug == $slug['plugin'] ) {
338 if ( isset( $this->plugins[ $this->last_slug ]['time'] ) ) {
339 $this->plugins[ $this->last_slug ]['time'] += $elapse;
340 }
341 // Update the old one and create the new one if it doesn't exist
342 } else {
343 if (! empty( $this->last_slug ) ) {
344 if ( isset( $this->plugins[ $this->last_slug ]['time'] ) ) {
345 $this->plugins[ $this->last_slug ]['time'] += $elapse;
346 }
347 }
348 if (! isset( $this->plugins[ $slug['plugin'] ]['time'] ) ) {
349 $this->plugins[ $slug['plugin'] ]['time'] = 0;
350 }
351 }
352
353 // Look for multiple copies of composer and warn the user
354 if ( preg_match("`^{$this->plugins_dir}[\\\/](?:[^\\\/]+)[\\\/].+?[\\\/]composer[\\\/]autoload_real\.php`", $caller ) ) {
355 // Save the slug first, we'll fetch the name later
356 if (! isset( $this->composer[ $slug['plugin'] ] ) ) {
357 $this->composer[ $slug['plugin'] ] = '';
358 }
359 }
360 }
361
362 // We don't have a slug: if there's an old record, update it
363 } else {
364 if (! empty( $this->last_type ) && ! empty( $this->last_slug ) ) {
365 if ( $this->last_type == 'plugin' ) {
366 if ( isset( $this->plugins[ $this->last_slug ]['time'] ) ) {
367 $this->plugins[ $this->last_slug ]['time'] += $elapse;
368 }
369 } elseif ( $this->last_type == 'theme' ) {
370 if ( isset( $this->themes[ $this->last_slug ]['time'] ) ) {
371 $this->themes[ $this->last_slug ]['time'] += $elapse;
372 }
373 }
374 }
375 }
376
377 // Update all last records
378 if (! empty( $slug['theme'] ) ) {
379 $this->last_type = 'theme';
380 $this->last_slug = $slug['theme'];
381 } elseif (! empty( $slug['plugin'] ) ) {
382 $this->last_type = 'plugin';
383 $this->last_slug = $slug['plugin'];
384 } else {
385 $this->last_type = '';
386 $this->last_slug = '';
387 }
388 $this->last_time = $stop;
389 }
390 }
391
392 fclose( $fh );
393 unlink( $this->tmp_ticks );
394 }
395
396 /********************************************************************
397 * Save IO stats
398 */
399 private function save_iostats() {
400
401 $buffer = json_decode ( file_get_contents( $this->tmp_iostats ) , true );
402
403 if ( empty( $buffer ) ) {
404 $error = sprintf(
405 esc_html__('Cannot decode JSON-encode file (%s)', 'code-profiler'),
406 'CODE_PROFILER_TMP_IOSTATS_LOG'
407 );
408 $this->return_error( $error );
409 }
410
411 $lines = '';
412 foreach( $buffer as $key => $value ) {
413 $lines .= "$key\t$value\n";
414 $this->total_io += $value;
415 }
416
417 file_put_contents(
418 CODE_PROFILER_UPLOAD_DIR ."/{$this->microtime}.{$this->profile_name}.iostats.profile",
419 $lines
420 );
421
422 unlink( $this->tmp_iostats );
423 }
424
425
426 /********************************************************************
427 * Save Read/Write stats
428 */
429 private function save_diskio() {
430
431 rename(
432 $this->tmp_diskio,
433 CODE_PROFILER_UPLOAD_DIR ."/{$this->microtime}.{$this->profile_name}.diskio.profile"
434 );
435 }
436
437
438 /********************************************************************
439 * Save list of plugins using composer
440 */
441 private function save_composer() {
442
443 if (! empty( $this->composer ) ) {
444 // Try to get the plugin's name
445 foreach( $this->composer as $slug => $v ) {
446 if ( isset( $this->plugins[ $slug ]['name'] ) ) {
447 $this->composer[ $slug ] = $this->plugins[ $slug ]['name'];
448 }
449 }
450 file_put_contents(
451 CODE_PROFILER_UPLOAD_DIR ."/{$this->microtime}.{$this->profile_name}.composer.profile",
452 json_encode( $this->composer )
453 );
454 }
455 }
456
457 }
458
459 // =====================================================================
460 // EOF
461