MediaWiki  REL1_19
Profiler.php
Go to the documentation of this file.
00001 <?php
00014 function wfProfileIn( $functionname ) {
00015         global $wgProfiler;
00016         if ( $wgProfiler instanceof Profiler || isset( $wgProfiler['class'] ) ) {
00017                 Profiler::instance()->profileIn( $functionname );
00018         }
00019 }
00020 
00025 function wfProfileOut( $functionname = 'missing' ) {
00026         global $wgProfiler;
00027         if ( $wgProfiler instanceof Profiler || isset( $wgProfiler['class'] ) ) {
00028                 Profiler::instance()->profileOut( $functionname );
00029         }
00030 }
00031 
00036 class Profiler {
00037         protected $mStack = array(), $mWorkStack = array (), $mCollated = array (),
00038                 $mCalls = array (), $mTotals = array ();
00039         protected $mTimeMetric = 'wall';
00040         protected $mProfileID = false, $mCollateDone = false, $mTemplated = false;
00041         private static $__instance = null;
00042 
00043         function __construct( $params ) {
00044                 if ( isset( $params['timeMetric'] ) ) {
00045                         $this->mTimeMetric = $params['timeMetric'];
00046                 }
00047                 if ( isset( $params['profileID'] ) ) {
00048                         $this->mProfileID = $params['profileID'];
00049                 }
00050 
00051                 // Push an entry for the pre-profile setup time onto the stack
00052                 $initial = $this->getInitialTime();
00053                 if ( $initial !== null ) {
00054                         $this->mWorkStack[] = array( '-total', 0, $initial, 0 );
00055                         $this->mStack[] = array( '-setup', 1, $initial, 0, $this->getTime(), 0 );
00056                 } else {
00057                         $this->profileIn( '-total' );
00058                 }
00059         }
00060 
00065         public static function instance() {
00066                 if( is_null( self::$__instance ) ) {
00067                         global $wgProfiler;
00068                         if( is_array( $wgProfiler ) ) {
00069                                 if( !isset( $wgProfiler['class'] ) ) {
00070                                         wfDebug( __METHOD__ . " called without \$wgProfiler['class']"
00071                                                 . " set, falling back to ProfilerStub for safety\n" );
00072                                         $class = 'ProfilerStub';
00073                                 } else {
00074                                         $class = $wgProfiler['class'];
00075                                 }
00076                                 self::$__instance = new $class( $wgProfiler );
00077                         } elseif( $wgProfiler instanceof Profiler ) {
00078                                 self::$__instance = $wgProfiler; // back-compat
00079                         } else {
00080                                 wfDebug( __METHOD__ . ' called with bogus $wgProfiler setting,'
00081                                                 . " falling back to ProfilerStub for safety\n" );
00082                                 self::$__instance = new ProfilerStub( $wgProfiler );
00083                         }
00084                 }
00085                 return self::$__instance;
00086         }
00087 
00092         public static function setInstance( Profiler $p ) {
00093                 self::$__instance = $p;
00094         }
00095 
00101         public function isStub() {
00102                 return false;
00103         }
00104 
00105         public function setProfileID( $id ) {
00106                 $this->mProfileID = $id;
00107         }
00108 
00109         public function getProfileID() {
00110                 if ( $this->mProfileID === false ) {
00111                         return wfWikiID();
00112                 } else {
00113                         return $this->mProfileID;
00114                 }
00115         }
00116 
00122         public function profileIn( $functionname ) {
00123                 global $wgDebugFunctionEntry;
00124                 if( $wgDebugFunctionEntry ){
00125                         $this->debug( str_repeat( ' ', count( $this->mWorkStack ) ) . 'Entering ' . $functionname . "\n" );
00126                 }
00127 
00128                 $this->mWorkStack[] = array( $functionname, count( $this->mWorkStack ), $this->getTime(), memory_get_usage() );
00129         }
00130 
00136         public function profileOut( $functionname ) {
00137                 global $wgDebugFunctionEntry;
00138                 $memory = memory_get_usage();
00139                 $time = $this->getTime();
00140 
00141                 if( $wgDebugFunctionEntry ){
00142                         $this->debug( str_repeat( ' ', count( $this->mWorkStack ) - 1 ) . 'Exiting ' . $functionname . "\n" );
00143                 }
00144 
00145                 $bit = array_pop($this->mWorkStack);
00146 
00147                 if (!$bit) {
00148                         $this->debug("Profiling error, !\$bit: $functionname\n");
00149                 } else {
00150                         //if( $wgDebugProfiling ){
00151                                 if( $functionname == 'close' ){
00152                                         $message = "Profile section ended by close(): {$bit[0]}";
00153                                         $this->debug( "$message\n" );
00154                                         $this->mStack[] = array( $message, 0, 0.0, 0, 0.0, 0 );
00155                                 }
00156                                 elseif( $bit[0] != $functionname ){
00157                                         $message = "Profiling error: in({$bit[0]}), out($functionname)";
00158                                         $this->debug( "$message\n" );
00159                                         $this->mStack[] = array( $message, 0, 0.0, 0, 0.0, 0 );
00160                                 }
00161                         //}
00162                         $bit[] = $time;
00163                         $bit[] = $memory;
00164                         $this->mStack[] = $bit;
00165                 }
00166         }
00167 
00171         public function close() {
00172                 while( count( $this->mWorkStack ) ){
00173                         $this->profileOut( 'close' );
00174                 }
00175         }
00176 
00182         function setTemplated( $t ) {
00183                 $this->mTemplated = $t;
00184         }
00185 
00191         public function getOutput() {
00192                 global $wgDebugFunctionEntry, $wgProfileCallTree;
00193                 $wgDebugFunctionEntry = false;
00194 
00195                 if( !count( $this->mStack ) && !count( $this->mCollated ) ){
00196                         return "No profiling output\n";
00197                 }
00198 
00199                 if( $wgProfileCallTree ) {
00200                         return $this->getCallTree();
00201                 } else {
00202                         return $this->getFunctionReport();
00203                 }
00204         }
00205 
00209         function getCallTree() {
00210                 return implode( '', array_map( array( &$this, 'getCallTreeLine' ), $this->remapCallTree( $this->mStack ) ) );
00211         }
00212 
00218         function remapCallTree( $stack ) {
00219                 if( count( $stack ) < 2 ){
00220                         return $stack;
00221                 }
00222                 $outputs = array ();
00223                 for( $max = count( $stack ) - 1; $max > 0; ){
00224                         /* Find all items under this entry */
00225                         $level = $stack[$max][1];
00226                         $working = array ();
00227                         for( $i = $max -1; $i >= 0; $i-- ){
00228                                 if( $stack[$i][1] > $level ){
00229                                         $working[] = $stack[$i];
00230                                 } else {
00231                                         break;
00232                                 }
00233                         }
00234                         $working = $this->remapCallTree( array_reverse( $working ) );
00235                         $output = array();
00236                         foreach( $working as $item ){
00237                                 array_push( $output, $item );
00238                         }
00239                         array_unshift( $output, $stack[$max] );
00240                         $max = $i;
00241 
00242                         array_unshift( $outputs, $output );
00243                 }
00244                 $final = array();
00245                 foreach( $outputs as $output ){
00246                         foreach( $output as $item ){
00247                                 $final[] = $item;
00248                         }
00249                 }
00250                 return $final;
00251         }
00252 
00256         function getCallTreeLine( $entry ) {
00257                 list( $fname, $level, $start, /* $x */, $end)  = $entry;
00258                 $delta = $end - $start;
00259                 $space = str_repeat(' ', $level);
00260                 # The ugly double sprintf is to work around a PHP bug,
00261                 # which has been fixed in recent releases.
00262                 return sprintf( "%10s %s %s\n", trim( sprintf( "%7.3f", $delta * 1000.0 ) ), $space, $fname );
00263         }
00264 
00265         function getTime() {
00266                 if ( $this->mTimeMetric === 'user' ) {
00267                         return $this->getUserTime();
00268                 } else {
00269                         return microtime( true );
00270                 }
00271         }
00272 
00273         function getUserTime() {
00274                 $ru = getrusage();
00275                 return $ru['ru_utime.tv_sec'] + $ru['ru_utime.tv_usec'] / 1e6;
00276         }
00277 
00278         private function getInitialTime() {
00279                 global $wgRequestTime, $wgRUstart;
00280 
00281                 if ( $this->mTimeMetric === 'user' ) {
00282                         if ( count( $wgRUstart ) ) {
00283                                 return $wgRUstart['ru_utime.tv_sec'] + $wgRUstart['ru_utime.tv_usec'] / 1e6;
00284                         } else {
00285                                 return null;
00286                         }
00287                 } else {
00288                         if ( empty( $wgRequestTime ) ) {
00289                                 return null;
00290                         } else {
00291                                 return $wgRequestTime;
00292                         }
00293                 }
00294         }
00295 
00296         protected function collateData() {
00297                 if ( $this->mCollateDone ) {
00298                         return;
00299                 }
00300                 $this->mCollateDone = true;
00301 
00302                 $this->close();
00303 
00304                 $this->mCollated = array();
00305                 $this->mCalls = array();
00306                 $this->mMemory = array();
00307 
00308                 # Estimate profiling overhead
00309                 $profileCount = count($this->mStack);
00310                 self::calculateOverhead( $profileCount );
00311 
00312                 # First, subtract the overhead!
00313                 $overheadTotal = $overheadMemory = $overheadInternal = array();
00314                 foreach( $this->mStack as $entry ){
00315                         $fname = $entry[0];
00316                         $start = $entry[2];
00317                         $end = $entry[4];
00318                         $elapsed = $end - $start;
00319                         $memory = $entry[5] - $entry[3];
00320 
00321                         if( $fname == '-overhead-total' ){
00322                                 $overheadTotal[] = $elapsed;
00323                                 $overheadMemory[] = $memory;
00324                         }
00325                         elseif( $fname == '-overhead-internal' ){
00326                                 $overheadInternal[] = $elapsed;
00327                         }
00328                 }
00329                 $overheadTotal = $overheadTotal ? array_sum( $overheadTotal ) / count( $overheadInternal ) : 0;
00330                 $overheadMemory = $overheadMemory ? array_sum( $overheadMemory ) / count( $overheadInternal ) : 0;
00331                 $overheadInternal = $overheadInternal ? array_sum( $overheadInternal ) / count( $overheadInternal ) : 0;
00332 
00333                 # Collate
00334                 foreach( $this->mStack as $index => $entry ){
00335                         $fname = $entry[0];
00336                         $start = $entry[2];
00337                         $end = $entry[4];
00338                         $elapsed = $end - $start;
00339 
00340                         $memory = $entry[5] - $entry[3];
00341                         $subcalls = $this->calltreeCount( $this->mStack, $index );
00342 
00343                         if( !preg_match( '/^-overhead/', $fname ) ){
00344                                 # Adjust for profiling overhead (except special values with elapsed=0
00345                                 if( $elapsed ) {
00346                                         $elapsed -= $overheadInternal;
00347                                         $elapsed -= ($subcalls * $overheadTotal);
00348                                         $memory -= ($subcalls * $overheadMemory);
00349                                 }
00350                         }
00351 
00352                         if( !array_key_exists( $fname, $this->mCollated ) ){
00353                                 $this->mCollated[$fname] = 0;
00354                                 $this->mCalls[$fname] = 0;
00355                                 $this->mMemory[$fname] = 0;
00356                                 $this->mMin[$fname] = 1 << 24;
00357                                 $this->mMax[$fname] = 0;
00358                                 $this->mOverhead[$fname] = 0;
00359                         }
00360 
00361                         $this->mCollated[$fname] += $elapsed;
00362                         $this->mCalls[$fname]++;
00363                         $this->mMemory[$fname] += $memory;
00364                         $this->mMin[$fname] = min($this->mMin[$fname], $elapsed);
00365                         $this->mMax[$fname] = max($this->mMax[$fname], $elapsed);
00366                         $this->mOverhead[$fname] += $subcalls;
00367                 }
00368 
00369                 $this->mCalls['-overhead-total'] = $profileCount;
00370                 arsort( $this->mCollated, SORT_NUMERIC );
00371         }
00372 
00378         function getFunctionReport() {
00379                 $this->collateData();
00380 
00381                 $width = 140;
00382                 $nameWidth = $width - 65;
00383                 $format =      "%-{$nameWidth}s %6d %13.3f %13.3f %13.3f%% %9d  (%13.3f -%13.3f) [%d]\n";
00384                 $titleFormat = "%-{$nameWidth}s %6s %13s %13s %13s %9s\n";
00385                 $prof = "\nProfiling data\n";
00386                 $prof .= sprintf( $titleFormat, 'Name', 'Calls', 'Total', 'Each', '%', 'Mem' );
00387 
00388                 $total = isset( $this->mCollated['-total'] ) ? $this->mCollated['-total'] : 0;
00389 
00390                 foreach( $this->mCollated as $fname => $elapsed ){
00391                         $calls = $this->mCalls[$fname];
00392                         $percent = $total ? 100. * $elapsed / $total : 0;
00393                         $memory = $this->mMemory[$fname];
00394                         $prof .= sprintf($format, substr($fname, 0, $nameWidth), $calls, (float) ($elapsed * 1000), (float) ($elapsed * 1000) / $calls, $percent, $memory, ($this->mMin[$fname] * 1000.0), ($this->mMax[$fname] * 1000.0), $this->mOverhead[$fname]);
00395                 }
00396                 $prof .= "\nTotal: $total\n\n";
00397 
00398                 return $prof;
00399         }
00400 
00404         protected static function calculateOverhead( $profileCount ) {
00405                 wfProfileIn( '-overhead-total' );
00406                 for( $i = 0; $i < $profileCount; $i++ ){
00407                         wfProfileIn( '-overhead-internal' );
00408                         wfProfileOut( '-overhead-internal' );
00409                 }
00410                 wfProfileOut( '-overhead-total' );
00411         }
00412         
00422         function calltreeCount($stack, $start) {
00423                 $level = $stack[$start][1];
00424                 $count = 0;
00425                 for ($i = $start -1; $i >= 0 && $stack[$i][1] > $level; $i --) {
00426                         $count ++;
00427                 }
00428                 return $count;
00429         }
00430 
00434         public function logData(){
00435                 global $wgProfilePerHost, $wgProfileToDatabase;
00436 
00437                 # Do not log anything if database is readonly (bug 5375)
00438                 if( wfReadOnly() || !$wgProfileToDatabase ) {
00439                         return;
00440                 }
00441 
00442                 $dbw = wfGetDB( DB_MASTER );
00443                 if( !is_object( $dbw ) ) {
00444                         return;
00445                 }
00446 
00447                 $errorState = $dbw->ignoreErrors( true );
00448 
00449                 if( $wgProfilePerHost ){
00450                         $pfhost = wfHostname();
00451                 } else {
00452                         $pfhost = '';
00453                 }
00454 
00455                 $this->collateData();
00456 
00457                 foreach( $this->mCollated as $name => $elapsed ){
00458                         $eventCount = $this->mCalls[$name];
00459                         $timeSum = (float) ($elapsed * 1000);
00460                         $memorySum = (float)$this->mMemory[$name];
00461                         $name = substr($name, 0, 255);
00462 
00463                         // Kludge
00464                         $timeSum = ($timeSum >= 0) ? $timeSum : 0;
00465                         $memorySum = ($memorySum >= 0) ? $memorySum : 0;
00466 
00467                         $dbw->update( 'profiling',
00468                                 array(
00469                                         "pf_count=pf_count+{$eventCount}",
00470                                         "pf_time=pf_time+{$timeSum}",
00471                                         "pf_memory=pf_memory+{$memorySum}",
00472                                 ),
00473                                 array(
00474                                         'pf_name' => $name,
00475                                         'pf_server' => $pfhost,
00476                                 ),
00477                                 __METHOD__ );
00478 
00479                         $rc = $dbw->affectedRows();
00480                         if ( $rc == 0 ) {
00481                                 $dbw->insert('profiling', array ('pf_name' => $name, 'pf_count' => $eventCount,
00482                                         'pf_time' => $timeSum, 'pf_memory' => $memorySum, 'pf_server' => $pfhost ), 
00483                                         __METHOD__, array ('IGNORE'));
00484                         }
00485                         // When we upgrade to mysql 4.1, the insert+update
00486                         // can be merged into just a insert with this construct added:
00487                         //     "ON DUPLICATE KEY UPDATE ".
00488                         //     "pf_count=pf_count + VALUES(pf_count), ".
00489                         //     "pf_time=pf_time + VALUES(pf_time)";
00490                 }
00491 
00492                 $dbw->ignoreErrors( $errorState );
00493         }
00494 
00498         function getCurrentSection() {
00499                 $elt = end( $this->mWorkStack );
00500                 return $elt[0];
00501         }
00502 
00508         function debug( $s ) {
00509                 if( defined( 'MW_COMPILED' ) || function_exists( 'wfDebug' ) ) {
00510                         wfDebug( $s );
00511                 }
00512         }
00513 }