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