PluginProbe
Code Profiler – WordPress Performance Profiling and Debugging Made Easy / 1.6.4
Code Profiler – WordPress Performance Profiling and Debugging Made Easy v1.6.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 / helper.php

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

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