new \Mustache_Loader_FilesystemLoader( dirname( __DIR__ ) . '/views' ), 'cache' => new Mustache_Cache_FrozenCache( dirname( __DIR__ ) . '/views/cache' ), ] ); }; $laps['stopwatch'] = function () { return new Stopwatch(); }; $laps->register( new Hook_Events_Provider() ); $laps->register( new Http_Events_Provider() ); foreach ( $values as $key => $value ) { $this->offsetSet( $key, $value ); } } public function register( ServiceProviderInterface $provider, array $values = [] ) { $this->providers[] = $provider; parent::register( $provider, $values ); return $this; } /** * Start Stopwatch and timing plugin load immediately, then set up core events and needed hooks. */ public function on_load() { if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { return; } $this['stopwatch']->start( 'Plugins Load', 'plugin' ); foreach ( $this->providers as $provider ) { if ( $provider instanceof Bootable_Provider_Interface ) { $provider->boot( $this ); } } add_action( 'pre_update_option_active_plugins', array( $this, 'pre_update_option_active_plugins' ) ); add_action( 'pre_update_site_option_active_sitewide_plugins', array( $this, 'pre_update_option_active_plugins' ) ); add_action( 'init', array( $this, 'init' ) ); if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) { add_filter( 'query', array( $this, 'query' ), 20 ); } } /** * Reorder active plugins so Laps is first and starts timing load early. * * @param array $plugins * * @return array */ public function pre_update_option_active_plugins( $plugins ) { $plugin = plugin_basename( dirname( __DIR__ ) . '/laps.php' ); $key = array_search( $plugin, $plugins ); if ( false !== $key && $key > 0 ) { unset( $plugins[ $key ] ); array_unshift( $plugins, $plugin ); $plugins = array_values( $plugins ); } return $plugins; } /** * Capture SQL queries start times * * @param string $query * * @return string */ public function query( $query ) { global $wpdb; if ( empty( $this->query_starts ) && ! empty( $wpdb->queries ) ) { $this->query_starts[ count( $wpdb->queries ) ] = microtime( true ) * 1000; } else { $this->query_starts[] = microtime( true ) * 1000; } return $query; } public function init() { add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); add_action( 'admin_bar_menu', array( $this, 'admin_bar_menu' ), 100 ); } public function enqueue_scripts() { $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; wp_register_script( 'laps', plugins_url( "js/laps{$suffix}.js", __DIR__ ), array( 'jquery' ), '3.3.1', true ); wp_register_style( 'laps', plugins_url( "css/laps{$suffix}.css", __DIR__ ) ); if ( is_admin_bar_showing() ) { wp_enqueue_script( 'laps' ); wp_enqueue_style( 'laps' ); } } /** * Render interface and add to the toolbar. * * @param \WP_Admin_Bar $wp_admin_bar */ public function admin_bar_menu( $wp_admin_bar ) { if ( ! apply_filters( 'laps_can_see', current_user_can( 'manage_options' ) ) ) { return; } global $timestart, $wpdb; /** @var Stopwatch $stopwatch */ $stopwatch = $this['stopwatch']; if ( $stopwatch->isStarted( 'Toolbar' ) ) { $stopwatch->stop( 'Toolbar' ); } $events = []; foreach ( $this->providers as $provider ) { if ( $provider instanceof Events_Provider_Interface ) { $events[] = $provider->get_events(); } } $events = array_merge(...$events); $start = $timestart * 1000; $end = microtime( true ) * 1000; $total = $end - $start; $event_data = array(); $http_data = array(); foreach ( $events as $name => $event ) { $offset = round( ( $event->getOrigin() - $start ) / $total * 100, 2 ); $duration = $event->getDuration(); $width = round( $duration / $total * 100, 2 ); $category = $event->getCategory(); if ( 'http' === $category ) { $http_data[] = compact( 'name', 'offset', 'duration', 'width', 'category' ); continue; } $memory = $event->getMemory() / 1024 / 1024; $event_data[] = compact( 'name', 'offset', 'duration', 'width', 'category', 'memory' ); } $query_data = array(); $last_query_end = 0; $last_offset = 0; $last_duration = 0; if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) { foreach ( $wpdb->queries as $key => $query ) { $query_start = isset( $this->query_starts[ $key ] ) ? $this->query_starts[ $key ] : $last_query_end; list( $sql, $duration, $trace ) = $query; $sql = trim( $sql ); $category = 'query-read'; if ( 0 === stripos( $sql, 'INSERT' ) || 0 === stripos( $sql, 'UPDATE' ) ) { $category = 'query-write'; } $duration *= 1000; $last_query_end = $query_start + $duration; $offset = round( ( $query_start - $start ) / $total * 100, 2 ); // if query is indistinguishably close to previous then stack it if ( $offset === $last_offset ) { $key = count( $query_data ) - 1; $query_data[ $key ]['sql'] .= '
' . $sql; $last_duration += $duration; $width = round( $last_duration / $total * 100, 2 ); $query_data[ $key ]['width'] = $width; continue; } $width = round( $duration / $total * 100, 2 ); $last_offset = $offset; $last_duration = $duration; $query_data[] = compact( 'sql', 'duration', 'offset', 'width', 'category' ); } } $html = $this['mustache']->render( 'laps', [ 'events' => $event_data, 'queries' => $query_data, 'savequeries' => defined( 'SAVEQUERIES' ) && SAVEQUERIES, 'http' => $http_data, 'savehttp' => ! empty( $http_data ), ] ); $wp_admin_bar->add_node( array( 'id' => 'laps', 'title' => sprintf( 'Lap: %ss', round( $total / 1000, 3 ) ), ) ); $wp_admin_bar->add_node( array( 'id' => 'laps_output', 'parent' => 'laps', 'meta' => array( 'html' => $html ), ) ); } }