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