[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/xhprof/ -> xhprof_moodle.php (source)

   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  /**
  18   * @package    core
  19   * @subpackage profiling
  20   * @copyright  2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
  21   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22   */
  23  
  24  defined('MOODLE_INTERNAL') || die();
  25  
  26  // Need some stuff from xhprof.
  27  require_once($CFG->libdir . '/xhprof/xhprof_lib/utils/xhprof_lib.php');
  28  require_once($CFG->libdir . '/xhprof/xhprof_lib/utils/xhprof_runs.php');
  29  // Need some stuff from moodle.
  30  require_once($CFG->libdir . '/tablelib.php');
  31  require_once($CFG->libdir . '/setuplib.php');
  32  require_once($CFG->libdir . '/phpunit/classes/util.php');
  33  require_once($CFG->dirroot . '/backup/util/xml/xml_writer.class.php');
  34  require_once($CFG->dirroot . '/backup/util/xml/output/xml_output.class.php');
  35  require_once($CFG->dirroot . '/backup/util/xml/output/file_xml_output.class.php');
  36  
  37  // TODO: Change the implementation below to proper profiling class.
  38  
  39  /**
  40   * Returns if profiling is running, optionally setting it
  41   */
  42  function profiling_is_running($value = null) {
  43      static $running = null;
  44  
  45      if (!is_null($value)) {
  46          $running = (bool)$value;
  47      }
  48  
  49      return $running;
  50  }
  51  
  52  /**
  53   * Returns if profiling has been saved, optionally setting it
  54   */
  55  function profiling_is_saved($value = null) {
  56      static $saved = null;
  57  
  58      if (!is_null($value)) {
  59          $saved = (bool)$value;
  60      }
  61  
  62      return $saved;
  63  }
  64  
  65  /**
  66   * Start profiling observing all the configuration
  67   */
  68  function profiling_start() {
  69      global $CFG, $SESSION, $SCRIPT;
  70  
  71      // If profiling isn't available, nothing to start
  72      if (!extension_loaded('xhprof') || !function_exists('xhprof_enable')) {
  73          return false;
  74      }
  75  
  76      // If profiling isn't enabled, nothing to start
  77      if (empty($CFG->profilingenabled) && empty($CFG->earlyprofilingenabled)) {
  78          return false;
  79      }
  80  
  81      // If profiling is already running or saved, nothing to start
  82      if (profiling_is_running() || profiling_is_saved()) {
  83          return false;
  84      }
  85  
  86      // Set script (from global if available, else our own)
  87      $script = !empty($SCRIPT) ? $SCRIPT : profiling_get_script();
  88  
  89      // Get PGC variables
  90      $check = 'PROFILEME';
  91      $profileme = isset($_POST[$check]) || isset($_GET[$check]) || isset($_COOKIE[$check]) ? true : false;
  92      $profileme = $profileme && !empty($CFG->profilingallowme);
  93      $check = 'DONTPROFILEME';
  94      $dontprofileme = isset($_POST[$check]) || isset($_GET[$check]) || isset($_COOKIE[$check]) ? true : false;
  95      $dontprofileme = $dontprofileme && !empty($CFG->profilingallowme);
  96      $check = 'PROFILEALL';
  97      $profileall = isset($_POST[$check]) || isset($_GET[$check]) || isset($_COOKIE[$check]) ? true : false;
  98      $profileall = $profileall && !empty($CFG->profilingallowall);
  99      $check = 'PROFILEALLSTOP';
 100      $profileallstop = isset($_POST[$check]) || isset($_GET[$check]) || isset($_COOKIE[$check]) ? true : false;
 101      $profileallstop = $profileallstop && !empty($CFG->profilingallowall);
 102  
 103      // DONTPROFILEME detected, nothing to start
 104      if ($dontprofileme) {
 105          return false;
 106      }
 107  
 108      // PROFILEALLSTOP detected, clean the mark in seesion and continue
 109      if ($profileallstop && !empty($SESSION)) {
 110          unset($SESSION->profileall);
 111      }
 112  
 113      // PROFILEALL detected, set the mark in session and continue
 114      if ($profileall && !empty($SESSION)) {
 115          $SESSION->profileall = true;
 116  
 117      // SESSION->profileall detected, set $profileall
 118      } else if (!empty($SESSION->profileall)) {
 119          $profileall = true;
 120      }
 121  
 122      // Evaluate automatic (random) profiling if necessary
 123      $profileauto = false;
 124      if (!empty($CFG->profilingautofrec)) {
 125          $profileauto = (mt_rand(1, $CFG->profilingautofrec) === 1);
 126      }
 127  
 128      // See if the $script matches any of the included patterns
 129      $included = empty($CFG->profilingincluded) ? '' : $CFG->profilingincluded;
 130      $profileincluded = profiling_string_matches($script, $included);
 131  
 132      // See if the $script matches any of the excluded patterns
 133      $excluded = empty($CFG->profilingexcluded) ? '' : $CFG->profilingexcluded;
 134      $profileexcluded = profiling_string_matches($script, $excluded);
 135  
 136      // Decide if profile auto must happen (observe matchings)
 137      $profileauto = $profileauto && $profileincluded && !$profileexcluded;
 138  
 139      // Decide if profile by match must happen (only if profileauto is disabled)
 140      $profilematch = $profileincluded && !$profileexcluded && empty($CFG->profilingautofrec);
 141  
 142      // If not auto, me, all, match have been detected, nothing to do
 143      if (!$profileauto && !$profileme && !$profileall && !$profilematch) {
 144          return false;
 145      }
 146  
 147      // Arrived here, the script is going to be profiled, let's do it
 148      $ignore = array('call_user_func', 'call_user_func_array');
 149      xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY, array('ignored_functions' =>  $ignore));
 150      profiling_is_running(true);
 151  
 152      // Started, return true
 153      return true;
 154  }
 155  
 156  /**
 157   * Stop profiling, gathering results and storing them
 158   */
 159  function profiling_stop() {
 160      global $CFG, $DB, $SCRIPT;
 161  
 162      // If profiling isn't available, nothing to stop
 163      if (!extension_loaded('xhprof') || !function_exists('xhprof_enable')) {
 164          return false;
 165      }
 166  
 167      // If profiling isn't enabled, nothing to stop
 168      if (empty($CFG->profilingenabled) && empty($CFG->earlyprofilingenabled)) {
 169          return false;
 170      }
 171  
 172      // If profiling is not running or is already saved, nothing to stop
 173      if (!profiling_is_running() || profiling_is_saved()) {
 174          return false;
 175      }
 176  
 177      // Set script (from global if available, else our own)
 178      $script = !empty($SCRIPT) ? $SCRIPT : profiling_get_script();
 179  
 180      // Arrived here, profiling is running, stop and save everything
 181      profiling_is_running(false);
 182      $data = xhprof_disable();
 183  
 184      // We only save the run after ensuring the DB table exists
 185      // (this prevents problems with profiling runs enabled in
 186      // config.php before Moodle is installed. Rare but...
 187      $tables = $DB->get_tables();
 188      if (!in_array('profiling', $tables)) {
 189          return false;
 190      }
 191  
 192      $run = new moodle_xhprofrun();
 193      $run->prepare_run($script);
 194      $runid = $run->save_run($data, null);
 195      profiling_is_saved(true);
 196  
 197      // Prune old runs
 198      profiling_prune_old_runs($runid);
 199  
 200      // Finished, return true
 201      return true;
 202  }
 203  
 204  function profiling_prune_old_runs($exception = 0) {
 205      global $CFG, $DB;
 206  
 207      // Setting to 0 = no prune
 208      if (empty($CFG->profilinglifetime)) {
 209          return;
 210      }
 211  
 212      $cuttime = time() - ($CFG->profilinglifetime * 60);
 213      $params = array('cuttime' => $cuttime, 'exception' => $exception);
 214  
 215      $DB->delete_records_select('profiling', 'runreference = 0 AND
 216                                               timecreated < :cuttime AND
 217                                               runid != :exception', $params);
 218  }
 219  
 220  /**
 221   * Returns the path to the php script being requested
 222   *
 223   * Note this function is a partial copy of initialise_fullme() and
 224   * setup_get_remote_url(), in charge of setting $FULLME, $SCRIPT and
 225   * friends. To be used by early profiling runs in situations where
 226   * $SCRIPT isn't defined yet
 227   *
 228   * @return string absolute path (wwwroot based) of the script being executed
 229   */
 230  function profiling_get_script() {
 231      global $CFG;
 232  
 233      $wwwroot = parse_url($CFG->wwwroot);
 234  
 235      if (!isset($wwwroot['path'])) {
 236          $wwwroot['path'] = '';
 237      }
 238      $wwwroot['path'] .= '/';
 239  
 240      $path = $_SERVER['SCRIPT_NAME'];
 241  
 242      if (strpos($path, $wwwroot['path']) === 0) {
 243          return substr($path, strlen($wwwroot['path']) - 1);
 244      }
 245      return '';
 246  }
 247  
 248  function profiling_urls($report, $runid, $runid2 = null) {
 249      global $CFG;
 250  
 251      $url = '';
 252      switch ($report) {
 253          case 'run':
 254              $url = $CFG->wwwroot . '/lib/xhprof/xhprof_html/index.php?run=' . $runid;
 255              break;
 256          case 'diff':
 257              $url = $CFG->wwwroot . '/lib/xhprof/xhprof_html/index.php?run1=' . $runid . '&amp;run2=' . $runid2;
 258              break;
 259          case 'graph':
 260              $url = $CFG->wwwroot . '/lib/xhprof/xhprof_html/callgraph.php?run=' . $runid;
 261              break;
 262      }
 263      return $url;
 264  }
 265  
 266  /**
 267   * Generate the output to print a profiling run including further actions you can then take.
 268   *
 269   * @param object $run The profiling run object we are going to display.
 270   * @param array $prevreferences A list of run objects to list as comparison targets.
 271   * @return string The output to display on the screen for this run.
 272   */
 273  function profiling_print_run($run, $prevreferences = null) {
 274      global $CFG, $OUTPUT;
 275  
 276      $output = '';
 277  
 278      // Prepare the runreference/runcomment form
 279      $checked = $run->runreference ? ' checked=checked' : '';
 280      $referenceform = "<form id=\"profiling_runreference\" action=\"index.php\" method=\"GET\">" .
 281                       "<input type=\"hidden\" name=\"sesskey\" value=\"" . sesskey() . "\"/>".
 282                       "<input type=\"hidden\" name=\"runid\" value=\"$run->runid\"/>".
 283                       "<input type=\"hidden\" name=\"listurl\" value=\"$run->url\"/>".
 284                       "<input type=\"checkbox\" name=\"runreference\" value=\"1\"$checked/>&nbsp;".
 285                       "<input type=\"text\" name=\"runcomment\" value=\"$run->runcomment\"/>&nbsp;".
 286                       "<input type=\"submit\" value=\"" . get_string('savechanges') ."\"/>".
 287                       "</form>";
 288  
 289      $table = new html_table();
 290      $table->align = array('right', 'left');
 291      $table->tablealign = 'center';
 292      $table->attributes['class'] = 'profilingruntable';
 293      $table->colclasses = array('label', 'value');
 294      $table->data = array(
 295         array(get_string('runid', 'tool_profiling'), $run->runid),
 296         array(get_string('url'), $run->url),
 297         array(get_string('date'), userdate($run->timecreated, '%d %B %Y, %H:%M')),
 298         array(get_string('executiontime', 'tool_profiling'), format_float($run->totalexecutiontime / 1000, 3) . ' ms'),
 299         array(get_string('cputime', 'tool_profiling'), format_float($run->totalcputime / 1000, 3) . ' ms'),
 300         array(get_string('calls', 'tool_profiling'), $run->totalcalls),
 301         array(get_string('memory', 'tool_profiling'), format_float($run->totalmemory / 1024, 0) . ' KB'),
 302         array(get_string('markreferencerun', 'tool_profiling'), $referenceform));
 303      $output = $OUTPUT->box(html_writer::table($table), 'generalbox boxwidthwide boxaligncenter profilingrunbox', 'profiling_summary');
 304      // Add link to details
 305      $strviewdetails = get_string('viewdetails', 'tool_profiling');
 306      $url = profiling_urls('run', $run->runid);
 307      $output .= $OUTPUT->heading('<a href="' . $url . '" onclick="javascript:window.open(' . "'" . $url . "'" . ');' .
 308                                  'return false;"' . ' title="">' . $strviewdetails . '</a>', 3, 'main profilinglink');
 309  
 310      // If there are previous run(s) marked as reference, add link to diff.
 311      if ($prevreferences) {
 312          $table = new html_table();
 313          $table->align = array('left', 'left');
 314          $table->head = array(get_string('date'), get_string('runid', 'tool_profiling'), get_string('comment', 'tool_profiling'));
 315          $table->tablealign = 'center';
 316          $table->attributes['class'] = 'flexible generaltable generalbox';
 317          $table->colclasses = array('value', 'value', 'value');
 318          $table->data = array();
 319  
 320          $output .= $OUTPUT->heading(get_string('viewdiff', 'tool_profiling'), 3, 'main profilinglink');
 321  
 322          foreach ($prevreferences as $reference) {
 323              $url = 'index.php?runid=' . $run->runid . '&amp;runid2=' . $reference->runid . '&amp;listurl=' . urlencode($run->url);
 324              $row = array(userdate($reference->timecreated), '<a href="' . $url . '" title="">'.$reference->runid.'</a>', $reference->runcomment);
 325              $table->data[] = $row;
 326          }
 327          $output .= $OUTPUT->box(html_writer::table($table), 'profilingrunbox', 'profiling_diffs');
 328  
 329      }
 330      // Add link to export this run.
 331      $strexport = get_string('exportthis', 'tool_profiling');
 332      $url = 'export.php?runid=' . $run->runid . '&amp;listurl=' . urlencode($run->url);
 333      $output.=$OUTPUT->heading('<a href="' . $url . '" title="">' . $strexport . '</a>', 3, 'main profilinglink');
 334  
 335      return $output;
 336  }
 337  
 338  function profiling_print_rundiff($run1, $run2) {
 339      global $CFG, $OUTPUT;
 340  
 341      $output = '';
 342  
 343      // Prepare the reference/comment information
 344      $referencetext1 = ($run1->runreference ? get_string('yes') : get_string('no')) .
 345                        ($run1->runcomment ? ' - ' . s($run1->runcomment) : '');
 346      $referencetext2 = ($run2->runreference ? get_string('yes') : get_string('no')) .
 347                        ($run2->runcomment ? ' - ' . s($run2->runcomment) : '');
 348  
 349      // Calculate global differences
 350      $diffexecutiontime = profiling_get_difference($run1->totalexecutiontime, $run2->totalexecutiontime, 'ms', 1000);
 351      $diffcputime       = profiling_get_difference($run1->totalcputime, $run2->totalcputime, 'ms', 1000);
 352      $diffcalls         = profiling_get_difference($run1->totalcalls, $run2->totalcalls);
 353      $diffmemory        = profiling_get_difference($run1->totalmemory, $run2->totalmemory, 'KB', 1024);
 354  
 355      $table = new html_table();
 356      $table->align = array('right', 'left', 'left', 'left');
 357      $table->tablealign = 'center';
 358      $table->attributes['class'] = 'profilingruntable';
 359      $table->colclasses = array('label', 'value1', 'value2');
 360      $table->data = array(
 361         array(get_string('runid', 'tool_profiling'),
 362             '<a href="index.php?runid=' . $run1->runid . '&listurl=' . urlencode($run1->url) . '" title="">' . $run1->runid . '</a>',
 363             '<a href="index.php?runid=' . $run2->runid . '&listurl=' . urlencode($run2->url) . '" title="">' . $run2->runid . '</a>'),
 364         array(get_string('url'), $run1->url, $run2->url),
 365         array(get_string('date'), userdate($run1->timecreated, '%d %B %Y, %H:%M'),
 366             userdate($run2->timecreated, '%d %B %Y, %H:%M')),
 367         array(get_string('executiontime', 'tool_profiling'),
 368             format_float($run1->totalexecutiontime / 1000, 3) . ' ms',
 369             format_float($run2->totalexecutiontime / 1000, 3) . ' ms ' . $diffexecutiontime),
 370         array(get_string('cputime', 'tool_profiling'),
 371             format_float($run1->totalcputime / 1000, 3) . ' ms',
 372             format_float($run2->totalcputime / 1000, 3) . ' ms ' . $diffcputime),
 373         array(get_string('calls', 'tool_profiling'), $run1->totalcalls, $run2->totalcalls . ' ' . $diffcalls),
 374         array(get_string('memory', 'tool_profiling'),
 375             format_float($run1->totalmemory / 1024, 0) . ' KB',
 376             format_float($run2->totalmemory / 1024, 0) . ' KB ' . $diffmemory),
 377         array(get_string('referencerun', 'tool_profiling'), $referencetext1, $referencetext2));
 378      $output = $OUTPUT->box(html_writer::table($table), 'generalbox boxwidthwide boxaligncenter profilingrunbox', 'profiling_summary');
 379      // Add link to details
 380      $strviewdetails = get_string('viewdiffdetails', 'tool_profiling');
 381      $url = profiling_urls('diff', $run1->runid, $run2->runid);
 382      //$url =  $CFG->wwwroot . '/admin/tool/profiling/index.php?run=' . $run->runid;
 383      $output.=$OUTPUT->heading('<a href="' . $url . '" onclick="javascript:window.open(' . "'" . $url . "'" . ');' .
 384                                'return false;"' . ' title="">' . $strviewdetails . '</a>', 3, 'main profilinglink');
 385      return $output;
 386  }
 387  
 388  /**
 389   * Helper function that returns the HTML fragment to
 390   * be displayed on listing mode, it includes actions
 391   * like deletion/export/import...
 392   */
 393  function profiling_list_controls($listurl) {
 394      global $CFG;
 395  
 396      $output = '<p class="centerpara buttons">';
 397      $output .= '&nbsp;<a href="import.php">[' . get_string('import', 'tool_profiling') . ']</a>';
 398      $output .= '</p>';
 399  
 400      return $output;
 401  }
 402  
 403  /**
 404   * Helper function that looks for matchings of one string
 405   * against an array of * wildchar patterns
 406   */
 407  function profiling_string_matches($string, $patterns) {
 408      $patterns = explode(',', $patterns);
 409      foreach ($patterns as $pattern) {
 410          // Trim and prepare pattern
 411          $pattern = str_replace('\*', '.*', preg_quote(trim($pattern), '~'));
 412          // Don't process empty patterns
 413          if (empty($pattern)) {
 414              continue;
 415          }
 416          if (preg_match('~' . $pattern . '~', $string)) {
 417              return true;
 418          }
 419      }
 420      return false;
 421  }
 422  
 423  /**
 424   * Helper function that, given to floats, returns their numerical
 425   * and percentual differences, propertly formated and cssstyled
 426   */
 427  function profiling_get_difference($number1, $number2, $units = '', $factor = 1, $numdec = 2) {
 428      $numdiff = $number2 - $number1;
 429      $perdiff = 0;
 430      if ($number1 != $number2) {
 431          $perdiff = $number1 != 0 ? ($number2 * 100 / $number1) - 100 : 0;
 432      }
 433      $sign      = $number2 > $number1 ? '+' : '';
 434      $delta     = abs($perdiff) > 0.25 ? '&Delta;' : '&asymp;';
 435      $spanclass = $number2 > $number1 ? 'worse' : ($number1 > $number2 ? 'better' : 'same');
 436      $importantclass= abs($perdiff) > 1 ? ' profiling_important' : '';
 437      $startspan = '<span class="profiling_' . $spanclass . $importantclass . '">';
 438      $endspan   = '</span>';
 439      $fnumdiff = $sign . format_float($numdiff / $factor, $numdec);
 440      $fperdiff = $sign . format_float($perdiff, $numdec);
 441      return $startspan . $delta . ' ' . $fnumdiff . ' ' . $units . ' (' . $fperdiff . '%)' . $endspan;
 442  }
 443  
 444  /**
 445   * Export profiling runs to a .mpr (moodle profile runs) file.
 446   *
 447   * This function gets an array of profiling runs (array of runids) and
 448   * saves a .mpr file into destination for ulterior handling.
 449   *
 450   * Format of .mpr files:
 451   *   mpr files are simple zip packages containing these files:
 452   *     - moodle_profiling_runs.xml: Metadata about the information
 453   *         exported. Contains some header information (version and
 454   *         release of moodle, database, git hash - if available, date
 455   *         of export...) and a list of all the runids included in the
 456   *         export.
 457   *    - runid.xml: One file per each run detailed in the main file,
 458   *        containing the raw dump of the given runid in the profiling table.
 459   *
 460   * Possible improvement: Start storing some extra information in the
 461   * profiling table for each run (moodle version, database, git hash...).
 462   *
 463   * @param array $runids list of runids to be exported.
 464   * @param string $file filesystem fullpath to destination .mpr file.
 465   * @return boolean the mpr file has been successfully exported (true) or no (false).
 466   */
 467  function profiling_export_runs(array $runids, $file) {
 468      global $CFG, $DB;
 469  
 470      // Verify we have passed proper runids.
 471      if (empty($runids)) {
 472          return false;
 473      }
 474  
 475      // Verify all the passed runids do exist.
 476      list ($insql, $inparams) = $DB->get_in_or_equal($runids);
 477      $reccount = $DB->count_records_select('profiling', 'runid ' . $insql, $inparams);
 478      if ($reccount != count($runids)) {
 479          return false;
 480      }
 481  
 482      // Verify the $file path is writeable.
 483      $base = dirname($file);
 484      if (!is_writable($base)) {
 485          return false;
 486      }
 487  
 488      // Create temp directory where the temp information will be generated.
 489      $tmpdir = $base . '/' . md5(implode($runids) . time() . random_string(20));
 490      mkdir($tmpdir);
 491  
 492      // Generate the xml contents in the temp directory.
 493      $status = profiling_export_generate($runids, $tmpdir);
 494  
 495      // Package (zip) all the information into the final .mpr file.
 496      if ($status) {
 497          $status = profiling_export_package($file, $tmpdir);
 498      }
 499  
 500      // Process finished ok, clean and return.
 501      fulldelete($tmpdir);
 502      return $status;
 503  }
 504  
 505  /**
 506   * Import a .mpr (moodle profile runs) file into moodle.
 507   *
 508   * See {@link profiling_export_runs()} for more details about the
 509   * implementation of .mpr files.
 510   *
 511   * @param string $file filesystem fullpath to target .mpr file.
 512   * @param string $commentprefix prefix to add to the comments of all the imported runs.
 513   * @return boolean the mpr file has been successfully imported (true) or no (false).
 514   */
 515  function profiling_import_runs($file, $commentprefix = '') {
 516      global $DB;
 517  
 518      // Any problem with the file or its directory, abort.
 519      if (!file_exists($file) or !is_readable($file) or !is_writable(dirname($file))) {
 520          return false;
 521      }
 522  
 523      // Unzip the file into temp directory.
 524      $tmpdir = dirname($file) . '/' . time() . '_' . random_string(4);
 525      $fp = get_file_packer('application/vnd.moodle.profiling');
 526      $status = $fp->extract_to_pathname($file, $tmpdir);
 527  
 528      // Look for master file and verify its format.
 529      if ($status) {
 530          $mfile = $tmpdir . '/moodle_profiling_runs.xml';
 531          if (!file_exists($mfile) or !is_readable($mfile)) {
 532              $status = false;
 533          } else {
 534              $mdom = new DOMDocument();
 535              if (!$mdom->load($mfile)) {
 536                  $status = false;
 537              } else {
 538                  $status = @$mdom->schemaValidateSource(profiling_get_import_main_schema());
 539              }
 540          }
 541      }
 542  
 543      // Verify all detail files exist and verify their format.
 544      if ($status) {
 545          $runs = $mdom->getElementsByTagName('run');
 546          foreach ($runs as $run) {
 547              $rfile = $tmpdir . '/' . clean_param($run->getAttribute('ref'), PARAM_FILE);
 548              if (!file_exists($rfile) or !is_readable($rfile)) {
 549                  $status = false;
 550              } else {
 551                  $rdom = new DOMDocument();
 552                  if (!$rdom->load($rfile)) {
 553                      $status = false;
 554                  } else {
 555                      $status = @$rdom->schemaValidateSource(profiling_get_import_run_schema());
 556                  }
 557              }
 558          }
 559      }
 560  
 561      // Everything looks ok, let's import all the runs.
 562      if ($status) {
 563          reset($runs);
 564          foreach ($runs as $run) {
 565              $rfile = $tmpdir . '/' . $run->getAttribute('ref');
 566              $rdom = new DOMDocument();
 567              $rdom->load($rfile);
 568              $runarr = array();
 569              $runarr['runid'] = clean_param($rdom->getElementsByTagName('runid')->item(0)->nodeValue, PARAM_ALPHANUMEXT);
 570              $runarr['url'] = clean_param($rdom->getElementsByTagName('url')->item(0)->nodeValue, PARAM_CLEAN);
 571              $runarr['runreference'] = clean_param($rdom->getElementsByTagName('runreference')->item(0)->nodeValue, PARAM_INT);
 572              $runarr['runcomment'] = $commentprefix . clean_param($rdom->getElementsByTagName('runcomment')->item(0)->nodeValue, PARAM_CLEAN);
 573              $runarr['timecreated'] = time(); // Now.
 574              $runarr['totalexecutiontime'] = clean_param($rdom->getElementsByTagName('totalexecutiontime')->item(0)->nodeValue, PARAM_INT);
 575              $runarr['totalcputime'] = clean_param($rdom->getElementsByTagName('totalcputime')->item(0)->nodeValue, PARAM_INT);
 576              $runarr['totalcalls'] = clean_param($rdom->getElementsByTagName('totalcalls')->item(0)->nodeValue, PARAM_INT);
 577              $runarr['totalmemory'] = clean_param($rdom->getElementsByTagName('totalmemory')->item(0)->nodeValue, PARAM_INT);
 578              $runarr['data'] = clean_param($rdom->getElementsByTagName('data')->item(0)->nodeValue, PARAM_CLEAN);
 579              // If the runid does not exist, insert it.
 580              if (!$DB->record_exists('profiling', array('runid' => $runarr['runid']))) {
 581                  $DB->insert_record('profiling', $runarr);
 582              } else {
 583                  return false;
 584              }
 585          }
 586      }
 587  
 588      // Clean the temp directory used for import.
 589      remove_dir($tmpdir);
 590  
 591      return $status;
 592  }
 593  
 594  /**
 595   * Generate the mpr contents (xml files) in the temporal directory.
 596   *
 597   * @param array $runids list of runids to be generated.
 598   * @param string $tmpdir filesystem fullpath of tmp generation.
 599   * @return boolean the mpr contents have been generated (true) or no (false).
 600   */
 601  function profiling_export_generate(array $runids, $tmpdir) {
 602      global $CFG, $DB;
 603  
 604      // Calculate the header information to be sent to moodle_profiling_runs.xml.
 605      $release = $CFG->release;
 606      $version = $CFG->version;
 607      $dbtype = $CFG->dbtype;
 608      $githash = phpunit_util::get_git_hash();
 609      $date = time();
 610  
 611      // Create the xml output and writer for the main file.
 612      $mainxo = new file_xml_output($tmpdir . '/moodle_profiling_runs.xml');
 613      $mainxw = new xml_writer($mainxo);
 614  
 615      // Output begins.
 616      $mainxw->start();
 617      $mainxw->begin_tag('moodle_profiling_runs');
 618  
 619      // Send header information.
 620      $mainxw->begin_tag('info');
 621      $mainxw->full_tag('release', $release);
 622      $mainxw->full_tag('version', $version);
 623      $mainxw->full_tag('dbtype', $dbtype);
 624      if ($githash) {
 625          $mainxw->full_tag('githash', $githash);
 626      }
 627      $mainxw->full_tag('date', $date);
 628      $mainxw->end_tag('info');
 629  
 630      // Send information about runs.
 631      $mainxw->begin_tag('runs');
 632      foreach ($runids as $runid) {
 633          // Get the run information from DB.
 634          $run = $DB->get_record('profiling', array('runid' => $runid), '*', MUST_EXIST);
 635          $attributes = array(
 636                  'id' => $run->id,
 637                  'ref' => $run->runid . '.xml');
 638          $mainxw->full_tag('run', null, $attributes);
 639          // Create the individual run file.
 640          $runxo = new file_xml_output($tmpdir . '/' . $attributes['ref']);
 641          $runxw = new xml_writer($runxo);
 642          $runxw->start();
 643          $runxw->begin_tag('moodle_profiling_run');
 644          $runxw->full_tag('id', $run->id);
 645          $runxw->full_tag('runid', $run->runid);
 646          $runxw->full_tag('url', $run->url);
 647          $runxw->full_tag('runreference', $run->runreference);
 648          $runxw->full_tag('runcomment', $run->runcomment);
 649          $runxw->full_tag('timecreated', $run->timecreated);
 650          $runxw->full_tag('totalexecutiontime', $run->totalexecutiontime);
 651          $runxw->full_tag('totalcputime', $run->totalcputime);
 652          $runxw->full_tag('totalcalls', $run->totalcalls);
 653          $runxw->full_tag('totalmemory', $run->totalmemory);
 654          $runxw->full_tag('data', $run->data);
 655          $runxw->end_tag('moodle_profiling_run');
 656          $runxw->stop();
 657      }
 658      $mainxw->end_tag('runs');
 659      $mainxw->end_tag('moodle_profiling_runs');
 660      $mainxw->stop();
 661  
 662      return true;
 663  }
 664  
 665  /**
 666   * Package (zip) the mpr contents (xml files) in the final location.
 667   *
 668   * @param string $file filesystem fullpath to destination .mpr file.
 669   * @param string $tmpdir filesystem fullpath of tmp generation.
 670   * @return boolean the mpr contents have been generated (true) or no (false).
 671   */
 672  function profiling_export_package($file, $tmpdir) {
 673      // Get the list of files in $tmpdir.
 674      $filestemp = get_directory_list($tmpdir, '', false, true, true);
 675      $files = array();
 676  
 677      // Add zip paths and fs paths to all them.
 678      foreach ($filestemp as $filetemp) {
 679          $files[$filetemp] = $tmpdir . '/' . $filetemp;
 680      }
 681  
 682      // Get the zip_packer.
 683      $zippacker = get_file_packer('application/zip');
 684  
 685      // Generate the packaged file.
 686      $zippacker->archive_to_pathname($files, $file);
 687  
 688      return true;
 689  }
 690  
 691  /**
 692   * Return the xml schema for the main import file.
 693   *
 694   * @return string
 695   *
 696   */
 697  function profiling_get_import_main_schema() {
 698      $schema = <<<EOS
 699  <?xml version="1.0" encoding="UTF-8"?>
 700  <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
 701    <xs:element name="moodle_profiling_runs">
 702      <xs:complexType>
 703        <xs:sequence>
 704          <xs:element ref="info"/>
 705          <xs:element ref="runs"/>
 706        </xs:sequence>
 707      </xs:complexType>
 708    </xs:element>
 709    <xs:element name="info">
 710      <xs:complexType>
 711        <xs:sequence>
 712          <xs:element type="xs:string" name="release"/>
 713          <xs:element type="xs:decimal" name="version"/>
 714          <xs:element type="xs:string" name="dbtype"/>
 715          <xs:element type="xs:string" minOccurs="0" name="githash"/>
 716          <xs:element type="xs:int" name="date"/>
 717        </xs:sequence>
 718      </xs:complexType>
 719    </xs:element>
 720    <xs:element name="runs">
 721      <xs:complexType>
 722        <xs:sequence>
 723          <xs:element maxOccurs="unbounded" ref="run"/>
 724        </xs:sequence>
 725      </xs:complexType>
 726    </xs:element>
 727    <xs:element name="run">
 728      <xs:complexType>
 729        <xs:attribute type="xs:int" name="id"/>
 730        <xs:attribute type="xs:string" name="ref"/>
 731      </xs:complexType>
 732    </xs:element>
 733  </xs:schema>
 734  EOS;
 735      return $schema;
 736  }
 737  
 738  /**
 739   * Return the xml schema for each individual run import file.
 740   *
 741   * @return string
 742   *
 743   */
 744  function profiling_get_import_run_schema() {
 745      $schema = <<<EOS
 746  <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
 747    <xs:element name="moodle_profiling_run">
 748      <xs:complexType>
 749        <xs:sequence>
 750          <xs:element type="xs:int" name="id"/>
 751          <xs:element type="xs:string" name="runid"/>
 752          <xs:element type="xs:string" name="url"/>
 753          <xs:element type="xs:int" name="runreference"/>
 754          <xs:element type="xs:string" name="runcomment"/>
 755          <xs:element type="xs:int" name="timecreated"/>
 756          <xs:element type="xs:int" name="totalexecutiontime"/>
 757          <xs:element type="xs:int" name="totalcputime"/>
 758          <xs:element type="xs:int" name="totalcalls"/>
 759          <xs:element type="xs:int" name="totalmemory"/>
 760          <xs:element type="xs:string" name="data"/>
 761        </xs:sequence>
 762      </xs:complexType>
 763    </xs:element>
 764  </xs:schema>
 765  EOS;
 766      return $schema;
 767  }
 768  /**
 769   * Custom implementation of iXHProfRuns
 770   *
 771   * This class is one implementation of the iXHProfRuns interface, in charge
 772   * of storing and retrieve profiling run data to/from DB (profiling table)
 773   *
 774   * The interface only defines two methods to be defined: get_run() and
 775   * save_run() we'll be implementing some more in order to keep all the
 776   * rest of information in our runs properly handled.
 777   */
 778  class moodle_xhprofrun implements iXHProfRuns {
 779  
 780      protected $runid = null;
 781      protected $url = null;
 782      protected $totalexecutiontime = 0;
 783      protected $totalcputime = 0;
 784      protected $totalcalls = 0;
 785      protected $totalmemory = 0;
 786      protected $timecreated = 0;
 787  
 788      public function __construct() {
 789          $this->timecreated = time();
 790      }
 791  
 792      /**
 793       * Given one runid and one type, return the run data
 794       * and some extra info in run_desc from DB
 795       *
 796       * Note that $type is completely ignored
 797       */
 798      public function get_run($run_id, $type, &$run_desc) {
 799          global $DB;
 800  
 801          $rec = $DB->get_record('profiling', array('runid' => $run_id), '*', MUST_EXIST);
 802  
 803          $this->runid = $rec->runid;
 804          $this->url = $rec->url;
 805          $this->totalexecutiontime = $rec->totalexecutiontime;
 806          $this->totalcputime = $rec->totalcputime;
 807          $this->totalcalls = $rec->totalcalls;
 808          $this->totalmemory = $rec->totalmemory;
 809          $this->timecreated = $rec->timecreated;
 810  
 811          $run_desc = $this->url . ($rec->runreference ? ' (R) ' : ' ') . ' - ' . s($rec->runcomment);
 812  
 813          return unserialize(base64_decode($rec->data));
 814      }
 815  
 816      /**
 817       * Given some run data, one type and, optionally, one runid
 818       * store the information in DB
 819       *
 820       * Note that $type is completely ignored
 821       */
 822      public function save_run($xhprof_data, $type, $run_id = null) {
 823          global $DB;
 824  
 825          if (is_null($this->url)) {
 826              xhprof_error("Warning: You must use the prepare_run() method before saving it");
 827          }
 828  
 829          // Calculate runid if needed
 830          $this->runid = is_null($run_id) ? md5($this->url . '-' . uniqid()) : $run_id;
 831  
 832          // Calculate totals
 833          $this->totalexecutiontime = $xhprof_data['main()']['wt'];
 834          $this->totalcputime = $xhprof_data['main()']['cpu'];
 835          $this->totalcalls = array_reduce($xhprof_data, array($this, 'sum_calls'));
 836          $this->totalmemory = $xhprof_data['main()']['mu'];
 837  
 838          // Prepare data
 839          $rec = new stdClass();
 840          $rec->runid = $this->runid;
 841          $rec->url = $this->url;
 842          $rec->data = base64_encode(serialize($xhprof_data));
 843          $rec->totalexecutiontime = $this->totalexecutiontime;
 844          $rec->totalcputime = $this->totalcputime;
 845          $rec->totalcalls = $this->totalcalls;
 846          $rec->totalmemory = $this->totalmemory;
 847          $rec->timecreated = $this->timecreated;
 848  
 849          $DB->insert_record('profiling', $rec);
 850          return $this->runid;
 851      }
 852  
 853      public function prepare_run($url) {
 854          $this->url = $url;
 855      }
 856  
 857      // Private API starts here
 858  
 859      protected function sum_calls($sum, $data) {
 860          return $sum + $data['ct'];
 861      }
 862  }
 863  
 864  /**
 865   * Simple subclass of {@link table_sql} that provides
 866   * some custom formatters for various columns, in order
 867   * to make the main profiles list nicer
 868   */
 869  class xhprof_table_sql extends table_sql {
 870  
 871      protected $listurlmode = false;
 872  
 873      /**
 874       * Get row classes to be applied based on row contents
 875       */
 876      function get_row_class($row) {
 877          return $row->runreference ? 'referencerun' : ''; // apply class to reference runs
 878      }
 879  
 880      /**
 881       * Define it the table is in listurlmode or not, output will
 882       * be different based on that
 883       */
 884      function set_listurlmode($listurlmode) {
 885          $this->listurlmode = $listurlmode;
 886      }
 887  
 888      /**
 889       * Format URL, so it points to last run for that url
 890       */
 891      protected function col_url($row) {
 892          global $OUTPUT;
 893  
 894          // Build the link to latest run for the script
 895          $scripturl = new moodle_url('/admin/tool/profiling/index.php', array('script' => $row->url, 'listurl' => $row->url));
 896          $scriptaction = $OUTPUT->action_link($scripturl, $row->url);
 897  
 898          // Decide, based on $this->listurlmode which actions to show
 899          if ($this->listurlmode) {
 900              $detailsaction = '';
 901          } else {
 902              // Build link icon to script details (pix + url + actionlink)
 903              $detailsimg = $OUTPUT->pix_icon('t/right', get_string('profilingfocusscript', 'tool_profiling', $row->url));
 904              $detailsurl = new moodle_url('/admin/tool/profiling/index.php', array('listurl' => $row->url));
 905              $detailsaction = $OUTPUT->action_link($detailsurl, $detailsimg);
 906          }
 907  
 908          return $scriptaction . '&nbsp;' . $detailsaction;
 909      }
 910  
 911      /**
 912       * Format profiling date, human and pointing to run
 913       */
 914      protected function col_timecreated($row) {
 915          global $OUTPUT;
 916          $fdate = userdate($row->timecreated, '%d %b %Y, %H:%M');
 917          $url = new moodle_url('/admin/tool/profiling/index.php', array('runid' => $row->runid, 'listurl' => $row->url));
 918          return $OUTPUT->action_link($url, $fdate);
 919      }
 920  
 921      /**
 922       * Format execution time
 923       */
 924      protected function col_totalexecutiontime($row) {
 925          return format_float($row->totalexecutiontime / 1000, 3) . ' ms';
 926      }
 927  
 928      /**
 929       * Format cpu time
 930       */
 931      protected function col_totalcputime($row) {
 932          return format_float($row->totalcputime / 1000, 3) . ' ms';
 933      }
 934  
 935      /**
 936       * Format memory
 937       */
 938      protected function col_totalmemory($row) {
 939          return format_float($row->totalmemory / 1024, 3) . ' KB';
 940      }
 941  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1