BryanDavis has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/168930

Change subject: [WIP] Enable profiling via xhprof
......................................................................

[WIP] Enable profiling via xhprof

Add a helper class to assist in collecting profiling information using
XHProf <https://github.com/phacility/xhprof> and a Profiler
implementation to hook it into the existing MediaWiki profiling system.

Bug: T759
Change-Id: I16a75cb7636cb5dcef3830d738b2dcd2047d0aaa
---
M includes/AutoLoader.php
A includes/libs/Xhprof.php
A includes/profiler/ProfilerXhprof.php
3 files changed, 369 insertions(+), 0 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core 
refs/changes/30/168930/1

diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php
index 99b2d84..c6a915f 100644
--- a/includes/AutoLoader.php
+++ b/includes/AutoLoader.php
@@ -695,6 +695,7 @@
        'SwiftVirtualRESTService' => 
'includes/libs/virtualrest/SwiftVirtualRESTService.php',
        'VirtualRESTService' => 
'includes/libs/virtualrest/VirtualRESTService.php',
        'VirtualRESTServiceClient' => 
'includes/libs/virtualrest/VirtualRESTServiceClient.php',
+       'Xhprof' => 'includes/libs/Xhprof.php',
        'XmlTypeCheck' => 'includes/libs/XmlTypeCheck.php',
 
        # includes/libs/lessphp
@@ -873,6 +874,7 @@
        'ProfilerStandard' => 'includes/profiler/ProfilerStandard.php',
        'ProfilerStub' => 'includes/profiler/ProfilerStub.php',
        'ProfileSection' => 'includes/profiler/Profiler.php',
+       'ProfilerXhprof' => 'includes/profiler/ProfilerXhprof.php',
        'TransactionProfiler' => 'includes/profiler/Profiler.php',
 
        # includes/rcfeed
diff --git a/includes/libs/Xhprof.php b/includes/libs/Xhprof.php
new file mode 100644
index 0000000..0e4bf48
--- /dev/null
+++ b/includes/libs/Xhprof.php
@@ -0,0 +1,261 @@
+<?php
+/**
+ * @section LICENSE
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Convience class for working with XHProf
+ * <https://github.com/phacility/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 <bd...@wikimedia.org>
+ * @copyright © 2014 Bryan Davis and Wikimedia Foundation.
+ * @since 1.25
+ */
+class Xhprof {
+
+       /**
+        * @var string
+        */
+       const TYPE_DEFAULT = 'default';
+
+       /**
+        * @var string
+        */
+       const TYPE_SAMPLE = 'sample';
+
+       /**
+        * @var array $config
+        */
+       protected $config;
+
+       /**
+        * Hierarchical profilind data returned by xhprof.
+        * @var array $hieraData
+        */
+       protected $hieraData;
+
+       /**
+        * Per-function inclusive data.
+        * @var array $inclusive
+        */
+       protected $inclusive;
+
+       /**
+        * Per-function inclusive and exclusive data.
+        */
+       protected $complete;
+
+       /**
+        * Configuration data can contain:
+        * - 'type': Type of data to collect.
+        *           (self::TYPE_DEFAULT or self::TYPE_SAMPLE)
+        * - 'flags': Optional flags to add additional information to the
+        *            profiling. (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.
+        *
+        * @param array $config
+        */
+       public function __construct( array $config = array() ) {
+               $this->config = array_merge(
+                       array(
+                               'type' => self::TYPE_DEFAULT,
+                               'flags' => 0,
+                               'exclude' => array(),
+                               'include' => null,
+                               'sort' => 'wt',
+                       ),
+                       $config
+               );
+
+               if ( $this->config['type'] === self::TYPE_DEFAULT ) {
+                       xhprof_enable( $this->config['flags'], array(
+                               'ignored_functions' => $this->config['exclude']
+                       ) );
+               } elseif ( $this->config['type'] === self::TYPE_SAMPLE ) {
+                       xhprof_sample_enable();
+               } else {
+                       throw new InvalidArgumentException(
+                               "Unknown xhprof type '{$this->config['type']}'"
+                       );
+               }
+       }
+
+       /**
+        * Stop collecting profiling data.
+        * @return array Collected profiling data
+        */
+       public function stop() {
+               if ( $this->hieraData === null ) {
+                       if ( $this->config['type'] === self::TYPE_DEFAULT ) {
+                               $this->hieraData = $this->pruneData( 
xhprof_disable() );
+                       } else {
+                               $this->hieraData = $this->pruneData( 
xhprof_sample_disable() );
+                       }
+               }
+               return $this->hieraData;
+       }
+
+       /**
+        * Get raw data collected by xhprof.
+        *
+        * If data collection has not been stopped yet this method will halt
+        * collection to gather the profiling data.
+        *
+        * @return array
+        * @see stop()
+        * @see getInclusiveMetrics()
+        * @see getCompleteMetrics()
+        */
+       public function getRawData() {
+               return $this->stop();
+       }
+
+       /**
+        * Convert an xhprof data key into an array of ['parent', 'child']
+        * function names.
+        * @return array
+        */
+       protected 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'] === null ) {
+                       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;
+                       }
+               }
+               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.
+        *
+        * @return array
+        * @see getRawData()
+        * @see getCompleteMetrics()
+        */
+       public function getInclusiveMetrics() {
+               if ( $this->inclusive === null ) {
+                       $this->stop();
+                       $this->inclusive = array();
+                       foreach ( $this->hieraData as $key => $stats ) {
+                               list($parent, $child) = self::splitKey( $key );
+                               if ( isset( $this->inclusive[$child] ) ) {
+                                       foreach ( $stats as $stat => $value ) {
+                                               $this->inclusive[$child][$stat] 
+= $value;
+                                       }
+                               } else {
+                                       $this->inclusive[$child] = $stats;
+                               }
+                       }
+                       uasort( $this->inclusive, self::makeSortFuction(
+                               $this->config['sort']
+                       ) );
+               }
+               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.
+        *
+        * @return array
+        * @see getRawData()
+        * @see getInclusiveMetrics()
+        */
+       public function getCompleteMetrics() {
+               if ( $this->complete === null ) {
+                       // Start with inclusive data
+                       $this->complete = $this->getInclusiveMetrics();
+
+                       // Initialize exclusive data with inclusive totals
+                       foreach ( $this->complete as $func => $stats ) {
+                               foreach ( $stats as $stat => $value ) {
+                                       if ( $stat !== 'ct' ) {
+                                               
$this->complete[$func]["excl_{$stat}"] = $value;
+                                       }
+                               }
+                       }
+
+                       // Deduct child inclusive data from exclusive data
+                       foreach( $this->hieraData as $key => $stats ) {
+                               list($parent, $child) = self::splitKey( $key );
+                               if ( isset( $this->complete[$parent] ) ) {
+                                       foreach ( $stats as $stat => $value ) {
+                                               if ( $stat !== 'ct' ) {
+                                                       
$this->complete[$parent]["excl_{$stat}"] -= $value;
+                                               }
+                                       }
+                               }
+                       }
+
+                       uasort( $this->complete, self::makeSortFuction(
+                               ( $this->config['sort'] === 'ct' ) ?
+                                       'ct' : "excl_{$this->config['sort']}"
+                       ) );
+               }
+               return $this->complete;
+       }
+
+       /**
+        * Make a closure to use as a sort function. The resulting function will
+        * sort by descending values.
+        *
+        * @param string $sortKey Data key to sort on
+        * @return Closure
+        */
+       protected static function makeSortFuction( $sortKey ) {
+               return function ( $a, $b ) use ( $sortKey ) {
+                       if ( isset( $a[$sortKey] ) && isset( $b[$sortKey] ) ) {
+                               return $b[$sortKey] - $a[$sortKey];
+                       } elseif ( isset( $a[$sortKey] ) ) {
+                               return 1;
+                       } else {
+                               return -1;
+                       }
+               };
+       }
+}
diff --git a/includes/profiler/ProfilerXhprof.php 
b/includes/profiler/ProfilerXhprof.php
new file mode 100644
index 0000000..5b2c8da
--- /dev/null
+++ b/includes/profiler/ProfilerXhprof.php
@@ -0,0 +1,106 @@
+<?php
+/**
+ * @section LICENSE
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Profiler wrapper for XHProf extension.
+ *
+ * Example StartProfiler.php:
+ * @code
+ * $wgProfiler['class'] = 'ProfilerXhprof';
+ * $wgProfiler['flags'] = XHPROF_FLAGS_NO_BUILTINS | XHPROF_FLAGS_MEMORY;
+ * $wgProfiler['exclude'] = array( 'call_user_func', 'call_user_func_array' );
+ * $wgProfiler['sort'] = 'wt';
+ * @endcode
+ *
+ * @author Bryan Davis <bd...@wikimedia.org>
+ * @copyright © 2014 Bryan Davis and Wikimedia Foundation.
+ * @ingroup Profiler
+ */
+class ProfilerXhprof extends Profiler {
+
+       /**
+        * @var Xhprof
+        */
+       protected $xhprof;
+
+       /**
+        * @param array $params
+        * @see Xhprof::__construct()
+        */
+       public function __construct( array $params = array() ) {
+               parent::__construct( $params );
+               $this->xhprof = new Xhprof( $params );
+               // TODO: add support for specfying output formatting
+       }
+
+       public function isStub() {
+               return false;
+       }
+
+       public function isPersistent() {
+               return true;
+       }
+
+       public function profileIn( $fn ) {
+       }
+
+       public function profileOut( $fn ) {
+       }
+
+       public function close() {
+       }
+
+       public function getCurrentSection() {
+               return '';
+       }
+
+       public function transactionWritingIn( $server, $db, $id = '' ) {
+       }
+
+       public function transactionWritingOut( $server, $db, $id = '' ) {
+       }
+
+       public function getRawData() {
+               // TODO: should there be a way to get the hierarchial data as 
well?
+               return $this->xhprof->getCompleteMetrics();
+       }
+
+       /**
+        * Log the data to some store or even the page output
+        */
+       public function logData() {
+               // TODO: add support for specfying output formatting
+               $data = $this->getRawData();
+               wfDebugLog( 'xhprof', var_export( $data, true ) );
+       }
+
+       /**
+        * Returns a profiling output to be stored in debug file
+        *
+        * @return string
+        */
+       public function getOutput() {
+               // TODO: add support for specfying output formatting
+               $data = $this->getRawData();
+               return var_export( $data, true );
+       }
+
+}

-- 
To view, visit https://gerrit.wikimedia.org/r/168930
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I16a75cb7636cb5dcef3830d738b2dcd2047d0aaa
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: BryanDavis <bda...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to