[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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 . '&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/> ". 285 "<input type=\"text\" name=\"runcomment\" value=\"$run->runcomment\"/> ". 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 . '&runid2=' . $reference->runid . '&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 . '&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 .= ' <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 ? 'Δ' : '≈'; 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 . ' ' . $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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |