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

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

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