From e3c2cb46560c0e1275149504ccf46e417d2d72d2 Mon Sep 17 00:00:00 2001 From: "Eloy Lafuente (stronk7)" Date: Mon, 20 May 2013 02:15:54 +0200 Subject: [PATCH 1/4] MDL-39733 xhprof: implement export of runs --- lib/xhprof/xhprof_moodle.php | 169 ++++++++++++++++++++++++++++++++++- 1 file changed, 165 insertions(+), 4 deletions(-) diff --git a/lib/xhprof/xhprof_moodle.php b/lib/xhprof/xhprof_moodle.php index a53fd61f54c..c8c7be7fc51 100644 --- a/lib/xhprof/xhprof_moodle.php +++ b/lib/xhprof/xhprof_moodle.php @@ -23,13 +23,18 @@ defined('MOODLE_INTERNAL') || die(); -// need some stuff from xhprof +// Need some stuff from xhprof. require_once($CFG->libdir . '/xhprof/xhprof_lib/utils/xhprof_lib.php'); require_once($CFG->libdir . '/xhprof/xhprof_lib/utils/xhprof_runs.php'); -// need some stuff from moodle -require_once($CFG->libdir.'/tablelib.php'); +// Need some stuff from moodle. +require_once($CFG->libdir . '/tablelib.php'); +require_once($CFG->libdir . '/setuplib.php'); +require_once($CFG->libdir . '/phpunit/classes/util.php'); +require_once($CFG->dirroot . '/backup/util/xml/xml_writer.class.php'); +require_once($CFG->dirroot . '/backup/util/xml/output/xml_output.class.php'); +require_once($CFG->dirroot . '/backup/util/xml/output/file_xml_output.class.php'); -// TODO: Change the implementation below to proper profiling class +// TODO: Change the implementation below to proper profiling class. /** * Returns if profiling is running, optionally setting it @@ -408,6 +413,162 @@ function profiling_get_difference($number1, $number2, $units = '', $factor = 1, return $startspan . $delta . ' ' . $fnumdiff . ' ' . $units . ' (' . $fperdiff . '%)' . $endspan; } +/** + * Export profiling runs to a .mpr (moodle profile runs) file. + * + * This function gets an array of profiling runs (array of runids) and + * saves a .mpr file into destinantion for ulterior handling. + * + * Format of .mpr files: + * mpr files are simple zip packages containing these files: + * - moodle_profiling_runs.xml: Metadata about the information + * exported. Contains some header information (version and + * release of moodle, database, git hash - if available, date + * of export...) and a list of all the runids included in the + * export. + * - runid.xml: One file per each run detailed in the main file, + * containing the raw dump of the given runid in the profiling table. + * + * Posible improvement: Start storing some extra information in the + * profiling table for each run (moodle version, database, git hash...). + * + * @param array $runids list of runids to be exported. + * @param string $file filesystem fullpath to destination .mpr file. + * @return boolean the mpr file has been succesfully exported (true) or no (false). + */ +function profiling_export_runs(array $runids, $file) { + global $CFG, $DB; + + // Verify we have passed proper runids. + if (empty($runids)) { + return false; + } + + // Verify all the passed runids do exist. + list ($insql, $inparams) = $DB->get_in_or_equal($runids); + $reccount = $DB->count_records_select('profiling', 'runid ' . $insql, $inparams); + if ($reccount != count($runids)) { + return false; + } + + // Verify the $file path is writeable. + $base = dirname($file); + if (!is_writable($base)) { + return false; + } + + // Create temp directory where the temp information will be generated. + $tmpdir = $base . '/' . md5(implode($runids) . time() . random_string(20)); + mkdir($tmpdir); + + // Generate the xml contents in the temp directory. + $status = profiling_export_generate($runids, $tmpdir); + + // Package (zip) all the information into the final .mpr file. + if ($status) { + $status = profiling_export_package($file, $tmpdir); + } + + // Process finished ok, clean and return + fulldelete($tmpdir); + return $status; +} + +/** + * Generate the mpr contents (xml files) in the temporal directory. + * + * @param array $runids list of runids to be generated. + * @param string $tmpdir filesystem fullpath of tmp generation. + * @return boolean the mpr contents have been generated (true) or no (false). + */ +function profiling_export_generate(array $runids, $tmpdir) { + global $CFG, $DB; + + // Calculate the header information to be sent to moodle_profiling_runs.xml. + $release = $CFG->release; + $version = $CFG->version; + $dbtype = $CFG->dbtype; + $githash = phpunit_util::get_git_hash(); + $date = time(); + + // Create the xml output and writer for the main file. + $mainxo = new file_xml_output($tmpdir . '/moodle_profiling_runs.xml'); + $mainxw = new xml_writer($mainxo); + + // Output begins. + $mainxw->start(); + $mainxw->begin_tag('moodle_profiling_runs'); + + // Send header information. + $mainxw->begin_tag('info'); + $mainxw->full_tag('release', $release); + $mainxw->full_tag('version', $version); + $mainxw->full_tag('dbtype', $dbtype); + $mainxw->full_tag('githash', $githash); + $mainxw->full_tag('date', $date); + $mainxw->end_tag('info'); + + // Send information about runs. + $mainxw->begin_tag('runs'); + foreach ($runids as $runid) { + // Get the run information from DB. + $run = $DB->get_record('profiling', array('runid' => $runid), '*', MUST_EXIST); + $attributes = array( + 'id' => $run->id, + 'ref' => $run->runid . '.xml'); + $mainxw->full_tag('run', null, $attributes); + // Create the individual run file. + $runxo = new file_xml_output($tmpdir . '/' . $attributes['ref']); + $runxw = new xml_writer($runxo); + $runxw->start(); + $runxw->begin_tag('moodle_profiling_run'); + $runxw->full_tag('id', $run->id); + $runxw->full_tag('runid', $run->runid); + $runxw->full_tag('url', $run->url); + $runxw->full_tag('runreference', $run->runreference); + $runxw->full_tag('runcomment', $run->runcomment); + $runxw->full_tag('timecreated', $run->timecreated); + $runxw->full_tag('totalexecutiontime', $run->totalexecutiontime); + $runxw->full_tag('totalcputime', $run->totalcputime); + $runxw->full_tag('totalcalls', $run->totalcalls); + $runxw->full_tag('totalmemory', $run->totalmemory); + $runxw->full_tag('data', $run->data); + $runxw->end_tag('moodle_profiling_run'); + $runxw->stop(); + } + $mainxw->end_tag('runs'); + $mainxw->end_tag('moodle_profiling_runs'); + $mainxw->stop(); + + return true; +} + +/** + * Package (zip) the mpr contents (xml files) in the final location. + * + * @param string $file filesystem fullpath to destination .mpr file. + * @param string $tmpdir filesystem fullpath of tmp generation. + * @return boolean the mpr contents have been generated (true) or no (false). + */ +function profiling_export_package($file, $tmpdir) { + // Get the list of files in $tmpdir. + $filestemp = get_directory_list($tmpdir, '', false, true, true); + $files = array(); + + // Add zip paths and fs paths to all them. + foreach ($filestemp as $filetemp) { + $files[$filetemp] = $tmpdir . '/' . $filetemp; + } + + // Get the zip_packer. + $zippacker = get_file_packer('application/zip'); + + // Generate the packaged file. + $zippacker->archive_to_pathname($files, $file); + + return true; +} + /** * Custom implementation of iXHProfRuns * From 706db165313293f6f068e8f817ab0a769a66d6de Mon Sep 17 00:00:00 2001 From: "Eloy Lafuente (stronk7)" Date: Mon, 20 May 2013 02:20:23 +0200 Subject: [PATCH 2/4] MDL-39733 profiling: UI for exporting runs --- admin/tool/profiling/export.php | 56 +++++++++++++++++++ admin/tool/profiling/index.php | 3 +- .../tool/profiling/lang/en/tool_profiling.php | 7 ++- lib/xhprof/xhprof_moodle.php | 4 ++ 4 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 admin/tool/profiling/export.php diff --git a/admin/tool/profiling/export.php b/admin/tool/profiling/export.php new file mode 100644 index 00000000000..32adc0d0c49 --- /dev/null +++ b/admin/tool/profiling/export.php @@ -0,0 +1,56 @@ +. + +/** + * Profiling tool export utility. + * + * @package tool_profiling + * @copyright 2013 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once(dirname(__FILE__) . '/../../../config.php'); +require_once($CFG->libdir.'/adminlib.php'); +require_once($CFG->libdir . '/xhprof/xhprof_moodle.php'); + +// Page parameters. +$runid = required_param('runid', PARAM_ALPHANUM); +$listurl = required_param('listurl', PARAM_PATH); + +admin_externalpage_setup('toolprofiling'); + +$PAGE->navbar->add(get_string('export', 'tool_profiling')); + +// Calculate export variables. +$tempdir = 'profiling'; +make_temp_directory($tempdir); +$runids = array($runid); +$filename = $runid . '.mpr'; +$filepath = $CFG->tempdir . '/' . $tempdir . '/' . $filename; + +// Generate the mpr file and send it. +if (profiling_export_runs($runids, $filepath)) { + send_file($filepath, $filename, 0, 0, false, false, '', true); + unlink($filepath); // Delete once sent. + die; +} + +// Something wrong happened, notice it and done. +$urlparams = array( + 'runid' => $runid, + 'listurl' => $listurl); +$url = new moodle_url('/admin/tool/profiling/index.php', $urlparams); +notice(get_string('exportproblem', 'tool_profiling', $urlparams), $url); diff --git a/admin/tool/profiling/index.php b/admin/tool/profiling/index.php index eebfcd842e6..1d591fab411 100644 --- a/admin/tool/profiling/index.php +++ b/admin/tool/profiling/index.php @@ -17,8 +17,7 @@ /** * Profiling tool. * - * @package tool - * @subpackage profiling + * @package tool_profiling * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ diff --git a/admin/tool/profiling/lang/en/tool_profiling.php b/admin/tool/profiling/lang/en/tool_profiling.php index bdc8dd7bd57..d02142de992 100644 --- a/admin/tool/profiling/lang/en/tool_profiling.php +++ b/admin/tool/profiling/lang/en/tool_profiling.php @@ -24,13 +24,18 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +defined('MOODLE_INTERNAL') || die(); + $string['calls'] = 'Function calls'; $string['cannotfindanyrunforurl'] = 'Sorry, cannot find any profiling run for the \'{$a}\' URL'; $string['cannotfindanyrunforrunid'] = 'Sorry, cannot find the \'{$a}\' profiling run'; $string['comment'] = 'Comment'; +$string['cputime'] = 'CPU time'; $string['differencesbetween2runsof'] = 'Differences between 2 runs of {$a}'; $string['executiontime'] = 'Execution time'; -$string['cputime'] = 'CPU time'; +$string['export'] = 'Export'; +$string['exportproblem'] = 'Some problem happened exporting the profile run "{$a->runid}" corresponding to the request "{$a->listurl}".'; +$string['exportthis'] = 'Export this profiling run'; $string['lastrunof'] = 'Summary of last run of {$a}'; $string['markreferencerun'] = 'Mark as reference run/comment'; $string['memory'] = 'Memory used'; diff --git a/lib/xhprof/xhprof_moodle.php b/lib/xhprof/xhprof_moodle.php index c8c7be7fc51..1a18e7af9d7 100644 --- a/lib/xhprof/xhprof_moodle.php +++ b/lib/xhprof/xhprof_moodle.php @@ -305,6 +305,10 @@ function profiling_print_run($run, $prevrunid = null) { $url = 'index.php?runid=' . $run->runid . '&runid2=' . $prevrunid . '&listurl=' . urlencode($run->url); $output.=$OUTPUT->heading('' . $strviewdiff . '', 3, 'main profilinglink'); } + // Add link to export this run. + $strexport = get_string('exportthis', 'tool_profiling'); + $url = 'export.php?runid=' . $run->runid . '&listurl=' . urlencode($run->url); + $output.=$OUTPUT->heading('' . $strexport . '', 3, 'main profilinglink'); return $output; } From 7af092c2d639ee64cdfc84fbe387161b2c9d0291 Mon Sep 17 00:00:00 2001 From: "Eloy Lafuente (stronk7)" Date: Mon, 20 May 2013 03:49:25 +0200 Subject: [PATCH 3/4] MDL-39733 profiling: UI for importing runs --- admin/tool/profiling/import.php | 66 +++++++++++++++++++ admin/tool/profiling/import_form.php | 40 +++++++++++ admin/tool/profiling/index.php | 6 +- .../tool/profiling/lang/en/tool_profiling.php | 3 + lib/xhprof/xhprof_moodle.php | 19 +++++- 5 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 admin/tool/profiling/import.php create mode 100644 admin/tool/profiling/import_form.php diff --git a/admin/tool/profiling/import.php b/admin/tool/profiling/import.php new file mode 100644 index 00000000000..d4d4727bbd4 --- /dev/null +++ b/admin/tool/profiling/import.php @@ -0,0 +1,66 @@ +. + +/** + * Profiling tool import utility. + * + * @package tool_profiling + * @copyright 2013 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once(dirname(__FILE__) . '/../../../config.php'); +require_once($CFG->libdir.'/adminlib.php'); +require_once($CFG->libdir . '/xhprof/xhprof_moodle.php'); +require_once(dirname(__FILE__) . '/import_form.php'); + +admin_externalpage_setup('toolprofiling'); + +$PAGE->navbar->add(get_string('import', 'tool_profiling')); + +// Calculate export variables. +$tempdir = 'profiling'; +make_temp_directory($tempdir); + +// URL where we'll end, both on success and failure. +$url = new moodle_url('/admin/tool/profiling/index.php'); + +// Instantiate the upload profiling runs form. +$mform = new profiling_import_form(); + +// If there is any file to import. +if ($data = $mform->get_data()) { + $filename = $mform->get_new_filename('mprfile'); + $file = $CFG->tempdir . '/' . $tempdir . '/' . $filename; + $status = $mform->save_file('mprfile', $file); + if ($status) { + // File saved properly, let's import it. + $status = profiling_import_runs($file); + } + if ($status) { + // Import ended ok, let's redirect to main profiling page. + redirect($url, get_string('importok', 'tool_profiling', $filename)); + } +} else { + echo $OUTPUT->header(); + echo $OUTPUT->heading(get_string('import', 'tool_profiling')); + $mform->display(); + echo $OUTPUT->footer(); + die; +} + +// Something wrong happened, notice it and done. +notice(get_string('importproblem', 'tool_profiling', $filename), $url); diff --git a/admin/tool/profiling/import_form.php b/admin/tool/profiling/import_form.php new file mode 100644 index 00000000000..e324fd30f5f --- /dev/null +++ b/admin/tool/profiling/import_form.php @@ -0,0 +1,40 @@ +. + +/** + * Profiling tool import utility form. + * + * @package tool_profiling + * @copyright 2013 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir . '/formslib.php'); + +class profiling_import_form extends moodleform { + function definition () { + $mform = $this->_form; + + $mform->addElement('header', 'settingsheader', get_string('upload')); + + $mform->addElement('filepicker', 'mprfile', get_string('file'), null, array('accepted_types' => array('.mpr'))); + $mform->addRule('mprfile', null, 'required'); + + $this->add_action_buttons(false, get_string('import', 'tool_profiling')); + } +} diff --git a/admin/tool/profiling/index.php b/admin/tool/profiling/index.php index 1d591fab411..2f0842709fe 100644 --- a/admin/tool/profiling/index.php +++ b/admin/tool/profiling/index.php @@ -160,6 +160,9 @@ if (isset($script)) { echo $OUTPUT->heading($header); + // Print the controller block with different options + echo profiling_list_controls($listurl); + // TODO: Fix flexitable to validate tsort/thide/tshow/tifirs/tilast/page // TODO: Fix table_sql to allow it to work without WHERE clause // add silly condition (1 = 1) because of table_sql bug @@ -178,9 +181,6 @@ if (isset($script)) { $table->define_baseurl($baseurl); $table->column_suppress('url'); $table->out(PROFILING_RUNSPERPAGE, true); - - // Print the controller block with different options - echo profiling_list_controls($listurl); } // Footer. diff --git a/admin/tool/profiling/lang/en/tool_profiling.php b/admin/tool/profiling/lang/en/tool_profiling.php index d02142de992..d9778a494f8 100644 --- a/admin/tool/profiling/lang/en/tool_profiling.php +++ b/admin/tool/profiling/lang/en/tool_profiling.php @@ -36,6 +36,9 @@ $string['executiontime'] = 'Execution time'; $string['export'] = 'Export'; $string['exportproblem'] = 'Some problem happened exporting the profile run "{$a->runid}" corresponding to the request "{$a->listurl}".'; $string['exportthis'] = 'Export this profiling run'; +$string['import'] = 'Import'; +$string['importok'] = 'File "{$a}" imported successfully.'; +$string['importproblem'] = 'Some problem happened importing the file "{$a}".'; $string['lastrunof'] = 'Summary of last run of {$a}'; $string['markreferencerun'] = 'Mark as reference run/comment'; $string['memory'] = 'Memory used'; diff --git a/lib/xhprof/xhprof_moodle.php b/lib/xhprof/xhprof_moodle.php index 1a18e7af9d7..f8fab17a707 100644 --- a/lib/xhprof/xhprof_moodle.php +++ b/lib/xhprof/xhprof_moodle.php @@ -369,9 +369,11 @@ function profiling_print_rundiff($run1, $run2) { * like deletion/export/import... */ function profiling_list_controls($listurl) { - global $CFG, $OUTPUT; + global $CFG; - $output = ''; + $output = '

'; + $output .= ' [' . get_string('import', 'tool_profiling') . ']'; + $output .= '

'; return $output; } @@ -478,6 +480,19 @@ function profiling_export_runs(array $runids, $file) { return $status; } +/** + * Import a .mpr (moodle profile runs) file into moodle. + * + * See {@link profiling_export_runs()} for more details about the + * implementation of .mpr files. + * + * @param string $file filesystem fullpath to target .mpr file. + * @return boolean the mpr file has been succesfully imported (true) or no (false). + */ +function profiling_import_runs($file) { + return true; +} + /** * Generate the mpr contents (xml files) in the temporal directory. * From 16a2860ad286a11d4c37118a59d6050d6a87dd91 Mon Sep 17 00:00:00 2001 From: "Eloy Lafuente (stronk7)" Date: Sun, 16 Jun 2013 17:02:27 +0200 Subject: [PATCH 4/4] MDL-39733 xhprof: implement import of runs --- admin/settings/development.php | 4 + admin/tool/profiling/import.php | 6 +- admin/tool/profiling/import_form.php | 11 +- admin/tool/profiling/index.php | 2 +- .../tool/profiling/lang/en/tool_profiling.php | 1 + admin/tool/profiling/version.php | 5 +- lang/en/admin.php | 2 + lib/filelib.php | 1 + lib/moodlelib.php | 1 + lib/xhprof/readme_moodle.txt | 6 +- lib/xhprof/xhprof_moodle.php | 171 +++++++++++++++++- 11 files changed, 192 insertions(+), 18 deletions(-) diff --git a/admin/settings/development.php b/admin/settings/development.php index 4d9cd84ab52..44d25462017 100644 --- a/admin/settings/development.php +++ b/admin/settings/development.php @@ -65,6 +65,10 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page 60 => new lang_string('numminutes', '', 60), 30 => new lang_string('numminutes', '', 30), 15 => new lang_string('numminutes', '', 15)))); + // Define the prefix to be added to imported profiling runs. + $temp->add(new admin_setting_configtext('profilingimportprefix', + new lang_string('profilingimportprefix', 'admin'), + new lang_string('profilingimportprefix_desc', 'admin'), '(I)', PARAM_TAG, 10)); // Add the 'profiling' page to admin block $ADMIN->add('development', $temp); diff --git a/admin/tool/profiling/import.php b/admin/tool/profiling/import.php index d4d4727bbd4..04c216e8f46 100644 --- a/admin/tool/profiling/import.php +++ b/admin/tool/profiling/import.php @@ -48,7 +48,11 @@ if ($data = $mform->get_data()) { $status = $mform->save_file('mprfile', $file); if ($status) { // File saved properly, let's import it. - $status = profiling_import_runs($file); + $status = profiling_import_runs($file, $data->importprefix); + } + // Delete the temp file, not needed anymore. + if (file_exists($file)) { + unlink($file); } if ($status) { // Import ended ok, let's redirect to main profiling page. diff --git a/admin/tool/profiling/import_form.php b/admin/tool/profiling/import_form.php index e324fd30f5f..f165a6b901a 100644 --- a/admin/tool/profiling/import_form.php +++ b/admin/tool/profiling/import_form.php @@ -27,14 +27,21 @@ defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . '/formslib.php'); class profiling_import_form extends moodleform { - function definition () { + public function definition () { + global $CFG; + $mform = $this->_form; $mform->addElement('header', 'settingsheader', get_string('upload')); - $mform->addElement('filepicker', 'mprfile', get_string('file'), null, array('accepted_types' => array('.mpr'))); + $mform->addElement('filepicker', 'mprfile', get_string('file'), null, array('accepted_types' => array('.mpr', '.zip'))); $mform->addRule('mprfile', null, 'required'); + $mform->addElement('text', 'importprefix', + get_string('importprefix', 'tool_profiling'), array('size' => 10)); + $mform->setDefault('importprefix', $CFG->profilingimportprefix); + $mform->setType('importprefix', PARAM_TAG); + $this->add_action_buttons(false, get_string('import', 'tool_profiling')); } } diff --git a/admin/tool/profiling/index.php b/admin/tool/profiling/index.php index 2f0842709fe..a5e21d3badf 100644 --- a/admin/tool/profiling/index.php +++ b/admin/tool/profiling/index.php @@ -160,7 +160,7 @@ if (isset($script)) { echo $OUTPUT->heading($header); - // Print the controller block with different options + // Print the controller block with different options. echo profiling_list_controls($listurl); // TODO: Fix flexitable to validate tsort/thide/tshow/tifirs/tilast/page diff --git a/admin/tool/profiling/lang/en/tool_profiling.php b/admin/tool/profiling/lang/en/tool_profiling.php index d9778a494f8..4a687f8f279 100644 --- a/admin/tool/profiling/lang/en/tool_profiling.php +++ b/admin/tool/profiling/lang/en/tool_profiling.php @@ -38,6 +38,7 @@ $string['exportproblem'] = 'Some problem happened exporting the profile run "{$a $string['exportthis'] = 'Export this profiling run'; $string['import'] = 'Import'; $string['importok'] = 'File "{$a}" imported successfully.'; +$string['importprefix'] = 'Import prefix'; $string['importproblem'] = 'Some problem happened importing the file "{$a}".'; $string['lastrunof'] = 'Summary of last run of {$a}'; $string['markreferencerun'] = 'Mark as reference run/comment'; diff --git a/admin/tool/profiling/version.php b/admin/tool/profiling/version.php index a8c6c0eaddc..883ef9a5749 100644 --- a/admin/tool/profiling/version.php +++ b/admin/tool/profiling/version.php @@ -17,14 +17,13 @@ /** * Version details. * - * @package tool - * @subpackage profiling + * @package tool_profiling * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2013050100; // The current plugin version (Date: YYYYMMDDXX) +$plugin->version = 2013050200; // The current plugin version (Date: YYYYMMDDXX) $plugin->requires = 2013050100; // Requires this Moodle version $plugin->component = 'tool_profiling'; // Full name of the plugin (used for diagnostics) diff --git a/lang/en/admin.php b/lang/en/admin.php index b1c5126c3de..0b35240302e 100644 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -873,6 +873,8 @@ $string['profilingenabled'] = 'Enable profiling'; $string['profilingenabled_help'] = 'If you enable this setting, then profiling will be available in this site and you will be able to define its behavior by configuring the next options.'; $string['profilingexcluded'] = 'Exclude profiling'; $string['profilingexcluded_help'] = 'List of (comma separated, absolute skipping wwwroot, callable) URLs that will be excluded from being profiled from the ones defined by \'Profile these\' setting.'; +$string['profilingimportprefix'] = 'Profiling import prefix'; +$string['profilingimportprefix_desc'] = 'For easier detection, all the imported profiling runs will be prefixed with the value specified here.'; $string['profilingincluded'] = 'Profile these'; $string['profilingincluded_help'] = 'List of (comma separated, absolute skipping wwwroot, callable) URLs that will be automatically profiled. Examples: /index.php, /course/view.php. Also accepts the * wildchar at any position. Examples: /mod/forum/*, /mod/*/view.php.'; $string['profilinglifetime'] = 'Keep profiling runs'; diff --git a/lib/filelib.php b/lib/filelib.php index 63cddfdd878..6c9096838e8 100644 --- a/lib/filelib.php +++ b/lib/filelib.php @@ -1516,6 +1516,7 @@ function &get_mimetypes_array() { 'mpeg' => array ('type'=>'video/mpeg', 'icon'=>'mpeg', 'groups'=>array('video','web_video'), 'string'=>'video'), 'mpe' => array ('type'=>'video/mpeg', 'icon'=>'mpeg', 'groups'=>array('video','web_video'), 'string'=>'video'), 'mpg' => array ('type'=>'video/mpeg', 'icon'=>'mpeg', 'groups'=>array('video','web_video'), 'string'=>'video'), + 'mpr' => array ('type'=>'application/vnd.moodle.profiling', 'icon'=>'moodle'), 'nbk' => array ('type'=>'application/x-smarttech-notebook', 'icon'=>'archive'), 'notebook' => array ('type'=>'application/x-smarttech-notebook', 'icon'=>'archive'), diff --git a/lib/moodlelib.php b/lib/moodlelib.php index 976059134cf..1b4581fe3b1 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -6057,6 +6057,7 @@ function get_file_packer($mimetype='application/zip') { switch ($mimetype) { case 'application/zip': case 'application/vnd.moodle.backup': + case 'application/vnd.moodle.profiling': $classname = 'zip_packer'; break; case 'application/x-tar': diff --git a/lib/xhprof/readme_moodle.txt b/lib/xhprof/readme_moodle.txt index f732a368f5b..8578a8e2d96 100644 --- a/lib/xhprof/readme_moodle.txt +++ b/lib/xhprof/readme_moodle.txt @@ -24,9 +24,6 @@ TODO: * with the 3 reports (index, callgraph and typeahead), close seesion asap, so user can continue working with moodle while the report (specially the graph is being generated). - * export/import profiling runs: Allow to pick any profile record, encapsulate - it into some serialized/encoded way and allow download/upload. It requires - DB changes in order to be able to specify the source of each record (own/imported). * improvements to the listing mode: various commodity details like: - allow to filter by various criteria - inline (and ajax) editing of reference/comment and deleting @@ -36,6 +33,9 @@ TODO: - memory - cpu times (all them are right now enabled for everybody by default) + * allow multiple runs to be exported together (right now only ONE can be + exported at a time). Note it is only an UI restriction, backend supports multiple. 20101122 - MDL-24600 - Eloy Lafuente (stronk7): Original import of 0.9.2 release 20110318 - MDL-26891 - Eloy Lafuente (stronk7): Implemented earlier profiling runs +20130621 - MDL-39733 - Eloy Lafuente (stronk7): Export & import of profiling runs diff --git a/lib/xhprof/xhprof_moodle.php b/lib/xhprof/xhprof_moodle.php index f8fab17a707..e013c83d287 100644 --- a/lib/xhprof/xhprof_moodle.php +++ b/lib/xhprof/xhprof_moodle.php @@ -423,7 +423,7 @@ function profiling_get_difference($number1, $number2, $units = '', $factor = 1, * Export profiling runs to a .mpr (moodle profile runs) file. * * This function gets an array of profiling runs (array of runids) and - * saves a .mpr file into destinantion for ulterior handling. + * saves a .mpr file into destination for ulterior handling. * * Format of .mpr files: * mpr files are simple zip packages containing these files: @@ -435,12 +435,12 @@ function profiling_get_difference($number1, $number2, $units = '', $factor = 1, * - runid.xml: One file per each run detailed in the main file, * containing the raw dump of the given runid in the profiling table. * - * Posible improvement: Start storing some extra information in the + * Possible improvement: Start storing some extra information in the * profiling table for each run (moodle version, database, git hash...). * * @param array $runids list of runids to be exported. * @param string $file filesystem fullpath to destination .mpr file. - * @return boolean the mpr file has been succesfully exported (true) or no (false). + * @return boolean the mpr file has been successfully exported (true) or no (false). */ function profiling_export_runs(array $runids, $file) { global $CFG, $DB; @@ -475,7 +475,7 @@ function profiling_export_runs(array $runids, $file) { $status = profiling_export_package($file, $tmpdir); } - // Process finished ok, clean and return + // Process finished ok, clean and return. fulldelete($tmpdir); return $status; } @@ -487,10 +487,86 @@ function profiling_export_runs(array $runids, $file) { * implementation of .mpr files. * * @param string $file filesystem fullpath to target .mpr file. - * @return boolean the mpr file has been succesfully imported (true) or no (false). + * @param string $commentprefix prefix to add to the comments of all the imported runs. + * @return boolean the mpr file has been successfully imported (true) or no (false). */ -function profiling_import_runs($file) { - return true; +function profiling_import_runs($file, $commentprefix = '') { + global $DB; + + // Any problem with the file or its directory, abort. + if (!file_exists($file) or !is_readable($file) or !is_writable(dirname($file))) { + return false; + } + + // Unzip the file into temp directory. + $tmpdir = dirname($file) . '/' . time() . '_' . random_string(4); + $fp = get_file_packer('application/vnd.moodle.profiling'); + $status = $fp->extract_to_pathname($file, $tmpdir); + + // Look for master file and verify its format. + if ($status) { + $mfile = $tmpdir . '/moodle_profiling_runs.xml'; + if (!file_exists($mfile) or !is_readable($mfile)) { + $status = false; + } else { + $mdom = new DOMDocument(); + if (!$mdom->load($mfile)) { + $status = false; + } else { + $status = @$mdom->schemaValidateSource(profiling_get_import_main_schema()); + } + } + } + + // Verify all detail files exist and verify their format. + if ($status) { + $runs = $mdom->getElementsByTagName('run'); + foreach ($runs as $run) { + $rfile = $tmpdir . '/' . clean_param($run->getAttribute('ref'), PARAM_FILE); + if (!file_exists($rfile) or !is_readable($rfile)) { + $status = false; + } else { + $rdom = new DOMDocument(); + if (!$rdom->load($rfile)) { + $status = false; + } else { + $status = @$rdom->schemaValidateSource(profiling_get_import_run_schema()); + } + } + } + } + + // Everything looks ok, let's import all the runs. + if ($status) { + reset($runs); + foreach ($runs as $run) { + $rfile = $tmpdir . '/' . $run->getAttribute('ref'); + $rdom = new DOMDocument(); + $rdom->load($rfile); + $runarr = array(); + $runarr['runid'] = clean_param($rdom->getElementsByTagName('runid')->item(0)->nodeValue, PARAM_ALPHANUMEXT); + $runarr['url'] = clean_param($rdom->getElementsByTagName('url')->item(0)->nodeValue, PARAM_CLEAN); + $runarr['runreference'] = clean_param($rdom->getElementsByTagName('runreference')->item(0)->nodeValue, PARAM_INT); + $runarr['runcomment'] = $commentprefix . clean_param($rdom->getElementsByTagName('runcomment')->item(0)->nodeValue, PARAM_CLEAN); + $runarr['timecreated'] = time(); // Now. + $runarr['totalexecutiontime'] = clean_param($rdom->getElementsByTagName('totalexecutiontime')->item(0)->nodeValue, PARAM_INT); + $runarr['totalcputime'] = clean_param($rdom->getElementsByTagName('totalcputime')->item(0)->nodeValue, PARAM_INT); + $runarr['totalcalls'] = clean_param($rdom->getElementsByTagName('totalcalls')->item(0)->nodeValue, PARAM_INT); + $runarr['totalmemory'] = clean_param($rdom->getElementsByTagName('totalmemory')->item(0)->nodeValue, PARAM_INT); + $runarr['data'] = clean_param($rdom->getElementsByTagName('data')->item(0)->nodeValue, PARAM_CLEAN); + // If the runid does not exist, insert it. + if (!$DB->record_exists('profiling', array('runid' => $runarr['runid']))) { + $DB->insert_record('profiling', $runarr); + } else { + return false; + } + } + } + + // Clean the temp directory used for import. + remove_dir($tmpdir); + + return $status; } /** @@ -523,7 +599,9 @@ function profiling_export_generate(array $runids, $tmpdir) { $mainxw->full_tag('release', $release); $mainxw->full_tag('version', $version); $mainxw->full_tag('dbtype', $dbtype); - $mainxw->full_tag('githash', $githash); + if ($githash) { + $mainxw->full_tag('githash', $githash); + } $mainxw->full_tag('date', $date); $mainxw->end_tag('info'); @@ -588,6 +666,83 @@ function profiling_export_package($file, $tmpdir) { return true; } +/** + * Return the xml schema for the main import file. + * + * @return string + * + */ +function profiling_get_import_main_schema() { + $schema = << + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +EOS; + return $schema; +} + +/** + * Return the xml schema for each individual run import file. + * + * @return string + * + */ +function profiling_get_import_run_schema() { + $schema = << + + + + + + + + + + + + + + + + + + +EOS; + return $schema; +} /** * Custom implementation of iXHProfRuns *