PageRenderTime 47ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/WikiZam/includes/profiler/Profiler.php

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