wp-profiler.php 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. <?php
  2. /*
  3. * A simple manually-instrumented profiler for WordPress.
  4. *
  5. * This records basic execution time, and a summary of the actions and SQL queries run within each block.
  6. *
  7. * start() and stop() must be called in pairs, for example:
  8. *
  9. * function something_to_profile() {
  10. * wppf_start(__FUNCTION__);
  11. * do_stuff();
  12. * wppf_stop();
  13. * }
  14. *
  15. * Multiple profile blocks are permitted, and they may be nested.
  16. */
  17. class WPProfiler {
  18. public $stack;
  19. public $profile;
  20. /**
  21. * PHP5 constructor.
  22. */
  23. public function __construct() {
  24. $this->stack = array();
  25. $this->profile = array();
  26. }
  27. public function start( $name ) {
  28. $time = $this->microtime();
  29. if ( ! $this->stack ) {
  30. // Log all actions and filters.
  31. add_filter( 'all', array( $this, 'log_filter' ) );
  32. }
  33. // Reset the wpdb queries log, storing it on the profile stack if necessary.
  34. global $wpdb;
  35. if ( $this->stack ) {
  36. $this->stack[ count( $this->stack ) - 1 ]['queries'] = $wpdb->queries;
  37. }
  38. $wpdb->queries = array();
  39. global $wp_object_cache;
  40. $this->stack[] = array(
  41. 'start' => $time,
  42. 'name' => $name,
  43. 'cache_cold_hits' => $wp_object_cache->cold_cache_hits,
  44. 'cache_warm_hits' => $wp_object_cache->warm_cache_hits,
  45. 'cache_misses' => $wp_object_cache->cache_misses,
  46. 'cache_dirty_objects' => $this->_dirty_objects_count( $wp_object_cache->dirty_objects ),
  47. 'actions' => array(),
  48. 'filters' => array(),
  49. 'queries' => array(),
  50. );
  51. }
  52. public function stop() {
  53. $item = array_pop( $this->stack );
  54. $time = $this->microtime( $item['start'] );
  55. $name = $item['name'];
  56. global $wpdb;
  57. $item['queries'] = $wpdb->queries;
  58. global $wp_object_cache;
  59. $cache_dirty_count = $this->_dirty_objects_count( $wp_object_cache->dirty_objects );
  60. $cache_dirty_delta = $this->array_sub( $cache_dirty_count, $item['cache_dirty_objects'] );
  61. if ( isset( $this->profile[ $name ] ) ) {
  62. $this->profile[ $name ]['time'] += $time;
  63. $this->profile[ $name ]['calls'] ++;
  64. $this->profile[ $name ]['cache_cold_hits'] += ( $wp_object_cache->cold_cache_hits - $item['cache_cold_hits'] );
  65. $this->profile[ $name ]['cache_warm_hits'] += ( $wp_object_cache->warm_cache_hits - $item['cache_warm_hits'] );
  66. $this->profile[ $name ]['cache_misses'] += ( $wp_object_cache->cache_misses - $item['cache_misses'] );
  67. $this->profile[ $name ]['cache_dirty_objects'] = array_add( $this->profile[ $name ]['cache_dirty_objects'], $cache_dirty_delta );
  68. $this->profile[ $name ]['actions'] = array_add( $this->profile[ $name ]['actions'], $item['actions'] );
  69. $this->profile[ $name ]['filters'] = array_add( $this->profile[ $name ]['filters'], $item['filters'] );
  70. $this->profile[ $name ]['queries'] = array_add( $this->profile[ $name ]['queries'], $item['queries'] );
  71. #$this->_query_summary($item['queries'], $this->profile[$name]['queries']);
  72. } else {
  73. $queries = array();
  74. $this->_query_summary( $item['queries'], $queries );
  75. $this->profile[ $name ] = array(
  76. 'time' => $time,
  77. 'calls' => 1,
  78. 'cache_cold_hits' => ( $wp_object_cache->cold_cache_hits - $item['cache_cold_hits'] ),
  79. 'cache_warm_hits' => ( $wp_object_cache->warm_cache_hits - $item['cache_warm_hits'] ),
  80. 'cache_misses' => ( $wp_object_cache->cache_misses - $item['cache_misses'] ),
  81. 'cache_dirty_objects' => $cache_dirty_delta,
  82. 'actions' => $item['actions'],
  83. 'filters' => $item['filters'],
  84. # 'queries' => $item['queries'],
  85. 'queries' => $queries,
  86. );
  87. }
  88. if ( ! $this->stack ) {
  89. remove_filter( 'all', array( $this, 'log_filter' ) );
  90. }
  91. }
  92. public function microtime( $since = 0.0 ) {
  93. list($usec, $sec) = explode( ' ', microtime() );
  94. return (float) $sec + (float) $usec - $since;
  95. }
  96. public function log_filter( $tag ) {
  97. if ( $this->stack ) {
  98. global $wp_actions;
  99. if ( end( $wp_actions ) === $tag ) {
  100. $this->stack[ count( $this->stack ) - 1 ]['actions'][ $tag ]++;
  101. } else {
  102. $this->stack[ count( $this->stack ) - 1 ]['filters'][ $tag ]++;
  103. }
  104. }
  105. return $arg;
  106. }
  107. public function log_action( $tag ) {
  108. if ( $this->stack ) {
  109. $this->stack[ count( $this->stack ) - 1 ]['actions'][ $tag ]++;
  110. }
  111. }
  112. public function _current_action() {
  113. global $wp_actions;
  114. return $wp_actions[ count( $wp_actions ) - 1 ];
  115. }
  116. public function results() {
  117. return $this->profile;
  118. }
  119. public function _query_summary( $queries, &$out ) {
  120. foreach ( $queries as $q ) {
  121. $sql = $q[0];
  122. $sql = preg_replace( '/(WHERE \w+ =) \d+/', '$1 x', $sql );
  123. $sql = preg_replace( '/(WHERE \w+ =) \'\[-\w]+\'/', '$1 \'xxx\'', $sql );
  124. $out[ $sql ] ++;
  125. }
  126. asort( $out );
  127. return;
  128. }
  129. public function _query_count( $queries ) {
  130. // This requires the SAVEQUERIES patch at https://core.trac.wordpress.org/ticket/5218
  131. $out = array();
  132. foreach ( $queries as $q ) {
  133. if ( empty( $q[2] ) ) {
  134. $out['unknown']++;
  135. } else {
  136. $out[ $q[2] ]++;
  137. }
  138. }
  139. return $out;
  140. }
  141. public function _dirty_objects_count( $dirty_objects ) {
  142. $out = array();
  143. foreach ( array_keys( $dirty_objects ) as $group ) {
  144. $out[ $group ] = count( $dirty_objects[ $group ] );
  145. }
  146. return $out;
  147. }
  148. public function array_add( $a, $b ) {
  149. $out = $a;
  150. foreach ( array_keys( $b ) as $key ) {
  151. if ( array_key_exists( $key, $out ) ) {
  152. $out[ $key ] += $b[ $key ];
  153. } else {
  154. $out[ $key ] = $b[ $key ];
  155. }
  156. }
  157. return $out;
  158. }
  159. public function array_sub( $a, $b ) {
  160. $out = $a;
  161. foreach ( array_keys( $b ) as $key ) {
  162. if ( array_key_exists( $key, $b ) ) {
  163. $out[ $key ] -= $b[ $key ];
  164. }
  165. }
  166. return $out;
  167. }
  168. public function print_summary() {
  169. $results = $this->results();
  170. printf( "\nname calls time action filter warm cold misses dirty\n" );
  171. foreach ( $results as $name => $stats ) {
  172. printf( "%24.24s %6d %6.4f %6d %6d %6d %6d %6d %6d\n", $name, $stats['calls'], $stats['time'], array_sum( $stats['actions'] ), array_sum( $stats['filters'] ), $stats['cache_warm_hits'], $stats['cache_cold_hits'], $stats['cache_misses'], array_sum( $stats['cache_dirty_objects'] ) );
  173. }
  174. }
  175. }
  176. global $wppf;
  177. $wppf = new WPProfiler();
  178. function wppf_start( $name ) {
  179. $GLOBALS['wppf']->start( $name );
  180. }
  181. function wppf_stop() {
  182. $GLOBALS['wppf']->stop();
  183. }
  184. function wppf_results() {
  185. return $GLOBALS['wppf']->results();
  186. }
  187. function wppf_print_summary() {
  188. $GLOBALS['wppf']->print_summary();
  189. }