MediaWiki  REL1_24
Profiler.php
Go to the documentation of this file.
00001 <?php
00033 function wfGetRusage() {
00034     if ( !function_exists( 'getrusage' ) ) {
00035         return false;
00036     } elseif ( defined ( 'HHVM_VERSION' ) ) {
00037         return getrusage( 2 /* RUSAGE_THREAD */ );
00038     } else {
00039         return getrusage( 0 /* RUSAGE_SELF */ );
00040     }
00041 }
00042 
00047 function wfProfileIn( $functionname ) {
00048     if ( Profiler::$__instance === null ) { // use this directly to reduce overhead
00049         Profiler::instance();
00050     }
00051     if ( !( Profiler::$__instance instanceof ProfilerStub ) ) {
00052         Profiler::$__instance->profileIn( $functionname );
00053     }
00054 }
00055 
00060 function wfProfileOut( $functionname = 'missing' ) {
00061     if ( Profiler::$__instance === null ) { // use this directly to reduce overhead
00062         Profiler::instance();
00063     }
00064     if ( !( Profiler::$__instance instanceof ProfilerStub ) ) {
00065         Profiler::$__instance->profileOut( $functionname );
00066     }
00067 }
00068 
00074 class ProfileSection {
00075     protected $name; // string; method name
00076     protected $enabled = false; // boolean; whether profiling is enabled
00077 
00089     public function __construct( $name ) {
00090         $this->name = $name;
00091         if ( Profiler::$__instance === null ) { // use this directly to reduce overhead
00092             Profiler::instance();
00093         }
00094         if ( !( Profiler::$__instance instanceof ProfilerStub ) ) {
00095             $this->enabled = true;
00096             Profiler::$__instance->profileIn( $this->name );
00097         }
00098     }
00099 
00100     function __destruct() {
00101         if ( $this->enabled ) {
00102             Profiler::$__instance->profileOut( $this->name );
00103         }
00104     }
00105 }
00106 
00112 abstract class Profiler {
00114     protected $mProfileID = false;
00116     protected $mTemplated = false;
00117 
00119     protected $trxProfiler;
00120 
00121     // @codingStandardsIgnoreStart PSR2.Classes.PropertyDeclaration.Underscore
00123     public static $__instance = null;
00124     // @codingStandardsIgnoreEnd
00125 
00129     public function __construct( array $params ) {
00130         if ( isset( $params['profileID'] ) ) {
00131             $this->mProfileID = $params['profileID'];
00132         }
00133         $this->trxProfiler = new TransactionProfiler();
00134     }
00135 
00140     final public static function instance() {
00141         if ( self::$__instance === null ) {
00142             global $wgProfiler;
00143             if ( is_array( $wgProfiler ) ) {
00144                 if ( !isset( $wgProfiler['class'] ) ) {
00145                     $class = 'ProfilerStub';
00146                 } elseif ( $wgProfiler['class'] === 'Profiler' ) {
00147                     $class = 'ProfilerStub'; // b/c; don't explode
00148                 } else {
00149                     $class = $wgProfiler['class'];
00150                 }
00151                 self::$__instance = new $class( $wgProfiler );
00152             } elseif ( $wgProfiler instanceof Profiler ) {
00153                 self::$__instance = $wgProfiler; // back-compat
00154             } else {
00155                 self::$__instance = new ProfilerStub( array() );
00156             }
00157         }
00158         return self::$__instance;
00159     }
00160 
00165     final public static function setInstance( Profiler $p ) {
00166         self::$__instance = $p;
00167     }
00168 
00174     abstract public function isStub();
00175 
00186     abstract public function isPersistent();
00187 
00191     public function setProfileID( $id ) {
00192         $this->mProfileID = $id;
00193     }
00194 
00198     public function getProfileID() {
00199         if ( $this->mProfileID === false ) {
00200             return wfWikiID();
00201         } else {
00202             return $this->mProfileID;
00203         }
00204     }
00205 
00211     abstract public function profileIn( $functionname );
00212 
00218     abstract public function profileOut( $functionname );
00219 
00229     public function transactionWritingIn( $server, $db, $id = '' ) {
00230         $this->trxProfiler->transactionWritingIn( $server, $db, $id );
00231     }
00232 
00244     public function transactionWritingOut( $server, $db, $id = '' ) {
00245         $this->trxProfiler->transactionWritingOut( $server, $db, $id );
00246     }
00247 
00251     abstract public function close();
00252 
00256     abstract public function logData();
00257 
00263     public function setTemplated( $t ) {
00264         $this->mTemplated = $t;
00265     }
00266 
00272     abstract public function getOutput();
00273 
00277     abstract public function getRawData();
00278 
00290     protected function getTime( $metric = 'wall' ) {
00291         if ( $metric === 'cpu' || $metric === 'user' ) {
00292             $ru = wfGetRusage();
00293             if ( !$ru ) {
00294                 return 0;
00295             }
00296             $time = $ru['ru_utime.tv_sec'] + $ru['ru_utime.tv_usec'] / 1e6;
00297             if ( $metric === 'cpu' ) {
00298                 # This is the time of system calls, added to the user time
00299                 # it gives the total CPU time
00300                 $time += $ru['ru_stime.tv_sec'] + $ru['ru_stime.tv_usec'] / 1e6;
00301             }
00302             return $time;
00303         } else {
00304             return microtime( true );
00305         }
00306     }
00307 
00319     protected function getInitialTime( $metric = 'wall' ) {
00320         global $wgRequestTime, $wgRUstart;
00321 
00322         if ( $metric === 'cpu' || $metric === 'user' ) {
00323             if ( !count( $wgRUstart ) ) {
00324                 return null;
00325             }
00326 
00327             $time = $wgRUstart['ru_utime.tv_sec'] + $wgRUstart['ru_utime.tv_usec'] / 1e6;
00328             if ( $metric === 'cpu' ) {
00329                 # This is the time of system calls, added to the user time
00330                 # it gives the total CPU time
00331                 $time += $wgRUstart['ru_stime.tv_sec'] + $wgRUstart['ru_stime.tv_usec'] / 1e6;
00332             }
00333             return $time;
00334         } else {
00335             if ( empty( $wgRequestTime ) ) {
00336                 return null;
00337             } else {
00338                 return $wgRequestTime;
00339             }
00340         }
00341     }
00342 
00348     protected function debug( $s ) {
00349         if ( function_exists( 'wfDebug' ) ) {
00350             wfDebug( $s );
00351         }
00352     }
00353 
00360     protected function debugGroup( $group, $s ) {
00361         if ( function_exists( 'wfDebugLog' ) ) {
00362             wfDebugLog( $group, $s );
00363         }
00364     }
00365 }
00366 
00375 class TransactionProfiler {
00377     protected $mDBLockThreshold = 3.0;
00379     protected $mDBTrxHoldingLocks = array();
00381     protected $mDBTrxMethodTimes = array();
00382 
00392     public function transactionWritingIn( $server, $db, $id ) {
00393         $name = "{$server} ({$db}) (TRX#$id)";
00394         if ( isset( $this->mDBTrxHoldingLocks[$name] ) ) {
00395             wfDebugLog( 'DBPerformance', "Nested transaction for '$name' - out of sync." );
00396         }
00397         $this->mDBTrxHoldingLocks[$name] =
00398             array( 'start' => microtime( true ), 'conns' => array() );
00399         $this->mDBTrxMethodTimes[$name] = array();
00400 
00401         foreach ( $this->mDBTrxHoldingLocks as $name => &$info ) {
00402             $info['conns'][$name] = 1; // track all DBs in transactions for this transaction
00403         }
00404     }
00405 
00414     public function recordFunctionCompletion( $method, $realtime ) {
00415         if ( !$this->mDBTrxHoldingLocks ) {
00416             return; // short-circuit
00417         // @todo hardcoded check is a tad janky (what about FOR UPDATE?)
00418         } elseif ( !preg_match( '/^query-m: (?!SELECT)/', $method )
00419             && $realtime < $this->mDBLockThreshold
00420         ) {
00421             return; // not a DB master query nor slow enough
00422         }
00423         $now = microtime( true );
00424         foreach ( $this->mDBTrxHoldingLocks as $name => $info ) {
00425             // Hacky check to exclude entries from before the first TRX write
00426             if ( ( $now - $realtime ) >= $info['start'] ) {
00427                 $this->mDBTrxMethodTimes[$name][] = array( $method, $realtime );
00428             }
00429         }
00430     }
00431 
00443     public function transactionWritingOut( $server, $db, $id ) {
00444         $name = "{$server} ({$db}) (TRX#$id)";
00445         if ( !isset( $this->mDBTrxMethodTimes[$name] ) ) {
00446             wfDebugLog( 'DBPerformance', "Detected no transaction for '$name' - out of sync." );
00447             return;
00448         }
00449         $slow = false;
00450         foreach ( $this->mDBTrxMethodTimes[$name] as $info ) {
00451             $realtime = $info[1];
00452             if ( $realtime >= $this->mDBLockThreshold ) {
00453                 $slow = true;
00454                 break;
00455             }
00456         }
00457         if ( $slow ) {
00458             $dbs = implode( ', ', array_keys( $this->mDBTrxHoldingLocks[$name]['conns'] ) );
00459             $msg = "Sub-optimal transaction on DB(s) [{$dbs}]:\n";
00460             foreach ( $this->mDBTrxMethodTimes[$name] as $i => $info ) {
00461                 list( $method, $realtime ) = $info;
00462                 $msg .= sprintf( "%d\t%.6f\t%s\n", $i, $realtime, $method );
00463             }
00464             wfDebugLog( 'DBPerformance', $msg );
00465         }
00466         unset( $this->mDBTrxHoldingLocks[$name] );
00467         unset( $this->mDBTrxMethodTimes[$name] );
00468     }
00469 }