[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |