X-Git-Url: https://git.heureux-cyclage.org/?p=lhc%2Fweb%2Fwiklou.git;a=blobdiff_plain;f=includes%2Flibs%2FXhprof.php;h=9c1ec8eb013bf671270eb9de6183b8a213b5a829;hp=d2cd0e4ea848a45a813f78af9b8abc96f3088289;hb=f43fa6f4f0e2cb60b8543daad661b48a3e0653a9;hpb=cb2896f90ebef77c02c9da4067b0e56e37471092 diff --git a/includes/libs/Xhprof.php b/includes/libs/Xhprof.php index d2cd0e4ea8..9c1ec8eb01 100644 --- a/includes/libs/Xhprof.php +++ b/includes/libs/Xhprof.php @@ -18,430 +18,46 @@ * @file */ -use RunningStat\RunningStat; - /** * Convenience class for working with XHProf * . XHProf can be installed as a PECL * package for use with PHP5 (Zend PHP) and is built-in to HHVM 3.3.0. * - * @author Bryan Davis - * @copyright © 2014 Bryan Davis and Wikimedia Foundation. - * @since 1.25 + * @since 1.28 */ class Xhprof { - - /** - * @var array $config - */ - protected $config; - - /** - * Hierarchical profiling data returned by xhprof. - * @var array $hieraData - */ - protected $hieraData; - /** - * Per-function inclusive data. - * @var array $inclusive + * @var bool $enabled Whether XHProf is currently running. */ - protected $inclusive; + protected static $enabled; /** - * Per-function inclusive and exclusive data. - * @var array $complete + * Start xhprof profiler */ - protected $complete; - - /** - * Configuration data can contain: - * - flags: Optional flags to add additional information to the - * profiling data collected. - * (XHPROF_FLAGS_NO_BUILTINS, XHPROF_FLAGS_CPU, - * XHPROF_FLAGS_MEMORY) - * - exclude: Array of function names to exclude from profiling. - * - include: Array of function names to include in profiling. - * - sort: Key to sort per-function reports on. - * - * Note: When running under HHVM, xhprof will always behave as though the - * XHPROF_FLAGS_NO_BUILTINS flag has been used unless the - * Eval.JitEnableRenameFunction option is enabled for the HHVM process. - * - * @param array $config - */ - public function __construct( array $config = array() ) { - $this->config = array_merge( - array( - 'flags' => 0, - 'exclude' => array(), - 'include' => null, - 'sort' => 'wt', - ), - $config - ); - - xhprof_enable( $this->config['flags'], array( - 'ignored_functions' => $this->config['exclude'] - ) ); + public static function isEnabled() { + return self::$enabled; } /** - * Stop collecting profiling data. - * - * Only the first invocation of this method will effect the internal - * object state. Subsequent calls will return the data collected by the - * initial call. - * - * @return array Collected profiling data (possibly cached) + * Start xhprof profiler */ - public function stop() { - if ( $this->hieraData === null ) { - $this->hieraData = $this->pruneData( xhprof_disable() ); + public static function enable( $flags = 0, $options = [] ) { + if ( self::isEnabled() ) { + throw new Exception( 'Xhprof profiling is already enabled.' ); } - return $this->hieraData; - } - - /** - * Load raw data from a prior run for analysis. - * Stops any existing data collection and clears internal caches. - * - * Any 'include' filters configured for this Xhprof instance will be - * enforced on the data as it is loaded. 'exclude' filters will however - * not be enforced as they are an XHProf intrinsic behavior. - * - * @param array $data - * @see getRawData() - */ - public function loadRawData( array $data ) { - $this->stop(); - $this->inclusive = null; - $this->complete = null; - $this->hieraData = $this->pruneData( $data ); - } - - /** - * Get raw data collected by xhprof. - * - * If data collection has not been stopped yet this method will halt - * collection to gather the profiling data. - * - * Each key in the returned array is an edge label for the call graph in - * the form "caller==>callee". There is once special case edge labled - * simply "main()" which represents the global scope entry point of the - * application. - * - * XHProf will collect different data depending on the flags that are used: - * - ct: Number of matching events seen. - * - wt: Inclusive elapsed wall time for this event in microseconds. - * - cpu: Inclusive elapsed cpu time for this event in microseconds. - * (XHPROF_FLAGS_CPU) - * - mu: Delta of memory usage from start to end of callee in bytes. - * (XHPROF_FLAGS_MEMORY) - * - pmu: Delta of peak memory usage from start to end of callee in - * bytes. (XHPROF_FLAGS_MEMORY) - * - alloc: Delta of amount memory requested from malloc() by the callee, - * in bytes. (XHPROF_FLAGS_MALLOC) - * - free: Delta of amount of memory passed to free() by the callee, in - * bytes. (XHPROF_FLAGS_MALLOC) - * - * @return array - * @see stop() - * @see getInclusiveMetrics() - * @see getCompleteMetrics() - */ - public function getRawData() { - return $this->stop(); + self::$enabled = true; + xhprof_enable( $flags, $options ); } /** - * Convert an xhprof data key into an array of ['parent', 'child'] - * function names. - * - * The resulting array is left padded with nulls, so a key - * with no parent (eg 'main()') will return [null, 'function']. + * Stop xhprof profiler * - * @return array + * @return array|null xhprof data from the run, or null if xhprof was not running. */ - public static function splitKey( $key ) { - return array_pad( explode( '==>', $key, 2 ), -2, null ); - } - - /** - * Remove data for functions that are not included in the 'include' - * configuration array. - * - * @param array $data Raw xhprof data - * @return array - */ - protected function pruneData( $data ) { - if ( !$this->config['include'] ) { - return $data; - } - - $want = array_fill_keys( $this->config['include'], true ); - $want['main()'] = true; - - $keep = array(); - foreach ( $data as $key => $stats ) { - list( $parent, $child ) = self::splitKey( $key ); - if ( isset( $want[$parent] ) || isset( $want[$child] ) ) { - $keep[$key] = $stats; - } + public static function disable() { + if ( self::isEnabled() ) { + self::$enabled = false; + return xhprof_disable(); } - return $keep; - } - - /** - * Get the inclusive metrics for each function call. Inclusive metrics - * for given function include the metrics for all functions that were - * called from that function during the measurement period. - * - * If data collection has not been stopped yet this method will halt - * collection to gather the profiling data. - * - * See getRawData() for a description of the metric that are returned for - * each funcition call. The values for the wt, cpu, mu and pmu metrics are - * arrays with these values: - * - total: Cumulative value - * - min: Minimum value - * - mean: Mean (average) value - * - max: Maximum value - * - variance: Variance (spread) of the values - * - * @return array - * @see getRawData() - * @see getCompleteMetrics() - */ - public function getInclusiveMetrics() { - if ( $this->inclusive === null ) { - // Make sure we have data to work with - $this->stop(); - - $main = $this->hieraData['main()']; - $hasCpu = isset( $main['cpu'] ); - $hasMu = isset( $main['mu'] ); - $hasAlloc = isset( $main['alloc'] ); - - $this->inclusive = array(); - foreach ( $this->hieraData as $key => $stats ) { - list( $parent, $child ) = self::splitKey( $key ); - if ( !isset( $this->inclusive[$child] ) ) { - $this->inclusive[$child] = array( - 'ct' => 0, - 'wt' => new RunningStat(), - ); - if ( $hasCpu ) { - $this->inclusive[$child]['cpu'] = new RunningStat(); - } - if ( $hasMu ) { - $this->inclusive[$child]['mu'] = new RunningStat(); - $this->inclusive[$child]['pmu'] = new RunningStat(); - } - if ( $hasAlloc ) { - $this->inclusive[$child]['alloc'] = new RunningStat(); - $this->inclusive[$child]['free'] = new RunningStat(); - } - } - - $this->inclusive[$child]['ct'] += $stats['ct']; - foreach ( $stats as $stat => $value ) { - if ( $stat === 'ct' ) { - continue; - } - - if ( !isset( $this->inclusive[$child][$stat] ) ) { - // Ignore unknown stats - continue; - } - - for ( $i = 0; $i < $stats['ct']; $i++ ) { - $this->inclusive[$child][$stat]->addObservation( - $value / $stats['ct'] - ); - } - } - } - - // Convert RunningStat instances to static arrays and add - // percentage stats. - foreach ( $this->inclusive as $func => $stats ) { - foreach ( $stats as $name => $value ) { - if ( $value instanceof RunningStat ) { - $total = $value->m1 * $value->n; - $percent = ( isset( $main[$name] ) && $main[$name] ) - ? 100 * $total / $main[$name] - : 0; - $this->inclusive[$func][$name] = array( - 'total' => $total, - 'min' => $value->min, - 'mean' => $value->m1, - 'max' => $value->max, - 'variance' => $value->m2, - 'percent' => $percent, - ); - } - } - } - - uasort( $this->inclusive, self::makeSortFunction( - $this->config['sort'], 'total' - ) ); - } - return $this->inclusive; - } - - /** - * Get the inclusive and exclusive metrics for each function call. - * - * If data collection has not been stopped yet this method will halt - * collection to gather the profiling data. - * - * In addition to the normal data contained in the inclusive metrics, the - * metrics have an additional 'exclusive' measurement which is the total - * minus the totals of all child function calls. - * - * @return array - * @see getRawData() - * @see getInclusiveMetrics() - */ - public function getCompleteMetrics() { - if ( $this->complete === null ) { - // Start with inclusive data - $this->complete = $this->getInclusiveMetrics(); - - foreach ( $this->complete as $func => $stats ) { - foreach ( $stats as $stat => $value ) { - if ( $stat === 'ct' ) { - continue; - } - // Initialize exclusive data with inclusive totals - $this->complete[$func][$stat]['exclusive'] = $value['total']; - } - // Add sapce for call tree information to be filled in later - $this->complete[$func]['calls'] = array(); - $this->complete[$func]['subcalls'] = array(); - } - - foreach ( $this->hieraData as $key => $stats ) { - list( $parent, $child ) = self::splitKey( $key ); - if ( $parent !== null ) { - // Track call tree information - $this->complete[$child]['calls'][$parent] = $stats; - $this->complete[$parent]['subcalls'][$child] = $stats; - } - - if ( isset( $this->complete[$parent] ) ) { - // Deduct child inclusive data from exclusive data - foreach ( $stats as $stat => $value ) { - if ( $stat === 'ct' ) { - continue; - } - - if ( !isset( $this->complete[$parent][$stat] ) ) { - // Ignore unknown stats - continue; - } - - $this->complete[$parent][$stat]['exclusive'] -= $value; - } - } - } - - uasort( $this->complete, self::makeSortFunction( - $this->config['sort'], 'exclusive' - ) ); - } - return $this->complete; - } - - /** - * Get a list of all callers of a given function. - * - * @param string $function Function name - * @return array - * @see getEdges() - */ - public function getCallers( $function ) { - $edges = $this->getCompleteMetrics(); - if ( isset( $edges[$function]['calls'] ) ) { - return array_keys( $edges[$function]['calls'] ); - } else { - return array(); - } - } - - /** - * Get a list of all callees from a given function. - * - * @param string $function Function name - * @return array - * @see getEdges() - */ - public function getCallees( $function ) { - $edges = $this->getCompleteMetrics(); - if ( isset( $edges[$function]['subcalls'] ) ) { - return array_keys( $edges[$function]['subcalls'] ); - } else { - return array(); - } - } - - /** - * Find the critical path for the given metric. - * - * @param string $metric Metric to find critical path for - * @return array - */ - public function getCriticalPath( $metric = 'wt' ) { - $this->stop(); - $func = 'main()'; - $path = array( - $func => $this->hieraData[$func], - ); - while ( $func ) { - $callees = $this->getCallees( $func ); - $maxCallee = null; - $maxCall = null; - foreach ( $callees as $callee ) { - $call = "{$func}==>{$callee}"; - if ( $maxCall === null || - $this->hieraData[$call][$metric] > - $this->hieraData[$maxCall][$metric] - ) { - $maxCallee = $callee; - $maxCall = $call; - } - } - if ( $maxCall !== null ) { - $path[$maxCall] = $this->hieraData[$maxCall]; - } - $func = $maxCallee; - } - return $path; - } - - /** - * Make a closure to use as a sort function. The resulting function will - * sort by descending numeric values (largest value first). - * - * @param string $key Data key to sort on - * @param string $sub Sub key to sort array values on - * @return Closure - */ - public static function makeSortFunction( $key, $sub ) { - return function ( $a, $b ) use ( $key, $sub ) { - if ( isset( $a[$key] ) && isset( $b[$key] ) ) { - // Descending sort: larger values will be first in result. - // Assumes all values are numeric. - // Values for 'main()' will not have sub keys - $valA = is_array( $a[$key] ) ? $a[$key][$sub] : $a[$key]; - $valB = is_array( $b[$key] ) ? $b[$key][$sub] : $b[$key]; - return $valB - $valA; - } else { - // Sort datum with the key before those without - return isset( $a[$key] ) ? -1 : 1; - } - }; } }