[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Common implementation class 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 */ 23 24 /** 25 * Standard profiler that tracks real time, cpu time, and memory deltas 26 * 27 * This supports profile reports, the debug toolbar, and high-contention 28 * DB query warnings. This does not persist the profiling data though. 29 * 30 * @ingroup Profiler 31 * @since 1.24 32 */ 33 class ProfilerStandard extends Profiler { 34 /** @var array List of resolved profile calls with start/end data */ 35 protected $mStack = array(); 36 /** @var array Queue of open profile calls with start data */ 37 protected $mWorkStack = array(); 38 39 /** @var array Map of (function name => aggregate data array) */ 40 protected $mCollated = array(); 41 /** @var bool */ 42 protected $mCollateDone = false; 43 /** @var bool */ 44 protected $mCollateOnly = false; 45 /** @var array Cache of a standard broken collation entry */ 46 protected $mErrorEntry; 47 48 /** 49 * @param array $params 50 */ 51 public function __construct( array $params ) { 52 parent::__construct( $params ); 53 54 $this->mCollateOnly = $this->collateOnly(); 55 56 $this->addInitialStack(); 57 } 58 59 /** 60 * Return whether this a stub profiler 61 * 62 * @return bool 63 */ 64 public function isStub() { 65 return false; 66 } 67 68 /** 69 * Return whether this profiler stores data 70 * 71 * @see Profiler::logData() 72 * @return bool 73 */ 74 public function isPersistent() { 75 return false; 76 } 77 78 /** 79 * Whether to internally just track aggregates and ignore the full stack trace 80 * 81 * Only doing collation saves memory overhead but limits the use of certain 82 * features like that of graph generation for the debug toolbar. 83 * 84 * @return bool 85 */ 86 protected function collateOnly() { 87 return false; 88 } 89 90 /** 91 * Add the inital item in the stack. 92 */ 93 protected function addInitialStack() { 94 $this->mErrorEntry = $this->getErrorEntry(); 95 96 $initialTime = $this->getInitialTime( 'wall' ); 97 $initialCpu = $this->getInitialTime( 'cpu' ); 98 if ( $initialTime !== null && $initialCpu !== null ) { 99 $this->mWorkStack[] = array( '-total', 0, $initialTime, $initialCpu, 0 ); 100 if ( $this->mCollateOnly ) { 101 $this->mWorkStack[] = array( '-setup', 1, $initialTime, $initialCpu, 0 ); 102 $this->profileOut( '-setup' ); 103 } else { 104 $this->mStack[] = array( '-setup', 1, $initialTime, $initialCpu, 0, 105 $this->getTime( 'wall' ), $this->getTime( 'cpu' ), 0 ); 106 } 107 } else { 108 $this->profileIn( '-total' ); 109 } 110 } 111 112 /** 113 * @return array Initial collation entry 114 */ 115 protected function getZeroEntry() { 116 return array( 117 'cpu' => 0.0, 118 'cpu_sq' => 0.0, 119 'real' => 0.0, 120 'real_sq' => 0.0, 121 'memory' => 0, 122 'count' => 0, 123 'min_cpu' => 0.0, 124 'max_cpu' => 0.0, 125 'min_real' => 0.0, 126 'max_real' => 0.0, 127 'periods' => array(), // not filled if mCollateOnly 128 'overhead' => 0 // not filled if mCollateOnly 129 ); 130 } 131 132 /** 133 * @return array Initial collation entry for errors 134 */ 135 protected function getErrorEntry() { 136 $entry = $this->getZeroEntry(); 137 $entry['count'] = 1; 138 return $entry; 139 } 140 141 /** 142 * Update the collation entry for a given method name 143 * 144 * @param string $name 145 * @param float $elapsedCpu 146 * @param float $elapsedReal 147 * @param int $memChange 148 * @param int $subcalls 149 * @param array|null $period Map of ('start','end','memory','subcalls') 150 */ 151 protected function updateEntry( 152 $name, $elapsedCpu, $elapsedReal, $memChange, $subcalls = 0, $period = null 153 ) { 154 $entry =& $this->mCollated[$name]; 155 if ( !is_array( $entry ) ) { 156 $entry = $this->getZeroEntry(); 157 $this->mCollated[$name] =& $entry; 158 } 159 $entry['cpu'] += $elapsedCpu; 160 $entry['cpu_sq'] += $elapsedCpu * $elapsedCpu; 161 $entry['real'] += $elapsedReal; 162 $entry['real_sq'] += $elapsedReal * $elapsedReal; 163 $entry['memory'] += $memChange > 0 ? $memChange : 0; 164 $entry['count']++; 165 $entry['min_cpu'] = $elapsedCpu < $entry['min_cpu'] ? $elapsedCpu : $entry['min_cpu']; 166 $entry['max_cpu'] = $elapsedCpu > $entry['max_cpu'] ? $elapsedCpu : $entry['max_cpu']; 167 $entry['min_real'] = $elapsedReal < $entry['min_real'] ? $elapsedReal : $entry['min_real']; 168 $entry['max_real'] = $elapsedReal > $entry['max_real'] ? $elapsedReal : $entry['max_real']; 169 // Apply optional fields 170 $entry['overhead'] += $subcalls; 171 if ( $period ) { 172 $entry['periods'][] = $period; 173 } 174 } 175 176 /** 177 * Called by wfProfieIn() 178 * 179 * @param string $functionname 180 */ 181 public function profileIn( $functionname ) { 182 global $wgDebugFunctionEntry; 183 184 if ( $wgDebugFunctionEntry ) { 185 $this->debug( str_repeat( ' ', count( $this->mWorkStack ) ) . 186 'Entering ' . $functionname . "\n" ); 187 } 188 189 $this->mWorkStack[] = array( 190 $functionname, 191 count( $this->mWorkStack ), 192 $this->getTime( 'time' ), 193 $this->getTime( 'cpu' ), 194 memory_get_usage() 195 ); 196 } 197 198 /** 199 * Called by wfProfieOut() 200 * 201 * @param string $functionname 202 */ 203 public function profileOut( $functionname ) { 204 global $wgDebugFunctionEntry; 205 206 if ( $wgDebugFunctionEntry ) { 207 $this->debug( str_repeat( ' ', count( $this->mWorkStack ) - 1 ) . 208 'Exiting ' . $functionname . "\n" ); 209 } 210 211 $item = array_pop( $this->mWorkStack ); 212 list( $ofname, /* $ocount */, $ortime, $octime, $omem ) = $item; 213 214 if ( $item === null ) { 215 $this->debugGroup( 'profileerror', "Profiling error: $functionname" ); 216 } else { 217 if ( $functionname === 'close' ) { 218 if ( $ofname !== '-total' ) { 219 $message = "Profile section ended by close(): {$ofname}"; 220 $this->debugGroup( 'profileerror', $message ); 221 if ( $this->mCollateOnly ) { 222 $this->mCollated[$message] = $this->mErrorEntry; 223 } else { 224 $this->mStack[] = array( $message, 0, 0.0, 0.0, 0, 0.0, 0.0, 0 ); 225 } 226 } 227 $functionname = $ofname; 228 } elseif ( $ofname !== $functionname ) { 229 $message = "Profiling error: in({$ofname}), out($functionname)"; 230 $this->debugGroup( 'profileerror', $message ); 231 if ( $this->mCollateOnly ) { 232 $this->mCollated[$message] = $this->mErrorEntry; 233 } else { 234 $this->mStack[] = array( $message, 0, 0.0, 0.0, 0, 0.0, 0.0, 0 ); 235 } 236 } 237 $realTime = $this->getTime( 'wall' ); 238 $cpuTime = $this->getTime( 'cpu' ); 239 if ( $this->mCollateOnly ) { 240 $elapsedcpu = $cpuTime - $octime; 241 $elapsedreal = $realTime - $ortime; 242 $memchange = memory_get_usage() - $omem; 243 $this->updateEntry( $functionname, $elapsedcpu, $elapsedreal, $memchange ); 244 } else { 245 $this->mStack[] = array_merge( $item, 246 array( $realTime, $cpuTime, memory_get_usage() ) ); 247 } 248 $this->trxProfiler->recordFunctionCompletion( $functionname, $realTime - $ortime ); 249 } 250 } 251 252 /** 253 * Close opened profiling sections 254 */ 255 public function close() { 256 while ( count( $this->mWorkStack ) ) { 257 $this->profileOut( 'close' ); 258 } 259 } 260 261 /** 262 * Log the data to some store or even the page output 263 */ 264 public function logData() { 265 /* Implement in subclasses */ 266 } 267 268 /** 269 * Returns a profiling output to be stored in debug file 270 * 271 * @return string 272 */ 273 public function getOutput() { 274 global $wgDebugFunctionEntry, $wgProfileCallTree; 275 276 $wgDebugFunctionEntry = false; // hack 277 278 if ( !count( $this->mStack ) && !count( $this->mCollated ) ) { 279 return "No profiling output\n"; 280 } 281 282 if ( $wgProfileCallTree ) { 283 return $this->getCallTree(); 284 } else { 285 return $this->getFunctionReport(); 286 } 287 } 288 289 /** 290 * Returns a tree of function call instead of a list of functions 291 * @return string 292 */ 293 protected function getCallTree() { 294 return implode( '', array_map( 295 array( &$this, 'getCallTreeLine' ), $this->remapCallTree( $this->mStack ) 296 ) ); 297 } 298 299 /** 300 * Recursive function the format the current profiling array into a tree 301 * 302 * @param array $stack Profiling array 303 * @return array 304 */ 305 protected function remapCallTree( array $stack ) { 306 if ( count( $stack ) < 2 ) { 307 return $stack; 308 } 309 $outputs = array(); 310 for ( $max = count( $stack ) - 1; $max > 0; ) { 311 /* Find all items under this entry */ 312 $level = $stack[$max][1]; 313 $working = array(); 314 for ( $i = $max -1; $i >= 0; $i-- ) { 315 if ( $stack[$i][1] > $level ) { 316 $working[] = $stack[$i]; 317 } else { 318 break; 319 } 320 } 321 $working = $this->remapCallTree( array_reverse( $working ) ); 322 $output = array(); 323 foreach ( $working as $item ) { 324 array_push( $output, $item ); 325 } 326 array_unshift( $output, $stack[$max] ); 327 $max = $i; 328 329 array_unshift( $outputs, $output ); 330 } 331 $final = array(); 332 foreach ( $outputs as $output ) { 333 foreach ( $output as $item ) { 334 $final[] = $item; 335 } 336 } 337 return $final; 338 } 339 340 /** 341 * Callback to get a formatted line for the call tree 342 * @param array $entry 343 * @return string 344 */ 345 protected function getCallTreeLine( $entry ) { 346 list( $fname, $level, $startreal, , , $endreal ) = $entry; 347 $delta = $endreal - $startreal; 348 $space = str_repeat( ' ', $level ); 349 # The ugly double sprintf is to work around a PHP bug, 350 # which has been fixed in recent releases. 351 return sprintf( "%10s %s %s\n", 352 trim( sprintf( "%7.3f", $delta * 1000.0 ) ), $space, $fname ); 353 } 354 355 /** 356 * Populate mCollated 357 */ 358 protected function collateData() { 359 if ( $this->mCollateDone ) { 360 return; 361 } 362 $this->mCollateDone = true; 363 $this->close(); // set "-total" entry 364 365 if ( $this->mCollateOnly ) { 366 return; // already collated as methods exited 367 } 368 369 $this->mCollated = array(); 370 371 # Estimate profiling overhead 372 $profileCount = count( $this->mStack ); 373 self::calculateOverhead( $profileCount ); 374 375 # First, subtract the overhead! 376 $overheadTotal = $overheadMemory = $overheadInternal = array(); 377 foreach ( $this->mStack as $entry ) { 378 // $entry is (name,pos,rtime0,cputime0,mem0,rtime1,cputime1,mem1) 379 $fname = $entry[0]; 380 $elapsed = $entry[5] - $entry[2]; 381 $memchange = $entry[7] - $entry[4]; 382 383 if ( $fname === '-overhead-total' ) { 384 $overheadTotal[] = $elapsed; 385 $overheadMemory[] = max( 0, $memchange ); 386 } elseif ( $fname === '-overhead-internal' ) { 387 $overheadInternal[] = $elapsed; 388 } 389 } 390 $overheadTotal = $overheadTotal ? 391 array_sum( $overheadTotal ) / count( $overheadInternal ) : 0; 392 $overheadMemory = $overheadMemory ? 393 array_sum( $overheadMemory ) / count( $overheadInternal ) : 0; 394 $overheadInternal = $overheadInternal ? 395 array_sum( $overheadInternal ) / count( $overheadInternal ) : 0; 396 397 # Collate 398 foreach ( $this->mStack as $index => $entry ) { 399 // $entry is (name,pos,rtime0,cputime0,mem0,rtime1,cputime1,mem1) 400 $fname = $entry[0]; 401 $elapsedCpu = $entry[6] - $entry[3]; 402 $elapsedReal = $entry[5] - $entry[2]; 403 $memchange = $entry[7] - $entry[4]; 404 $subcalls = $this->calltreeCount( $this->mStack, $index ); 405 406 if ( substr( $fname, 0, 9 ) !== '-overhead' ) { 407 # Adjust for profiling overhead (except special values with elapsed=0 408 if ( $elapsed ) { 409 $elapsed -= $overheadInternal; 410 $elapsed -= ( $subcalls * $overheadTotal ); 411 $memchange -= ( $subcalls * $overheadMemory ); 412 } 413 } 414 415 $period = array( 'start' => $entry[2], 'end' => $entry[5], 416 'memory' => $memchange, 'subcalls' => $subcalls ); 417 $this->updateEntry( $fname, $elapsedCpu, $elapsedReal, $memchange, $subcalls, $period ); 418 } 419 420 $this->mCollated['-overhead-total']['count'] = $profileCount; 421 arsort( $this->mCollated, SORT_NUMERIC ); 422 } 423 424 /** 425 * Returns a list of profiled functions. 426 * 427 * @return string 428 */ 429 protected function getFunctionReport() { 430 $this->collateData(); 431 432 $width = 140; 433 $nameWidth = $width - 65; 434 $format = "%-{$nameWidth}s %6d %13.3f %13.3f %13.3f%% %9d (%13.3f -%13.3f) [%d]\n"; 435 $titleFormat = "%-{$nameWidth}s %6s %13s %13s %13s %9s\n"; 436 $prof = "\nProfiling data\n"; 437 $prof .= sprintf( $titleFormat, 'Name', 'Calls', 'Total', 'Each', '%', 'Mem' ); 438 439 $total = isset( $this->mCollated['-total'] ) 440 ? $this->mCollated['-total']['real'] 441 : 0; 442 443 foreach ( $this->mCollated as $fname => $data ) { 444 $calls = $data['count']; 445 $percent = $total ? 100 * $data['real'] / $total : 0; 446 $memory = $data['memory']; 447 $prof .= sprintf( $format, 448 substr( $fname, 0, $nameWidth ), 449 $calls, 450 (float)( $data['real'] * 1000 ), 451 (float)( $data['real'] * 1000 ) / $calls, 452 $percent, 453 $memory, 454 ( $data['min_real'] * 1000.0 ), 455 ( $data['max_real'] * 1000.0 ), 456 $data['overhead'] 457 ); 458 } 459 $prof .= "\nTotal: $total\n\n"; 460 461 return $prof; 462 } 463 464 /** 465 * @return array 466 */ 467 public function getRawData() { 468 // This method is called before shutdown in the footer method on Skins. 469 // If some outer methods have not yet called wfProfileOut(), work around 470 // that by clearing anything in the work stack to just the "-total" entry. 471 // Collate after doing this so the results do not include profile errors. 472 if ( count( $this->mWorkStack ) > 1 ) { 473 $oldWorkStack = $this->mWorkStack; 474 $this->mWorkStack = array( $this->mWorkStack[0] ); // just the "-total" one 475 } else { 476 $oldWorkStack = null; 477 } 478 $this->collateData(); 479 // If this trick is used, then the old work stack is swapped back afterwards 480 // and mCollateDone is reset to false. This means that logData() will still 481 // make use of all the method data since the missing wfProfileOut() calls 482 // should be made by the time it is called. 483 if ( $oldWorkStack ) { 484 $this->mWorkStack = $oldWorkStack; 485 $this->mCollateDone = false; 486 } 487 488 $total = isset( $this->mCollated['-total'] ) 489 ? $this->mCollated['-total']['real'] 490 : 0; 491 492 $profile = array(); 493 foreach ( $this->mCollated as $fname => $data ) { 494 $periods = array(); 495 foreach ( $data['periods'] as $period ) { 496 $period['start'] *= 1000; 497 $period['end'] *= 1000; 498 $periods[] = $period; 499 } 500 $profile[] = array( 501 'name' => $fname, 502 'calls' => $data['count'], 503 'elapsed' => $data['real'] * 1000, 504 'percent' => $total ? 100 * $data['real'] / $total : 0, 505 'memory' => $data['memory'], 506 'min' => $data['min_real'] * 1000, 507 'max' => $data['max_real'] * 1000, 508 'overhead' => $data['overhead'], 509 'periods' => $periods 510 ); 511 } 512 513 return $profile; 514 } 515 516 /** 517 * Dummy calls to wfProfileIn/wfProfileOut to calculate its overhead 518 * @param int $profileCount 519 */ 520 protected static function calculateOverhead( $profileCount ) { 521 wfProfileIn( '-overhead-total' ); 522 for ( $i = 0; $i < $profileCount; $i++ ) { 523 wfProfileIn( '-overhead-internal' ); 524 wfProfileOut( '-overhead-internal' ); 525 } 526 wfProfileOut( '-overhead-total' ); 527 } 528 529 /** 530 * Counts the number of profiled function calls sitting under 531 * the given point in the call graph. Not the most efficient algo. 532 * 533 * @param array $stack 534 * @param int $start 535 * @return int 536 */ 537 protected function calltreeCount( $stack, $start ) { 538 $level = $stack[$start][1]; 539 $count = 0; 540 for ( $i = $start -1; $i >= 0 && $stack[$i][1] > $level; $i-- ) { 541 $count ++; 542 } 543 return $count; 544 } 545 546 /** 547 * Get the content type sent out to the client. 548 * Used for profilers that output instead of store data. 549 * @return string 550 */ 551 protected function getContentType() { 552 foreach ( headers_list() as $header ) { 553 if ( preg_match( '#^content-type: (\w+/\w+);?#i', $header, $m ) ) { 554 return $m[1]; 555 } 556 } 557 return null; 558 } 559 }
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 |