MediaWiki  REL1_21
Profiler.php
Go to the documentation of this file.
00001 <?php
00033 function wfProfileIn( $functionname ) {
00034         global $wgProfiler;
00035         if ( $wgProfiler instanceof Profiler || isset( $wgProfiler['class'] ) ) {
00036                 Profiler::instance()->profileIn( $functionname );
00037         }
00038 }
00039 
00044 function wfProfileOut( $functionname = 'missing' ) {
00045         global $wgProfiler;
00046         if ( $wgProfiler instanceof Profiler || isset( $wgProfiler['class'] ) ) {
00047                 Profiler::instance()->profileOut( $functionname );
00048         }
00049 }
00050 
00055 class Profiler {
00056         protected $mStack = array(), $mWorkStack = array (), $mCollated = array (),
00057                 $mCalls = array (), $mTotals = array ();
00058         protected $mTimeMetric = 'wall';
00059         protected $mProfileID = false, $mCollateDone = false, $mTemplated = false;
00060         private static $__instance = null;
00061 
00062         function __construct( $params ) {
00063                 if ( isset( $params['timeMetric'] ) ) {
00064                         $this->mTimeMetric = $params['timeMetric'];
00065                 }
00066                 if ( isset( $params['profileID'] ) ) {
00067                         $this->mProfileID = $params['profileID'];
00068                 }
00069 
00070                 $this->addInitialStack();
00071         }
00072 
00077         public static function instance() {
00078                 if( is_null( self::$__instance ) ) {
00079                         global $wgProfiler;
00080                         if( is_array( $wgProfiler ) ) {
00081                                 if( !isset( $wgProfiler['class'] ) ) {
00082                                         wfDebug( __METHOD__ . " called without \$wgProfiler['class']"
00083                                                 . " set, falling back to ProfilerStub for safety\n" );
00084                                         $class = 'ProfilerStub';
00085                                 } else {
00086                                         $class = $wgProfiler['class'];
00087                                 }
00088                                 self::$__instance = new $class( $wgProfiler );
00089                         } elseif( $wgProfiler instanceof Profiler ) {
00090                                 self::$__instance = $wgProfiler; // back-compat
00091                         } else {
00092                                 wfDebug( __METHOD__ . ' called with bogus $wgProfiler setting,'
00093                                                 . " falling back to ProfilerStub for safety\n" );
00094                                 self::$__instance = new ProfilerStub( $wgProfiler );
00095                         }
00096                 }
00097                 return self::$__instance;
00098         }
00099 
00104         public static function setInstance( Profiler $p ) {
00105                 self::$__instance = $p;
00106         }
00107 
00113         public function isStub() {
00114                 return false;
00115         }
00116 
00123         public function isPersistent() {
00124                 return true;
00125         }
00126 
00127         public function setProfileID( $id ) {
00128                 $this->mProfileID = $id;
00129         }
00130 
00131         public function getProfileID() {
00132                 if ( $this->mProfileID === false ) {
00133                         return wfWikiID();
00134                 } else {
00135                         return $this->mProfileID;
00136                 }
00137         }
00138 
00142         protected function addInitialStack() {
00143                 // Push an entry for the pre-profile setup time onto the stack
00144                 $initial = $this->getInitialTime();
00145                 if ( $initial !== null ) {
00146                         $this->mWorkStack[] = array( '-total', 0, $initial, 0 );
00147                         $this->mStack[] = array( '-setup', 1, $initial, 0, $this->getTime(), 0 );
00148                 } else {
00149                         $this->profileIn( '-total' );
00150                 }
00151         }
00152 
00158         public function profileIn( $functionname ) {
00159                 global $wgDebugFunctionEntry;
00160                 if( $wgDebugFunctionEntry ) {
00161                         $this->debug( str_repeat( ' ', count( $this->mWorkStack ) ) . 'Entering ' . $functionname . "\n" );
00162                 }
00163 
00164                 $this->mWorkStack[] = array( $functionname, count( $this->mWorkStack ), $this->getTime(), memory_get_usage() );
00165         }
00166 
00172         public function profileOut( $functionname ) {
00173                 global $wgDebugFunctionEntry;
00174                 $memory = memory_get_usage();
00175                 $time = $this->getTime();
00176 
00177                 if( $wgDebugFunctionEntry ) {
00178                         $this->debug( str_repeat( ' ', count( $this->mWorkStack ) - 1 ) . 'Exiting ' . $functionname . "\n" );
00179                 }
00180 
00181                 $bit = array_pop( $this->mWorkStack );
00182 
00183                 if ( !$bit ) {
00184                         $this->debug( "Profiling error, !\$bit: $functionname\n" );
00185                 } else {
00186                         //if( $wgDebugProfiling ) {
00187                                 if( $functionname == 'close' ) {
00188                                         $message = "Profile section ended by close(): {$bit[0]}";
00189                                         $this->debug( "$message\n" );
00190                                         $this->mStack[] = array( $message, 0, 0.0, 0, 0.0, 0 );
00191                                 }
00192                                 elseif( $bit[0] != $functionname ) {
00193                                         $message = "Profiling error: in({$bit[0]}), out($functionname)";
00194                                         $this->debug( "$message\n" );
00195                                         $this->mStack[] = array( $message, 0, 0.0, 0, 0.0, 0 );
00196                                 }
00197                         //}
00198                         $bit[] = $time;
00199                         $bit[] = $memory;
00200                         $this->mStack[] = $bit;
00201                 }
00202         }
00203 
00207         public function close() {
00208                 while( count( $this->mWorkStack ) ) {
00209                         $this->profileOut( 'close' );
00210                 }
00211         }
00212 
00218         function setTemplated( $t ) {
00219                 $this->mTemplated = $t;
00220         }
00221 
00227         public function getOutput() {
00228                 global $wgDebugFunctionEntry, $wgProfileCallTree;
00229                 $wgDebugFunctionEntry = false;
00230 
00231                 if( !count( $this->mStack ) && !count( $this->mCollated ) ) {
00232                         return "No profiling output\n";
00233                 }
00234 
00235                 if( $wgProfileCallTree ) {
00236                         return $this->getCallTree();
00237                 } else {
00238                         return $this->getFunctionReport();
00239                 }
00240         }
00241 
00246         function getCallTree() {
00247                 return implode( '', array_map( array( &$this, 'getCallTreeLine' ), $this->remapCallTree( $this->mStack ) ) );
00248         }
00249 
00256         function remapCallTree( $stack ) {
00257                 if( count( $stack ) < 2 ) {
00258                         return $stack;
00259                 }
00260                 $outputs = array ();
00261                 for( $max = count( $stack ) - 1; $max > 0; ) {
00262                         /* Find all items under this entry */
00263                         $level = $stack[$max][1];
00264                         $working = array ();
00265                         for( $i = $max -1; $i >= 0; $i-- ) {
00266                                 if( $stack[$i][1] > $level ) {
00267                                         $working[] = $stack[$i];
00268                                 } else {
00269                                         break;
00270                                 }
00271                         }
00272                         $working = $this->remapCallTree( array_reverse( $working ) );
00273                         $output = array();
00274                         foreach( $working as $item ) {
00275                                 array_push( $output, $item );
00276                         }
00277                         array_unshift( $output, $stack[$max] );
00278                         $max = $i;
00279 
00280                         array_unshift( $outputs, $output );
00281                 }
00282                 $final = array();
00283                 foreach( $outputs as $output ) {
00284                         foreach( $output as $item ) {
00285                                 $final[] = $item;
00286                         }
00287                 }
00288                 return $final;
00289         }
00290 
00295         function getCallTreeLine( $entry ) {
00296                 list( $fname, $level, $start, /* $x */, $end ) = $entry;
00297                 $delta = $end - $start;
00298                 $space = str_repeat( ' ', $level );
00299                 # The ugly double sprintf is to work around a PHP bug,
00300                 # which has been fixed in recent releases.
00301                 return sprintf( "%10s %s %s\n", trim( sprintf( "%7.3f", $delta * 1000.0 ) ), $space, $fname );
00302         }
00303 
00315         function getTime( $metric = false ) {
00316                 if ( $metric === false ) {
00317                         $metric = $this->mTimeMetric;
00318                 }
00319 
00320                 if ( $metric === 'cpu' || $this->mTimeMetric === 'user' ) {
00321                         if ( !function_exists( 'getrusage' ) ) {
00322                                 return 0;
00323                         }
00324                         $ru = getrusage();
00325                         $time = $ru['ru_utime.tv_sec'] + $ru['ru_utime.tv_usec'] / 1e6;
00326                         if ( $metric === 'cpu' ) {
00327                                 # This is the time of system calls, added to the user time
00328                                 # it gives the total CPU time
00329                                 $time += $ru['ru_stime.tv_sec'] + $ru['ru_stime.tv_usec'] / 1e6;
00330                         }
00331                         return $time;
00332                 } else {
00333                         return microtime( true );
00334                 }
00335         }
00336 
00348         protected function getInitialTime( $metric = false ) {
00349                 global $wgRequestTime, $wgRUstart;
00350 
00351                 if ( $metric === false ) {
00352                         $metric = $this->mTimeMetric;
00353                 }
00354 
00355                 if ( $metric === 'cpu' || $this->mTimeMetric === 'user' ) {
00356                         if ( !count( $wgRUstart ) ) {
00357                                 return null;
00358                         }
00359 
00360                         $time = $wgRUstart['ru_utime.tv_sec'] + $wgRUstart['ru_utime.tv_usec'] / 1e6;
00361                         if ( $metric === 'cpu' ) {
00362                                 # This is the time of system calls, added to the user time
00363                                 # it gives the total CPU time
00364                                 $time += $wgRUstart['ru_stime.tv_sec'] + $wgRUstart['ru_stime.tv_usec'] / 1e6;
00365                         }
00366                         return $time;
00367                 } else {
00368                         if ( empty( $wgRequestTime ) ) {
00369                                 return null;
00370                         } else {
00371                                 return $wgRequestTime;
00372                         }
00373                 }
00374         }
00375 
00376         protected function collateData() {
00377                 if ( $this->mCollateDone ) {
00378                         return;
00379                 }
00380                 $this->mCollateDone = true;
00381 
00382                 $this->close();
00383 
00384                 $this->mCollated = array();
00385                 $this->mCalls = array();
00386                 $this->mMemory = array();
00387 
00388                 # Estimate profiling overhead
00389                 $profileCount = count( $this->mStack );
00390                 self::calculateOverhead( $profileCount );
00391 
00392                 # First, subtract the overhead!
00393                 $overheadTotal = $overheadMemory = $overheadInternal = array();
00394                 foreach( $this->mStack as $entry ) {
00395                         $fname = $entry[0];
00396                         $start = $entry[2];
00397                         $end = $entry[4];
00398                         $elapsed = $end - $start;
00399                         $memory = $entry[5] - $entry[3];
00400 
00401                         if( $fname == '-overhead-total' ) {
00402                                 $overheadTotal[] = $elapsed;
00403                                 $overheadMemory[] = $memory;
00404                         }
00405                         elseif( $fname == '-overhead-internal' ) {
00406                                 $overheadInternal[] = $elapsed;
00407                         }
00408                 }
00409                 $overheadTotal = $overheadTotal ? array_sum( $overheadTotal ) / count( $overheadInternal ) : 0;
00410                 $overheadMemory = $overheadMemory ? array_sum( $overheadMemory ) / count( $overheadInternal ) : 0;
00411                 $overheadInternal = $overheadInternal ? array_sum( $overheadInternal ) / count( $overheadInternal ) : 0;
00412 
00413                 # Collate
00414                 foreach( $this->mStack as $index => $entry ) {
00415                         $fname = $entry[0];
00416                         $start = $entry[2];
00417                         $end = $entry[4];
00418                         $elapsed = $end - $start;
00419 
00420                         $memory = $entry[5] - $entry[3];
00421                         $subcalls = $this->calltreeCount( $this->mStack, $index );
00422 
00423                         if( !preg_match( '/^-overhead/', $fname ) ) {
00424                                 # Adjust for profiling overhead (except special values with elapsed=0
00425                                 if( $elapsed ) {
00426                                         $elapsed -= $overheadInternal;
00427                                         $elapsed -= ($subcalls * $overheadTotal);
00428                                         $memory -= ($subcalls * $overheadMemory);
00429                                 }
00430                         }
00431 
00432                         if( !array_key_exists( $fname, $this->mCollated ) ) {
00433                                 $this->mCollated[$fname] = 0;
00434                                 $this->mCalls[$fname] = 0;
00435                                 $this->mMemory[$fname] = 0;
00436                                 $this->mMin[$fname] = 1 << 24;
00437                                 $this->mMax[$fname] = 0;
00438                                 $this->mOverhead[$fname] = 0;
00439                         }
00440 
00441                         $this->mCollated[$fname] += $elapsed;
00442                         $this->mCalls[$fname]++;
00443                         $this->mMemory[$fname] += $memory;
00444                         $this->mMin[$fname] = min( $this->mMin[$fname], $elapsed );
00445                         $this->mMax[$fname] = max( $this->mMax[$fname], $elapsed );
00446                         $this->mOverhead[$fname] += $subcalls;
00447                 }
00448 
00449                 $this->mCalls['-overhead-total'] = $profileCount;
00450                 arsort( $this->mCollated, SORT_NUMERIC );
00451         }
00452 
00458         function getFunctionReport() {
00459                 $this->collateData();
00460 
00461                 $width = 140;
00462                 $nameWidth = $width - 65;
00463                 $format = "%-{$nameWidth}s %6d %13.3f %13.3f %13.3f%% %9d  (%13.3f -%13.3f) [%d]\n";
00464                 $titleFormat = "%-{$nameWidth}s %6s %13s %13s %13s %9s\n";
00465                 $prof = "\nProfiling data\n";
00466                 $prof .= sprintf( $titleFormat, 'Name', 'Calls', 'Total', 'Each', '%', 'Mem' );
00467 
00468                 $total = isset( $this->mCollated['-total'] ) ? $this->mCollated['-total'] : 0;
00469 
00470                 foreach( $this->mCollated as $fname => $elapsed ) {
00471                         $calls = $this->mCalls[$fname];
00472                         $percent = $total ? 100. * $elapsed / $total : 0;
00473                         $memory = $this->mMemory[$fname];
00474                         $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] );
00475                 }
00476                 $prof .= "\nTotal: $total\n\n";
00477 
00478                 return $prof;
00479         }
00480 
00484         protected static function calculateOverhead( $profileCount ) {
00485                 wfProfileIn( '-overhead-total' );
00486                 for( $i = 0; $i < $profileCount; $i++ ) {
00487                         wfProfileIn( '-overhead-internal' );
00488                         wfProfileOut( '-overhead-internal' );
00489                 }
00490                 wfProfileOut( '-overhead-total' );
00491         }
00492 
00502         function calltreeCount( $stack, $start ) {
00503                 $level = $stack[$start][1];
00504                 $count = 0;
00505                 for ( $i = $start -1; $i >= 0 && $stack[$i][1] > $level; $i-- ) {
00506                         $count ++;
00507                 }
00508                 return $count;
00509         }
00510 
00514         public function logData() {
00515                 global $wgProfilePerHost, $wgProfileToDatabase;
00516 
00517                 # Do not log anything if database is readonly (bug 5375)
00518                 if( wfReadOnly() || !$wgProfileToDatabase ) {
00519                         return;
00520                 }
00521 
00522                 $dbw = wfGetDB( DB_MASTER );
00523                 if( !is_object( $dbw ) ) {
00524                         return;
00525                 }
00526 
00527                 if( $wgProfilePerHost ) {
00528                         $pfhost = wfHostname();
00529                 } else {
00530                         $pfhost = '';
00531                 }
00532 
00533                 try {
00534                         $this->collateData();
00535 
00536                         foreach( $this->mCollated as $name => $elapsed ) {
00537                                 $eventCount = $this->mCalls[$name];
00538                                 $timeSum = (float) ($elapsed * 1000);
00539                                 $memorySum = (float)$this->mMemory[$name];
00540                                 $name = substr($name, 0, 255);
00541 
00542                                 // Kludge
00543                                 $timeSum = ($timeSum >= 0) ? $timeSum : 0;
00544                                 $memorySum = ($memorySum >= 0) ? $memorySum : 0;
00545 
00546                                 $dbw->update( 'profiling',
00547                                         array(
00548                                                 "pf_count=pf_count+{$eventCount}",
00549                                                 "pf_time=pf_time+{$timeSum}",
00550                                                 "pf_memory=pf_memory+{$memorySum}",
00551                                         ),
00552                                         array(
00553                                                 'pf_name' => $name,
00554                                                 'pf_server' => $pfhost,
00555                                         ),
00556                                         __METHOD__ );
00557 
00558                                 $rc = $dbw->affectedRows();
00559                                 if ( $rc == 0 ) {
00560                                         $dbw->insert( 'profiling', array ( 'pf_name' => $name, 'pf_count' => $eventCount,
00561                                                 'pf_time' => $timeSum, 'pf_memory' => $memorySum, 'pf_server' => $pfhost ),
00562                                                 __METHOD__, array ( 'IGNORE' ) );
00563                                 }
00564                                 // When we upgrade to mysql 4.1, the insert+update
00565                                 // can be merged into just a insert with this construct added:
00566                                 //     "ON DUPLICATE KEY UPDATE ".
00567                                 //     "pf_count=pf_count + VALUES(pf_count), ".
00568                                 //     "pf_time=pf_time + VALUES(pf_time)";
00569                         }
00570                 } catch ( DBError $e ) {}
00571         }
00572 
00577         function getCurrentSection() {
00578                 $elt = end( $this->mWorkStack );
00579                 return $elt[0];
00580         }
00581 
00587         function debug( $s ) {
00588                 if( defined( 'MW_COMPILED' ) || function_exists( 'wfDebug' ) ) {
00589                         wfDebug( $s );
00590                 }
00591         }
00592 
00598         protected function getContentType() {
00599                 foreach ( headers_list() as $header ) {
00600                         if ( preg_match( '#^content-type: (\w+/\w+);?#i', $header, $m ) ) {
00601                                 return $m[1];
00602                         }
00603                 }
00604                 return null;
00605         }
00606 }