MediaWiki  REL1_24
ProfilerStandard.php
Go to the documentation of this file.
00001 <?php
00033 class ProfilerStandard extends Profiler {
00035     protected $mStack = array();
00037     protected $mWorkStack = array();
00038 
00040     protected $mCollated = array();
00042     protected $mCollateDone = false;
00044     protected $mCollateOnly = false;
00046     protected $mErrorEntry;
00047 
00051     public function __construct( array $params ) {
00052         parent::__construct( $params );
00053 
00054         $this->mCollateOnly = $this->collateOnly();
00055 
00056         $this->addInitialStack();
00057     }
00058 
00064     public function isStub() {
00065         return false;
00066     }
00067 
00074     public function isPersistent() {
00075         return false;
00076     }
00077 
00086     protected function collateOnly() {
00087         return false;
00088     }
00089 
00093     protected function addInitialStack() {
00094         $this->mErrorEntry = $this->getErrorEntry();
00095 
00096         $initialTime = $this->getInitialTime( 'wall' );
00097         $initialCpu = $this->getInitialTime( 'cpu' );
00098         if ( $initialTime !== null && $initialCpu !== null ) {
00099             $this->mWorkStack[] = array( '-total', 0, $initialTime, $initialCpu, 0 );
00100             if ( $this->mCollateOnly ) {
00101                 $this->mWorkStack[] = array( '-setup', 1, $initialTime, $initialCpu, 0 );
00102                 $this->profileOut( '-setup' );
00103             } else {
00104                 $this->mStack[] = array( '-setup', 1, $initialTime, $initialCpu, 0,
00105                     $this->getTime( 'wall' ), $this->getTime( 'cpu' ), 0 );
00106             }
00107         } else {
00108             $this->profileIn( '-total' );
00109         }
00110     }
00111 
00115     protected function getZeroEntry() {
00116         return array(
00117             'cpu'      => 0.0,
00118             'cpu_sq'   => 0.0,
00119             'real'     => 0.0,
00120             'real_sq'  => 0.0,
00121             'memory'   => 0,
00122             'count'    => 0,
00123             'min_cpu'  => 0.0,
00124             'max_cpu'  => 0.0,
00125             'min_real' => 0.0,
00126             'max_real' => 0.0,
00127             'periods'  => array(), // not filled if mCollateOnly
00128             'overhead' => 0 // not filled if mCollateOnly
00129         );
00130     }
00131 
00135     protected function getErrorEntry() {
00136         $entry = $this->getZeroEntry();
00137         $entry['count'] = 1;
00138         return $entry;
00139     }
00140 
00151     protected function updateEntry(
00152         $name, $elapsedCpu, $elapsedReal, $memChange, $subcalls = 0, $period = null
00153     ) {
00154         $entry =& $this->mCollated[$name];
00155         if ( !is_array( $entry ) ) {
00156             $entry = $this->getZeroEntry();
00157             $this->mCollated[$name] =& $entry;
00158         }
00159         $entry['cpu'] += $elapsedCpu;
00160         $entry['cpu_sq'] += $elapsedCpu * $elapsedCpu;
00161         $entry['real'] += $elapsedReal;
00162         $entry['real_sq'] += $elapsedReal * $elapsedReal;
00163         $entry['memory'] += $memChange > 0 ? $memChange : 0;
00164         $entry['count']++;
00165         $entry['min_cpu'] = $elapsedCpu < $entry['min_cpu'] ? $elapsedCpu : $entry['min_cpu'];
00166         $entry['max_cpu'] = $elapsedCpu > $entry['max_cpu'] ? $elapsedCpu : $entry['max_cpu'];
00167         $entry['min_real'] = $elapsedReal < $entry['min_real'] ? $elapsedReal : $entry['min_real'];
00168         $entry['max_real'] = $elapsedReal > $entry['max_real'] ? $elapsedReal : $entry['max_real'];
00169         // Apply optional fields
00170         $entry['overhead'] += $subcalls;
00171         if ( $period ) {
00172             $entry['periods'][] = $period;
00173         }
00174     }
00175 
00181     public function profileIn( $functionname ) {
00182         global $wgDebugFunctionEntry;
00183 
00184         if ( $wgDebugFunctionEntry ) {
00185             $this->debug( str_repeat( ' ', count( $this->mWorkStack ) ) .
00186                 'Entering ' . $functionname . "\n" );
00187         }
00188 
00189         $this->mWorkStack[] = array(
00190             $functionname,
00191             count( $this->mWorkStack ),
00192             $this->getTime( 'time' ),
00193             $this->getTime( 'cpu' ),
00194             memory_get_usage()
00195         );
00196     }
00197 
00203     public function profileOut( $functionname ) {
00204         global $wgDebugFunctionEntry;
00205 
00206         if ( $wgDebugFunctionEntry ) {
00207             $this->debug( str_repeat( ' ', count( $this->mWorkStack ) - 1 ) .
00208                 'Exiting ' . $functionname . "\n" );
00209         }
00210 
00211         $item = array_pop( $this->mWorkStack );
00212         list( $ofname, /* $ocount */, $ortime, $octime, $omem ) = $item;
00213 
00214         if ( $item === null ) {
00215             $this->debugGroup( 'profileerror', "Profiling error: $functionname" );
00216         } else {
00217             if ( $functionname === 'close' ) {
00218                 if ( $ofname !== '-total' ) {
00219                     $message = "Profile section ended by close(): {$ofname}";
00220                     $this->debugGroup( 'profileerror', $message );
00221                     if ( $this->mCollateOnly ) {
00222                         $this->mCollated[$message] = $this->mErrorEntry;
00223                     } else {
00224                         $this->mStack[] = array( $message, 0, 0.0, 0.0, 0, 0.0, 0.0, 0 );
00225                     }
00226                 }
00227                 $functionname = $ofname;
00228             } elseif ( $ofname !== $functionname ) {
00229                 $message = "Profiling error: in({$ofname}), out($functionname)";
00230                 $this->debugGroup( 'profileerror', $message );
00231                 if ( $this->mCollateOnly ) {
00232                     $this->mCollated[$message] = $this->mErrorEntry;
00233                 } else {
00234                     $this->mStack[] = array( $message, 0, 0.0, 0.0, 0, 0.0, 0.0, 0 );
00235                 }
00236             }
00237             $realTime = $this->getTime( 'wall' );
00238             $cpuTime = $this->getTime( 'cpu' );
00239             if ( $this->mCollateOnly ) {
00240                 $elapsedcpu = $cpuTime - $octime;
00241                 $elapsedreal = $realTime - $ortime;
00242                 $memchange = memory_get_usage() - $omem;
00243                 $this->updateEntry( $functionname, $elapsedcpu, $elapsedreal, $memchange );
00244             } else {
00245                 $this->mStack[] = array_merge( $item,
00246                     array( $realTime, $cpuTime, memory_get_usage() ) );
00247             }
00248             $this->trxProfiler->recordFunctionCompletion( $functionname, $realTime - $ortime );
00249         }
00250     }
00251 
00255     public function close() {
00256         while ( count( $this->mWorkStack ) ) {
00257             $this->profileOut( 'close' );
00258         }
00259     }
00260 
00264     public function logData() {
00265         /* Implement in subclasses */
00266     }
00267 
00273     public function getOutput() {
00274         global $wgDebugFunctionEntry, $wgProfileCallTree;
00275 
00276         $wgDebugFunctionEntry = false; // hack
00277 
00278         if ( !count( $this->mStack ) && !count( $this->mCollated ) ) {
00279             return "No profiling output\n";
00280         }
00281 
00282         if ( $wgProfileCallTree ) {
00283             return $this->getCallTree();
00284         } else {
00285             return $this->getFunctionReport();
00286         }
00287     }
00288 
00293     protected function getCallTree() {
00294         return implode( '', array_map(
00295             array( &$this, 'getCallTreeLine' ), $this->remapCallTree( $this->mStack )
00296         ) );
00297     }
00298 
00305     protected function remapCallTree( array $stack ) {
00306         if ( count( $stack ) < 2 ) {
00307             return $stack;
00308         }
00309         $outputs = array();
00310         for ( $max = count( $stack ) - 1; $max > 0; ) {
00311             /* Find all items under this entry */
00312             $level = $stack[$max][1];
00313             $working = array();
00314             for ( $i = $max -1; $i >= 0; $i-- ) {
00315                 if ( $stack[$i][1] > $level ) {
00316                     $working[] = $stack[$i];
00317                 } else {
00318                     break;
00319                 }
00320             }
00321             $working = $this->remapCallTree( array_reverse( $working ) );
00322             $output = array();
00323             foreach ( $working as $item ) {
00324                 array_push( $output, $item );
00325             }
00326             array_unshift( $output, $stack[$max] );
00327             $max = $i;
00328 
00329             array_unshift( $outputs, $output );
00330         }
00331         $final = array();
00332         foreach ( $outputs as $output ) {
00333             foreach ( $output as $item ) {
00334                 $final[] = $item;
00335             }
00336         }
00337         return $final;
00338     }
00339 
00345     protected function getCallTreeLine( $entry ) {
00346         list( $fname, $level, $startreal, , , $endreal ) = $entry;
00347         $delta = $endreal - $startreal;
00348         $space = str_repeat( ' ', $level );
00349         # The ugly double sprintf is to work around a PHP bug,
00350         # which has been fixed in recent releases.
00351         return sprintf( "%10s %s %s\n",
00352             trim( sprintf( "%7.3f", $delta * 1000.0 ) ), $space, $fname );
00353     }
00354 
00358     protected function collateData() {
00359         if ( $this->mCollateDone ) {
00360             return;
00361         }
00362         $this->mCollateDone = true;
00363         $this->close(); // set "-total" entry
00364 
00365         if ( $this->mCollateOnly ) {
00366             return; // already collated as methods exited
00367         }
00368 
00369         $this->mCollated = array();
00370 
00371         # Estimate profiling overhead
00372         $profileCount = count( $this->mStack );
00373         self::calculateOverhead( $profileCount );
00374 
00375         # First, subtract the overhead!
00376         $overheadTotal = $overheadMemory = $overheadInternal = array();
00377         foreach ( $this->mStack as $entry ) {
00378             // $entry is (name,pos,rtime0,cputime0,mem0,rtime1,cputime1,mem1)
00379             $fname = $entry[0];
00380             $elapsed = $entry[5] - $entry[2];
00381             $memchange = $entry[7] - $entry[4];
00382 
00383             if ( $fname === '-overhead-total' ) {
00384                 $overheadTotal[] = $elapsed;
00385                 $overheadMemory[] = max( 0, $memchange );
00386             } elseif ( $fname === '-overhead-internal' ) {
00387                 $overheadInternal[] = $elapsed;
00388             }
00389         }
00390         $overheadTotal = $overheadTotal ?
00391             array_sum( $overheadTotal ) / count( $overheadInternal ) : 0;
00392         $overheadMemory = $overheadMemory ?
00393             array_sum( $overheadMemory ) / count( $overheadInternal ) : 0;
00394         $overheadInternal = $overheadInternal ?
00395             array_sum( $overheadInternal ) / count( $overheadInternal ) : 0;
00396 
00397         # Collate
00398         foreach ( $this->mStack as $index => $entry ) {
00399             // $entry is (name,pos,rtime0,cputime0,mem0,rtime1,cputime1,mem1)
00400             $fname = $entry[0];
00401             $elapsedCpu = $entry[6] - $entry[3];
00402             $elapsedReal = $entry[5] - $entry[2];
00403             $memchange = $entry[7] - $entry[4];
00404             $subcalls = $this->calltreeCount( $this->mStack, $index );
00405 
00406             if ( substr( $fname, 0, 9 ) !== '-overhead' ) {
00407                 # Adjust for profiling overhead (except special values with elapsed=0
00408                 if ( $elapsed ) {
00409                     $elapsed -= $overheadInternal;
00410                     $elapsed -= ( $subcalls * $overheadTotal );
00411                     $memchange -= ( $subcalls * $overheadMemory );
00412                 }
00413             }
00414 
00415             $period = array( 'start' => $entry[2], 'end' => $entry[5],
00416                 'memory' => $memchange, 'subcalls' => $subcalls );
00417             $this->updateEntry( $fname, $elapsedCpu, $elapsedReal, $memchange, $subcalls, $period );
00418         }
00419 
00420         $this->mCollated['-overhead-total']['count'] = $profileCount;
00421         arsort( $this->mCollated, SORT_NUMERIC );
00422     }
00423 
00429     protected function getFunctionReport() {
00430         $this->collateData();
00431 
00432         $width = 140;
00433         $nameWidth = $width - 65;
00434         $format = "%-{$nameWidth}s %6d %13.3f %13.3f %13.3f%% %9d  (%13.3f -%13.3f) [%d]\n";
00435         $titleFormat = "%-{$nameWidth}s %6s %13s %13s %13s %9s\n";
00436         $prof = "\nProfiling data\n";
00437         $prof .= sprintf( $titleFormat, 'Name', 'Calls', 'Total', 'Each', '%', 'Mem' );
00438 
00439         $total = isset( $this->mCollated['-total'] )
00440             ? $this->mCollated['-total']['real']
00441             : 0;
00442 
00443         foreach ( $this->mCollated as $fname => $data ) {
00444             $calls = $data['count'];
00445             $percent = $total ? 100 * $data['real'] / $total : 0;
00446             $memory = $data['memory'];
00447             $prof .= sprintf( $format,
00448                 substr( $fname, 0, $nameWidth ),
00449                 $calls,
00450                 (float)( $data['real'] * 1000 ),
00451                 (float)( $data['real'] * 1000 ) / $calls,
00452                 $percent,
00453                 $memory,
00454                 ( $data['min_real'] * 1000.0 ),
00455                 ( $data['max_real'] * 1000.0 ),
00456                 $data['overhead']
00457             );
00458         }
00459         $prof .= "\nTotal: $total\n\n";
00460 
00461         return $prof;
00462     }
00463 
00467     public function getRawData() {
00468         // This method is called before shutdown in the footer method on Skins.
00469         // If some outer methods have not yet called wfProfileOut(), work around
00470         // that by clearing anything in the work stack to just the "-total" entry.
00471         // Collate after doing this so the results do not include profile errors.
00472         if ( count( $this->mWorkStack ) > 1 ) {
00473             $oldWorkStack = $this->mWorkStack;
00474             $this->mWorkStack = array( $this->mWorkStack[0] ); // just the "-total" one
00475         } else {
00476             $oldWorkStack = null;
00477         }
00478         $this->collateData();
00479         // If this trick is used, then the old work stack is swapped back afterwards
00480         // and mCollateDone is reset to false. This means that logData() will still
00481         // make use of all the method data since the missing wfProfileOut() calls
00482         // should be made by the time it is called.
00483         if ( $oldWorkStack ) {
00484             $this->mWorkStack = $oldWorkStack;
00485             $this->mCollateDone = false;
00486         }
00487 
00488         $total = isset( $this->mCollated['-total'] )
00489             ? $this->mCollated['-total']['real']
00490             : 0;
00491 
00492         $profile = array();
00493         foreach ( $this->mCollated as $fname => $data ) {
00494             $periods = array();
00495             foreach ( $data['periods'] as $period ) {
00496                 $period['start'] *= 1000;
00497                 $period['end'] *= 1000;
00498                 $periods[] = $period;
00499             }
00500             $profile[] = array(
00501                 'name' => $fname,
00502                 'calls' => $data['count'],
00503                 'elapsed' => $data['real'] * 1000,
00504                 'percent' => $total ? 100 * $data['real'] / $total : 0,
00505                 'memory' => $data['memory'],
00506                 'min' => $data['min_real'] * 1000,
00507                 'max' => $data['max_real'] * 1000,
00508                 'overhead' => $data['overhead'],
00509                 'periods' => $periods
00510             );
00511         }
00512 
00513         return $profile;
00514     }
00515 
00520     protected static function calculateOverhead( $profileCount ) {
00521         wfProfileIn( '-overhead-total' );
00522         for ( $i = 0; $i < $profileCount; $i++ ) {
00523             wfProfileIn( '-overhead-internal' );
00524             wfProfileOut( '-overhead-internal' );
00525         }
00526         wfProfileOut( '-overhead-total' );
00527     }
00528 
00537     protected function calltreeCount( $stack, $start ) {
00538         $level = $stack[$start][1];
00539         $count = 0;
00540         for ( $i = $start -1; $i >= 0 && $stack[$i][1] > $level; $i-- ) {
00541             $count ++;
00542         }
00543         return $count;
00544     }
00545 
00551     protected function getContentType() {
00552         foreach ( headers_list() as $header ) {
00553             if ( preg_match( '#^content-type: (\w+/\w+);?#i', $header, $m ) ) {
00554                 return $m[1];
00555             }
00556         }
00557         return null;
00558     }
00559 }