PageRenderTime 39ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/phase3/includes/profiler/Profiler.php

https://github.com/ChuguluGames/mediawiki-svn
PHP | 493 lines | 325 code | 58 blank | 110 comment | 52 complexity | aa81bca63e919779546664ba2cf6ca1c MD5 | raw file
  1. <?php
  2. /**
  3. * @defgroup Profiler Profiler
  4. *
  5. * @file
  6. * @ingroup Profiler
  7. * This file is only included if profiling is enabled
  8. */
  9. /**
  10. * Begin profiling of a function
  11. * @param $functionname String: name of the function we will profile
  12. */
  13. function wfProfileIn( $functionname ) {
  14. global $wgProfiler;
  15. if ( isset( $wgProfiler['class'] ) ) {
  16. Profiler::instance()->profileIn( $functionname );
  17. }
  18. }
  19. /**
  20. * Stop profiling of a function
  21. * @param $functionname String: name of the function we have profiled
  22. */
  23. function wfProfileOut( $functionname = 'missing' ) {
  24. global $wgProfiler;
  25. if ( isset( $wgProfiler['class'] ) ) {
  26. Profiler::instance()->profileOut( $functionname );
  27. }
  28. }
  29. /**
  30. * @ingroup Profiler
  31. * @todo document
  32. */
  33. class Profiler {
  34. protected $mStack = array(), $mWorkStack = array (), $mCollated = array (),
  35. $mCalls = array (), $mTotals = array ();
  36. protected $mTimeMetric = 'wall';
  37. protected $mProfileID = false, $mCollateDone = false, $mTemplated = false;
  38. private static $__instance = null;
  39. function __construct( $params ) {
  40. // Push an entry for the pre-profile setup time onto the stack
  41. global $wgRequestTime;
  42. if ( !empty( $wgRequestTime ) ) {
  43. $this->mWorkStack[] = array( '-total', 0, $wgRequestTime, 0 );
  44. $this->mStack[] = array( '-setup', 1, $wgRequestTime, 0, microtime(true), 0 );
  45. } else {
  46. $this->profileIn( '-total' );
  47. }
  48. if ( isset( $params['timeMetric'] ) ) {
  49. $this->mTimeMetric = $params['timeMetric'];
  50. }
  51. if ( isset( $params['profileID'] ) ) {
  52. $this->mProfileID = $params['profileID'];
  53. }
  54. }
  55. /**
  56. * Singleton
  57. * @return Profiler
  58. */
  59. public static function instance() {
  60. if( is_null( self::$__instance ) ) {
  61. global $wgProfiler;
  62. if( is_array( $wgProfiler ) ) {
  63. if( !isset( $wgProfiler['class'] ) ) {
  64. wfDebug( __METHOD__ . " called without \$wgProfiler['class']"
  65. . " set, falling back to ProfilerStub for safety\n" );
  66. $class = 'ProfilerStub';
  67. } else {
  68. $class = $wgProfiler['class'];
  69. }
  70. self::$__instance = new $class( $wgProfiler );
  71. } elseif( $wgProfiler instanceof Profiler ) {
  72. self::$__instance = $wgProfiler; // back-compat
  73. } else {
  74. wfDebug( __METHOD__ . ' called with bogus $wgProfiler setting,'
  75. . " falling back to ProfilerStub for safety\n" );
  76. self::$__instance = new ProfilerStub( $wgProfiler );
  77. }
  78. }
  79. return self::$__instance;
  80. }
  81. /**
  82. * Set the profiler to a specific profiler instance. Mostly for dumpHTML
  83. * @param $p Profiler object
  84. */
  85. public static function setInstance( Profiler $p ) {
  86. self::$__instance = $p;
  87. }
  88. /**
  89. * Return whether this a stub profiler
  90. *
  91. * @return Boolean
  92. */
  93. public function isStub() {
  94. return false;
  95. }
  96. public function setProfileID( $id ) {
  97. $this->mProfileID = $id;
  98. }
  99. public function getProfileID() {
  100. if ( $this->mProfileID === false ) {
  101. return wfWikiID();
  102. } else {
  103. return $this->mProfileID;
  104. }
  105. }
  106. /**
  107. * Called by wfProfieIn()
  108. *
  109. * @param $functionname String
  110. */
  111. public function profileIn( $functionname ) {
  112. global $wgDebugFunctionEntry;
  113. if( $wgDebugFunctionEntry ){
  114. $this->debug( str_repeat( ' ', count( $this->mWorkStack ) ) . 'Entering ' . $functionname . "\n" );
  115. }
  116. $this->mWorkStack[] = array( $functionname, count( $this->mWorkStack ), $this->getTime(), memory_get_usage() );
  117. }
  118. /**
  119. * Called by wfProfieOut()
  120. *
  121. * @param $functionname String
  122. */
  123. public function profileOut( $functionname ) {
  124. global $wgDebugFunctionEntry;
  125. $memory = memory_get_usage();
  126. $time = $this->getTime();
  127. if( $wgDebugFunctionEntry ){
  128. $this->debug( str_repeat( ' ', count( $this->mWorkStack ) - 1 ) . 'Exiting ' . $functionname . "\n" );
  129. }
  130. $bit = array_pop($this->mWorkStack);
  131. if (!$bit) {
  132. $this->debug("Profiling error, !\$bit: $functionname\n");
  133. } else {
  134. //if( $wgDebugProfiling ){
  135. if( $functionname == 'close' ){
  136. $message = "Profile section ended by close(): {$bit[0]}";
  137. $this->debug( "$message\n" );
  138. $this->mStack[] = array( $message, 0, '0 0', 0, '0 0', 0 );
  139. }
  140. elseif( $bit[0] != $functionname ){
  141. $message = "Profiling error: in({$bit[0]}), out($functionname)";
  142. $this->debug( "$message\n" );
  143. $this->mStack[] = array( $message, 0, '0 0', 0, '0 0', 0 );
  144. }
  145. //}
  146. $bit[] = $time;
  147. $bit[] = $memory;
  148. $this->mStack[] = $bit;
  149. }
  150. }
  151. /**
  152. * Close opened profiling sections
  153. */
  154. public function close() {
  155. while( count( $this->mWorkStack ) ){
  156. $this->profileOut( 'close' );
  157. }
  158. }
  159. /**
  160. * Mark this call as templated or not
  161. *
  162. * @param $t Boolean
  163. */
  164. function setTemplated( $t ) {
  165. $this->mTemplated = $t;
  166. }
  167. /**
  168. * Returns a profiling output to be stored in debug file
  169. *
  170. * @return String
  171. */
  172. public function getOutput() {
  173. global $wgDebugFunctionEntry, $wgProfileCallTree;
  174. $wgDebugFunctionEntry = false;
  175. if( !count( $this->mStack ) && !count( $this->mCollated ) ){
  176. return "No profiling output\n";
  177. }
  178. if( $wgProfileCallTree ) {
  179. return $this->getCallTree();
  180. } else {
  181. return $this->getFunctionReport();
  182. }
  183. }
  184. /**
  185. * Returns a tree of function call instead of a list of functions
  186. */
  187. function getCallTree() {
  188. return implode( '', array_map( array( &$this, 'getCallTreeLine' ), $this->remapCallTree( $this->mStack ) ) );
  189. }
  190. /**
  191. * Recursive function the format the current profiling array into a tree
  192. *
  193. * @param $stack profiling array
  194. */
  195. function remapCallTree( $stack ) {
  196. if( count( $stack ) < 2 ){
  197. return $stack;
  198. }
  199. $outputs = array ();
  200. for( $max = count( $stack ) - 1; $max > 0; ){
  201. /* Find all items under this entry */
  202. $level = $stack[$max][1];
  203. $working = array ();
  204. for( $i = $max -1; $i >= 0; $i-- ){
  205. if( $stack[$i][1] > $level ){
  206. $working[] = $stack[$i];
  207. } else {
  208. break;
  209. }
  210. }
  211. $working = $this->remapCallTree( array_reverse( $working ) );
  212. $output = array();
  213. foreach( $working as $item ){
  214. array_push( $output, $item );
  215. }
  216. array_unshift( $output, $stack[$max] );
  217. $max = $i;
  218. array_unshift( $outputs, $output );
  219. }
  220. $final = array();
  221. foreach( $outputs as $output ){
  222. foreach( $output as $item ){
  223. $final[] = $item;
  224. }
  225. }
  226. return $final;
  227. }
  228. /**
  229. * Callback to get a formatted line for the call tree
  230. */
  231. function getCallTreeLine( $entry ) {
  232. list( $fname, $level, $start, /* $x */, $end) = $entry;
  233. $delta = $end - $start;
  234. $space = str_repeat(' ', $level);
  235. # The ugly double sprintf is to work around a PHP bug,
  236. # which has been fixed in recent releases.
  237. return sprintf( "%10s %s %s\n", trim( sprintf( "%7.3f", $delta * 1000.0 ) ), $space, $fname );
  238. }
  239. function getTime() {
  240. if ( $this->mTimeMetric === 'user' ) {
  241. return $this->getUserTime();
  242. } else {
  243. return microtime(true);
  244. }
  245. }
  246. function getUserTime() {
  247. $ru = getrusage();
  248. return $ru['ru_utime.tv_sec'].' '.$ru['ru_utime.tv_usec'] / 1e6;
  249. }
  250. protected function collateData() {
  251. if ( $this->mCollateDone ) {
  252. return;
  253. }
  254. $this->mCollateDone = true;
  255. $this->close();
  256. $this->mCollated = array();
  257. $this->mCalls = array();
  258. $this->mMemory = array();
  259. # Estimate profiling overhead
  260. $profileCount = count($this->mStack);
  261. self::calculateOverhead( $profileCount );
  262. # First, subtract the overhead!
  263. $overheadTotal = $overheadMemory = $overheadInternal = array();
  264. foreach( $this->mStack as $entry ){
  265. $fname = $entry[0];
  266. $start = $entry[2];
  267. $end = $entry[4];
  268. $elapsed = $end - $start;
  269. $memory = $entry[5] - $entry[3];
  270. if( $fname == '-overhead-total' ){
  271. $overheadTotal[] = $elapsed;
  272. $overheadMemory[] = $memory;
  273. }
  274. elseif( $fname == '-overhead-internal' ){
  275. $overheadInternal[] = $elapsed;
  276. }
  277. }
  278. $overheadTotal = $overheadTotal ? array_sum( $overheadTotal ) / count( $overheadInternal ) : 0;
  279. $overheadMemory = $overheadMemory ? array_sum( $overheadMemory ) / count( $overheadInternal ) : 0;
  280. $overheadInternal = $overheadInternal ? array_sum( $overheadInternal ) / count( $overheadInternal ) : 0;
  281. # Collate
  282. foreach( $this->mStack as $index => $entry ){
  283. $fname = $entry[0];
  284. $start = $entry[2];
  285. $end = $entry[4];
  286. $elapsed = $end - $start;
  287. $memory = $entry[5] - $entry[3];
  288. $subcalls = $this->calltreeCount( $this->mStack, $index );
  289. if( !preg_match( '/^-overhead/', $fname ) ){
  290. # Adjust for profiling overhead (except special values with elapsed=0
  291. if( $elapsed ) {
  292. $elapsed -= $overheadInternal;
  293. $elapsed -= ($subcalls * $overheadTotal);
  294. $memory -= ($subcalls * $overheadMemory);
  295. }
  296. }
  297. if( !array_key_exists( $fname, $this->mCollated ) ){
  298. $this->mCollated[$fname] = 0;
  299. $this->mCalls[$fname] = 0;
  300. $this->mMemory[$fname] = 0;
  301. $this->mMin[$fname] = 1 << 24;
  302. $this->mMax[$fname] = 0;
  303. $this->mOverhead[$fname] = 0;
  304. }
  305. $this->mCollated[$fname] += $elapsed;
  306. $this->mCalls[$fname]++;
  307. $this->mMemory[$fname] += $memory;
  308. $this->mMin[$fname] = min($this->mMin[$fname], $elapsed);
  309. $this->mMax[$fname] = max($this->mMax[$fname], $elapsed);
  310. $this->mOverhead[$fname] += $subcalls;
  311. }
  312. $this->mCalls['-overhead-total'] = $profileCount;
  313. arsort( $this->mCollated, SORT_NUMERIC );
  314. }
  315. /**
  316. * Returns a list of profiled functions.
  317. * Also log it into the database if $wgProfileToDatabase is set to true.
  318. */
  319. function getFunctionReport() {
  320. $this->collateData();
  321. $width = 140;
  322. $nameWidth = $width - 65;
  323. $format = "%-{$nameWidth}s %6d %13.3f %13.3f %13.3f%% %9d (%13.3f -%13.3f) [%d]\n";
  324. $titleFormat = "%-{$nameWidth}s %6s %13s %13s %13s %9s\n";
  325. $prof = "\nProfiling data\n";
  326. $prof .= sprintf( $titleFormat, 'Name', 'Calls', 'Total', 'Each', '%', 'Mem' );
  327. $total = isset( $this->mCollated['-total'] ) ? $this->mCollated['-total'] : 0;
  328. foreach( $this->mCollated as $fname => $elapsed ){
  329. $calls = $this->mCalls[$fname];
  330. $percent = $total ? 100. * $elapsed / $total : 0;
  331. $memory = $this->mMemory[$fname];
  332. $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]);
  333. }
  334. $prof .= "\nTotal: $total\n\n";
  335. return $prof;
  336. }
  337. /**
  338. * Dummy calls to wfProfileIn/wfProfileOut to calculate its overhead
  339. */
  340. protected static function calculateOverhead( $profileCount ) {
  341. wfProfileIn( '-overhead-total' );
  342. for( $i = 0; $i < $profileCount; $i++ ){
  343. wfProfileIn( '-overhead-internal' );
  344. wfProfileOut( '-overhead-internal' );
  345. }
  346. wfProfileOut( '-overhead-total' );
  347. }
  348. /**
  349. * Counts the number of profiled function calls sitting under
  350. * the given point in the call graph. Not the most efficient algo.
  351. *
  352. * @param $stack Array:
  353. * @param $start Integer:
  354. * @return Integer
  355. * @private
  356. */
  357. function calltreeCount($stack, $start) {
  358. $level = $stack[$start][1];
  359. $count = 0;
  360. for ($i = $start -1; $i >= 0 && $stack[$i][1] > $level; $i --) {
  361. $count ++;
  362. }
  363. return $count;
  364. }
  365. /**
  366. * Log the whole profiling data into the database.
  367. */
  368. public function logData(){
  369. global $wgProfilePerHost, $wgProfileToDatabase;
  370. # Do not log anything if database is readonly (bug 5375)
  371. if( wfReadOnly() || !$wgProfileToDatabase ) {
  372. return;
  373. }
  374. $dbw = wfGetDB( DB_MASTER );
  375. if( !is_object( $dbw ) ) {
  376. return;
  377. }
  378. $errorState = $dbw->ignoreErrors( true );
  379. if( $wgProfilePerHost ){
  380. $pfhost = wfHostname();
  381. } else {
  382. $pfhost = '';
  383. }
  384. $this->collateData();
  385. foreach( $this->mCollated as $name => $elapsed ){
  386. $eventCount = $this->mCalls[$name];
  387. $timeSum = (float) ($elapsed * 1000);
  388. $memorySum = (float)$this->mMemory[$name];
  389. $name = substr($name, 0, 255);
  390. // Kludge
  391. $timeSum = ($timeSum >= 0) ? $timeSum : 0;
  392. $memorySum = ($memorySum >= 0) ? $memorySum : 0;
  393. $dbw->update( 'profiling',
  394. array(
  395. "pf_count=pf_count+{$eventCount}",
  396. "pf_time=pf_time+{$timeSum}",
  397. "pf_memory=pf_memory+{$memorySum}",
  398. ),
  399. array(
  400. 'pf_name' => $name,
  401. 'pf_server' => $pfhost,
  402. ),
  403. __METHOD__ );
  404. $rc = $dbw->affectedRows();
  405. if ( $rc == 0 ) {
  406. $dbw->insert('profiling', array ('pf_name' => $name, 'pf_count' => $eventCount,
  407. 'pf_time' => $timeSum, 'pf_memory' => $memorySum, 'pf_server' => $pfhost ),
  408. __METHOD__, array ('IGNORE'));
  409. }
  410. // When we upgrade to mysql 4.1, the insert+update
  411. // can be merged into just a insert with this construct added:
  412. // "ON DUPLICATE KEY UPDATE ".
  413. // "pf_count=pf_count + VALUES(pf_count), ".
  414. // "pf_time=pf_time + VALUES(pf_time)";
  415. }
  416. $dbw->ignoreErrors( $errorState );
  417. }
  418. /**
  419. * Get the function name of the current profiling section
  420. */
  421. function getCurrentSection() {
  422. $elt = end( $this->mWorkStack );
  423. return $elt[0];
  424. }
  425. /**
  426. * Add an entry in the debug log file
  427. *
  428. * @param $s String to output
  429. */
  430. function debug( $s ) {
  431. if( defined( 'MW_COMPILED' ) || function_exists( 'wfDebug' ) ) {
  432. wfDebug( $s );
  433. }
  434. }
  435. }