[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/profiler/ -> Profiler.php (source)

   1  <?php
   2  /**
   3   * Base class and functions for profiling.
   4   *
   5   * This program is free software; you can redistribute it and/or modify
   6   * it under the terms of the GNU General Public License as published by
   7   * the Free Software Foundation; either version 2 of the License, or
   8   * (at your option) any later version.
   9   *
  10   * This program is distributed in the hope that it will be useful,
  11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13   * GNU General Public License for more details.
  14   *
  15   * You should have received a copy of the GNU General Public License along
  16   * with this program; if not, write to the Free Software Foundation, Inc.,
  17   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18   * http://www.gnu.org/copyleft/gpl.html
  19   *
  20   * @file
  21   * @ingroup Profiler
  22   * @defgroup Profiler Profiler
  23   */
  24  
  25  /**
  26   * Get system resource usage of current request context.
  27   * Invokes the getrusage(2) system call, requesting RUSAGE_SELF if on PHP5
  28   * or RUSAGE_THREAD if on HHVM. Returns false if getrusage is not available.
  29   *
  30   * @since 1.24
  31   * @return array|bool Resource usage data or false if no data available.
  32   */
  33  function wfGetRusage() {
  34      if ( !function_exists( 'getrusage' ) ) {
  35          return false;
  36      } elseif ( defined ( 'HHVM_VERSION' ) ) {
  37          return getrusage( 2 /* RUSAGE_THREAD */ );
  38      } else {
  39          return getrusage( 0 /* RUSAGE_SELF */ );
  40      }
  41  }
  42  
  43  /**
  44   * Begin profiling of a function
  45   * @param string $functionname Name of the function we will profile
  46   */
  47  function wfProfileIn( $functionname ) {
  48      if ( Profiler::$__instance === null ) { // use this directly to reduce overhead
  49          Profiler::instance();
  50      }
  51      if ( !( Profiler::$__instance instanceof ProfilerStub ) ) {
  52          Profiler::$__instance->profileIn( $functionname );
  53      }
  54  }
  55  
  56  /**
  57   * Stop profiling of a function
  58   * @param string $functionname Name of the function we have profiled
  59   */
  60  function wfProfileOut( $functionname = 'missing' ) {
  61      if ( Profiler::$__instance === null ) { // use this directly to reduce overhead
  62          Profiler::instance();
  63      }
  64      if ( !( Profiler::$__instance instanceof ProfilerStub ) ) {
  65          Profiler::$__instance->profileOut( $functionname );
  66      }
  67  }
  68  
  69  /**
  70   * Class for handling function-scope profiling
  71   *
  72   * @since 1.22
  73   */
  74  class ProfileSection {
  75      protected $name; // string; method name
  76      protected $enabled = false; // boolean; whether profiling is enabled
  77  
  78      /**
  79       * Begin profiling of a function and return an object that ends profiling of
  80       * the function when that object leaves scope. As long as the object is not
  81       * specifically linked to other objects, it will fall out of scope at the same
  82       * moment that the function to be profiled terminates.
  83       *
  84       * This is typically called like:
  85       * <code>$section = new ProfileSection( __METHOD__ );</code>
  86       *
  87       * @param string $name Name of the function to profile
  88       */
  89  	public function __construct( $name ) {
  90          $this->name = $name;
  91          if ( Profiler::$__instance === null ) { // use this directly to reduce overhead
  92              Profiler::instance();
  93          }
  94          if ( !( Profiler::$__instance instanceof ProfilerStub ) ) {
  95              $this->enabled = true;
  96              Profiler::$__instance->profileIn( $this->name );
  97          }
  98      }
  99  
 100  	function __destruct() {
 101          if ( $this->enabled ) {
 102              Profiler::$__instance->profileOut( $this->name );
 103          }
 104      }
 105  }
 106  
 107  /**
 108   * Profiler base class that defines the interface and some trivial functionality
 109   *
 110   * @ingroup Profiler
 111   */
 112  abstract class Profiler {
 113      /** @var string|bool Profiler ID for bucketing data */
 114      protected $mProfileID = false;
 115      /** @var bool Whether MediaWiki is in a SkinTemplate output context */
 116      protected $mTemplated = false;
 117  
 118      /** @var TransactionProfiler */
 119      protected $trxProfiler;
 120  
 121      // @codingStandardsIgnoreStart PSR2.Classes.PropertyDeclaration.Underscore
 122      /** @var Profiler Do not call this outside Profiler and ProfileSection */
 123      public static $__instance = null;
 124      // @codingStandardsIgnoreEnd
 125  
 126      /**
 127       * @param array $params
 128       */
 129  	public function __construct( array $params ) {
 130          if ( isset( $params['profileID'] ) ) {
 131              $this->mProfileID = $params['profileID'];
 132          }
 133          $this->trxProfiler = new TransactionProfiler();
 134      }
 135  
 136      /**
 137       * Singleton
 138       * @return Profiler
 139       */
 140  	final public static function instance() {
 141          if ( self::$__instance === null ) {
 142              global $wgProfiler;
 143              if ( is_array( $wgProfiler ) ) {
 144                  if ( !isset( $wgProfiler['class'] ) ) {
 145                      $class = 'ProfilerStub';
 146                  } elseif ( $wgProfiler['class'] === 'Profiler' ) {
 147                      $class = 'ProfilerStub'; // b/c; don't explode
 148                  } else {
 149                      $class = $wgProfiler['class'];
 150                  }
 151                  self::$__instance = new $class( $wgProfiler );
 152              } elseif ( $wgProfiler instanceof Profiler ) {
 153                  self::$__instance = $wgProfiler; // back-compat
 154              } else {
 155                  self::$__instance = new ProfilerStub( array() );
 156              }
 157          }
 158          return self::$__instance;
 159      }
 160  
 161      /**
 162       * Set the profiler to a specific profiler instance. Mostly for dumpHTML
 163       * @param Profiler $p
 164       */
 165  	final public static function setInstance( Profiler $p ) {
 166          self::$__instance = $p;
 167      }
 168  
 169      /**
 170       * Return whether this a stub profiler
 171       *
 172       * @return bool
 173       */
 174      abstract public function isStub();
 175  
 176      /**
 177       * Return whether this profiler stores data
 178       *
 179       * Called by Parser::braceSubstitution. If true, the parser will not
 180       * generate per-title profiling sections, to avoid overloading the
 181       * profiling data collector.
 182       *
 183       * @see Profiler::logData()
 184       * @return bool
 185       */
 186      abstract public function isPersistent();
 187  
 188      /**
 189       * @param string $id
 190       */
 191  	public function setProfileID( $id ) {
 192          $this->mProfileID = $id;
 193      }
 194  
 195      /**
 196       * @return string
 197       */
 198  	public function getProfileID() {
 199          if ( $this->mProfileID === false ) {
 200              return wfWikiID();
 201          } else {
 202              return $this->mProfileID;
 203          }
 204      }
 205  
 206      /**
 207       * Called by wfProfieIn()
 208       *
 209       * @param string $functionname
 210       */
 211      abstract public function profileIn( $functionname );
 212  
 213      /**
 214       * Called by wfProfieOut()
 215       *
 216       * @param string $functionname
 217       */
 218      abstract public function profileOut( $functionname );
 219  
 220      /**
 221       * Mark a DB as in a transaction with one or more writes pending
 222       *
 223       * Note that there can be multiple connections to a single DB.
 224       *
 225       * @param string $server DB server
 226       * @param string $db DB name
 227       * @param string $id Resource ID string of connection
 228       */
 229  	public function transactionWritingIn( $server, $db, $id = '' ) {
 230          $this->trxProfiler->transactionWritingIn( $server, $db, $id );
 231      }
 232  
 233      /**
 234       * Mark a DB as no longer in a transaction
 235       *
 236       * This will check if locks are possibly held for longer than
 237       * needed and log any affected transactions to a special DB log.
 238       * Note that there can be multiple connections to a single DB.
 239       *
 240       * @param string $server DB server
 241       * @param string $db DB name
 242       * @param string $id Resource ID string of connection
 243       */
 244  	public function transactionWritingOut( $server, $db, $id = '' ) {
 245          $this->trxProfiler->transactionWritingOut( $server, $db, $id );
 246      }
 247  
 248      /**
 249       * Close opened profiling sections
 250       */
 251      abstract public function close();
 252  
 253      /**
 254       * Log the data to some store or even the page output
 255       */
 256      abstract public function logData();
 257  
 258      /**
 259       * Mark this call as templated or not
 260       *
 261       * @param bool $t
 262       */
 263  	public function setTemplated( $t ) {
 264          $this->mTemplated = $t;
 265      }
 266  
 267      /**
 268       * Returns a profiling output to be stored in debug file
 269       *
 270       * @return string
 271       */
 272      abstract public function getOutput();
 273  
 274      /**
 275       * @return array
 276       */
 277      abstract public function getRawData();
 278  
 279      /**
 280       * Get the initial time of the request, based either on $wgRequestTime or
 281       * $wgRUstart. Will return null if not able to find data.
 282       *
 283       * @param string|bool $metric Metric to use, with the following possibilities:
 284       *   - user: User CPU time (without system calls)
 285       *   - cpu: Total CPU time (user and system calls)
 286       *   - wall (or any other string): elapsed time
 287       *   - false (default): will fall back to default metric
 288       * @return float|null
 289       */
 290  	protected function getTime( $metric = 'wall' ) {
 291          if ( $metric === 'cpu' || $metric === 'user' ) {
 292              $ru = wfGetRusage();
 293              if ( !$ru ) {
 294                  return 0;
 295              }
 296              $time = $ru['ru_utime.tv_sec'] + $ru['ru_utime.tv_usec'] / 1e6;
 297              if ( $metric === 'cpu' ) {
 298                  # This is the time of system calls, added to the user time
 299                  # it gives the total CPU time
 300                  $time += $ru['ru_stime.tv_sec'] + $ru['ru_stime.tv_usec'] / 1e6;
 301              }
 302              return $time;
 303          } else {
 304              return microtime( true );
 305          }
 306      }
 307  
 308      /**
 309       * Get the initial time of the request, based either on $wgRequestTime or
 310       * $wgRUstart. Will return null if not able to find data.
 311       *
 312       * @param string|bool $metric Metric to use, with the following possibilities:
 313       *   - user: User CPU time (without system calls)
 314       *   - cpu: Total CPU time (user and system calls)
 315       *   - wall (or any other string): elapsed time
 316       *   - false (default): will fall back to default metric
 317       * @return float|null
 318       */
 319  	protected function getInitialTime( $metric = 'wall' ) {
 320          global $wgRequestTime, $wgRUstart;
 321  
 322          if ( $metric === 'cpu' || $metric === 'user' ) {
 323              if ( !count( $wgRUstart ) ) {
 324                  return null;
 325              }
 326  
 327              $time = $wgRUstart['ru_utime.tv_sec'] + $wgRUstart['ru_utime.tv_usec'] / 1e6;
 328              if ( $metric === 'cpu' ) {
 329                  # This is the time of system calls, added to the user time
 330                  # it gives the total CPU time
 331                  $time += $wgRUstart['ru_stime.tv_sec'] + $wgRUstart['ru_stime.tv_usec'] / 1e6;
 332              }
 333              return $time;
 334          } else {
 335              if ( empty( $wgRequestTime ) ) {
 336                  return null;
 337              } else {
 338                  return $wgRequestTime;
 339              }
 340          }
 341      }
 342  
 343      /**
 344       * Add an entry in the debug log file
 345       *
 346       * @param string $s String to output
 347       */
 348  	protected function debug( $s ) {
 349          if ( function_exists( 'wfDebug' ) ) {
 350              wfDebug( $s );
 351          }
 352      }
 353  
 354      /**
 355       * Add an entry in the debug log group
 356       *
 357       * @param string $group Group to send the message to
 358       * @param string $s String to output
 359       */
 360  	protected function debugGroup( $group, $s ) {
 361          if ( function_exists( 'wfDebugLog' ) ) {
 362              wfDebugLog( $group, $s );
 363          }
 364      }
 365  }
 366  
 367  /**
 368   * Helper class that detects high-contention DB queries via profiling calls
 369   *
 370   * This class is meant to work with a Profiler, as the later already knows
 371   * when methods start and finish (which may take place during transactions).
 372   *
 373   * @since 1.24
 374   */
 375  class TransactionProfiler {
 376      /** @var float Seconds */
 377      protected $mDBLockThreshold = 3.0;
 378      /** @var array DB/server name => (active trx count, time, DBs involved) */
 379      protected $mDBTrxHoldingLocks = array();
 380      /** @var array DB/server name => list of (function name, elapsed time) */
 381      protected $mDBTrxMethodTimes = array();
 382  
 383      /**
 384       * Mark a DB as in a transaction with one or more writes pending
 385       *
 386       * Note that there can be multiple connections to a single DB.
 387       *
 388       * @param string $server DB server
 389       * @param string $db DB name
 390       * @param string $id ID string of transaction
 391       */
 392  	public function transactionWritingIn( $server, $db, $id ) {
 393          $name = "{$server} ({$db}) (TRX#$id)";
 394          if ( isset( $this->mDBTrxHoldingLocks[$name] ) ) {
 395              wfDebugLog( 'DBPerformance', "Nested transaction for '$name' - out of sync." );
 396          }
 397          $this->mDBTrxHoldingLocks[$name] =
 398              array( 'start' => microtime( true ), 'conns' => array() );
 399          $this->mDBTrxMethodTimes[$name] = array();
 400  
 401          foreach ( $this->mDBTrxHoldingLocks as $name => &$info ) {
 402              $info['conns'][$name] = 1; // track all DBs in transactions for this transaction
 403          }
 404      }
 405  
 406      /**
 407       * Register the name and time of a method for slow DB trx detection
 408       *
 409       * This method is only to be called by the Profiler class as methods finish
 410       *
 411       * @param string $method Function name
 412       * @param float $realtime Wal time ellapsed
 413       */
 414  	public function recordFunctionCompletion( $method, $realtime ) {
 415          if ( !$this->mDBTrxHoldingLocks ) {
 416              return; // short-circuit
 417          // @todo hardcoded check is a tad janky (what about FOR UPDATE?)
 418          } elseif ( !preg_match( '/^query-m: (?!SELECT)/', $method )
 419              && $realtime < $this->mDBLockThreshold
 420          ) {
 421              return; // not a DB master query nor slow enough
 422          }
 423          $now = microtime( true );
 424          foreach ( $this->mDBTrxHoldingLocks as $name => $info ) {
 425              // Hacky check to exclude entries from before the first TRX write
 426              if ( ( $now - $realtime ) >= $info['start'] ) {
 427                  $this->mDBTrxMethodTimes[$name][] = array( $method, $realtime );
 428              }
 429          }
 430      }
 431  
 432      /**
 433       * Mark a DB as no longer in a transaction
 434       *
 435       * This will check if locks are possibly held for longer than
 436       * needed and log any affected transactions to a special DB log.
 437       * Note that there can be multiple connections to a single DB.
 438       *
 439       * @param string $server DB server
 440       * @param string $db DB name
 441       * @param string $id ID string of transaction
 442       */
 443  	public function transactionWritingOut( $server, $db, $id ) {
 444          $name = "{$server} ({$db}) (TRX#$id)";
 445          if ( !isset( $this->mDBTrxMethodTimes[$name] ) ) {
 446              wfDebugLog( 'DBPerformance', "Detected no transaction for '$name' - out of sync." );
 447              return;
 448          }
 449          $slow = false;
 450          foreach ( $this->mDBTrxMethodTimes[$name] as $info ) {
 451              $realtime = $info[1];
 452              if ( $realtime >= $this->mDBLockThreshold ) {
 453                  $slow = true;
 454                  break;
 455              }
 456          }
 457          if ( $slow ) {
 458              $dbs = implode( ', ', array_keys( $this->mDBTrxHoldingLocks[$name]['conns'] ) );
 459              $msg = "Sub-optimal transaction on DB(s) [{$dbs}]:\n";
 460              foreach ( $this->mDBTrxMethodTimes[$name] as $i => $info ) {
 461                  list( $method, $realtime ) = $info;
 462                  $msg .= sprintf( "%d\t%.6f\t%s\n", $i, $realtime, $method );
 463              }
 464              wfDebugLog( 'DBPerformance', $msg );
 465          }
 466          unset( $this->mDBTrxHoldingLocks[$name] );
 467          unset( $this->mDBTrxMethodTimes[$name] );
 468      }
 469  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1