├── README.md └── time-stack.php /README.md: -------------------------------------------------------------------------------- 1 | Time-Stack-Plugin 2 | ================= 3 | 4 | Plugin to be use with the WordPress TimeStack web app: https://github.com/joehoyle/Time-Stack . 5 | 6 | ## Requirements 7 | 8 | Any WordPress Persistant Object Caching or PHP-APC -------------------------------------------------------------------------------- /time-stack.php: -------------------------------------------------------------------------------- 1 | start_time = $hm_time_stack_start; 56 | else 57 | $this->start_time = hm_time_stack_time(); 58 | 59 | // store it in object cache for persistant logging 60 | $t = $this; 61 | add_action( 'shutdown', function() use ( $t ) { 62 | $t->end(); 63 | }, 11 ); 64 | 65 | $this->setup_hooks(); 66 | 67 | $this->stack = new HM_Time_Stack_Operation( 'wp' ); 68 | $this->stack->start_time = $this->start_time; 69 | //$this->stack->start_memory_usage = 0; 70 | 71 | 72 | } 73 | 74 | public function end() { 75 | 76 | $this->end_operation( 'wp' ); 77 | 78 | $all_stacks = HM_Time_Stack::get_data(); 79 | 80 | if ( is_user_logged_in() ) 81 | $user = wp_get_current_user()->display_name; 82 | 83 | else 84 | $user = 'Anonymouse'; 85 | 86 | $all_stacks = array_reverse( (array) $all_stacks ); 87 | 88 | $all_stacks[] = $this->archived_data = array( 89 | 'stack' => $this->stack->archive(), 90 | 'date' => time(), 91 | 'url' => $_SERVER['REQUEST_URI'], 92 | 'full_url' => $_SERVER['HTTP_HOST'], 93 | 'site_id' => get_current_blog_id(), 94 | 'get_vars' => stripslashes_deep( $_GET ), 95 | 'request_type' => $_SERVER['REQUEST_METHOD'], 96 | 'user' => $user 97 | ); 98 | 99 | $all_stacks = array_reverse( $all_stacks ); 100 | 101 | HM_Time_Stack::set_data( $all_stacks ); 102 | } 103 | 104 | public function get_archived_data() { 105 | return $this->archived_data['stack']; 106 | } 107 | 108 | public function get_id() { 109 | return $_SERVER['REQUEST_URI'] . time(); 110 | } 111 | 112 | public function start_operation( $id, $label = '' ) { 113 | 114 | if ( ! $this->stack ) 115 | return; 116 | 117 | $operation = new HM_Time_Stack_Operation( $id, $label ); 118 | $this->stack->add_operation( $operation ); 119 | 120 | } 121 | 122 | public function end_operation( $id, $vars = null ) { 123 | if ( ! $this->stack ) 124 | return; 125 | 126 | $this->stack->end_operation( $this->stack->get_child_operation_by_id( $id ), $vars ); 127 | } 128 | 129 | public function add_event( $id, $label = '' ) { 130 | 131 | $event = new HM_Time_Stack_Event( $id, $label ); 132 | $this->stack->add_event( $event ); 133 | } 134 | 135 | private function setup_hooks() { 136 | 137 | $t = $this; 138 | 139 | // global adding from actions 140 | add_action( 'start_operation', function( $id, $label = '' ) use ( $t ) { 141 | 142 | $t->start_operation( $id, $label ); 143 | 144 | }, 10, 2 ); 145 | 146 | add_action( 'end_operation', function( $id, $args = array() ) use ( $t ) { 147 | 148 | if ( $t->stack->get_child_operation_by_id( $id ) ) { 149 | $t->stack->get_child_operation_by_id( $id )->vars = $args; 150 | $t->end_operation( $id ); 151 | } 152 | 153 | }, 10, 2 ); 154 | 155 | add_action( 'add_event', function( $id, $label = '' ) use ( $t ) { 156 | 157 | $t->add_event( $id, $label ); 158 | 159 | }, 10, 1 ); 160 | 161 | add_action( 'log', function( $data ) { 162 | if ( is_scalar( $data ) ) 163 | do_action( 'add_event', $data ); 164 | else 165 | do_action( 'add_event', print_r( $data, true ) ); 166 | } ); 167 | 168 | 169 | 170 | add_action( 'init', function() use ( $t ) { 171 | $t->start_operation( 'hook: init' ); 172 | }, 0 ); 173 | 174 | add_action( 'init', function() use ( $t ) { 175 | $t->end_operation( 'hook: init' ); 176 | }, 999999 ); 177 | 178 | add_action( 'parse_query', function( $wp_query ) use ( $t ) { 179 | 180 | $query = is_string( $wp_query->query ) ? $wp_query->query : json_encode( $wp_query->query ); 181 | global $wp_the_query; 182 | 183 | if ( $wp_the_query == $wp_query ) { 184 | $name = 'Main WP Query'; 185 | } 186 | 187 | else { 188 | $trace = debug_backtrace(); 189 | 190 | if ( isset( $trace[6]['function'] ) && isset( $trace[7]['file'] ) ) 191 | $name = $trace[6]['function'] . ' - ' . $trace[7]['file'] . '[' . $trace[7]['line'] . ']'; 192 | else 193 | $name = 'WP_Query'; 194 | } 195 | 196 | $t->start_operation( 'wp_query::' . spl_object_hash( $wp_query ), 'WP_Query - ' . $name ); 197 | 198 | $wp_query->query_vars['suppress_filters'] = false; 199 | }, 1 ); 200 | 201 | add_action( 'the_posts', function( $posts, $wp_query ) use ( $t ) { 202 | $t->end_operation( 'wp_query::' . spl_object_hash( $wp_query ) ); 203 | return $posts; 204 | }, 99, 2 ); 205 | 206 | add_action( 'shutdown', function() use ( $t ) { 207 | 208 | $t->add_event( 'shutdown' ); 209 | 210 | } ); 211 | 212 | add_action( 'template_redirect', function() use ( $t ) { 213 | 214 | $t->add_event( 'template_redirect' ); 215 | 216 | }, 1 ); 217 | 218 | add_action( 'wp_head', function() use ( $t ) { 219 | 220 | $t->add_event( 'wp_head' ); 221 | 222 | }, 1 ); 223 | 224 | add_action( 'loop_start', function( $wp_query ) use ( $t ) { 225 | 226 | $query = is_string( $wp_query->query ) ? $wp_query->query : json_encode( $wp_query->query ); 227 | $t->add_event( 'the_loop::' . spl_object_hash( $wp_query ), 'Loop Start' ); 228 | 229 | }, 1 ); 230 | 231 | add_action( 'loop_end', function( $wp_query ) use ( $t ) { 232 | 233 | $query = is_string( $wp_query->query ) ? $wp_query->query : json_encode( $wp_query->query ); 234 | $t->add_event( 'the_loop::' . spl_object_hash( $wp_query ), 'Loop End' ); 235 | 236 | }, 1 ); 237 | 238 | add_action( 'get_sidebar', function( $name ) use ( $t ) { 239 | 240 | $t->add_event( 'get_sidebar', 'get_sidebar - ' . $name ); 241 | 242 | }, 1 ); 243 | 244 | add_action( 'wp_footer', function() use ( $t ) { 245 | 246 | $t->add_event( 'wp_footer' ); 247 | 248 | }, 1 ); 249 | 250 | // hooks for remote rewuest, (but hacky) 251 | add_filter( 'https_ssl_verify', function( $var ) { 252 | 253 | do_action( 'start_operation', 'Remote Request' ); 254 | return $var; 255 | 256 | } ); 257 | 258 | add_action( 'http_api_debug', function( $response, $type, $class, $args, $url ) use ( $t ) { 259 | $t->end_operation( 'Remote Request', array( 'url' => $url ) ); 260 | }, 10, 5 ); 261 | 262 | 263 | add_action( 'plugins_loaded', function() use ( $t ) { 264 | $t->add_event( 'loaded plugins' ); 265 | }, 9999999 ); 266 | 267 | 268 | } 269 | 270 | public function get_start_time() { 271 | return $this->start_time; 272 | } 273 | } 274 | 275 | class HM_Time_Stack_Operation { 276 | 277 | public $start_time; 278 | public $end_time; 279 | public $duration; 280 | public $id; 281 | public $label; 282 | public $is_open; 283 | private $open_operation; 284 | public $children; 285 | public $peak_memory_usage; 286 | public $start_memory_usage; 287 | public $end_memory_usage; 288 | public $start_query_count; 289 | public $end_query_count; 290 | public $query_count; 291 | public $vars = array(); 292 | public $time; 293 | 294 | public function __construct( $id, $label = '' ) { 295 | 296 | $this->children = array(); 297 | $this->id = $id; 298 | $this->start_time = hm_time_stack_time(); 299 | 300 | if ( $id !== 'wp' ) 301 | $this->time = round( hm_time_stack_time() - HM_Time_Stack::instance()->get_start_time(), 3 ); 302 | 303 | $this->label = $label; 304 | $this->is_open = true; 305 | $this->start_memory_usage = memory_get_peak_usage(); 306 | 307 | global $wpdb; 308 | 309 | if ( ! defined( 'SAVEQUERIES' ) ) 310 | define( 'SAVEQUERIES', true ); 311 | 312 | $this->start_query_count = count( $wpdb->queries ); 313 | } 314 | 315 | public function end() { 316 | $this->end_time = hm_time_stack_time(); 317 | $this->end_memory_usage = memory_get_peak_usage(); 318 | $this->peak_memory_usage = round( ( $this->end_memory_usage - $this->start_memory_usage ) / 1024 / 1024, 4 ); 319 | $this->duration = round( $this->end_time - $this->start_time, 4 ); 320 | $this->is_open = false; 321 | 322 | global $wpdb; 323 | $this->end_query_count = count( $wpdb->queries ); 324 | 325 | $this->query_count = $this->end_query_count - $this->start_query_count; 326 | 327 | if ( $wpdb->queries ) 328 | $this->queries = array_slice( $wpdb->queries, $this->start_query_count ); 329 | 330 | else 331 | $this->queries = array(); 332 | } 333 | 334 | public function add_operation( $operation ) { 335 | 336 | if ( ! empty( $this->open_operation ) ) { 337 | $this->open_operation->add_operation( $operation ); 338 | } 339 | 340 | else { 341 | $this->children[] = $operation; 342 | 343 | $this->open_operation = $operation; 344 | } 345 | } 346 | 347 | public function add_event( $event ) { 348 | 349 | if ( ! empty( $this->open_operation ) ) { 350 | $this->open_operation->add_event( $event ); 351 | } else { 352 | $this->children[] = $event; 353 | } 354 | } 355 | 356 | public function end_operation( $operation, $vars = null ) { 357 | 358 | if ( ! empty( $this->open_operation ) ) { 359 | 360 | if ( $this->open_operation == $operation ) { 361 | if ( $vars ) 362 | $this->open_operation->vars = array_merge( $this->open_operation->vars, $vars ); 363 | 364 | $this->open_operation->end(); 365 | $this->open_operation = null; 366 | } 367 | else { 368 | $this->open_operation->end_operation( $operation, $vars ); 369 | 370 | } 371 | 372 | } 373 | 374 | if ( $operation === $this ) { 375 | 376 | if ( $vars ) 377 | $this->vars = array_merge( $this->vars, $vars ); 378 | 379 | $this->end(); 380 | 381 | } 382 | } 383 | 384 | public function get_child_operation_by_id( $id ) { 385 | 386 | $return = null; 387 | 388 | foreach ( $this->children as $child ) { 389 | if ( $operation = $child->get_child_operation_by_id( $id ) ) { 390 | $return = $operation; 391 | break; 392 | } 393 | } 394 | 395 | if ( $this->is_open && $this->id == $id ) 396 | $return = $this; 397 | 398 | return $return; 399 | } 400 | 401 | public function archive() { 402 | 403 | $archive = (object) array(); 404 | 405 | $archive->type = get_class($this); 406 | $archive->queries = ! empty( $this->queries ) ? $this->queries : array(); 407 | $archive->query_time = $this->get_query_time(); 408 | $archive->is_open = $this->is_open; 409 | $archive->duration = round( $this->duration, 4 ); 410 | $archive->memory_usage = round( $this->peak_memory_usage, 4 ); 411 | $archive->label = $this->label ? $this->label : $this->id; 412 | $archive->vars = $this->vars; 413 | $archive->time = $this->time; 414 | 415 | foreach ( $this->children as $operation ) 416 | $archive->children[] = $operation->archive(); 417 | 418 | //special case for "wp" 419 | if ( $this->id === 'wp' ) { 420 | $archive->request = array( 421 | 'user_agent', 422 | ); 423 | } 424 | 425 | return $archive; 426 | } 427 | 428 | private function get_query_time() { 429 | 430 | $query_time = 0; 431 | 432 | if ( !empty( $this->queries ) ) 433 | foreach ( (array) $this->queries as $q ) 434 | $query_time += $q[1]; 435 | 436 | } 437 | 438 | public function _print() { 439 | 440 | $query_time = 0; 441 | 442 | if ( !empty( $this->queries ) ) 443 | foreach ( (array) $this->queries as $q ) 444 | $query_time += $q[1]; 445 | ?> 446 |
  • 447 | 448 | operation: label ? $this->label : $this->id ?> 449 | query_count ?> Queries [] 450 | peak_memory_usage ?>MB 451 | duration ?> 452 | 453 | 454 | vars ) : ?> 455 | vars ) ?> 456 | 457 | 458 | 459 | is_open ) : ?> 460 | Warning: Not Ended; 461 | 462 | 463 | 470 |
  • 471 | id = $id; 485 | $this->time = round( hm_time_stack_time() - HM_Time_Stack::instance()->get_start_time(), 3 ); 486 | $this->children = array(); 487 | $this->label = $label; 488 | $this->peak_memory_usage = round( memory_get_peak_usage() / 1024 / 1024, 3 ); 489 | } 490 | 491 | function _print() { 492 | 493 | ?> 494 |
  • 495 | label ? $this->label : $this->id ?> 496 | 497 | peak_memory_usage ?>MB 498 | 499 | time ?> in 500 |
  • 501 | duration ); 510 | $archive->time = $this->time; 511 | 512 | return $archive; 513 | } 514 | 515 | } 516 | 517 | function hm_time_stack_time() { 518 | 519 | $time = explode( ' ', microtime() ); 520 | $time = $time[1] + $time[0]; 521 | return $time; 522 | } 523 | 524 | HM_Time_Stack::instance(); 525 | 526 | // show persistant stacks 527 | if ( isset( $_GET['action'] ) && $_GET['action'] == 'hm_get_stacks' ) { 528 | 529 | header('Content-Type: application/javascript'); 530 | echo $_GET['jsoncallback'] . '(' . json_encode( HM_Time_Stack::get_data( true ) ) . ')'; 531 | exit; 532 | } 533 | 534 | --------------------------------------------------------------------------------