MediaWiki
REL1_24
|
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 }