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

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