PluginProbe
Code Profiler – WordPress Performance Profiling and Debugging Made Easy / trunk
Code Profiler – WordPress Performance Profiling and Debugging Made Easy vtrunk
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-cli.php

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

364 lines 9.8 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://nintechnet.com/codeprofiler/ |
11 +=====================================================================+
12 */
13
14 if (! defined('ABSPATH') ) { die('Forbidden'); }
15
16 // =====================================================================
17
18 class CodeProfiler_CLI extends WP_CLI_Command {
19
20 private $file;
21 private $out = 'view';
22 private $cmd_view = 'wp code-profiler view';
23 private $cmd_run = 'wp code-profiler run';
24
25 /**
26 * Run the profiler.
27 *
28 */
29 public function run( $args, $assoc_args ) {
30
31 $this->is_enabled();
32
33 $_POST['cp_nonce'] = wp_create_nonce('start_profiler_nonce');
34
35 $_POST['post'] = home_url( '/' );
36 // Mark the connection as 'HTTPS' if needed.
37 if ( strtolower( parse_url( $_POST['post'], PHP_URL_SCHEME ) ) == 'https') {
38 $_SERVER['HTTPS'] = 'on';
39 }
40
41 if (! empty( $assoc_args['wpcron'] ) ) {
42 $_POST['x_end'] = 'wpcron';
43 $_POST['post'] = $assoc_args['wpcron'];
44
45 } else {
46 $_POST['x_end'] = 'frontend';
47 // Detect if we're authenticated or not
48 if ( is_user_logged_in() === true ) {
49 $_POST['x_auth'] = 'authenticated';
50 } else {
51 $_POST['x_auth'] = 'unauthenticated';
52 }
53 }
54
55 $_POST['profile'] = 'WP-CLI_' . time();
56 $_POST['user_agent'] = 'Firefox';
57
58 if (! empty( $assoc_args['dest'] ) ) {
59 $message = sprintf(
60 __('The "%s" option is only available in the pro version of Code Profiler.', 'code-profiler'),
61 '--dest'
62 );
63 WP_CLI::error( $message );
64 exit;
65 }
66
67 if (! empty( $assoc_args['out'] ) && in_array( $assoc_args['out'], ['json', 'csv'] ) ) {
68 $this->out = $assoc_args['out'];
69 }
70
71 // HTTP basic authentication
72 if (! empty( $assoc_args['u'] ) ) {
73 _e('Enter your HTTP basic authentication password:', 'code-profiler');
74 echo ' ';
75 echo "\033[30;40m";
76 $password = trim( stream_get_line( STDIN, 255, PHP_EOL) );
77 echo "\033[0m";
78 $_POST['Authorization'] = 'Basic '. base64_encode( "{$assoc_args['u']}:{$password}" );
79 }
80
81 if ( $this->out == 'view') {
82 WP_CLI::log( sprintf(
83 __('Starting Code Profiler v%s (profile: %s)', 'code-profiler'),
84 CODE_PROFILER_VERSION, $_POST['profile'] ) . "\n"
85 );
86
87 $progress = \WP_CLI\Utils\make_progress_bar( '', 3 );
88 $progress->tick();
89 }
90
91 // Run the profiler
92 $response = json_decode( CodeProfiler_helpers::codeprofiler_start_profiler(), true );
93 if ( $response === false ) {
94 $message = __('Unknown error returned by AJAX', 'code-profiler');
95 WP_CLI::error( $message );
96 exit;
97 }
98 if ( $response['status'] == 'error' ) {
99 WP_CLI::error( $response['message'] );
100 exit;
101 }
102
103 if ( $this->out == 'view') {
104 $progress->tick();
105 }
106
107 // All good, run the parser
108 $_POST['microtime'] = $response['microtime'];
109 $response = json_decode( CodeProfiler_helpers::codeprofiler_prepare_report(), true );
110 if ( $response === false ) {
111 $message = __('Unknown error returned by AJAX', 'code-profiler');
112 WP_CLI::error( $message );
113 exit;
114 }
115 if ( $response['status'] == 'error' ) {
116 WP_CLI::error( $response['message'] );
117 exit;
118 }
119
120 if ( $this->out == 'view') {
121 $progress->tick();
122 $progress->finish();
123 }
124
125 // Run the parser and show the results
126 $this->view( $args, $assoc_args, $response['cp_profile'] );
127
128 exit;
129 }
130
131
132 /**
133 * Display last created profile.
134 *
135 */
136 public function view( $args = [], $assoc_args = [], $profile = '') {
137
138 $this->is_enabled();
139
140 if (! empty( $assoc_args['out'] ) && in_array( $assoc_args['out'], ['json', 'csv'] ) ) {
141 $this->out = $assoc_args['out'];
142 }
143
144 // The profile was just created (`wp code-profiler run`)...
145 if (! empty( $profile ) ) {
146 $this->file = code_profiler_get_profile_path( $profile );
147 if ( $this->file === false ) {
148 $message = __('Cannot find the profile file', 'code-profiler');
149 WP_CLI::error( $message );
150 exit;
151 }
152 $this->file .= '.slugs.profile';
153
154 // ... or we show last created one (`wp code-profiler view`)
155 } else {
156 $this->file = $this->find_last_profile();
157 if ( $this->file === false ) {
158 $message = sprintf(
159 __('No profile found. Run the profiler at least once to create a profile: %s', 'code-profiler'),
160 $this->cmd_run
161 );
162 WP_CLI::error( $message );
163 exit;
164 }
165 }
166
167 // Fetch and display the profile's name only
168 preg_match(
169 '`'. CODE_PROFILER_UPLOAD_DIR .'/\d{10}\.\d+\.(.+?)\.slugs\.profile$`',
170 $this->file,
171 $match
172 );
173
174 $cp_slug = __('Slug', 'code-profiler');
175 $cp_time = __('Execution time', 'code-profiler');
176 $cp_name = __('Name', 'code-profiler');
177 $cp_type = __('Type', 'code-profiler');
178
179 if ( $this->out == 'view' ) {
180 $buffer = '';
181 $message = sprintf( __('Viewing: %s', 'code-profiler'), $match[1] );
182 $date = date('Y/m/d \@ H:i:s', filemtime( $this->file ) );
183 echo WP_CLI::colorize("\n%Y$message ~ $date%n\n\n");
184 // Display stats
185 $summary_file = str_replace('.slugs.profile', '', $this->file );
186 echo code_profiler_getsummarystats( $summary_file, 'text');
187 /**
188 * CSV output.
189 */
190 } elseif ( $this->out == 'csv' ) {
191 $buffer = "$cp_slug,\"$cp_time\",$cp_name,$cp_type\n";
192 /**
193 * JSON-encoded output.
194 */
195 } else {
196 $buffer = [];
197 }
198
199 $slugs = $this->read_profile();
200
201 $cp_options = get_option('code-profiler');
202
203 // Get the total time and the slowest item
204 $total_time = 0;
205 foreach( $slugs as $k => $v ) {
206 $total_time += $v[1];
207 }
208
209 $coeff = number_format( $slugs[0][1] / $total_time * 100 );
210
211 foreach( $slugs as $k => $v ) {
212
213 if ( $this->out == 'csv') {
214 $buffer .= "{$v[0]},{$v[1]},{$v[2]},{$v[3]}\n";
215
216 } elseif( $this->out == 'json') {
217 $buffer[] = [
218 $cp_slug = $v[0],
219 $cp_time = $v[1],
220 $cp_name = $v[2],
221 $cp_type = $v[3]
222 ];
223
224 } else {
225 // Display name, time and %
226 if ( isset( $cp_options['display_name'] ) && $cp_options['display_name'] == 'slug' ) {
227 $name = $v[0];
228 } else {
229 $name = $v[2];
230 }
231 // Inform if it's the theme or a mu-plugin
232 if ( $v[3] == 'theme') {
233 $name .= ' (theme)';
234 } elseif ( $v[3] == 'mu-plugin') {
235 $name .= ' (mu-plugin)';
236 }
237
238 $time = number_format( $v[1], 3 );
239 $percent = number_format( $v[1] / $total_time * 100 );
240 $chars = number_format( $percent * 80 / $coeff );
241 // We use `echo` instead of `WP_CLI::log` because the layout could
242 // be all messed-up when some caching plugins such as LiteSpeed Cache
243 // are activated.
244 echo " $name | {$time}s | {$percent}%\n";
245 if (! $percent ) {
246 echo " \u{258C}\n\n";
247 } else {
248 $bar = '';
249 for ( $i = 0; $i < $chars; $i++ ) {
250 $bar .= ' ';
251 }
252 echo WP_CLI::colorize(" %8$bar%n\n\n");
253 }
254 }
255 }
256 if ( $this->out == 'csv') {
257 echo $buffer;
258
259 } elseif ( $this->out == 'json' ) {
260 echo json_encode( $buffer );
261 }
262
263 exit;
264 }
265
266
267 /**
268 * Retrieve the full path/name to the last created profile
269 *
270 */
271 private function find_last_profile() {
272
273 $profiles = code_profiler_glob( CODE_PROFILER_UPLOAD_DIR, '\.slugs\.profile$', true );
274
275 array_multisort(
276 array_map('filectime', $profiles ),
277 SORT_NUMERIC,
278 SORT_DESC,
279 $profiles
280 );
281
282 if (! empty( $profiles[0] ) ) {
283 return $profiles[0];
284 }
285
286 return false;
287 }
288
289
290 /**
291 * Parse and return the profile's data
292 *
293 */
294 private function read_profile() {
295
296 $profile = str_replace('.slugs.profile', '', $this->file );
297
298 $res = code_profiler_get_profile_data( $profile );
299 if ( isset( $res['error'] ) ) {
300 WP_CLI::error( $res['error'] );
301 exit;
302 }
303
304 // Sort data (slowest plugin/theme first)
305 usort( $res, function( $a, $b ) {
306 return $b[1] <=> $a[1];
307 } );
308
309 return $res;
310 }
311
312
313 /**
314 * Verify wether WP CLI integration is enabled or not
315 *
316 */
317 private function is_enabled() {
318
319 $cp_options = get_option('code-profiler');
320 if ( empty( $cp_options['enable_wpcli'] ) ) {
321 $message = __('WP-CLI integration is disabled. To enable it, log in to your admin dashboard and go to the Code Profiler settings page.', 'code-profiler');
322 WP_CLI::error( $message );
323 exit;
324 }
325 }
326
327
328 /**
329 * Display help screen and quit.
330 *
331 */
332 public function help() {
333
334 $this->is_enabled();
335
336 WP_CLI::log("\nCode Profiler v". CODE_PROFILER_VERSION .
337 " (c)". date('Y') ." Jerome Bruandet & NinTechNet Limited ~ https://nintechnet.com/codeprofiler/\n\n".
338 " {$this->cmd_view} ". __('View last created profile', 'code-profiler') ."\n".
339 " {$this->cmd_run} ". __('Run the profiler in the frontend', 'code-profiler') ."\n\n".
340 __('GLOBAL PARAMETERS', 'code-profiler') ."\n\n".
341 " --dest=<URL to profile> **". __('Pro version only', 'code-profiler') ."**\n".
342 " ". __('Path to the WordPress page or post to profile. If missing, profile the frontend.', 'code-profiler') ."\n\n".
343 " --wpcron=<cron event> (optional)\n".
344 " ". __('WordPress cron event to profile.', 'code-profiler') ."\n\n".
345 " --user=<id|login|email> (optional)\n".
346 " ". __('Run the profiler as the corresponding WordPress user. If missing, run as an unauthenticated user.', 'code-profiler') ."\n\n".
347 " --u=<username> (optional)\n".
348 " ". __('HTTP Basic authentication username. You will be prompted to enter your password.', 'code-profiler') ."\n\n".
349 " --out=<json|csv> (optional)\n".
350 " ". __('Return the results in JSON-encoded or CSV format.', 'code-profiler') ."\n\n" );
351 exit;
352 }
353
354 }
355
356 WP_CLI::add_command(
357 'code-profiler',
358 'CodeProfiler_CLI',
359 ['shortdesc' => 'Profile your blog with Code Profiler.']
360 );
361
362 // =====================================================================
363 // EOF
364