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 / helper.php

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

509 lines 14.1 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 // Various constants
18
19 $upload_dir = wp_upload_dir();
20 define('CODE_PROFILER_UPLOAD_DIR', $upload_dir['basedir'] .'/code-profiler');
21 define('CODE_PROFILER_LOG', CODE_PROFILER_UPLOAD_DIR .'/log.php');
22 define('CODE_PROFILER_TMP_IOSTATS_LOG', 'iostats.tmp');
23 define('CODE_PROFILER_TMP_SUMMARY_LOG', 'summary.tmp');
24 define('CODE_PROFILER_TMP_TICKS_LOG', 'ticks.tmp');
25 define('CODE_PROFILER_TMP_DISKIO_LOG', 'diskio.tmp');
26 define('CODE_PROFILER_UPDATE_NOTICE', '<div class="updated notice is-dismissible"><p>%s</p></div>');
27 define('CODE_PROFILER_ERROR_NOTICE', '<div class="error notice is-dismissible"><p>%s</p></div>');
28 global $wp_version;
29 if (! defined('CODE_PROFILER_UA') ) { // UA signatures can be user-defined in the wp-config.php
30 define ('CODE_PROFILER_UA', [
31 esc_html__('Desktop', 'code-profiler') => [
32 'Firefox' => 'Mozilla/5.0 (Linux x86_64; rv:110.0) Gecko/20100101 Firefox/110.0',
33 'Chrome' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko)'.
34 ' Chrome/111.0.0.0 Safari/537.36',
35 'Edge' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,'.
36 ' like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/110.0.1587.63'
37 ],
38 esc_html__('Mobile', 'code-profiler') => [
39 'Android Phone' => 'Mozilla/5.0 (Android 13; Mobile; rv:68.0) Gecko/68.0 Firefox/110.0',
40 'Android Tablet' => 'Mozilla/5.0 (Linux; Android 13.0; SAMSUNG-SM-T377A Build/NMF26X)'.
41 ' AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Mobile Safari/537.36',
42 'iPhone' => 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_3_1 like Mac OS X) AppleWebKit/605.1.15'.
43 ' (KHTML, like Gecko) Version/16.3 Mobile/15E148 Safari/604.1',
44 'iPad' => 'Mozilla/5.0 (iPad; CPU OS 16_3_1 like Mac OS X) AppleWebKit/605.1.15'.
45 ' (KHTML, like Gecko) GSA/213.0.449417121 Mobile/15E148 Safari/605.1.15'
46 ],
47 esc_html__('Bot', 'code-profiler') => [
48 'Google Bot' => 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
49 'WordPress' => 'Mozilla/5.0 (compatible; CodeProfiler for WordPress/'. $wp_version .
50 '; https://code-profiler.com/)'
51 ]
52
53 ] );
54 }
55
56 if (! defined('CODE_PROFILER_MUPLUGIN') ) {
57 // MU plugin's name can be defined in the wp-config.php
58 define('CODE_PROFILER_MUPLUGIN', '0----code-profiler.php');
59 }
60 define('CODE_PROFILER_ACCURACY', [
61 1 => __('Highest (default)', 'code-profiler'),
62 5 => __('High', 'code-profiler'),
63 10 => __('Moderate', 'code-profiler'),
64 15 => __('Low', 'code-profiler'),
65 20 => __('Lowest', 'code-profiler')
66 ]);
67 // =====================================================================
68 // Prevent cURL timeout if a plugin changes its timeout options.
69
70 function code_profiler_curl_timeout( $handle, $parsed_args, $url ) {
71
72 if ( isset( $_REQUEST['action'] ) &&
73 $_REQUEST['action'] == 'codeprofiler_start_profiler') {
74
75 curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, 300 );
76 curl_setopt( $handle, CURLOPT_TIMEOUT, 300 );
77 }
78 }
79 add_action('http_api_curl', 'code_profiler_curl_timeout', 1000, 3 );
80
81 // =====================================================================
82 // Update version in the DB and check if options must be updated.
83
84 function code_profiler_init_update() {
85
86 $cp_options = get_option('code-profiler');
87 if ( empty( $cp_options['version'] ) ||
88 version_compare( $cp_options['version'], CODE_PROFILER_VERSION, '<') ) {
89
90 $cp_options['version'] = CODE_PROFILER_VERSION;
91
92 // Version 1.1
93 if (! isset( $cp_options['enable_wpcli'] ) ) {
94 $cp_options['enable_wpcli'] = 1;
95 }
96
97 // Version 1.2
98 if (! isset( $cp_options['disable_wpcron'] ) ) {
99 $cp_options['disable_wpcron'] = 1;
100 }
101
102 // Version 1.3.1
103 if (! isset( $cp_options['http_response'] ) ) {
104 $cp_options['http_response'] = '^(?:3|4|5)\d{2}$';
105 }
106
107 // Version 1.5
108 if (! isset( $cp_options['accuracy'] ) ) {
109 $cp_options['accuracy'] = 1;
110 }
111
112 // Update version in the DB
113 update_option('code-profiler', $cp_options );
114 }
115
116 }
117 add_action('admin_init', 'code_profiler_init_update');
118
119 // =====================================================================
120 // Verify or create our storage folder in the uploads directory.
121 // Call during activation and access to the plugin.
122
123 function code_profiler_check_uploadsdir() {
124
125 if (! file_exists( CODE_PROFILER_UPLOAD_DIR ) ) {
126 mkdir( CODE_PROFILER_UPLOAD_DIR, 0755 );
127 }
128 if (! file_exists( CODE_PROFILER_UPLOAD_DIR .'/index.html') ) {
129 touch( CODE_PROFILER_UPLOAD_DIR .'/index.html');
130 }
131 // For Apache & Litespeed
132 if (! file_exists( CODE_PROFILER_UPLOAD_DIR .'/.htaccess') ) {
133 file_put_contents(
134 CODE_PROFILER_UPLOAD_DIR .'/.htaccess',
135 'Require all denied'
136 );
137 }
138 // Make sure there's a MU plugin directory
139 if (! is_dir( WPMU_PLUGIN_DIR ) ) {
140 wp_mkdir_p( WPMU_PLUGIN_DIR );
141 }
142
143
144 if (! file_exists( WPMU_PLUGIN_DIR .'/'. CODE_PROFILER_MUPLUGIN ) ) {
145
146 if (! is_writable( WPMU_PLUGIN_DIR ) ) {
147 wp_die(
148 sprintf(
149 __('Error: The MU folder is read only, please make it writable: %s', 'code-profiler'),
150 esc_html( WPMU_PLUGIN_DIR ) .'/'
151 )
152 );
153 }
154
155 if (! file_exists( __DIR__ .'/mu.plugin') ) {
156 code_profiler_log_error( sprintf(
157 esc_html__('Cannot find the MU plugin: %s', 'code-profiler'),
158 __DIR__ .'/mu.plugin'
159 ));
160 } else {
161 $res = copy( __DIR__ .'/mu.plugin', WPMU_PLUGIN_DIR .'/'. CODE_PROFILER_MUPLUGIN );
162 if ( $res === false ) {
163 code_profiler_log_error( sprintf(
164 esc_html__('Cannot copy the MU plugin to %s', 'code-profiler'),
165 WPMU_PLUGIN_DIR
166 ));
167 }
168 }
169 } else {
170 // Update MU plugin it if needed
171 if ( md5_file( WPMU_PLUGIN_DIR .'/'. CODE_PROFILER_MUPLUGIN ) !== md5_file( __DIR__ .'/mu.plugin') ) {
172 copy( __DIR__ .'/mu.plugin', WPMU_PLUGIN_DIR .'/'. CODE_PROFILER_MUPLUGIN );
173 }
174 }
175
176 // Log file
177 if (! file_exists( CODE_PROFILER_LOG ) ) {
178 file_put_contents( CODE_PROFILER_LOG, "<?php exit; ?>\n" );
179 }
180
181 }
182
183 // =====================================================================
184 // Create the default options.
185
186 function code_profiler_default_options() {
187
188 $cp_options = [
189 'show_paths' => 'relative',
190 'display_name' => 'full',
191 'truncate_name' => 30,
192 'chart_type' => 'x',
193 'chart_max_plugins' => 25,
194 'hide_empty_value' => 1,
195 'table_max_rows' => 30,
196 'warn_composer' => 1,
197 'enable_wpcli' => 1,
198 'disable_wpcron' => 1,
199 'http_response' => '^(?:3|4|5)\d{2}$',
200 'accuracy' => 1
201 ];
202
203 update_option('code-profiler', $cp_options );
204
205 }
206
207 // =====================================================================
208 // Create a profile name.
209
210 function code_profiler_profile_name() {
211
212 return date('Y-m-d_') . substr( time(), 4 );
213
214 }
215
216 // =====================================================================
217 // Disable opcode cache.
218
219 function code_profiler_disable_opcode() {
220
221 try {
222 if (extension_loaded('Zend OPcache')) {
223 ini_set('opcache.enable', 0);
224 } elseif ( extension_loaded('wincache') ) {
225 ini_set('wincache.fcenabled', 0);
226 }
227 set_time_limit(0);
228
229 } catch ( Exception $e ) { }
230
231 $cp_options = get_option('code-profiler');
232 if (! empty( $cp_options['disable_wpcron'] ) ) {
233 if (! defined('DISABLE_WP_CRON') ) {
234 define('DISABLE_WP_CRON', true );
235 }
236 }
237
238 }
239 // =====================================================================
240 // Verify the security key when running the profile.
241
242 function code_profiler_verify_key() {
243
244 $response = [
245 'status' => 'error',
246 'message' => __('Security keys do not match. Reload the page and try again (%s)', 'code-profiler')
247 ];
248
249 $cp_options = get_option('code-profiler');
250
251 if ( empty( $cp_options['hash'] ) ) {
252 $response['message'] = sprintf( $response['message'], '#1');
253
254 } else {
255 if ( empty( $_REQUEST['profiler_key'] ) ) {
256 $response['message'] = sprintf( $response['message'], '#2');
257
258 } else {
259 if ( $cp_options['hash'] == sha1( $_REQUEST['profiler_key'] ) ) {
260 return;
261 } else {
262 $response['message'] = sprintf( $response['message'], '#3');
263 }
264 }
265 }
266 wp_send_json( $response );
267
268 }
269
270 // =====================================================================
271 // Write message to the log.
272 // Log level can be a combination of INFO (1), WARN (2), ERROR (4)
273 // and DEBUG (8).
274
275 function code_profiler_write2log( $message, $level, $create ) {
276
277 if ( empty( $create ) ) {
278 file_put_contents(
279 CODE_PROFILER_LOG,
280 time() ."~~$level~~$message\n",
281 FILE_APPEND
282 );
283 } else {
284 file_put_contents(
285 CODE_PROFILER_LOG,
286 time() ."~~$level~~$message\n"
287 );
288 }
289 }
290
291 function code_profiler_log_info( $string, $create = 0 ) {
292 code_profiler_write2log( $string, 1, $create );
293 }
294 function code_profiler_log_warn( $string, $create = 0 ) {
295 code_profiler_write2log( $string, 2, $create );
296 }
297 function code_profiler_log_error( $string, $create = 0 ) {
298 code_profiler_write2log( $string, 4, $create );
299 }
300 function code_profiler_log_debug( $string, $create = 0 ) {
301 code_profiler_write2log( $string, 8, $create );
302 }
303
304 // =====================================================================
305 // Verify if a profile exists and return its full path.
306
307 function code_profiler_get_profile_path( $id, $type = 'slugs') {
308
309 $return = false;
310
311 if (! empty( $id ) && preg_match('/^\d{10}\.\d+$/', $id ) ) {
312 $glob = glob( CODE_PROFILER_UPLOAD_DIR ."/$id.*.$type.profile" );
313 if ( is_array( $glob ) && ! empty( $glob[0] ) ) {
314 if ( preg_match( "`/$id\.(.+?).$type.profile$`", $glob[0], $match ) ) {
315
316 return CODE_PROFILER_UPLOAD_DIR. "/$id.{$match[1]}";
317 }
318 }
319 }
320 return false;
321 }
322
323 // =====================================================================
324 // Open, parse and return the content of a profile file.
325
326 function code_profiler_get_profile_data( $file, $type = 'slugs') {
327
328 $buffer = [];
329
330 $fh = fopen( "$file.$type.profile", 'rb');
331
332 if ( $fh === false ) {
333 $err = sprintf(
334 CODE_PROFILER_ERROR_NOTICE,
335 sprintf(
336 esc_html__('Unable to open profile file: %s.', 'code-profiler'),
337 esc_html( "$file.$type.profile" )
338 )
339 );
340 $buffer['error'] = $err;
341 return $buffer;
342 }
343
344 while (! feof( $fh ) ) {
345 $buffer[] = fgetcsv( $fh, 1000, "\t" );
346 }
347
348 fclose( $fh );
349
350 if ( empty( $buffer ) ) {
351 $err = sprintf(
352 CODE_PROFILER_ERROR_NOTICE,
353 sprintf(
354 esc_html__('Profile file is empty or corrupted: %s.', 'code-profiler'),
355 esc_html( "$file.$type.profile" )
356 )
357 );
358 $buffer['error'] = $err;
359 return $buffer;
360 }
361
362 // Get rid of the empty field created by fgetcsv
363 return array_filter( $buffer );
364 }
365
366 // =====================================================================
367 // Exit with a json-encoded error message except if we're running
368 // from WP CLI
369
370 function code_profiler_wp_send_json( $response ) {
371
372 if ( defined('WP_CLI') && WP_CLI ) {
373 WP_CLI::error( $response['message'] );
374 }
375 wp_send_json( $response );
376 }
377
378 // =====================================================================
379 // Retrieve the summary stats for a given profile.
380
381 function code_profiler_getsummarystats( $profile_path, $type = 'html') {
382
383 if ( $type == 'html') {
384 $open = '<ul><li>&#10148; ';
385 $div = '</li><li>&#10148; ';
386 $close = '</li></ul>';
387 } else {
388 // WP-CLI
389 $open = " \u{27A4} ";
390 $div = "\n \u{27A4} ";
391 $close = "\n\n";
392 }
393
394 if (! file_exists( "$profile_path.summary.profile" ) ) {
395 return false;
396 }
397 $decode = json_decode( file_get_contents( "$profile_path.summary.profile" ) );
398
399 $string = $open;
400
401 if ( empty( $decode->items ) ) {
402 $tmp = 'N/A';
403 } else {
404 $tmp = (int) --$decode->items;
405 }
406 $string .= sprintf(
407 __('%s plugins and 1 theme', 'code-profiler'),
408 $tmp
409 );
410
411 $string .= $div;
412
413 if ( empty( $decode->time ) ) {
414 $tmp = 'N/A';
415 } else {
416 $tmp = number_format( $decode->time, 4 );
417 }
418 $string .= sprintf(
419 __('Execution time: %ss', 'code-profiler'),
420 $tmp
421 );
422
423 $string .= $div;
424
425 if ( empty( $decode->memory ) ) {
426 $tmp = 'N/A';
427 } else {
428 $tmp = number_format( $decode->memory, 2);
429 }
430 $string .= sprintf(
431 __('Peak memory: %s MB', 'code-profiler'),
432 $tmp
433 );
434
435 $string .= $div;
436
437 if ( empty( $decode->io ) ) {
438 $tmp = 'N/A';
439 } else {
440 $tmp = number_format( $decode->io );
441 }
442 $string .= sprintf(
443 __('File I/O operations: %s', 'code-profiler'),
444 $tmp
445 );
446
447 $string .= $div;
448
449 if ( empty( $decode->queries ) ) {
450 $tmp = 'N/A';
451 } else {
452 $tmp = number_format( $decode->queries );
453 }
454 $string .= sprintf(
455 __('SQL queries: %s', 'code-profiler'),
456 $tmp
457 );
458
459 $string .= $close;
460
461 return $string;
462 }
463
464 // =====================================================================
465 // Remove *tmp files left after an HTTP error (4xx or 5xx).
466
467 function code_profiler_cleantmpfiles() {
468
469 $glob = glob( CODE_PROFILER_UPLOAD_DIR .'/*.tmp');
470 if ( is_array( $glob ) ) {
471 $count = 0;
472 foreach( $glob as $file ) {
473 $count++;
474 unlink( $file );
475 }
476 if ( $count ) {
477 code_profiler_log_info( sprintf(
478 __('Deleting %s temporary files found in the profiles folder', 'code-profiler'),
479 (int) $count
480 ) );
481 }
482 }
483 }
484
485 // =====================================================================
486 // Clear the log if it's bigger than 100KB.
487
488 function code_profiler_clearlog() {
489
490 if ( file_exists( CODE_PROFILER_LOG ) && filesize( CODE_PROFILER_LOG ) > 100000 ) {
491 file_put_contents( CODE_PROFILER_LOG, "<?php exit; ?>\n" );
492 }
493 }
494
495 // =====================================================================
496 // We don't want to be bothered by other themes/plugins' admin notices.
497
498 add_action('admin_head', 'code_profiler_hide_admin_notices');
499
500 function code_profiler_hide_admin_notices() {
501 if ( isset( $_GET['page'] ) && $_GET['page'] == 'code-profiler') {
502 remove_all_actions('admin_notices');
503 remove_all_actions('all_admin_notices');
504 }
505 }
506
507 // =====================================================================
508 // EOF
509