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

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