PageRenderTime 50ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/xhprof/xhprof_moodle.php

https://bitbucket.org/kudutest1/moodlegit
PHP | 584 lines | 340 code | 93 blank | 151 comment | 57 complexity | db7168572820bd9f7e9d152ae1cbc9f4 MD5 | raw file
  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * @package core
  18. * @subpackage profiling
  19. * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
  20. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  21. */
  22. defined('MOODLE_INTERNAL') || die();
  23. // need some stuff from xhprof
  24. require_once($CFG->libdir . '/xhprof/xhprof_lib/utils/xhprof_lib.php');
  25. require_once($CFG->libdir . '/xhprof/xhprof_lib/utils/xhprof_runs.php');
  26. // need some stuff from moodle
  27. require_once($CFG->libdir.'/tablelib.php');
  28. // TODO: Change the implementation below to proper profiling class
  29. /**
  30. * Returns if profiling is running, optionally setting it
  31. */
  32. function profiling_is_running($value = null) {
  33. static $running = null;
  34. if (!is_null($value)) {
  35. $running = (bool)$value;
  36. }
  37. return $running;
  38. }
  39. /**
  40. * Returns if profiling has been saved, optionally setting it
  41. */
  42. function profiling_is_saved($value = null) {
  43. static $saved = null;
  44. if (!is_null($value)) {
  45. $saved = (bool)$value;
  46. }
  47. return $saved;
  48. }
  49. /**
  50. * Start profiling observing all the configuration
  51. */
  52. function profiling_start() {
  53. global $CFG, $SESSION, $SCRIPT;
  54. // If profiling isn't available, nothing to start
  55. if (!extension_loaded('xhprof') || !function_exists('xhprof_enable')) {
  56. return false;
  57. }
  58. // If profiling isn't enabled, nothing to start
  59. if (empty($CFG->profilingenabled) && empty($CFG->earlyprofilingenabled)) {
  60. return false;
  61. }
  62. // If profiling is already running or saved, nothing to start
  63. if (profiling_is_running() || profiling_is_saved()) {
  64. return false;
  65. }
  66. // Set script (from global if available, else our own)
  67. $script = !empty($SCRIPT) ? $SCRIPT : profiling_get_script();
  68. // Get PGC variables
  69. $check = 'PROFILEME';
  70. $profileme = isset($_POST[$check]) || isset($_GET[$check]) || isset($_COOKIE[$check]) ? true : false;
  71. $profileme = $profileme && !empty($CFG->profilingallowme);
  72. $check = 'DONTPROFILEME';
  73. $dontprofileme = isset($_POST[$check]) || isset($_GET[$check]) || isset($_COOKIE[$check]) ? true : false;
  74. $dontprofileme = $dontprofileme && !empty($CFG->profilingallowme);
  75. $check = 'PROFILEALL';
  76. $profileall = isset($_POST[$check]) || isset($_GET[$check]) || isset($_COOKIE[$check]) ? true : false;
  77. $profileall = $profileall && !empty($CFG->profilingallowall);
  78. $check = 'PROFILEALLSTOP';
  79. $profileallstop = isset($_POST[$check]) || isset($_GET[$check]) || isset($_COOKIE[$check]) ? true : false;
  80. $profileallstop = $profileallstop && !empty($CFG->profilingallowall);
  81. // DONTPROFILEME detected, nothing to start
  82. if ($dontprofileme) {
  83. return false;
  84. }
  85. // PROFILEALLSTOP detected, clean the mark in seesion and continue
  86. if ($profileallstop && !empty($SESSION)) {
  87. unset($SESSION->profileall);
  88. }
  89. // PROFILEALL detected, set the mark in session and continue
  90. if ($profileall && !empty($SESSION)) {
  91. $SESSION->profileall = true;
  92. // SESSION->profileall detected, set $profileall
  93. } else if (!empty($SESSION->profileall)) {
  94. $profileall = true;
  95. }
  96. // Evaluate automatic (random) profiling if necessary
  97. $profileauto = false;
  98. if (!empty($CFG->profilingautofrec)) {
  99. $profileauto = (mt_rand(1, $CFG->profilingautofrec) === 1);
  100. }
  101. // See if the $script matches any of the included patterns
  102. $included = empty($CFG->profilingincluded) ? '' : $CFG->profilingincluded;
  103. $profileincluded = profiling_string_matches($script, $included);
  104. // See if the $script matches any of the excluded patterns
  105. $excluded = empty($CFG->profilingexcluded) ? '' : $CFG->profilingexcluded;
  106. $profileexcluded = profiling_string_matches($script, $excluded);
  107. // Decide if profile auto must happen (observe matchings)
  108. $profileauto = $profileauto && $profileincluded && !$profileexcluded;
  109. // Decide if profile by match must happen (only if profileauto is disabled)
  110. $profilematch = $profileincluded && !$profileexcluded && empty($CFG->profilingautofrec);
  111. // If not auto, me, all, match have been detected, nothing to do
  112. if (!$profileauto && !$profileme && !$profileall && !$profilematch) {
  113. return false;
  114. }
  115. // Arrived here, the script is going to be profiled, let's do it
  116. $ignore = array('call_user_func', 'call_user_func_array');
  117. xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY, array('ignored_functions' => $ignore));
  118. profiling_is_running(true);
  119. // Started, return true
  120. return true;
  121. }
  122. /**
  123. * Stop profiling, gathering results and storing them
  124. */
  125. function profiling_stop() {
  126. global $CFG, $DB, $SCRIPT;
  127. // If profiling isn't available, nothing to stop
  128. if (!extension_loaded('xhprof') || !function_exists('xhprof_enable')) {
  129. return false;
  130. }
  131. // If profiling isn't enabled, nothing to stop
  132. if (empty($CFG->profilingenabled) && empty($CFG->earlyprofilingenabled)) {
  133. return false;
  134. }
  135. // If profiling is not running or is already saved, nothing to stop
  136. if (!profiling_is_running() || profiling_is_saved()) {
  137. return false;
  138. }
  139. // Set script (from global if available, else our own)
  140. $script = !empty($SCRIPT) ? $SCRIPT : profiling_get_script();
  141. // Arrived here, profiling is running, stop and save everything
  142. profiling_is_running(false);
  143. $data = xhprof_disable();
  144. // We only save the run after ensuring the DB table exists
  145. // (this prevents problems with profiling runs enabled in
  146. // config.php before Moodle is installed. Rare but...
  147. $tables = $DB->get_tables();
  148. if (!in_array('profiling', $tables)) {
  149. return false;
  150. }
  151. $run = new moodle_xhprofrun();
  152. $run->prepare_run($script);
  153. $runid = $run->save_run($data, null);
  154. profiling_is_saved(true);
  155. // Prune old runs
  156. profiling_prune_old_runs($runid);
  157. // Finished, return true
  158. return true;
  159. }
  160. function profiling_prune_old_runs($exception = 0) {
  161. global $CFG, $DB;
  162. // Setting to 0 = no prune
  163. if (empty($CFG->profilinglifetime)) {
  164. return;
  165. }
  166. $cuttime = time() - ($CFG->profilinglifetime * 60);
  167. $params = array('cuttime' => $cuttime, 'exception' => $exception);
  168. $DB->delete_records_select('profiling', 'runreference = 0 AND
  169. timecreated < :cuttime AND
  170. runid != :exception', $params);
  171. }
  172. /**
  173. * Returns the path to the php script being requested
  174. *
  175. * Note this function is a partial copy of initialise_fullme() and
  176. * setup_get_remote_url(), in charge of setting $FULLME, $SCRIPT and
  177. * friends. To be used by early profiling runs in situations where
  178. * $SCRIPT isn't defined yet
  179. *
  180. * @return string absolute path (wwwroot based) of the script being executed
  181. */
  182. function profiling_get_script() {
  183. global $CFG;
  184. $wwwroot = parse_url($CFG->wwwroot);
  185. if (!isset($wwwroot['path'])) {
  186. $wwwroot['path'] = '';
  187. }
  188. $wwwroot['path'] .= '/';
  189. $path = $_SERVER['SCRIPT_NAME'];
  190. if (strpos($path, $wwwroot['path']) === 0) {
  191. return substr($path, strlen($wwwroot['path']) - 1);
  192. }
  193. return '';
  194. }
  195. function profiling_urls($report, $runid, $runid2 = null) {
  196. global $CFG;
  197. $url = '';
  198. switch ($report) {
  199. case 'run':
  200. $url = $CFG->wwwroot . '/lib/xhprof/xhprof_html/index.php?run=' . $runid;
  201. break;
  202. case 'diff':
  203. $url = $CFG->wwwroot . '/lib/xhprof/xhprof_html/index.php?run1=' . $runid . '&amp;run2=' . $runid2;
  204. break;
  205. case 'graph':
  206. $url = $CFG->wwwroot . '/lib/xhprof/xhprof_html/callgraph.php?run=' . $runid;
  207. break;
  208. }
  209. return $url;
  210. }
  211. function profiling_print_run($run, $prevrunid = null) {
  212. global $CFG, $OUTPUT;
  213. $output = '';
  214. // Prepare the runreference/runcomment form
  215. $checked = $run->runreference ? ' checked=checked' : '';
  216. $referenceform = "<form id=\"profiling_runreference\" action=\"index.php\" method=\"GET\">" .
  217. "<input type=\"hidden\" name=\"sesskey\" value=\"" . sesskey() . "\"/>".
  218. "<input type=\"hidden\" name=\"runid\" value=\"$run->runid\"/>".
  219. "<input type=\"hidden\" name=\"listurl\" value=\"$run->url\"/>".
  220. "<input type=\"checkbox\" name=\"runreference\" value=\"1\"$checked/>&nbsp;".
  221. "<input type=\"text\" name=\"runcomment\" value=\"$run->runcomment\"/>&nbsp;".
  222. "<input type=\"submit\" value=\"" . get_string('savechanges') ."\"/>".
  223. "</form>";
  224. $table = new html_table();
  225. $table->align = array('right', 'left');
  226. $table->tablealign = 'center';
  227. $table->attributes['class'] = 'profilingruntable';
  228. $table->colclasses = array('label', 'value');
  229. $table->data = array(
  230. array(get_string('runid', 'tool_profiling'), $run->runid),
  231. array(get_string('url'), $run->url),
  232. array(get_string('date'), userdate($run->timecreated, '%d %B %Y, %H:%M')),
  233. array(get_string('executiontime', 'tool_profiling'), format_float($run->totalexecutiontime / 1000, 3) . ' ms'),
  234. array(get_string('cputime', 'tool_profiling'), format_float($run->totalcputime / 1000, 3) . ' ms'),
  235. array(get_string('calls', 'tool_profiling'), $run->totalcalls),
  236. array(get_string('memory', 'tool_profiling'), format_float($run->totalmemory / 1024, 0) . ' KB'),
  237. array(get_string('markreferencerun', 'tool_profiling'), $referenceform));
  238. $output = $OUTPUT->box(html_writer::table($table), 'generalbox boxwidthwide boxaligncenter profilingrunbox', 'profiling_summary', true);
  239. // Add link to details
  240. $strviewdetails = get_string('viewdetails', 'tool_profiling');
  241. $url = profiling_urls('run', $run->runid);
  242. $output.=$OUTPUT->heading('<a href="' . $url . '" onclick="javascript:window.open(' . "'" . $url . "'" . ');' .
  243. 'return false;"' . ' title="">' . $strviewdetails . '</a>', 3, 'main profilinglink');
  244. // If there is one previous run marked as reference, add link to diff
  245. if ($prevrunid) {
  246. $strviewdiff = get_string('viewdiff', 'tool_profiling');
  247. $url = 'index.php?runid=' . $run->runid . '&amp;runid2=' . $prevrunid . '&amp;listurl=' . urlencode($run->url);
  248. $output.=$OUTPUT->heading('<a href="' . $url . '" title="">' . $strviewdiff . '</a>', 3, 'main profilinglink');
  249. }
  250. return $output;
  251. }
  252. function profiling_print_rundiff($run1, $run2) {
  253. global $CFG, $OUTPUT;
  254. $output = '';
  255. // Prepare the reference/comment information
  256. $referencetext1 = ($run1->runreference ? get_string('yes') : get_string('no')) .
  257. ($run1->runcomment ? ' - ' . s($run1->runcomment) : '');
  258. $referencetext2 = ($run2->runreference ? get_string('yes') : get_string('no')) .
  259. ($run2->runcomment ? ' - ' . s($run2->runcomment) : '');
  260. // Calculate global differences
  261. $diffexecutiontime = profiling_get_difference($run1->totalexecutiontime, $run2->totalexecutiontime, 'ms', 1000);
  262. $diffcputime = profiling_get_difference($run1->totalcputime, $run2->totalcputime, 'ms', 1000);
  263. $diffcalls = profiling_get_difference($run1->totalcalls, $run2->totalcalls);
  264. $diffmemory = profiling_get_difference($run1->totalmemory, $run2->totalmemory, 'KB', 1024);
  265. $table = new html_table();
  266. $table->align = array('right', 'left', 'left', 'left');
  267. $table->tablealign = 'center';
  268. $table->attributes['class'] = 'profilingruntable';
  269. $table->colclasses = array('label', 'value1', 'value2');
  270. $table->data = array(
  271. array(get_string('runid', 'tool_profiling'),
  272. '<a href="index.php?runid=' . $run1->runid . '&listurl=' . urlencode($run1->url) . '" title="">' . $run1->runid . '</a>',
  273. '<a href="index.php?runid=' . $run2->runid . '&listurl=' . urlencode($run2->url) . '" title="">' . $run2->runid . '</a>'),
  274. array(get_string('url'), $run1->url, $run2->url),
  275. array(get_string('date'), userdate($run1->timecreated, '%d %B %Y, %H:%M'),
  276. userdate($run2->timecreated, '%d %B %Y, %H:%M')),
  277. array(get_string('executiontime', 'tool_profiling'),
  278. format_float($run1->totalexecutiontime / 1000, 3) . ' ms',
  279. format_float($run2->totalexecutiontime / 1000, 3) . ' ms ' . $diffexecutiontime),
  280. array(get_string('cputime', 'tool_profiling'),
  281. format_float($run1->totalcputime / 1000, 3) . ' ms',
  282. format_float($run2->totalcputime / 1000, 3) . ' ms ' . $diffcputime),
  283. array(get_string('calls', 'tool_profiling'), $run1->totalcalls, $run2->totalcalls . ' ' . $diffcalls),
  284. array(get_string('memory', 'tool_profiling'),
  285. format_float($run1->totalmemory / 1024, 0) . ' KB',
  286. format_float($run2->totalmemory / 1024, 0) . ' KB ' . $diffmemory),
  287. array(get_string('referencerun', 'tool_profiling'), $referencetext1, $referencetext2));
  288. $output = $OUTPUT->box(html_writer::table($table), 'generalbox boxwidthwide boxaligncenter profilingrunbox', 'profiling_summary', true);
  289. // Add link to details
  290. $strviewdetails = get_string('viewdiffdetails', 'tool_profiling');
  291. $url = profiling_urls('diff', $run1->runid, $run2->runid);
  292. //$url = $CFG->wwwroot . '/admin/tool/profiling/index.php?run=' . $run->runid;
  293. $output.=$OUTPUT->heading('<a href="' . $url . '" onclick="javascript:window.open(' . "'" . $url . "'" . ');' .
  294. 'return false;"' . ' title="">' . $strviewdetails . '</a>', 3, 'main profilinglink');
  295. return $output;
  296. }
  297. /**
  298. * Helper function that returns the HTML fragment to
  299. * be displayed on listing mode, it includes actions
  300. * like deletion/export/import...
  301. */
  302. function profiling_list_controls($listurl) {
  303. global $CFG, $OUTPUT;
  304. $output = '';
  305. return $output;
  306. }
  307. /**
  308. * Helper function that looks for matchings of one string
  309. * against an array of * wildchar patterns
  310. */
  311. function profiling_string_matches($string, $patterns) {
  312. $patterns = explode(',', $patterns);
  313. foreach ($patterns as $pattern) {
  314. // Trim and prepare pattern
  315. $pattern = str_replace('\*', '.*', preg_quote(trim($pattern), '~'));
  316. // Don't process empty patterns
  317. if (empty($pattern)) {
  318. continue;
  319. }
  320. if (preg_match('~' . $pattern . '~', $string)) {
  321. return true;
  322. }
  323. }
  324. return false;
  325. }
  326. /**
  327. * Helper function that, given to floats, returns their numerical
  328. * and percentual differences, propertly formated and cssstyled
  329. */
  330. function profiling_get_difference($number1, $number2, $units = '', $factor = 1, $numdec = 2) {
  331. $numdiff = $number2 - $number1;
  332. $perdiff = 0;
  333. if ($number1 != $number2) {
  334. $perdiff = $number1 != 0 ? ($number2 * 100 / $number1) - 100 : 0;
  335. }
  336. $sign = $number2 > $number1 ? '+' : '';
  337. $delta = abs($perdiff) > 0.25 ? '&Delta;' : '&asymp;';
  338. $spanclass = $number2 > $number1 ? 'worse' : ($number1 > $number2 ? 'better' : 'same');
  339. $importantclass= abs($perdiff) > 1 ? ' profiling_important' : '';
  340. $startspan = '<span class="profiling_' . $spanclass . $importantclass . '">';
  341. $endspan = '</span>';
  342. $fnumdiff = $sign . format_float($numdiff / $factor, $numdec);
  343. $fperdiff = $sign . format_float($perdiff, $numdec);
  344. return $startspan . $delta . ' ' . $fnumdiff . ' ' . $units . ' (' . $fperdiff . '%)' . $endspan;
  345. }
  346. /**
  347. * Custom implementation of iXHProfRuns
  348. *
  349. * This class is one implementation of the iXHProfRuns interface, in charge
  350. * of storing and retrieve profiling run data to/from DB (profiling table)
  351. *
  352. * The interface only defines two methods to be defined: get_run() and
  353. * save_run() we'll be implementing some more in order to keep all the
  354. * rest of information in our runs properly handled.
  355. */
  356. class moodle_xhprofrun implements iXHProfRuns {
  357. protected $runid = null;
  358. protected $url = null;
  359. protected $totalexecutiontime = 0;
  360. protected $totalcputime = 0;
  361. protected $totalcalls = 0;
  362. protected $totalmemory = 0;
  363. protected $timecreated = 0;
  364. public function __construct() {
  365. $this->timecreated = time();
  366. }
  367. /**
  368. * Given one runid and one type, return the run data
  369. * and some extra info in run_desc from DB
  370. *
  371. * Note that $type is completely ignored
  372. */
  373. public function get_run($run_id, $type, &$run_desc) {
  374. global $DB;
  375. $rec = $DB->get_record('profiling', array('runid' => $run_id), '*', MUST_EXIST);
  376. $this->runid = $rec->runid;
  377. $this->url = $rec->url;
  378. $this->totalexecutiontime = $rec->totalexecutiontime;
  379. $this->totalcputime = $rec->totalcputime;
  380. $this->totalcalls = $rec->totalcalls;
  381. $this->totalmemory = $rec->totalmemory;
  382. $this->timecreated = $rec->timecreated;
  383. $run_desc = $this->url . ($rec->runreference ? ' (R) ' : ' ') . ' - ' . s($rec->runcomment);
  384. return unserialize(base64_decode($rec->data));
  385. }
  386. /**
  387. * Given some run data, one type and, optionally, one runid
  388. * store the information in DB
  389. *
  390. * Note that $type is completely ignored
  391. */
  392. public function save_run($xhprof_data, $type, $run_id = null) {
  393. global $DB;
  394. if (is_null($this->url)) {
  395. xhprof_error("Warning: You must use the prepare_run() method before saving it");
  396. }
  397. // Calculate runid if needed
  398. $this->runid = is_null($run_id) ? md5($this->url . '-' . uniqid()) : $run_id;
  399. // Calculate totals
  400. $this->totalexecutiontime = $xhprof_data['main()']['wt'];
  401. $this->totalcputime = $xhprof_data['main()']['cpu'];
  402. $this->totalcalls = array_reduce($xhprof_data, array($this, 'sum_calls'));
  403. $this->totalmemory = $xhprof_data['main()']['mu'];
  404. // Prepare data
  405. $rec = new stdClass();
  406. $rec->runid = $this->runid;
  407. $rec->url = $this->url;
  408. $rec->data = base64_encode(serialize($xhprof_data));
  409. $rec->totalexecutiontime = $this->totalexecutiontime;
  410. $rec->totalcputime = $this->totalcputime;
  411. $rec->totalcalls = $this->totalcalls;
  412. $rec->totalmemory = $this->totalmemory;
  413. $rec->timecreated = $this->timecreated;
  414. $DB->insert_record('profiling', $rec);
  415. return $this->runid;
  416. }
  417. public function prepare_run($url) {
  418. $this->url = $url;
  419. }
  420. // Private API starts here
  421. protected function sum_calls($sum, $data) {
  422. return $sum + $data['ct'];
  423. }
  424. }
  425. /**
  426. * Simple subclass of {@link table_sql} that provides
  427. * some custom formatters for various columns, in order
  428. * to make the main profiles list nicer
  429. */
  430. class xhprof_table_sql extends table_sql {
  431. protected $listurlmode = false;
  432. /**
  433. * Get row classes to be applied based on row contents
  434. */
  435. function get_row_class($row) {
  436. return $row->runreference ? 'referencerun' : ''; // apply class to reference runs
  437. }
  438. /**
  439. * Define it the table is in listurlmode or not, output will
  440. * be different based on that
  441. */
  442. function set_listurlmode($listurlmode) {
  443. $this->listurlmode = $listurlmode;
  444. }
  445. /**
  446. * Format URL, so it points to last run for that url
  447. */
  448. protected function col_url($row) {
  449. global $OUTPUT;
  450. // Build the link to latest run for the script
  451. $scripturl = new moodle_url('/admin/tool/profiling/index.php', array('script' => $row->url, 'listurl' => $row->url));
  452. $scriptaction = $OUTPUT->action_link($scripturl, $row->url);
  453. // Decide, based on $this->listurlmode which actions to show
  454. if ($this->listurlmode) {
  455. $detailsaction = '';
  456. } else {
  457. // Build link icon to script details (pix + url + actionlink)
  458. $detailsimg = $OUTPUT->pix_icon('t/right', get_string('profilingfocusscript', 'tool_profiling', $row->url));
  459. $detailsurl = new moodle_url('/admin/tool/profiling/index.php', array('listurl' => $row->url));
  460. $detailsaction = $OUTPUT->action_link($detailsurl, $detailsimg);
  461. }
  462. return $scriptaction . '&nbsp;' . $detailsaction;
  463. }
  464. /**
  465. * Format profiling date, human and pointing to run
  466. */
  467. protected function col_timecreated($row) {
  468. global $OUTPUT;
  469. $fdate = userdate($row->timecreated, '%d %b %Y, %H:%M');
  470. $url = new moodle_url('/admin/tool/profiling/index.php', array('runid' => $row->runid, 'listurl' => $row->url));
  471. return $OUTPUT->action_link($url, $fdate);
  472. }
  473. /**
  474. * Format execution time
  475. */
  476. protected function col_totalexecutiontime($row) {
  477. return format_float($row->totalexecutiontime / 1000, 3) . ' ms';
  478. }
  479. /**
  480. * Format cpu time
  481. */
  482. protected function col_totalcputime($row) {
  483. return format_float($row->totalcputime / 1000, 3) . ' ms';
  484. }
  485. /**
  486. * Format memory
  487. */
  488. protected function col_totalmemory($row) {
  489. return format_float($row->totalmemory / 1024, 3) . ' KB';
  490. }
  491. }