MDL-24600 profiling - add xhprof integration
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* @package core
|
||||
* @subpackage profiling
|
||||
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
// TODO: Move all the DB stuff to profiling_db_xxxx() function in xhprof_moodle.php
|
||||
|
||||
require_once(dirname(__FILE__).'/../../../config.php');
|
||||
require_once($CFG->libdir.'/adminlib.php');
|
||||
require_once($CFG->libdir . '/xhprof/xhprof_moodle.php');
|
||||
|
||||
define('PROFILING_RUNSPERPAGE', 50);
|
||||
|
||||
// page parameters
|
||||
$script = optional_param('script', null, PARAM_PATH);
|
||||
$runid = optional_param('runid', null, PARAM_ALPHANUM);
|
||||
$runid2 = optional_param('runid2', null, PARAM_ALPHANUM);
|
||||
$listurl = optional_param('listurl', null, PARAM_PATH);
|
||||
$runreference= optional_param('runreference', 0, PARAM_INT);
|
||||
$runcomment = optional_param('runcomment', null, PARAM_TEXT);
|
||||
|
||||
$dbfields = 'runid, url, totalexecutiontime, totalcputime, ' .
|
||||
'totalcalls, totalmemory, runreference, runcomment, timecreated';
|
||||
|
||||
admin_externalpage_setup('reportprofiling');
|
||||
|
||||
// Always add listurl if available
|
||||
if ($listurl) {
|
||||
$listurlnav = new moodle_url('/admin/report/profiling/index.php', array('listurl' => $listurl));
|
||||
$PAGE->navbar->add($listurl, $listurlnav);
|
||||
}
|
||||
|
||||
// Header
|
||||
echo $OUTPUT->header();
|
||||
|
||||
// We have requested the last available run for one script
|
||||
if (isset($script)) {
|
||||
// Get the last available run for the given script
|
||||
$run = $DB->get_record_sql("SELECT $dbfields
|
||||
FROM {profiling}
|
||||
WHERE url = ?
|
||||
AND id = (SELECT MAX(id)
|
||||
FROM {profiling}
|
||||
WHERE url = ?)",
|
||||
array($script, $script), IGNORE_MISSING);
|
||||
|
||||
// No run found for script, warn and exit
|
||||
if (!$run) {
|
||||
notice(get_string('cannotfindanyrunforurl', 'report_profiling', $script), 'index.php');
|
||||
}
|
||||
|
||||
// Check if there is any previous run marked as reference one
|
||||
$prevreferences = $DB->get_records_select('profiling',
|
||||
'url = ? AND runreference = 1 AND timecreated < ?',
|
||||
array($run->url, $run->timecreated),
|
||||
'timecreated DESC', 'runid', 0, 1);
|
||||
$prevrunid = $prevreferences ? reset($prevreferences)->runid : false;
|
||||
echo $OUTPUT->box_start('generalbox boxwidthwide boxaligncenter');
|
||||
$header = get_string('lastrunof', 'report_profiling', $script);
|
||||
echo $OUTPUT->heading($header);
|
||||
$table = profiling_print_run($run, $prevrunid);
|
||||
echo $table;
|
||||
echo $OUTPUT->box_end();
|
||||
|
||||
|
||||
// We have requested the diff between 2 runs
|
||||
} else if (isset($runid) && isset($runid2)) {
|
||||
$run1 = $DB->get_record('profiling', array('runid'=>$runid), $dbfields, MUST_EXIST);
|
||||
$run2 = $DB->get_record('profiling', array('runid'=>$runid2), $dbfields, MUST_EXIST);
|
||||
if ($run1->url == $run2->url && $run1->runid != $run2->runid) {
|
||||
if ($run2->timecreated < $run1->timecreated) {
|
||||
$runtemp = $run1;
|
||||
$run1 = $run2;
|
||||
$run2 = $runtemp;
|
||||
}
|
||||
echo $OUTPUT->box_start('generalbox boxwidthwide boxaligncenter');
|
||||
$header = get_string('differencesbetween2runsof', 'report_profiling', $run1->url);
|
||||
echo $OUTPUT->heading($header);
|
||||
$table = profiling_print_rundiff($run1, $run2);
|
||||
echo $table;
|
||||
echo $OUTPUT->box_end();
|
||||
}
|
||||
|
||||
|
||||
// We have requested one run, invoke it
|
||||
} else if (isset($runid)) {
|
||||
// Check if we are trying to update the runreference/runcomment for the run
|
||||
if (isset($runcomment) && confirm_sesskey()) {
|
||||
$id = $DB->get_field('profiling', 'id', array('runid' => $runid), MUST_EXIST);
|
||||
$rec = new stdClass();
|
||||
$rec->id = $id;
|
||||
$rec->runreference = (bool)$runreference;
|
||||
$rec->runcomment = $runcomment;
|
||||
$DB->update_record('profiling', $rec);
|
||||
}
|
||||
// Get the requested runid
|
||||
$run = $DB->get_record('profiling', array('runid'=>$runid), $dbfields, IGNORE_MISSING);
|
||||
|
||||
// No run found for runid, warn and exit
|
||||
if (!$run) {
|
||||
notice(get_string('cannotfindanyrunforrunid', 'report_profiling', $runid), 'index.php');
|
||||
}
|
||||
|
||||
// Check if there is any previous run marked as reference one
|
||||
$prevreferences = $DB->get_records_select('profiling',
|
||||
'url = ? AND runreference = 1 AND timecreated < ?',
|
||||
array($run->url, $run->timecreated),
|
||||
'timecreated DESC', 'runid', 0, 1);
|
||||
$prevrunid = $prevreferences ? reset($prevreferences)->runid : false;
|
||||
echo $OUTPUT->box_start('generalbox boxwidthwide boxaligncenter');
|
||||
$header = get_string('summaryof', 'report_profiling', $run->url);
|
||||
echo $OUTPUT->heading($header);
|
||||
$table = profiling_print_run($run, $prevrunid);
|
||||
echo $table;
|
||||
echo $OUTPUT->box_end();
|
||||
|
||||
|
||||
// Default: List one page of runs
|
||||
} else {
|
||||
|
||||
// The flexitable that will root listings
|
||||
$table = new xhprof_table_sql('profiling-list-table');
|
||||
$baseurl = $CFG->wwwroot . '/admin/report/profiling/index.php';
|
||||
|
||||
// Check if we are listing all or some URL ones
|
||||
$sqlconditions = '';
|
||||
$sqlparams = array();
|
||||
if (!isset($listurl)) {
|
||||
$header = get_string('pluginname', 'report_profiling');
|
||||
$sqlconditions = '1 = 1';
|
||||
$table->set_listurlmode(false);
|
||||
} else {
|
||||
$header = get_string('profilingrunsfor', 'report_profiling', $listurl);
|
||||
$sqlconditions = 'url = :url';
|
||||
$sqlparams['url'] = $listurl;
|
||||
$table->set_listurlmode(true);
|
||||
$baseurl .= '?listurl=' . urlencode($listurl);
|
||||
}
|
||||
|
||||
echo $OUTPUT->heading($header);
|
||||
|
||||
// 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
|
||||
$table->set_sql($dbfields, '{profiling}', $sqlconditions, $sqlparams);
|
||||
$table->set_count_sql("SELECT COUNT(*) FROM {profiling} WHERE $sqlconditions", $sqlparams);
|
||||
$columns = array(
|
||||
'url', 'timecreated', 'totalexecutiontime', 'totalcputime',
|
||||
'totalcalls', 'totalmemory', 'runcomment');
|
||||
$headers = array(
|
||||
get_string('url'), get_string('date'), get_string('executiontime', 'report_profiling'),
|
||||
get_string('cputime', 'report_profiling'), get_string('calls', 'report_profiling'),
|
||||
get_string('memory', 'report_profiling'), get_string('comment', 'report_profiling'));
|
||||
$table->define_columns($columns);
|
||||
$table->define_headers($headers);
|
||||
$table->sortable(true, 'timecreated', SORT_DESC);
|
||||
$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.
|
||||
echo $OUTPUT->footer();
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Strings for component 'report_profiling', language 'en', branch 'MOODLE_20_STABLE'
|
||||
*
|
||||
* @package report_profiling
|
||||
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$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['differencesbetween2runsof'] = 'Differences between 2 runs of {$a}';
|
||||
$string['executiontime'] = 'Execution time';
|
||||
$string['cputime'] = 'CPU time';
|
||||
$string['lastrunof'] = 'Summary of last run of {$a}';
|
||||
$string['markreferencerun'] = 'Mark as reference run/comment';
|
||||
$string['memory'] = 'Memory used';
|
||||
$string['pluginname'] = 'Profiling runs';
|
||||
$string['profilingfocusscript'] = 'Focus on profiling runs for the script: {$a}';
|
||||
$string['profilingruns'] = 'Profiling runs';
|
||||
$string['profilingrunsfor'] = 'Profiling runs for {$a}';
|
||||
$string['referencerun'] = 'Reference run/comment';
|
||||
$string['runid'] = 'Run ID';
|
||||
$string['summaryof'] = 'Summary of {$a}';
|
||||
$string['viewdetails'] = 'View profiling details';
|
||||
$string['viewdiff'] = 'View profiling differences with last reference run';
|
||||
$string['viewdiffdetails'] = 'View profiling diff details';
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
// profiling report, added to development
|
||||
if (extension_loaded('xhprof') && function_exists('xhprof_enable') && !empty($CFG->profilingenabled)) {
|
||||
$ADMIN->add('development', new admin_externalpage('reportprofiling', get_string('pluginname', 'report_profiling'), "$CFG->wwwroot/$CFG->admin/report/profiling/index.php", 'moodle/site:config'));
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/* report_profiling styles */
|
||||
|
||||
.path-admin-report-profiling .profilingruntable .label {
|
||||
font-weight: bold;
|
||||
}
|
||||
.path-admin-report-profiling .profiling_worse {
|
||||
color: red;
|
||||
}
|
||||
.path-admin-report-profiling .profiling_better {
|
||||
color: green;
|
||||
}
|
||||
.path-admin-report-profiling .profiling_same {
|
||||
color: dimgrey;
|
||||
}
|
||||
.path-admin-report-profiling .profiling_important,
|
||||
.path-admin-report-profiling .flexible .referencerun {
|
||||
font-weight: bold;
|
||||
}
|
||||
.path-admin-report-profiling .flexible .r1 .cell {
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
.path-admin-report-profiling .flexible {
|
||||
margin-left:auto;
|
||||
margin-right:auto
|
||||
}
|
||||
@@ -35,6 +35,44 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
|
||||
$temp->add(new admin_setting_configcheckbox('debugpageinfo', get_string('debugpageinfo', 'admin'), get_string('configdebugpageinfo', 'admin'), 0));
|
||||
$ADMIN->add('development', $temp);
|
||||
|
||||
// "profiling" settingpage (conditionally if the 'xhprof' extension is available only)
|
||||
if (extension_loaded('xhprof') && function_exists('xhprof_enable')) {
|
||||
$temp = new admin_settingpage('profiling', get_string('profiling', 'admin'));
|
||||
// Main profiling switch
|
||||
$temp->add(new admin_setting_configcheckbox('profilingenabled', get_string('profilingenabled', 'admin'), get_string('profilingenabled_help', 'admin'), false));
|
||||
// List of URLs that will be automatically profiled
|
||||
$temp->add(new admin_setting_configtextarea('profilingincluded', get_string('profilingincluded', 'admin'), get_string('profilingincluded_help', 'admin'), ''));
|
||||
// List of URLs that won't be profiled ever
|
||||
$temp->add(new admin_setting_configtextarea('profilingexcluded', get_string('profilingexcluded', 'admin'), get_string('profilingexcluded_help', 'admin'), ''));
|
||||
// Allow random profiling each XX requests
|
||||
$temp->add(new admin_setting_configtext('profilingautofrec', get_string('profilingautofrec', 'admin'), get_string('profilingautofrec_help', 'admin'), 0, PARAM_INT));
|
||||
// Allow PROFILEME/DONTPROFILEME GPC
|
||||
$temp->add(new admin_setting_configcheckbox('profilingallowme', get_string('profilingallowme', 'admin'), get_string('profilingallowme_help', 'admin'), false));
|
||||
// Allow PROFILEALL/PROFILEALLSTOP GPC
|
||||
$temp->add(new admin_setting_configcheckbox('profilingallowall', get_string('profilingallowall', 'admin'), get_string('profilingallowall_help', 'admin'), false));
|
||||
// TODO: Allow to skip PHP functions (XHPROF_FLAGS_NO_BUILTINS)
|
||||
// TODO: Allow to skip call_user functions (ignored_functions array)
|
||||
// Specify the life time (in minutes) of profiling runs
|
||||
$temp->add(new admin_setting_configselect('profilinglifetime', get_string('profilinglifetime', 'admin'), get_string('profilinglifetime_help', 'admin'), 24*60, array(
|
||||
0 => get_string('neverdeleteruns', 'admin'),
|
||||
30*24*60 => get_string('numdays', '', 30),
|
||||
15*24*60 => get_string('numdays', '', 15),
|
||||
7*24*60 => get_string('numdays', '', 7),
|
||||
4*24*60 => get_string('numdays', '', 4),
|
||||
2*24*60 => get_string('numdays', '', 2),
|
||||
24*60 => get_string('numhours', '', 24),
|
||||
16*80 => get_string('numhours', '', 16),
|
||||
8*60 => get_string('numhours', '', 8),
|
||||
4*60 => get_string('numhours', '', 4),
|
||||
2*60 => get_string('numhours', '', 2),
|
||||
60 => get_string('numminutes', '', 60),
|
||||
30 => get_string('numminutes', '', 30),
|
||||
15 => get_string('numminutes', '', 15))));
|
||||
|
||||
// Add the 'profiling' page to admin block
|
||||
$ADMIN->add('development', $temp);
|
||||
}
|
||||
|
||||
|
||||
// XMLDB editor
|
||||
$ADMIN->add('development', new admin_externalpage('xmldbeditor', get_string('xmldbeditor'), "$CFG->wwwroot/$CFG->admin/xmldb/"));
|
||||
|
||||
@@ -12,6 +12,7 @@ $temp->add(new admin_setting_configselect('gdversion', get_string('gdversion','a
|
||||
'2' => get_string('gd2'))));
|
||||
$temp->add(new admin_setting_configexecutable('pathtodu', get_string('pathtodu', 'admin'), get_string('configpathtodu', 'admin'), ''));
|
||||
$temp->add(new admin_setting_configexecutable('aspellpath', get_string('aspellpath', 'admin'), get_string('edhelpaspellpath'), ''));
|
||||
$temp->add(new admin_setting_configexecutable('pathtodot', get_string('pathtodot', 'admin'), get_string('pathtodot_help', 'admin'), ''));
|
||||
$ADMIN->add('server', $temp);
|
||||
|
||||
|
||||
|
||||
@@ -735,6 +735,7 @@ $string['navigationupgrade'] = 'This upgrade introduces two new navigation block
|
||||
$string['navcourselimit'] = 'Course limit';
|
||||
$string['navshowallcourses'] = 'Show all courses';
|
||||
$string['navshowcategories'] = 'Show course categories';
|
||||
$string['neverdeleteruns'] = 'Never delete runs';
|
||||
$string['nobookmarksforuser'] = 'You do not have any bookmarks.';
|
||||
$string['nodatabase'] = 'No database';
|
||||
$string['nodefaultuserrolelists'] = 'Don\'t return all default role users';
|
||||
@@ -766,6 +767,8 @@ $string['pathconvert'] = 'Path of <i>convert</i> binary';
|
||||
$string['pathdvips'] = 'Path of <i>dvips</i> binary';
|
||||
$string['pathlatex'] = 'Path of <i>latex</i> binary';
|
||||
$string['pathtoclam'] = 'clam AV path';
|
||||
$string['pathtodot'] = 'Path to dot';
|
||||
$string['pathtodot_help'] = 'Path to dot. Probably something like /usr/bin/dot. To be able to generate graphics from DOT files, you must have installed the dot executable and point to it here. Note that, for now, this only used by the profiling features (Development->Profiling) built into Moodle.';
|
||||
$string['pathtodu'] = 'Path to du';
|
||||
$string['pathtopgdump'] = 'Path to pg_dump';
|
||||
$string['pathtopgdumpdesc'] = 'This is only necessary to enter if you have more than one pg_dump on your system (for example if you have more than one version of postgresql installed)';
|
||||
@@ -801,6 +804,8 @@ $string['profiledefaultchecked'] = 'Checked by default';
|
||||
$string['profiledeletecategory'] = 'Deleting a category';
|
||||
$string['profiledeletefield'] = 'Deleting a field';
|
||||
$string['profiledescription'] = 'Description of the field';
|
||||
$string['profiledscript'] = 'This script has been profiled';
|
||||
$string['profiledscriptview'] = 'View profiling information for this script';
|
||||
$string['profileeditcategory'] = 'Editing category: {$a}';
|
||||
$string['profileeditfield'] = 'Editing profile field: {$a}';
|
||||
$string['profilefield'] = 'Profile field';
|
||||
@@ -836,6 +841,21 @@ $string['profilevisible_help'] = '* Not visible - For private data only viewable
|
||||
$string['profilevisibleall'] = 'Visible to everyone';
|
||||
$string['profilevisiblenone'] = 'Not visible';
|
||||
$string['profilevisibleprivate'] = 'Visible to user';
|
||||
$string['profiling'] = 'Profiling';
|
||||
$string['profilingallowall'] = 'Continuous profiling';
|
||||
$string['profilingallowall_help'] = 'If you enable this setting, then, at any moment, you can use the PROFILEALL parameter anywhere (PGC) to enable profiling for all the executed scripts along the Moodle session life. Analogously, you can use the PROFILEALLSTOP parameter to stop it.';
|
||||
$string['profilingallowme'] = 'Selective profiling';
|
||||
$string['profilingallowme_help'] = 'If you enable this setting, then, selectively, you can use the PROFILEME parameter anywhere (PGC) and profiling for that script will happen. Analogously, you can use the DONTPROFILEME parameter to prevent profiling to happen';
|
||||
$string['profilingautofrec'] = 'Automatic profiling';
|
||||
$string['profilingautofrec_help'] = 'By configuring this setting, some request (randomly, based on the frecuency specified - 1 of XXX) will be picked and automatically profiled, storing results for further analysis. Note that this way of profiling observes the include/exclude settings. Set it to 0 to disable automatic profiling.';
|
||||
$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['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';
|
||||
$string['profilinglifetime_help'] = 'Specify the time you want to keep information about old profiling runs. Older ones will be pruned periodically. Note that this excludes any profiling run marked as \'reference run\'.';
|
||||
$string['protectusernames'] = 'Protect usernames';
|
||||
$string['proxybypass'] = 'Proxy bypass hosts';
|
||||
$string['proxyhost'] = 'Proxy host';
|
||||
|
||||
Regular → Executable
+27
-4
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<XMLDB PATH="lib/db" VERSION="20101026" COMMENT="XMLDB file for core Moodle tables"
|
||||
<XMLDB PATH="lib/db" VERSION="20101121" COMMENT="XMLDB file for core Moodle tables"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
|
||||
>
|
||||
@@ -2688,7 +2688,7 @@
|
||||
<INDEX NAME="backupid_contextid_component_filearea_itemid_ix" UNIQUE="false" FIELDS="backupid, contextid, component, filearea, itemid"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
<TABLE NAME="backup_logs" COMMENT="To store all the logs from backup and restore operations (by db logger)" PREVIOUS="backup_files_template" NEXT="course_published">
|
||||
<TABLE NAME="backup_logs" COMMENT="To store all the logs from backup and restore operations (by db logger)" PREVIOUS="backup_files_template" NEXT="profiling">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" NEXT="backupid"/>
|
||||
<FIELD NAME="backupid" TYPE="char" LENGTH="32" NOTNULL="true" SEQUENCE="false" COMMENT="backupid the log record belongs to" PREVIOUS="id" NEXT="loglevel"/>
|
||||
@@ -2704,7 +2704,30 @@
|
||||
<INDEX NAME="backupid-id" UNIQUE="true" FIELDS="backupid, id" COMMENT="for quick ordered retrieval by backupid"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
<TABLE NAME="course_published" COMMENT="Information about how and when an local courses were published to hubs" PREVIOUS="backup_logs">
|
||||
<TABLE NAME="profiling" COMMENT="Stores the results of all the profiling runs" PREVIOUS="backup_logs" NEXT="course_published">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" NEXT="runid"/>
|
||||
<FIELD NAME="runid" TYPE="char" LENGTH="32" NOTNULL="true" SEQUENCE="false" COMMENT="the unique id for this run (as generated by xhprof)" PREVIOUS="id" NEXT="url"/>
|
||||
<FIELD NAME="url" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="the url this profiling record is about (without wwwroot nor query params)" PREVIOUS="runid" NEXT="data"/>
|
||||
<FIELD NAME="data" TYPE="text" LENGTH="big" NOTNULL="true" SEQUENCE="false" COMMENT="the raw data gathered by xhprof" PREVIOUS="url" NEXT="totalexecutiontime"/>
|
||||
<FIELD NAME="totalexecutiontime" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" COMMENT="time (in microseconds) spent by the run" PREVIOUS="data" NEXT="totalcputime"/>
|
||||
<FIELD NAME="totalcputime" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" COMMENT="time (in microseconds) spent by the CPU in this run" PREVIOUS="totalexecutiontime" NEXT="totalcalls"/>
|
||||
<FIELD NAME="totalcalls" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" COMMENT="Total number of calls performed by the run" PREVIOUS="totalcputime" NEXT="totalmemory"/>
|
||||
<FIELD NAME="totalmemory" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" COMMENT="Total memory used byt the run" PREVIOUS="totalcalls" NEXT="runreference"/>
|
||||
<FIELD NAME="runreference" TYPE="int" LENGTH="2" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="Is this run a reference one" PREVIOUS="totalmemory" NEXT="runcomment"/>
|
||||
<FIELD NAME="runcomment" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="Brief comment for this run" PREVIOUS="runreference" NEXT="timecreated"/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" COMMENT="unix timestap of the creation of this run" PREVIOUS="runcomment"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id" NEXT="runid_uk"/>
|
||||
<KEY NAME="runid_uk" TYPE="unique" FIELDS="runid" PREVIOUS="primary"/>
|
||||
</KEYS>
|
||||
<INDEXES>
|
||||
<INDEX NAME="url_runreference_ix" UNIQUE="false" FIELDS="url, runreference" NEXT="timecreated_runreference_ix"/>
|
||||
<INDEX NAME="timecreated_runreference_ix" UNIQUE="false" FIELDS="timecreated, runreference" PREVIOUS="url_runreference_ix"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
<TABLE NAME="course_published" COMMENT="Information about how and when an local courses were published to hubs" PREVIOUS="profiling">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" NEXT="huburl"/>
|
||||
<FIELD NAME="huburl" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="the url of the "registered on" hub" PREVIOUS="id" NEXT="courseid"/>
|
||||
@@ -2720,4 +2743,4 @@
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
</TABLES>
|
||||
</XMLDB>
|
||||
</XMLDB>
|
||||
|
||||
@@ -5477,6 +5477,41 @@ WHERE gradeitemid IS NOT NULL AND grademax IS NOT NULL");
|
||||
upgrade_main_savepoint(true, 2010111702);
|
||||
}
|
||||
|
||||
if ($oldversion < 2010121401) {
|
||||
|
||||
// Define table profiling to be created
|
||||
$table = new xmldb_table('profiling');
|
||||
|
||||
// Adding fields to table profiling
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('runid', XMLDB_TYPE_CHAR, '32', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('url', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('data', XMLDB_TYPE_TEXT, 'big', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('totalexecutiontime', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('totalcputime', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('totalcalls', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('totalmemory', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('runreference', XMLDB_TYPE_INTEGER, '2', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
|
||||
$table->add_field('runcomment', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null);
|
||||
|
||||
// Adding keys to table profiling
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
|
||||
$table->add_key('runid_uk', XMLDB_KEY_UNIQUE, array('runid'));
|
||||
|
||||
// Adding indexes to table profiling
|
||||
$table->add_index('url_runreference_ix', XMLDB_INDEX_NOTUNIQUE, array('url', 'runreference'));
|
||||
$table->add_index('timecreated_runreference_ix', XMLDB_INDEX_NOTUNIQUE, array('timecreated', 'runreference'));
|
||||
|
||||
// Conditionally launch create table for profiling
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Main savepoint reached
|
||||
upgrade_main_savepoint(true, 2010121401);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -353,7 +353,7 @@ class core_renderer extends renderer_base {
|
||||
* @return string HTML fragment.
|
||||
*/
|
||||
public function standard_footer_html() {
|
||||
global $CFG;
|
||||
global $CFG, $SCRIPT;
|
||||
|
||||
// This function is normally called from a layout.php file in {@link header()}
|
||||
// but some of the content won't be known until later, so we return a placeholder
|
||||
@@ -367,6 +367,14 @@ class core_renderer extends renderer_base {
|
||||
$output .= '<div class="performanceinfo pageinfo">This page is: ' . $this->page->debug_summary() . '</div>';
|
||||
}
|
||||
if (debugging(null, DEBUG_DEVELOPER) and has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM))) { // Only in developer mode
|
||||
// Add link to profiling report if necessary
|
||||
if (function_exists('profiling_is_running') && profiling_is_running()) {
|
||||
$txt = get_string('profiledscript', 'admin');
|
||||
$title = get_string('profiledscriptview', 'admin');
|
||||
$url = $CFG->wwwroot . '/admin/report/profiling/index.php?script=' . urlencode($SCRIPT);
|
||||
$link= '<a title="' . $title . '" href="' . $url . '">' . $txt . '</a>';
|
||||
$output .= '<div class="profilingfooter">' . $link . '</div>';
|
||||
}
|
||||
$output .= '<div class="purgecaches"><a href="'.$CFG->wwwroot.'/admin/purgecaches.php?confirm=1&sesskey='.sesskey().'">'.get_string('purgecaches', 'admin').'</a></div>';
|
||||
}
|
||||
if (!empty($CFG->debugvalidators)) {
|
||||
|
||||
+7
-1
@@ -646,7 +646,6 @@ if (isset($_SERVER['PHP_SELF'])) {
|
||||
// initialise ME's
|
||||
initialise_fullme();
|
||||
|
||||
|
||||
// init session prevention flag - this is defined on pages that do not want session
|
||||
if (CLI_SCRIPT) {
|
||||
// no sessions in CLI scripts possible
|
||||
@@ -669,6 +668,13 @@ session_get_instance();
|
||||
$SESSION = &$_SESSION['SESSION'];
|
||||
$USER = &$_SESSION['USER'];
|
||||
|
||||
// include and start profiling if needed, and register profiling_stop as shutdown function
|
||||
if (!empty($CFG->profilingenabled)) {
|
||||
require_once($CFG->libdir . '/xhprof/xhprof_moodle.php');
|
||||
profiling_start();
|
||||
register_shutdown_function('profiling_stop');
|
||||
}
|
||||
|
||||
// Process theme change in the URL.
|
||||
if (!empty($CFG->allowthemechangeonurl) and !empty($_GET['theme'])) {
|
||||
// we have to use _GET directly because we do not want this to interfere with _POST
|
||||
|
||||
@@ -294,6 +294,13 @@
|
||||
<version>1.10</version>
|
||||
<licenseversion></licenseversion>
|
||||
</library>
|
||||
<library>
|
||||
<location>xhprof</location>
|
||||
<name>XHProf</name>
|
||||
<license>Apache</license>
|
||||
<version>0.9.2</version>
|
||||
<licenseversion>2.0</licenseversion>
|
||||
</library>
|
||||
<library>
|
||||
<location>xmlize.php</location>
|
||||
<name>XMLize</name>
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
Modification History:
|
||||
|
||||
*** NOTE ***
|
||||
DO NOT UPDATE THIS FILE. UPDATE package.xml INSTEAD.
|
||||
This file contains the CHANGELOG for the initial release. For subsequent
|
||||
releases, the CHANGLELOG is maintained in the package.xml file itself.
|
||||
Please edit package.xml instead.
|
||||
************
|
||||
|
||||
03/02/2008 kannan, checkin xhprof_html/ and xhprof_lib/ directories.
|
||||
cjiang [These contain PHP sources for the UI as well
|
||||
as various supporting libraries to compute
|
||||
"flat" info, diff reports, aggregate results
|
||||
of multiple runs, typeahead support, etc.]
|
||||
02/20/2008 kannan add basic sanity tests for the extension
|
||||
02/19/2008 kannan register constants for optional profiler flags;
|
||||
add xhprof.output_dir ini setting.
|
||||
01/22/2008 ps port cpu affinity functions to FreeBSD
|
||||
01/15/2008 kannan intercept builtins even if zend_execute_internal
|
||||
were null to begin with
|
||||
01/14/2008 kannan track builtins by default;
|
||||
fix compiler warnings with fwd decls
|
||||
12/22/2008 cjiang Further refactoring of the code for open sourcing:
|
||||
(1). Remove level 1 profiling mode.
|
||||
(2). Add xhprof_sample_enable, xhprof_sample_disable.
|
||||
(3). Unifiy function and global variable prefix.
|
||||
(4). Group relevant functions together.
|
||||
(5). Migrate change history to CHANAGELOG file.
|
||||
12/19/2008 kannan First step refactoring for open sourcing:
|
||||
(1). Put basic direcotry structure
|
||||
(2). Rename extension and function names
|
||||
(3). Add LICENCE header.
|
||||
06/17/2008 veeve use cycle_timer() for XHPROF_MODE_SAMPLED
|
||||
03/27/2008 cjiang Add a 'hash-based' filter to reduce the number
|
||||
of expensive call-stack tranversal on recursion
|
||||
detection.
|
||||
03/17/2008 kannan must not keep state on C stack to handle
|
||||
exit (which causes _zend_bailout to longjmp
|
||||
02/25/2008 kannan add xhprof_flags to toggle various metric
|
||||
collections (buitins on/off, cpu metric on/off
|
||||
memory stats on/off)
|
||||
02/14/2008 cjiang Use cycle_timer based on 'rdtsc' instruction
|
||||
on x86 machines to replace gettimeofday. rdtsc
|
||||
is extremely cheap compared with gettimeofday
|
||||
or getrusage.
|
||||
12/06/2007 veeve bump version 1.1.2,
|
||||
added hp_global_t
|
||||
added mode callbacks, made modes extensible
|
||||
added sampler mode
|
||||
12/05/2007 veeve added doc; house cleaning
|
||||
11/28/2007 kannan split include accounting into load/run_init
|
||||
11/09/2007 kannan memory usage profiling
|
||||
10/27/2007 kannan handle recursive calls, "include" operations
|
||||
10/20/2007 kannan add hierarchical profiling; incl vs. exclusive
|
||||
function times; browser based UI; diff and
|
||||
aggregation support
|
||||
10/10/2007 hzhao creation (flat function profiles)
|
||||
|
||||
Authors:
|
||||
Haiping Zhao [email protected]
|
||||
Kannan Muthukkaruppan [email protected]
|
||||
Venkat Venkataramani [email protected]
|
||||
Changhao Jiang [email protected]
|
||||
@@ -0,0 +1,12 @@
|
||||
Originally developed at Facebook, XHProf was open sourced in Mar, 2009.
|
||||
|
||||
Creators:
|
||||
Changhao Jiang
|
||||
Kannan Muthukkaruppan
|
||||
Venkat Venkataramani
|
||||
Haiping Zhao
|
||||
|
||||
Additional Contributors:
|
||||
George Cabrera - UI enhancements
|
||||
Paul Saab - FreeBSD port
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
@@ -0,0 +1,7 @@
|
||||
For installation and usage notes refer to:
|
||||
xhprof_html/docs/index.html
|
||||
|
||||
To view the latest version of the doc, go to:
|
||||
http://pecl.php.net/package/xhprof ---> [View Documentation]
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
Description of XHProf 0.9.2 library/viewer import into Moodle
|
||||
|
||||
Removed:
|
||||
* examples - examples dir removed completely
|
||||
* extension - extension dir removed completely
|
||||
* package.xml - PECL package definition removed completely
|
||||
* xhprof_html/docs - documentation dir removed completely
|
||||
|
||||
Added:
|
||||
* index.html - prevent directory browsing on misconfigured servers
|
||||
* xhprof_moodle.php - containing all the stuff needed to run the xhprof profiler within Moodle
|
||||
* readme_moodle.txt - this file ;-)
|
||||
|
||||
Our changes: Look for "moodle" in code
|
||||
* xhprof_html/index.php ----|
|
||||
* xhprof_html/callgraph.php -|=> Changed to use own DB iXHProfRuns implementation (moodle_xhprofrun)
|
||||
* xhprof_html/typeahead.php -|
|
||||
* xhprof_html/css/xhprof.css: Minor tweaks to report styles
|
||||
* xhprof_lib/utils/callgraph_utils.php: Modified to use $CFG->pathtodot
|
||||
|
||||
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).
|
||||
* move profiling start to earlier place: detect if all the needed $CFG->profilingXXX variables
|
||||
have been defined in config.php file and if that condition is fullfilled, start profiling
|
||||
@ the very first lines of setup.php (as early as possible).
|
||||
* improvements to the listing mode: various commodity details like:
|
||||
- allow to filter by various criteria
|
||||
- inline (and ajax) editing of reference/comment and deleting
|
||||
- whatever daily usage discovers ;-)
|
||||
* add new settings to control if we want to profile things like:
|
||||
- php functions
|
||||
- memory
|
||||
- cpu times
|
||||
(all them are right now enabled for everybody by default)
|
||||
|
||||
20101122 - Eloy Lafuente (stronk7): Original import of 0.9.2 release
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
// Copyright (c) 2009 Facebook
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
/**
|
||||
*
|
||||
* A callgraph generator for XHProf.
|
||||
*
|
||||
* * This file is part of the UI/reporting component,
|
||||
* used for viewing results of XHProf runs from a
|
||||
* browser.
|
||||
*
|
||||
* Modification History:
|
||||
* 02/15/2008 - cjiang - The first version of callgraph visualizer
|
||||
* based on Graphviz's DOT tool.
|
||||
*
|
||||
* @author Changhao Jiang ([email protected])
|
||||
*/
|
||||
|
||||
// start moodle modification: moodleize this script
|
||||
require_once(dirname(dirname(dirname(dirname(__FILE__)))).'/config.php');
|
||||
require_once($CFG->libdir . '/xhprof/xhprof_moodle.php');
|
||||
require_login();
|
||||
require_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM));
|
||||
// end moodle modification
|
||||
|
||||
// by default assume that xhprof_html & xhprof_lib directories
|
||||
// are at the same level.
|
||||
$GLOBALS['XHPROF_LIB_ROOT'] = dirname(__FILE__) . '/../xhprof_lib';
|
||||
|
||||
require_once $GLOBALS['XHPROF_LIB_ROOT'].'/display/xhprof.php';
|
||||
|
||||
ini_set('max_execution_time', 100);
|
||||
|
||||
$params = array(// run id param
|
||||
'run' => array(XHPROF_STRING_PARAM, ''),
|
||||
|
||||
// source/namespace/type of run
|
||||
'source' => array(XHPROF_STRING_PARAM, 'xhprof'),
|
||||
|
||||
// the focus function, if it is set, only directly
|
||||
// parents/children functions of it will be shown.
|
||||
'func' => array(XHPROF_STRING_PARAM, ''),
|
||||
|
||||
// image type, can be 'jpg', 'gif', 'ps', 'png'
|
||||
'type' => array(XHPROF_STRING_PARAM, 'png'),
|
||||
|
||||
// only functions whose exclusive time over the total time
|
||||
// is larger than this threshold will be shown.
|
||||
// default is 0.01.
|
||||
'threshold' => array(XHPROF_FLOAT_PARAM, 0.01),
|
||||
|
||||
// whether to show critical_path
|
||||
'critical' => array(XHPROF_BOOL_PARAM, true),
|
||||
|
||||
// first run in diff mode.
|
||||
'run1' => array(XHPROF_STRING_PARAM, ''),
|
||||
|
||||
// second run in diff mode.
|
||||
'run2' => array(XHPROF_STRING_PARAM, '')
|
||||
);
|
||||
|
||||
// pull values of these params, and create named globals for each param
|
||||
xhprof_param_init($params);
|
||||
|
||||
// if invalid value specified for threshold, then use the default
|
||||
if ($threshold < 0 || $threshold > 1) {
|
||||
$threshold = $params['threshold'][1];
|
||||
}
|
||||
|
||||
// if invalid value specified for type, use the default
|
||||
if (!array_key_exists($type, $xhprof_legal_image_types)) {
|
||||
$type = $params['type'][1]; // default image type.
|
||||
}
|
||||
|
||||
// start moodle modification: use own XHProfRuns implementation
|
||||
//$xhprof_runs_impl = new XHProfRuns_Default();
|
||||
require_once($GLOBALS['XHPROF_LIB_ROOT'].'/../xhprof_moodle.php');
|
||||
$xhprof_runs_impl = new moodle_xhprofrun();
|
||||
// end moodle modification
|
||||
|
||||
if (!empty($run)) {
|
||||
// single run call graph image generation
|
||||
xhprof_render_image($xhprof_runs_impl, $run, $type,
|
||||
$threshold, $func, $source, $critical);
|
||||
} else {
|
||||
// diff report call graph image generation
|
||||
xhprof_render_diff_image($xhprof_runs_impl, $run1, $run2,
|
||||
$type, $threshold, $source);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/* Copyright (c) 2009 Facebook
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* start moodle modification: add basic, smaller, font specs */
|
||||
body, p, table, li {
|
||||
font: normal normal normal 13px/1.231 arial, helvetica, clean, sans-serif;
|
||||
}
|
||||
|
||||
/* end moodle modification */
|
||||
|
||||
|
||||
td.sorted {
|
||||
color:#0000FF;
|
||||
}
|
||||
|
||||
td.vbar, th.vbar {
|
||||
text-align: right;
|
||||
border-left:
|
||||
solid 1px #bdc7d8;
|
||||
}
|
||||
|
||||
td.vbbar, th.vbar {
|
||||
text-align: right;
|
||||
border-left:
|
||||
solid 1px #bdc7d8;
|
||||
color:blue;
|
||||
}
|
||||
|
||||
/* diff reports: display regressions in red */
|
||||
td.vrbar {
|
||||
text-align: right;
|
||||
border-left:solid 1px #bdc7d8;
|
||||
color:red;
|
||||
}
|
||||
|
||||
/* diff reports: display improvements in green */
|
||||
td.vgbar {
|
||||
text-align: right;
|
||||
border-left: solid 1px #bdc7d8;
|
||||
color:green;
|
||||
}
|
||||
|
||||
td.vwbar, th.vwbar {
|
||||
text-align: right;
|
||||
border-left: solid 1px white;
|
||||
}
|
||||
|
||||
td.vwlbar, th.vwlbar {
|
||||
text-align: left;
|
||||
border-left: solid 1px white;
|
||||
}
|
||||
|
||||
p.blue {
|
||||
color:blue
|
||||
}
|
||||
|
||||
.bubble {
|
||||
background-color:#C3D9FF
|
||||
}
|
||||
|
||||
ul.xhprof_actions {
|
||||
float: right;
|
||||
padding-left: 16px;
|
||||
list-style-image: none;
|
||||
list-style-type: none;
|
||||
margin:10px 10px 10px 3em;
|
||||
position:relative;
|
||||
}
|
||||
|
||||
ul.xhprof_actions li {
|
||||
border-bottom:1px solid #D8DFEA;
|
||||
}
|
||||
|
||||
ul.xhprof_actions li a:hover {
|
||||
background:#3B5998 none repeat scroll 0 0;
|
||||
color:#FFFFFF;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
// Copyright (c) 2009 Facebook
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
//
|
||||
// XHProf: A Hierarchical Profiler for PHP
|
||||
//
|
||||
// XHProf has two components:
|
||||
//
|
||||
// * This module is the UI/reporting component, used
|
||||
// for viewing results of XHProf runs from a browser.
|
||||
//
|
||||
// * Data collection component: This is implemented
|
||||
// as a PHP extension (XHProf).
|
||||
//
|
||||
//
|
||||
//
|
||||
// @author(s) Kannan Muthukkaruppan
|
||||
// Changhao Jiang
|
||||
//
|
||||
|
||||
// start moodle modification: moodleize this script
|
||||
require_once(dirname(dirname(dirname(dirname(__FILE__)))).'/config.php');
|
||||
require_once($CFG->libdir . '/xhprof/xhprof_moodle.php');
|
||||
require_login();
|
||||
require_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM));
|
||||
// end moodle modification
|
||||
|
||||
// by default assume that xhprof_html & xhprof_lib directories
|
||||
// are at the same level.
|
||||
$GLOBALS['XHPROF_LIB_ROOT'] = dirname(__FILE__) . '/../xhprof_lib';
|
||||
|
||||
require_once $GLOBALS['XHPROF_LIB_ROOT'].'/display/xhprof.php';
|
||||
|
||||
// param name, its type, and default value
|
||||
$params = array('run' => array(XHPROF_STRING_PARAM, ''),
|
||||
'wts' => array(XHPROF_STRING_PARAM, ''),
|
||||
'symbol' => array(XHPROF_STRING_PARAM, ''),
|
||||
'sort' => array(XHPROF_STRING_PARAM, 'wt'), // wall time
|
||||
'run1' => array(XHPROF_STRING_PARAM, ''),
|
||||
'run2' => array(XHPROF_STRING_PARAM, ''),
|
||||
'source' => array(XHPROF_STRING_PARAM, 'xhprof'),
|
||||
'all' => array(XHPROF_UINT_PARAM, 0),
|
||||
);
|
||||
|
||||
// pull values of these params, and create named globals for each param
|
||||
xhprof_param_init($params);
|
||||
|
||||
/* reset params to be a array of variable names to values
|
||||
by the end of this page, param should only contain values that need
|
||||
to be preserved for the next page. unset all unwanted keys in $params.
|
||||
*/
|
||||
foreach ($params as $k => $v) {
|
||||
$params[$k] = $$k;
|
||||
|
||||
// unset key from params that are using default values. So URLs aren't
|
||||
// ridiculously long.
|
||||
if ($params[$k] == $v[1]) {
|
||||
unset($params[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
echo "<html>";
|
||||
|
||||
echo "<head><title>XHProf: Hierarchical Profiler Report</title>";
|
||||
xhprof_include_js_css();
|
||||
echo "</head>";
|
||||
|
||||
echo "<body>";
|
||||
|
||||
$vbar = ' class="vbar"';
|
||||
$vwbar = ' class="vwbar"';
|
||||
$vwlbar = ' class="vwlbar"';
|
||||
$vbbar = ' class="vbbar"';
|
||||
$vrbar = ' class="vrbar"';
|
||||
$vgbar = ' class="vgbar"';
|
||||
|
||||
// start moodle modification: use own XHProfRuns implementation
|
||||
//$xhprof_runs_impl = new XHProfRuns_Default()
|
||||
$xhprof_runs_impl = new moodle_xhprofrun();
|
||||
// end moodle modification
|
||||
|
||||
displayXHProfReport($xhprof_runs_impl, $params, $source, $run, $wts,
|
||||
$symbol, $sort, $run1, $run2);
|
||||
|
||||
|
||||
echo "</body>";
|
||||
echo "</html>";
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
+3549
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Autocomplete - jQuery plugin 1.0.2
|
||||
*
|
||||
* Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
|
||||
*
|
||||
* Dual licensed under the MIT and GPL licenses:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*
|
||||
* Revision: $Id$
|
||||
*
|
||||
*/
|
||||
|
||||
.ac_results {
|
||||
padding: 0px;
|
||||
border: 1px solid black;
|
||||
background-color: white;
|
||||
overflow: hidden;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
.ac_results ul {
|
||||
width: 100%;
|
||||
list-style-position: outside;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ac_results li {
|
||||
margin: 0px;
|
||||
padding: 2px 5px;
|
||||
cursor: default;
|
||||
display: block;
|
||||
/*
|
||||
if width will be 100% horizontal scrollbar will apear
|
||||
when scroll mode will be used
|
||||
*/
|
||||
/*width: 100%;*/
|
||||
font: menu;
|
||||
font-size: 12px;
|
||||
/*
|
||||
it is very important, if line-height not setted or setted
|
||||
in relative units scroll will be broken in firefox
|
||||
*/
|
||||
line-height: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ac_loading {
|
||||
background: white url('indicator.gif') right center no-repeat;
|
||||
}
|
||||
|
||||
.ac_odd {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.ac_over {
|
||||
background-color: #0A246A;
|
||||
color: white;
|
||||
}
|
||||
@@ -0,0 +1,759 @@
|
||||
/*
|
||||
* Autocomplete - jQuery plugin 1.0.2
|
||||
*
|
||||
* Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
|
||||
*
|
||||
* Dual licensed under the MIT and GPL licenses:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*
|
||||
* Revision: $Id: jquery.autocomplete.js,v 1.1.1.1 2009-03-17 18:35:18 kannan Exp $
|
||||
*
|
||||
*/
|
||||
|
||||
;(function($) {
|
||||
|
||||
$.fn.extend({
|
||||
autocomplete: function(urlOrData, options) {
|
||||
var isUrl = typeof urlOrData == "string";
|
||||
options = $.extend({}, $.Autocompleter.defaults, {
|
||||
url: isUrl ? urlOrData : null,
|
||||
data: isUrl ? null : urlOrData,
|
||||
delay: isUrl ? $.Autocompleter.defaults.delay : 10,
|
||||
max: options && !options.scroll ? 10 : 150
|
||||
}, options);
|
||||
|
||||
// if highlight is set to false, replace it with a do-nothing function
|
||||
options.highlight = options.highlight || function(value) { return value; };
|
||||
|
||||
// if the formatMatch option is not specified, then use formatItem for backwards compatibility
|
||||
options.formatMatch = options.formatMatch || options.formatItem;
|
||||
|
||||
return this.each(function() {
|
||||
new $.Autocompleter(this, options);
|
||||
});
|
||||
},
|
||||
result: function(handler) {
|
||||
return this.bind("result", handler);
|
||||
},
|
||||
search: function(handler) {
|
||||
return this.trigger("search", [handler]);
|
||||
},
|
||||
flushCache: function() {
|
||||
return this.trigger("flushCache");
|
||||
},
|
||||
setOptions: function(options){
|
||||
return this.trigger("setOptions", [options]);
|
||||
},
|
||||
unautocomplete: function() {
|
||||
return this.trigger("unautocomplete");
|
||||
}
|
||||
});
|
||||
|
||||
$.Autocompleter = function(input, options) {
|
||||
|
||||
var KEY = {
|
||||
UP: 38,
|
||||
DOWN: 40,
|
||||
DEL: 46,
|
||||
TAB: 9,
|
||||
RETURN: 13,
|
||||
ESC: 27,
|
||||
COMMA: 188,
|
||||
PAGEUP: 33,
|
||||
PAGEDOWN: 34,
|
||||
BACKSPACE: 8
|
||||
};
|
||||
|
||||
// Create $ object for input element
|
||||
var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
|
||||
|
||||
var timeout;
|
||||
var previousValue = "";
|
||||
var cache = $.Autocompleter.Cache(options);
|
||||
var hasFocus = 0;
|
||||
var lastKeyPressCode;
|
||||
var config = {
|
||||
mouseDownOnSelect: false
|
||||
};
|
||||
var select = $.Autocompleter.Select(options, input, selectCurrent, config);
|
||||
|
||||
var blockSubmit;
|
||||
|
||||
// prevent form submit in opera when selecting with return key
|
||||
$.browser.opera && $(input.form).bind("submit.autocomplete", function() {
|
||||
if (blockSubmit) {
|
||||
blockSubmit = false;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
|
||||
$input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
|
||||
// track last key pressed
|
||||
lastKeyPressCode = event.keyCode;
|
||||
switch(event.keyCode) {
|
||||
|
||||
case KEY.UP:
|
||||
event.preventDefault();
|
||||
if ( select.visible() ) {
|
||||
select.prev();
|
||||
} else {
|
||||
onChange(0, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.DOWN:
|
||||
event.preventDefault();
|
||||
if ( select.visible() ) {
|
||||
select.next();
|
||||
} else {
|
||||
onChange(0, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.PAGEUP:
|
||||
event.preventDefault();
|
||||
if ( select.visible() ) {
|
||||
select.pageUp();
|
||||
} else {
|
||||
onChange(0, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.PAGEDOWN:
|
||||
event.preventDefault();
|
||||
if ( select.visible() ) {
|
||||
select.pageDown();
|
||||
} else {
|
||||
onChange(0, true);
|
||||
}
|
||||
break;
|
||||
|
||||
// matches also semicolon
|
||||
case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
|
||||
case KEY.TAB:
|
||||
case KEY.RETURN:
|
||||
if( selectCurrent() ) {
|
||||
// stop default to prevent a form submit, Opera needs special handling
|
||||
event.preventDefault();
|
||||
blockSubmit = true;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.ESC:
|
||||
select.hide();
|
||||
break;
|
||||
|
||||
default:
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(onChange, options.delay);
|
||||
break;
|
||||
}
|
||||
}).focus(function(){
|
||||
// track whether the field has focus, we shouldn't process any
|
||||
// results if the field no longer has focus
|
||||
hasFocus++;
|
||||
}).blur(function() {
|
||||
hasFocus = 0;
|
||||
if (!config.mouseDownOnSelect) {
|
||||
hideResults();
|
||||
}
|
||||
}).click(function() {
|
||||
// show select when clicking in a focused field
|
||||
if ( hasFocus++ > 1 && !select.visible() ) {
|
||||
onChange(0, true);
|
||||
}
|
||||
}).bind("search", function() {
|
||||
// TODO why not just specifying both arguments?
|
||||
var fn = (arguments.length > 1) ? arguments[1] : null;
|
||||
function findValueCallback(q, data) {
|
||||
var result;
|
||||
if( data && data.length ) {
|
||||
for (var i=0; i < data.length; i++) {
|
||||
if( data[i].result.toLowerCase() == q.toLowerCase() ) {
|
||||
result = data[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( typeof fn == "function" ) fn(result);
|
||||
else $input.trigger("result", result && [result.data, result.value]);
|
||||
}
|
||||
$.each(trimWords($input.val()), function(i, value) {
|
||||
request(value, findValueCallback, findValueCallback);
|
||||
});
|
||||
}).bind("flushCache", function() {
|
||||
cache.flush();
|
||||
}).bind("setOptions", function() {
|
||||
$.extend(options, arguments[1]);
|
||||
// if we've updated the data, repopulate
|
||||
if ( "data" in arguments[1] )
|
||||
cache.populate();
|
||||
}).bind("unautocomplete", function() {
|
||||
select.unbind();
|
||||
$input.unbind();
|
||||
$(input.form).unbind(".autocomplete");
|
||||
});
|
||||
|
||||
|
||||
function selectCurrent() {
|
||||
var selected = select.selected();
|
||||
if( !selected )
|
||||
return false;
|
||||
|
||||
var v = selected.result;
|
||||
previousValue = v;
|
||||
|
||||
if ( options.multiple ) {
|
||||
var words = trimWords($input.val());
|
||||
if ( words.length > 1 ) {
|
||||
v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v;
|
||||
}
|
||||
v += options.multipleSeparator;
|
||||
}
|
||||
|
||||
$input.val(v);
|
||||
hideResultsNow();
|
||||
$input.trigger("result", [selected.data, selected.value]);
|
||||
return true;
|
||||
}
|
||||
|
||||
function onChange(crap, skipPrevCheck) {
|
||||
if( lastKeyPressCode == KEY.DEL ) {
|
||||
select.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
var currentValue = $input.val();
|
||||
|
||||
if ( !skipPrevCheck && currentValue == previousValue )
|
||||
return;
|
||||
|
||||
previousValue = currentValue;
|
||||
|
||||
currentValue = lastWord(currentValue);
|
||||
if ( currentValue.length >= options.minChars) {
|
||||
$input.addClass(options.loadingClass);
|
||||
if (!options.matchCase)
|
||||
currentValue = currentValue.toLowerCase();
|
||||
request(currentValue, receiveData, hideResultsNow);
|
||||
} else {
|
||||
stopLoading();
|
||||
select.hide();
|
||||
}
|
||||
};
|
||||
|
||||
function trimWords(value) {
|
||||
if ( !value ) {
|
||||
return [""];
|
||||
}
|
||||
var words = value.split( options.multipleSeparator );
|
||||
var result = [];
|
||||
$.each(words, function(i, value) {
|
||||
if ( $.trim(value) )
|
||||
result[i] = $.trim(value);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function lastWord(value) {
|
||||
if ( !options.multiple )
|
||||
return value;
|
||||
var words = trimWords(value);
|
||||
return words[words.length - 1];
|
||||
}
|
||||
|
||||
// fills in the input box w/the first match (assumed to be the best match)
|
||||
// q: the term entered
|
||||
// sValue: the first matching result
|
||||
function autoFill(q, sValue){
|
||||
// autofill in the complete box w/the first match as long as the user hasn't entered in more data
|
||||
// if the last user key pressed was backspace, don't autofill
|
||||
if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
|
||||
// fill in the value (keep the case the user has typed)
|
||||
$input.val($input.val() + sValue.substring(lastWord(previousValue).length));
|
||||
// select the portion of the value not typed by the user (so the next character will erase)
|
||||
$.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length);
|
||||
}
|
||||
};
|
||||
|
||||
function hideResults() {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(hideResultsNow, 200);
|
||||
};
|
||||
|
||||
function hideResultsNow() {
|
||||
var wasVisible = select.visible();
|
||||
select.hide();
|
||||
clearTimeout(timeout);
|
||||
stopLoading();
|
||||
if (options.mustMatch) {
|
||||
// call search and run callback
|
||||
$input.search(
|
||||
function (result){
|
||||
// if no value found, clear the input box
|
||||
if( !result ) {
|
||||
if (options.multiple) {
|
||||
var words = trimWords($input.val()).slice(0, -1);
|
||||
$input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
|
||||
}
|
||||
else
|
||||
$input.val( "" );
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
if (wasVisible)
|
||||
// position cursor at end of input field
|
||||
$.Autocompleter.Selection(input, input.value.length, input.value.length);
|
||||
};
|
||||
|
||||
function receiveData(q, data) {
|
||||
if ( data && data.length && hasFocus ) {
|
||||
stopLoading();
|
||||
select.display(data, q);
|
||||
autoFill(q, data[0].value);
|
||||
select.show();
|
||||
} else {
|
||||
hideResultsNow();
|
||||
}
|
||||
};
|
||||
|
||||
function request(term, success, failure) {
|
||||
if (!options.matchCase)
|
||||
term = term.toLowerCase();
|
||||
var data = cache.load(term);
|
||||
// recieve the cached data
|
||||
if (data && data.length) {
|
||||
success(term, data);
|
||||
// if an AJAX url has been supplied, try loading the data now
|
||||
} else if( (typeof options.url == "string") && (options.url.length > 0) ){
|
||||
|
||||
var extraParams = {
|
||||
timestamp: +new Date()
|
||||
};
|
||||
$.each(options.extraParams, function(key, param) {
|
||||
extraParams[key] = typeof param == "function" ? param() : param;
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
// try to leverage ajaxQueue plugin to abort previous requests
|
||||
mode: "abort",
|
||||
// limit abortion to this input
|
||||
port: "autocomplete" + input.name,
|
||||
dataType: options.dataType,
|
||||
url: options.url,
|
||||
data: $.extend({
|
||||
q: lastWord(term),
|
||||
limit: options.max
|
||||
}, extraParams),
|
||||
success: function(data) {
|
||||
var parsed = options.parse && options.parse(data) || parse(data);
|
||||
cache.add(term, parsed);
|
||||
success(term, parsed);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
|
||||
select.emptyList();
|
||||
failure(term);
|
||||
}
|
||||
};
|
||||
|
||||
function parse(data) {
|
||||
var parsed = [];
|
||||
var rows = data.split("\n");
|
||||
for (var i=0; i < rows.length; i++) {
|
||||
var row = $.trim(rows[i]);
|
||||
if (row) {
|
||||
row = row.split("|");
|
||||
parsed[parsed.length] = {
|
||||
data: row,
|
||||
value: row[0],
|
||||
result: options.formatResult && options.formatResult(row, row[0]) || row[0]
|
||||
};
|
||||
}
|
||||
}
|
||||
return parsed;
|
||||
};
|
||||
|
||||
function stopLoading() {
|
||||
$input.removeClass(options.loadingClass);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
$.Autocompleter.defaults = {
|
||||
inputClass: "ac_input",
|
||||
resultsClass: "ac_results",
|
||||
loadingClass: "ac_loading",
|
||||
minChars: 1,
|
||||
delay: 400,
|
||||
matchCase: false,
|
||||
matchSubset: true,
|
||||
matchContains: false,
|
||||
cacheLength: 10,
|
||||
max: 100,
|
||||
mustMatch: false,
|
||||
extraParams: {},
|
||||
selectFirst: true,
|
||||
formatItem: function(row) { return row[0]; },
|
||||
formatMatch: null,
|
||||
autoFill: false,
|
||||
width: 0,
|
||||
multiple: false,
|
||||
multipleSeparator: ", ",
|
||||
highlight: function(value, term) {
|
||||
return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
|
||||
},
|
||||
scroll: true,
|
||||
scrollHeight: 180
|
||||
};
|
||||
|
||||
$.Autocompleter.Cache = function(options) {
|
||||
|
||||
var data = {};
|
||||
var length = 0;
|
||||
|
||||
function matchSubset(s, sub) {
|
||||
if (!options.matchCase)
|
||||
s = s.toLowerCase();
|
||||
var i = s.indexOf(sub);
|
||||
if (i == -1) return false;
|
||||
return i == 0 || options.matchContains;
|
||||
};
|
||||
|
||||
function add(q, value) {
|
||||
if (length > options.cacheLength){
|
||||
flush();
|
||||
}
|
||||
if (!data[q]){
|
||||
length++;
|
||||
}
|
||||
data[q] = value;
|
||||
}
|
||||
|
||||
function populate(){
|
||||
if( !options.data ) return false;
|
||||
// track the matches
|
||||
var stMatchSets = {},
|
||||
nullData = 0;
|
||||
|
||||
// no url was specified, we need to adjust the cache length to make sure it fits the local data store
|
||||
if( !options.url ) options.cacheLength = 1;
|
||||
|
||||
// track all options for minChars = 0
|
||||
stMatchSets[""] = [];
|
||||
|
||||
// loop through the array and create a lookup structure
|
||||
for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
|
||||
var rawValue = options.data[i];
|
||||
// if rawValue is a string, make an array otherwise just reference the array
|
||||
rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
|
||||
|
||||
var value = options.formatMatch(rawValue, i+1, options.data.length);
|
||||
if ( value === false )
|
||||
continue;
|
||||
|
||||
var firstChar = value.charAt(0).toLowerCase();
|
||||
// if no lookup array for this character exists, look it up now
|
||||
if( !stMatchSets[firstChar] )
|
||||
stMatchSets[firstChar] = [];
|
||||
|
||||
// if the match is a string
|
||||
var row = {
|
||||
value: value,
|
||||
data: rawValue,
|
||||
result: options.formatResult && options.formatResult(rawValue) || value
|
||||
};
|
||||
|
||||
// push the current match into the set list
|
||||
stMatchSets[firstChar].push(row);
|
||||
|
||||
// keep track of minChars zero items
|
||||
if ( nullData++ < options.max ) {
|
||||
stMatchSets[""].push(row);
|
||||
}
|
||||
};
|
||||
|
||||
// add the data items to the cache
|
||||
$.each(stMatchSets, function(i, value) {
|
||||
// increase the cache size
|
||||
options.cacheLength++;
|
||||
// add to the cache
|
||||
add(i, value);
|
||||
});
|
||||
}
|
||||
|
||||
// populate any existing data
|
||||
setTimeout(populate, 25);
|
||||
|
||||
function flush(){
|
||||
data = {};
|
||||
length = 0;
|
||||
}
|
||||
|
||||
return {
|
||||
flush: flush,
|
||||
add: add,
|
||||
populate: populate,
|
||||
load: function(q) {
|
||||
if (!options.cacheLength || !length)
|
||||
return null;
|
||||
/*
|
||||
* if dealing w/local data and matchContains than we must make sure
|
||||
* to loop through all the data collections looking for matches
|
||||
*/
|
||||
if( !options.url && options.matchContains ){
|
||||
// track all matches
|
||||
var csub = [];
|
||||
// loop through all the data grids for matches
|
||||
for( var k in data ){
|
||||
// don't search through the stMatchSets[""] (minChars: 0) cache
|
||||
// this prevents duplicates
|
||||
if( k.length > 0 ){
|
||||
var c = data[k];
|
||||
$.each(c, function(i, x) {
|
||||
// if we've got a match, add it to the array
|
||||
if (matchSubset(x.value, q)) {
|
||||
csub.push(x);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return csub;
|
||||
} else
|
||||
// if the exact item exists, use it
|
||||
if (data[q]){
|
||||
return data[q];
|
||||
} else
|
||||
if (options.matchSubset) {
|
||||
for (var i = q.length - 1; i >= options.minChars; i--) {
|
||||
var c = data[q.substr(0, i)];
|
||||
if (c) {
|
||||
var csub = [];
|
||||
$.each(c, function(i, x) {
|
||||
if (matchSubset(x.value, q)) {
|
||||
csub[csub.length] = x;
|
||||
}
|
||||
});
|
||||
return csub;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
$.Autocompleter.Select = function (options, input, select, config) {
|
||||
var CLASSES = {
|
||||
ACTIVE: "ac_over"
|
||||
};
|
||||
|
||||
var listItems,
|
||||
active = -1,
|
||||
data,
|
||||
term = "",
|
||||
needsInit = true,
|
||||
element,
|
||||
list;
|
||||
|
||||
// Create results
|
||||
function init() {
|
||||
if (!needsInit)
|
||||
return;
|
||||
element = $("<div/>")
|
||||
.hide()
|
||||
.addClass(options.resultsClass)
|
||||
.css("position", "absolute")
|
||||
.appendTo(document.body);
|
||||
|
||||
list = $("<ul/>").appendTo(element).mouseover( function(event) {
|
||||
if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
|
||||
active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
|
||||
$(target(event)).addClass(CLASSES.ACTIVE);
|
||||
}
|
||||
}).click(function(event) {
|
||||
$(target(event)).addClass(CLASSES.ACTIVE);
|
||||
select();
|
||||
// TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
|
||||
input.focus();
|
||||
return false;
|
||||
}).mousedown(function() {
|
||||
config.mouseDownOnSelect = true;
|
||||
}).mouseup(function() {
|
||||
config.mouseDownOnSelect = false;
|
||||
});
|
||||
|
||||
if( options.width > 0 )
|
||||
element.css("width", options.width);
|
||||
|
||||
needsInit = false;
|
||||
}
|
||||
|
||||
function target(event) {
|
||||
var element = event.target;
|
||||
while(element && element.tagName != "LI")
|
||||
element = element.parentNode;
|
||||
// more fun with IE, sometimes event.target is empty, just ignore it then
|
||||
if(!element)
|
||||
return [];
|
||||
return element;
|
||||
}
|
||||
|
||||
function moveSelect(step) {
|
||||
listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
|
||||
movePosition(step);
|
||||
var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
|
||||
if(options.scroll) {
|
||||
var offset = 0;
|
||||
listItems.slice(0, active).each(function() {
|
||||
offset += this.offsetHeight;
|
||||
});
|
||||
if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
|
||||
list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
|
||||
} else if(offset < list.scrollTop()) {
|
||||
list.scrollTop(offset);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function movePosition(step) {
|
||||
active += step;
|
||||
if (active < 0) {
|
||||
active = listItems.size() - 1;
|
||||
} else if (active >= listItems.size()) {
|
||||
active = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function limitNumberOfItems(available) {
|
||||
return options.max && options.max < available
|
||||
? options.max
|
||||
: available;
|
||||
}
|
||||
|
||||
function fillList() {
|
||||
list.empty();
|
||||
var max = limitNumberOfItems(data.length);
|
||||
for (var i=0; i < max; i++) {
|
||||
if (!data[i])
|
||||
continue;
|
||||
var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
|
||||
if ( formatted === false )
|
||||
continue;
|
||||
var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
|
||||
$.data(li, "ac_data", data[i]);
|
||||
}
|
||||
listItems = list.find("li");
|
||||
if ( options.selectFirst ) {
|
||||
listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
|
||||
active = 0;
|
||||
}
|
||||
// apply bgiframe if available
|
||||
if ( $.fn.bgiframe )
|
||||
list.bgiframe();
|
||||
}
|
||||
|
||||
return {
|
||||
display: function(d, q) {
|
||||
init();
|
||||
data = d;
|
||||
term = q;
|
||||
fillList();
|
||||
},
|
||||
next: function() {
|
||||
moveSelect(1);
|
||||
},
|
||||
prev: function() {
|
||||
moveSelect(-1);
|
||||
},
|
||||
pageUp: function() {
|
||||
if (active != 0 && active - 8 < 0) {
|
||||
moveSelect( -active );
|
||||
} else {
|
||||
moveSelect(-8);
|
||||
}
|
||||
},
|
||||
pageDown: function() {
|
||||
if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
|
||||
moveSelect( listItems.size() - 1 - active );
|
||||
} else {
|
||||
moveSelect(8);
|
||||
}
|
||||
},
|
||||
hide: function() {
|
||||
element && element.hide();
|
||||
listItems && listItems.removeClass(CLASSES.ACTIVE);
|
||||
active = -1;
|
||||
},
|
||||
visible : function() {
|
||||
return element && element.is(":visible");
|
||||
},
|
||||
current: function() {
|
||||
return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
|
||||
},
|
||||
show: function() {
|
||||
var offset = $(input).offset();
|
||||
element.css({
|
||||
width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
|
||||
top: offset.top + input.offsetHeight,
|
||||
left: offset.left
|
||||
}).show();
|
||||
if(options.scroll) {
|
||||
list.scrollTop(0);
|
||||
list.css({
|
||||
maxHeight: options.scrollHeight,
|
||||
overflow: 'auto'
|
||||
});
|
||||
|
||||
if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
|
||||
var listHeight = 0;
|
||||
listItems.each(function() {
|
||||
listHeight += this.offsetHeight;
|
||||
});
|
||||
var scrollbarsVisible = listHeight > options.scrollHeight;
|
||||
list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
|
||||
if (!scrollbarsVisible) {
|
||||
// IE doesn't recalculate width when scrollbar disappears
|
||||
listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
selected: function() {
|
||||
var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
|
||||
return selected && selected.length && $.data(selected[0], "ac_data");
|
||||
},
|
||||
emptyList: function (){
|
||||
list && list.empty();
|
||||
},
|
||||
unbind: function() {
|
||||
element && element.remove();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
$.Autocompleter.Selection = function(field, start, end) {
|
||||
if( field.createTextRange ){
|
||||
var selRange = field.createTextRange();
|
||||
selRange.collapse(true);
|
||||
selRange.moveStart("character", start);
|
||||
selRange.moveEnd("character", end);
|
||||
selRange.select();
|
||||
} else if( field.setSelectionRange ){
|
||||
field.setSelectionRange(start, end);
|
||||
} else {
|
||||
if( field.selectionStart ){
|
||||
field.selectionStart = start;
|
||||
field.selectionEnd = end;
|
||||
}
|
||||
}
|
||||
field.focus();
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* jQuery Tooltip plugin 1.3
|
||||
*
|
||||
* http://bassistance.de/jquery-plugins/jquery-plugin-tooltip/
|
||||
* http://docs.jquery.com/Plugins/Tooltip
|
||||
*
|
||||
* Copyright (c) 2006 - 2008 Jörn Zaefferer
|
||||
*
|
||||
* $Id$
|
||||
*
|
||||
* Dual licensed under the MIT and GPL licenses:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
#tooltip {
|
||||
position: absolute;
|
||||
z-index: 3000;
|
||||
border: 1px solid #111;
|
||||
background-color: lightyellow;
|
||||
padding: 5px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
#tooltip h3, #tooltip div { margin: 0; }
|
||||
@@ -0,0 +1,294 @@
|
||||
/*
|
||||
* jQuery Tooltip plugin 1.3
|
||||
*
|
||||
* http://bassistance.de/jquery-plugins/jquery-plugin-tooltip/
|
||||
* http://docs.jquery.com/Plugins/Tooltip
|
||||
*
|
||||
* Copyright (c) 2006 - 2008 Jörn Zaefferer
|
||||
*
|
||||
* $Id: jquery.tooltip.js,v 1.1.1.1 2009-03-17 18:35:18 kannan Exp $
|
||||
*
|
||||
* Dual licensed under the MIT and GPL licenses:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
;(function($) {
|
||||
|
||||
// the tooltip element
|
||||
var helper = {},
|
||||
// the current tooltipped element
|
||||
current,
|
||||
// the title of the current element, used for restoring
|
||||
title,
|
||||
// timeout id for delayed tooltips
|
||||
tID,
|
||||
// IE 5.5 or 6
|
||||
IE = $.browser.msie && /MSIE\s(5\.5|6\.)/.test(navigator.userAgent),
|
||||
// flag for mouse tracking
|
||||
track = false;
|
||||
|
||||
$.tooltip = {
|
||||
blocked: false,
|
||||
defaults: {
|
||||
delay: 200,
|
||||
fade: false,
|
||||
showURL: true,
|
||||
extraClass: "",
|
||||
top: 15,
|
||||
left: 15,
|
||||
id: "tooltip"
|
||||
},
|
||||
block: function() {
|
||||
$.tooltip.blocked = !$.tooltip.blocked;
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.extend({
|
||||
tooltip: function(settings) {
|
||||
settings = $.extend({}, $.tooltip.defaults, settings);
|
||||
createHelper(settings);
|
||||
return this.each(function() {
|
||||
$.data(this, "tooltip", settings);
|
||||
this.tOpacity = helper.parent.css("opacity");
|
||||
// copy tooltip into its own expando and remove the title
|
||||
this.tooltipText = this.title;
|
||||
$(this).removeAttr("title");
|
||||
// also remove alt attribute to prevent default tooltip in IE
|
||||
this.alt = "";
|
||||
})
|
||||
.mouseover(save)
|
||||
.mouseout(hide)
|
||||
.click(hide);
|
||||
},
|
||||
fixPNG: IE ? function() {
|
||||
return this.each(function () {
|
||||
var image = $(this).css('backgroundImage');
|
||||
if (image.match(/^url\(["']?(.*\.png)["']?\)$/i)) {
|
||||
image = RegExp.$1;
|
||||
$(this).css({
|
||||
'backgroundImage': 'none',
|
||||
'filter': "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='" + image + "')"
|
||||
}).each(function () {
|
||||
var position = $(this).css('position');
|
||||
if (position != 'absolute' && position != 'relative')
|
||||
$(this).css('position', 'relative');
|
||||
});
|
||||
}
|
||||
});
|
||||
} : function() { return this; },
|
||||
unfixPNG: IE ? function() {
|
||||
return this.each(function () {
|
||||
$(this).css({'filter': '', backgroundImage: ''});
|
||||
});
|
||||
} : function() { return this; },
|
||||
hideWhenEmpty: function() {
|
||||
return this.each(function() {
|
||||
$(this)[ $(this).html() ? "show" : "hide" ]();
|
||||
});
|
||||
},
|
||||
url: function() {
|
||||
return this.attr('href') || this.attr('src');
|
||||
}
|
||||
});
|
||||
|
||||
function createHelper(settings) {
|
||||
// there can be only one tooltip helper
|
||||
if( helper.parent )
|
||||
return;
|
||||
// create the helper, h3 for title, div for url
|
||||
helper.parent = $('<div id="' + settings.id + '"><h3></h3><div class="body"></div><div class="url"></div></div>')
|
||||
// add to document
|
||||
.appendTo(document.body)
|
||||
// hide it at first
|
||||
.hide();
|
||||
|
||||
// apply bgiframe if available
|
||||
if ( $.fn.bgiframe )
|
||||
helper.parent.bgiframe();
|
||||
|
||||
// save references to title and url elements
|
||||
helper.title = $('h3', helper.parent);
|
||||
helper.body = $('div.body', helper.parent);
|
||||
helper.url = $('div.url', helper.parent);
|
||||
}
|
||||
|
||||
function settings(element) {
|
||||
return $.data(element, "tooltip");
|
||||
}
|
||||
|
||||
// main event handler to start showing tooltips
|
||||
function handle(event) {
|
||||
// show helper, either with timeout or on instant
|
||||
if( settings(this).delay )
|
||||
tID = setTimeout(show, settings(this).delay);
|
||||
else
|
||||
show();
|
||||
|
||||
// if selected, update the helper position when the mouse moves
|
||||
track = !!settings(this).track;
|
||||
$(document.body).bind('mousemove', update);
|
||||
|
||||
// update at least once
|
||||
update(event);
|
||||
}
|
||||
|
||||
// save elements title before the tooltip is displayed
|
||||
function save() {
|
||||
// if this is the current source, or it has no title (occurs with click event), stop
|
||||
if ( $.tooltip.blocked || this == current || (!this.tooltipText && !settings(this).bodyHandler) )
|
||||
return;
|
||||
|
||||
// save current
|
||||
current = this;
|
||||
title = this.tooltipText;
|
||||
|
||||
if ( settings(this).bodyHandler ) {
|
||||
helper.title.hide();
|
||||
var bodyContent = settings(this).bodyHandler.call(this);
|
||||
if (bodyContent.nodeType || bodyContent.jquery) {
|
||||
helper.body.empty().append(bodyContent)
|
||||
} else {
|
||||
helper.body.html( bodyContent );
|
||||
}
|
||||
helper.body.show();
|
||||
} else if ( settings(this).showBody ) {
|
||||
var parts = title.split(settings(this).showBody);
|
||||
helper.title.html(parts.shift()).show();
|
||||
helper.body.empty();
|
||||
for(var i = 0, part; (part = parts[i]); i++) {
|
||||
if(i > 0)
|
||||
helper.body.append("<br/>");
|
||||
helper.body.append(part);
|
||||
}
|
||||
helper.body.hideWhenEmpty();
|
||||
} else {
|
||||
helper.title.html(title).show();
|
||||
helper.body.hide();
|
||||
}
|
||||
|
||||
// if element has href or src, add and show it, otherwise hide it
|
||||
if( settings(this).showURL && $(this).url() )
|
||||
helper.url.html( $(this).url().replace('http://', '') ).show();
|
||||
else
|
||||
helper.url.hide();
|
||||
|
||||
// add an optional class for this tip
|
||||
helper.parent.addClass(settings(this).extraClass);
|
||||
|
||||
// fix PNG background for IE
|
||||
if (settings(this).fixPNG )
|
||||
helper.parent.fixPNG();
|
||||
|
||||
handle.apply(this, arguments);
|
||||
}
|
||||
|
||||
// delete timeout and show helper
|
||||
function show() {
|
||||
tID = null;
|
||||
if ((!IE || !$.fn.bgiframe) && settings(current).fade) {
|
||||
if (helper.parent.is(":animated"))
|
||||
helper.parent.stop().show().fadeTo(settings(current).fade, current.tOpacity);
|
||||
else
|
||||
helper.parent.is(':visible') ? helper.parent.fadeTo(settings(current).fade, current.tOpacity) : helper.parent.fadeIn(settings(current).fade);
|
||||
} else {
|
||||
helper.parent.show();
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* callback for mousemove
|
||||
* updates the helper position
|
||||
* removes itself when no current element
|
||||
*/
|
||||
function update(event) {
|
||||
if($.tooltip.blocked)
|
||||
return;
|
||||
|
||||
if (event && event.target.tagName == "OPTION") {
|
||||
return;
|
||||
}
|
||||
|
||||
// stop updating when tracking is disabled and the tooltip is visible
|
||||
if ( !track && helper.parent.is(":visible")) {
|
||||
$(document.body).unbind('mousemove', update)
|
||||
}
|
||||
|
||||
// if no current element is available, remove this listener
|
||||
if( current == null ) {
|
||||
$(document.body).unbind('mousemove', update);
|
||||
return;
|
||||
}
|
||||
|
||||
// remove position helper classes
|
||||
helper.parent.removeClass("viewport-right").removeClass("viewport-bottom");
|
||||
|
||||
var left = helper.parent[0].offsetLeft;
|
||||
var top = helper.parent[0].offsetTop;
|
||||
if (event) {
|
||||
// position the helper 15 pixel to bottom right, starting from mouse position
|
||||
left = event.pageX + settings(current).left;
|
||||
top = event.pageY + settings(current).top;
|
||||
var right='auto';
|
||||
if (settings(current).positionLeft) {
|
||||
right = $(window).width() - left;
|
||||
left = 'auto';
|
||||
}
|
||||
helper.parent.css({
|
||||
left: left,
|
||||
right: right,
|
||||
top: top
|
||||
});
|
||||
}
|
||||
|
||||
var v = viewport(),
|
||||
h = helper.parent[0];
|
||||
// check horizontal position
|
||||
if (v.x + v.cx < h.offsetLeft + h.offsetWidth) {
|
||||
left -= h.offsetWidth + 20 + settings(current).left;
|
||||
helper.parent.css({left: left + 'px'}).addClass("viewport-right");
|
||||
}
|
||||
// check vertical position
|
||||
if (v.y + v.cy < h.offsetTop + h.offsetHeight) {
|
||||
top -= h.offsetHeight + 20 + settings(current).top;
|
||||
helper.parent.css({top: top + 'px'}).addClass("viewport-bottom");
|
||||
}
|
||||
}
|
||||
|
||||
function viewport() {
|
||||
return {
|
||||
x: $(window).scrollLeft(),
|
||||
y: $(window).scrollTop(),
|
||||
cx: $(window).width(),
|
||||
cy: $(window).height()
|
||||
};
|
||||
}
|
||||
|
||||
// hide helper and restore added classes and the title
|
||||
function hide(event) {
|
||||
if($.tooltip.blocked)
|
||||
return;
|
||||
// clear timeout if possible
|
||||
if(tID)
|
||||
clearTimeout(tID);
|
||||
// no more current element
|
||||
current = null;
|
||||
|
||||
var tsettings = settings(this);
|
||||
function complete() {
|
||||
helper.parent.removeClass( tsettings.extraClass ).hide().css("opacity", "");
|
||||
}
|
||||
if ((!IE || !$.fn.bgiframe) && tsettings.fade) {
|
||||
if (helper.parent.is(':animated'))
|
||||
helper.parent.stop().fadeTo(tsettings.fade, 0, complete);
|
||||
else
|
||||
helper.parent.stop().fadeOut(tsettings.fade, complete);
|
||||
} else
|
||||
complete();
|
||||
|
||||
if( settings(this).fixPNG )
|
||||
helper.parent.unfixPNG();
|
||||
}
|
||||
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,204 @@
|
||||
/* Copyright (c) 2009 Facebook
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helper javascript functions for XHProf report tooltips.
|
||||
*
|
||||
* @author Kannan Muthukkaruppan
|
||||
*/
|
||||
|
||||
// Take a string which is actually a number in comma separated format
|
||||
// and return a string representing the absolute value of the number.
|
||||
function stringAbs(x) {
|
||||
return x.replace("-", "");
|
||||
}
|
||||
|
||||
// Takes a number in comma-separated string format, and
|
||||
// returns a boolean to indicate if the number is negative
|
||||
// or not.
|
||||
function isNegative(x) {
|
||||
|
||||
return (x.indexOf("-") == 0);
|
||||
|
||||
}
|
||||
|
||||
function addCommas(nStr)
|
||||
{
|
||||
nStr += '';
|
||||
x = nStr.split('.');
|
||||
x1 = x[0];
|
||||
x2 = x.length > 1 ? '.' + x[1] : '';
|
||||
var rgx = /(\d+)(\d{3})/;
|
||||
while (rgx.test(x1)) {
|
||||
x1 = x1.replace(rgx, '$1' + ',' + '$2');
|
||||
}
|
||||
return x1 + x2;
|
||||
}
|
||||
|
||||
// Mouseover tips for parent rows in parent/child report..
|
||||
function ParentRowToolTip(cell, metric)
|
||||
{
|
||||
var metric_val;
|
||||
var parent_metric_val;
|
||||
var parent_metric_pct_val;
|
||||
var col_index;
|
||||
var diff_text;
|
||||
|
||||
row = cell.parentNode;
|
||||
tds = row.getElementsByTagName("td");
|
||||
|
||||
parent_func = tds[0].innerHTML; // name
|
||||
|
||||
if (diff_mode) {
|
||||
diff_text = " diff ";
|
||||
} else {
|
||||
diff_text = "";
|
||||
}
|
||||
|
||||
s = '<center>';
|
||||
|
||||
if (metric == "ct") {
|
||||
parent_ct = tds[1].innerHTML; // calls
|
||||
parent_ct_pct = tds[2].innerHTML;
|
||||
|
||||
func_ct = addCommas(func_ct);
|
||||
|
||||
if (diff_mode) {
|
||||
s += 'There are ' + stringAbs(parent_ct) +
|
||||
(isNegative(parent_ct) ? ' fewer ' : ' more ') +
|
||||
' calls to ' + func_name + ' from ' + parent_func + '<br>';
|
||||
|
||||
text = " of diff in calls ";
|
||||
} else {
|
||||
text = " of calls ";
|
||||
}
|
||||
|
||||
s += parent_ct_pct + text + '(' + parent_ct + '/' + func_ct + ') to '
|
||||
+ func_name + ' are from ' + parent_func + '<br>';
|
||||
} else {
|
||||
|
||||
// help for other metrics such as wall time, user cpu time, memory usage
|
||||
col_index = metrics_col[metric];
|
||||
parent_metric_val = tds[col_index].innerHTML;
|
||||
parent_metric_pct_val = tds[col_index+1].innerHTML;
|
||||
|
||||
metric_val = addCommas(func_metrics[metric]);
|
||||
|
||||
s += parent_metric_pct_val + '(' + parent_metric_val + '/' + metric_val
|
||||
+ ') of ' + metrics_desc[metric] +
|
||||
(diff_mode ? ((isNegative(parent_metric_val) ?
|
||||
" decrease" : " increase")) : "") +
|
||||
' in ' + func_name + ' is due to calls from ' + parent_func + '<br>';
|
||||
}
|
||||
|
||||
s += '</center>';
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
// Mouseover tips for child rows in parent/child report..
|
||||
function ChildRowToolTip(cell, metric)
|
||||
{
|
||||
var metric_val;
|
||||
var child_metric_val;
|
||||
var child_metric_pct_val;
|
||||
var col_index;
|
||||
var diff_text;
|
||||
|
||||
row = cell.parentNode;
|
||||
tds = row.getElementsByTagName("td");
|
||||
|
||||
child_func = tds[0].innerHTML; // name
|
||||
|
||||
if (diff_mode) {
|
||||
diff_text = " diff ";
|
||||
} else {
|
||||
diff_text = "";
|
||||
}
|
||||
|
||||
s = '<center>';
|
||||
|
||||
if (metric == "ct") {
|
||||
|
||||
child_ct = tds[1].innerHTML; // calls
|
||||
child_ct_pct = tds[2].innerHTML;
|
||||
|
||||
s += func_name + ' called ' + child_func + ' ' + stringAbs(child_ct) +
|
||||
(diff_mode ? (isNegative(child_ct) ? " fewer" : " more") : "" )
|
||||
+ ' times.<br>';
|
||||
s += 'This accounts for ' + child_ct_pct + ' (' + child_ct
|
||||
+ '/' + total_child_ct
|
||||
+ ') of function calls made by ' + func_name + '.';
|
||||
|
||||
} else {
|
||||
|
||||
// help for other metrics such as wall time, user cpu time, memory usage
|
||||
col_index = metrics_col[metric];
|
||||
child_metric_val = tds[col_index].innerHTML;
|
||||
child_metric_pct_val = tds[col_index+1].innerHTML;
|
||||
|
||||
metric_val = addCommas(func_metrics[metric]);
|
||||
|
||||
if (child_func.indexOf("Exclusive Metrics") != -1) {
|
||||
s += 'The exclusive ' + metrics_desc[metric] + diff_text
|
||||
+ ' for ' + func_name
|
||||
+ ' is ' + child_metric_val + " <br>";
|
||||
|
||||
s += "which is " + child_metric_pct_val + " of the inclusive "
|
||||
+ metrics_desc[metric]
|
||||
+ diff_text + " for " + func_name + " (" + metric_val + ").";
|
||||
|
||||
} else {
|
||||
|
||||
s += child_func + ' when called from ' + func_name
|
||||
+ ' takes ' + stringAbs(child_metric_val)
|
||||
+ (diff_mode ? (isNegative(child_metric_val) ? " less" : " more") : "")
|
||||
+ " of " + metrics_desc[metric] + " <br>";
|
||||
|
||||
s += "which is " + child_metric_pct_val + " of the inclusive "
|
||||
+ metrics_desc[metric]
|
||||
+ diff_text + " for " + func_name + " (" + metric_val + ").";
|
||||
}
|
||||
}
|
||||
|
||||
s += '</center>';
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$('td[@metric]').tooltip(
|
||||
{ bodyHandler: function() {
|
||||
var type = $(this).attr('type');
|
||||
var metric = $(this).attr('metric');
|
||||
if (type == 'Parent') {
|
||||
return ParentRowToolTip(this, metric);
|
||||
} else if (type == 'Child') {
|
||||
return ChildRowToolTip(this, metric);
|
||||
}
|
||||
},
|
||||
showURL : false
|
||||
});
|
||||
var cur_params = {} ;
|
||||
$.each(location.search.replace('?','').split('&'), function(i, x) {
|
||||
var y = x.split('='); cur_params[y[0]] = y[1];
|
||||
});
|
||||
$('input.function_typeahead')
|
||||
.autocomplete('typeahead.php', { extraParams : cur_params })
|
||||
.result(function(event, item) {
|
||||
cur_params['symbol'] = item;
|
||||
location.search = '?' + jQuery.param(cur_params);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
// Copyright (c) 2009 Facebook
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
/**
|
||||
* AJAX endpoint for XHProf function name typeahead.
|
||||
*
|
||||
* @author(s) Kannan Muthukkaruppan
|
||||
* Changhao Jiang
|
||||
*/
|
||||
|
||||
// start moodle modification: moodleize this script
|
||||
require_once(dirname(dirname(dirname(dirname(__FILE__)))).'/config.php');
|
||||
require_once($CFG->libdir . '/xhprof/xhprof_moodle.php');
|
||||
require_login();
|
||||
require_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM));
|
||||
// end moodle modification
|
||||
|
||||
// by default assume that xhprof_html & xhprof_lib directories
|
||||
// are at the same level.
|
||||
$GLOBALS['XHPROF_LIB_ROOT'] = dirname(__FILE__) . '/../xhprof_lib';
|
||||
|
||||
require_once $GLOBALS['XHPROF_LIB_ROOT'].'/display/xhprof.php';
|
||||
|
||||
// start moodle modification: use own XHProfRuns implementation
|
||||
//$xhprof_runs_impl = new XHProfRuns_Default();
|
||||
require_once($GLOBALS['XHPROF_LIB_ROOT'].'/../xhprof_moodle.php');
|
||||
$xhprof_runs_impl = new moodle_xhprofrun();
|
||||
// end moodle modification
|
||||
|
||||
require_once $GLOBALS['XHPROF_LIB_ROOT'].'/display/typeahead_common.php';
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
// Copyright (c) 2009 Facebook
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
/**
|
||||
* AJAX endpoint for XHProf function name typeahead is implemented
|
||||
* as a thin wrapper around this file. The wrapper must set up
|
||||
* the global $xhprof_runs_impl to correspond to an object that
|
||||
* implements the iXHProfRuns interface.
|
||||
*
|
||||
* @author(s) Kannan Muthukkaruppan
|
||||
* Changhao Jiang
|
||||
*/
|
||||
|
||||
|
||||
require_once $GLOBALS['XHPROF_LIB_ROOT'].'/utils/xhprof_lib.php';
|
||||
|
||||
// param name, its type, and default value
|
||||
$params = array('q' => array(XHPROF_STRING_PARAM, ''),
|
||||
'run' => array(XHPROF_STRING_PARAM, ''),
|
||||
'run1' => array(XHPROF_STRING_PARAM, ''),
|
||||
'run2' => array(XHPROF_STRING_PARAM, ''),
|
||||
'source' => array(XHPROF_STRING_PARAM, 'xhprof'),
|
||||
);
|
||||
|
||||
// pull values of these params, and create named globals for each param
|
||||
xhprof_param_init($params);
|
||||
|
||||
if (!empty($run)) {
|
||||
|
||||
// single run mode
|
||||
$raw_data = $xhprof_runs_impl->get_run($run, $source, $desc_unused);
|
||||
$functions = xhprof_get_matching_functions($q, $raw_data);
|
||||
|
||||
} else if (!empty($run1) && !empty($run2)) {
|
||||
|
||||
// diff mode
|
||||
$raw_data = $xhprof_runs_impl->get_run($run1, $source, $desc_unused);
|
||||
$functions1 = xhprof_get_matching_functions($q, $raw_data);
|
||||
|
||||
$raw_data = $xhprof_runs_impl->get_run($run2, $source, $desc_unused);
|
||||
$functions2 = xhprof_get_matching_functions($q, $raw_data);
|
||||
|
||||
|
||||
$functions = array_unique(array_merge($functions1, $functions2));
|
||||
asort($functions);
|
||||
} else {
|
||||
xhprof_error("no valid runs specified to typeahead endpoint");
|
||||
$functions = array();
|
||||
}
|
||||
|
||||
// If exact match is present move it to the front
|
||||
if (in_array($q, $functions)) {
|
||||
$old_functions = $functions;
|
||||
|
||||
$functions = array($q);
|
||||
foreach ($old_functions as $f) {
|
||||
// exact match case has already been added to the front
|
||||
if ($f != $q) {
|
||||
$functions[] = $f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($functions as $f) {
|
||||
echo $f."\n";
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,486 @@
|
||||
<?php
|
||||
// Copyright (c) 2009 Facebook
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
/*
|
||||
* This file contains callgraph image generation related XHProf utility
|
||||
* functions
|
||||
*
|
||||
*/
|
||||
|
||||
// Supported ouput format
|
||||
$xhprof_legal_image_types = array(
|
||||
"jpg" => 1,
|
||||
"gif" => 1,
|
||||
"png" => 1,
|
||||
"ps" => 1,
|
||||
);
|
||||
|
||||
/**
|
||||
* Send an HTTP header with the response. You MUST use this function instead
|
||||
* of header() so that we can debug header issues because they're virtually
|
||||
* impossible to debug otherwise. If you try to commit header(), SVN will
|
||||
* reject your commit.
|
||||
*
|
||||
* @param string HTTP header name, like 'Location'
|
||||
* @param string HTTP header value, like 'http://www.example.com/'
|
||||
*
|
||||
*/
|
||||
function xhprof_http_header($name, $value) {
|
||||
|
||||
if (!$name) {
|
||||
xhprof_error('http_header usage');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!is_string($value)) {
|
||||
xhprof_error('http_header value not a string');
|
||||
}
|
||||
|
||||
header($name.': '.$value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Genearte and send MIME header for the output image to client browser.
|
||||
*
|
||||
* @author cjiang
|
||||
*/
|
||||
function xhprof_generate_mime_header($type, $length) {
|
||||
switch ($type) {
|
||||
case 'jpg':
|
||||
$mime = 'image/jpeg';
|
||||
break;
|
||||
case 'gif':
|
||||
$mime = 'image/gif';
|
||||
break;
|
||||
case 'png':
|
||||
$mime = 'image/png';
|
||||
break;
|
||||
case 'ps':
|
||||
$mime = 'application/postscript';
|
||||
default:
|
||||
$mime = false;
|
||||
}
|
||||
|
||||
if ($mime) {
|
||||
xhprof_http_header('Content-type', $mime);
|
||||
xhprof_http_header('Content-length', (string)$length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate image according to DOT script. This function will spawn a process
|
||||
* with "dot" command and pipe the "dot_script" to it and pipe out the
|
||||
* generated image content.
|
||||
*
|
||||
* @param dot_script, string, the script for DOT to generate the image.
|
||||
* @param type, one of the supported image types, see
|
||||
* $xhprof_legal_image_types.
|
||||
* @returns, binary content of the generated image on success. empty string on
|
||||
* failure.
|
||||
*
|
||||
* @author cjiang
|
||||
*/
|
||||
function xhprof_generate_image_by_dot($dot_script, $type) {
|
||||
$descriptorspec = array(
|
||||
// stdin is a pipe that the child will read from
|
||||
0 => array("pipe", "r"),
|
||||
// stdout is a pipe that the child will write to
|
||||
1 => array("pipe", "w"),
|
||||
// stderr is a pipe that the child will write to
|
||||
2 => array("pipe", "w")
|
||||
);
|
||||
|
||||
// start moodle modification: use $CFG->pathtodot for executing this
|
||||
//$cmd = " dot -T".$type;
|
||||
global $CFG;
|
||||
$cmd = (!empty($CFG->pathtodot) ? $CFG->pathtodot : 'dot') . ' -T' . $type;
|
||||
// end moodle modification
|
||||
|
||||
$process = proc_open($cmd, $descriptorspec, $pipes, "/tmp", array());
|
||||
if (is_resource($process)) {
|
||||
fwrite($pipes[0], $dot_script);
|
||||
fclose($pipes[0]);
|
||||
|
||||
$output = stream_get_contents($pipes[1]);
|
||||
|
||||
$err = stream_get_contents($pipes[2]);
|
||||
if (!empty($err)) {
|
||||
print "failed to execute cmd: \"$cmd\". stderr: `$err'\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
fclose($pipes[2]);
|
||||
fclose($pipes[1]);
|
||||
proc_close($process);
|
||||
return $output;
|
||||
}
|
||||
print "failed to execute cmd \"$cmd\"";
|
||||
exit();
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the children list of all nodes.
|
||||
*/
|
||||
function xhprof_get_children_table($raw_data) {
|
||||
$children_table = array();
|
||||
foreach ($raw_data as $parent_child => $info) {
|
||||
list($parent, $child) = xhprof_parse_parent_child($parent_child);
|
||||
if (!isset($children_table[$parent])) {
|
||||
$children_table[$parent] = array($child);
|
||||
} else {
|
||||
$children_table[$parent][] = $child;
|
||||
}
|
||||
}
|
||||
return $children_table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate DOT script from the given raw phprof data.
|
||||
*
|
||||
* @param raw_data, phprof profile data.
|
||||
* @param threshold, float, the threshold value [0,1). The functions in the
|
||||
* raw_data whose exclusive wall times ratio are below the
|
||||
* threshold will be filtered out and won't apprear in the
|
||||
* generated image.
|
||||
* @param page, string(optional), the root node name. This can be used to
|
||||
* replace the 'main()' as the root node.
|
||||
* @param func, string, the focus function.
|
||||
* @param critical_path, bool, whether or not to display critical path with
|
||||
* bold lines.
|
||||
* @returns, string, the DOT script to generate image.
|
||||
*
|
||||
* @author cjiang
|
||||
*/
|
||||
function xhprof_generate_dot_script($raw_data, $threshold, $source, $page,
|
||||
$func, $critical_path, $right=null,
|
||||
$left=null) {
|
||||
|
||||
$max_width = 5;
|
||||
$max_height = 3.5;
|
||||
$max_fontsize = 35;
|
||||
$max_sizing_ratio = 20;
|
||||
|
||||
$totals;
|
||||
|
||||
if ($left === null) {
|
||||
// init_metrics($raw_data, null, null);
|
||||
}
|
||||
$sym_table = xhprof_compute_flat_info($raw_data, $totals);
|
||||
|
||||
if ($critical_path) {
|
||||
$children_table = xhprof_get_children_table($raw_data);
|
||||
$node = "main()";
|
||||
$path = array();
|
||||
$path_edges = array();
|
||||
$visited = array();
|
||||
while ($node) {
|
||||
$visited[$node] = true;
|
||||
if (isset($children_table[$node])) {
|
||||
$max_child = null;
|
||||
foreach ($children_table[$node] as $child) {
|
||||
|
||||
if (isset($visited[$child])) {
|
||||
continue;
|
||||
}
|
||||
if ($max_child === null ||
|
||||
abs($raw_data[xhprof_build_parent_child_key($node,
|
||||
$child)]["wt"]) >
|
||||
abs($raw_data[xhprof_build_parent_child_key($node,
|
||||
$max_child)]["wt"])) {
|
||||
$max_child = $child;
|
||||
}
|
||||
}
|
||||
if ($max_child !== null) {
|
||||
$path[$max_child] = true;
|
||||
$path_edges[xhprof_build_parent_child_key($node, $max_child)] = true;
|
||||
}
|
||||
$node = $max_child;
|
||||
} else {
|
||||
$node = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if it is a benchmark callgraph, we make the benchmarked function the root.
|
||||
if ($source == "bm" && array_key_exists("main()", $sym_table)) {
|
||||
$total_times = $sym_table["main()"]["ct"];
|
||||
$remove_funcs = array("main()",
|
||||
"hotprofiler_disable",
|
||||
"call_user_func_array",
|
||||
"xhprof_disable");
|
||||
|
||||
foreach ($remove_funcs as $cur_del_func) {
|
||||
if (array_key_exists($cur_del_func, $sym_table) &&
|
||||
$sym_table[$cur_del_func]["ct"] == $total_times) {
|
||||
unset($sym_table[$cur_del_func]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// use the function to filter out irrelevant functions.
|
||||
if (!empty($func)) {
|
||||
$interested_funcs = array();
|
||||
foreach ($raw_data as $parent_child => $info) {
|
||||
list($parent, $child) = xhprof_parse_parent_child($parent_child);
|
||||
if ($parent == $func || $child == $func) {
|
||||
$interested_funcs[$parent] = 1;
|
||||
$interested_funcs[$child] = 1;
|
||||
}
|
||||
}
|
||||
foreach ($sym_table as $symbol => $info) {
|
||||
if (!array_key_exists($symbol, $interested_funcs)) {
|
||||
unset($sym_table[$symbol]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$result = "digraph call_graph {\n";
|
||||
|
||||
// Filter out functions whose exclusive time ratio is below threshold, and
|
||||
// also assign a unique integer id for each function to be generated. In the
|
||||
// meantime, find the function with the most exclusive time (potentially the
|
||||
// performance bottleneck).
|
||||
$cur_id = 0; $max_wt = 0;
|
||||
foreach ($sym_table as $symbol => $info) {
|
||||
if (empty($func) && abs($info["wt"] / $totals["wt"]) < $threshold) {
|
||||
unset($sym_table[$symbol]);
|
||||
continue;
|
||||
}
|
||||
if ($max_wt == 0 || $max_wt < abs($info["excl_wt"])) {
|
||||
$max_wt = abs($info["excl_wt"]);
|
||||
}
|
||||
$sym_table[$symbol]["id"] = $cur_id;
|
||||
$cur_id ++;
|
||||
}
|
||||
|
||||
// Generate all nodes' information.
|
||||
foreach ($sym_table as $symbol => $info) {
|
||||
if ($info["excl_wt"] == 0) {
|
||||
$sizing_factor = $max_sizing_ratio;
|
||||
} else {
|
||||
$sizing_factor = $max_wt / abs($info["excl_wt"]) ;
|
||||
if ($sizing_factor > $max_sizing_ratio) {
|
||||
$sizing_factor = $max_sizing_ratio;
|
||||
}
|
||||
}
|
||||
$fillcolor = (($sizing_factor < 1.5) ?
|
||||
", style=filled, fillcolor=red" : "");
|
||||
|
||||
if ($critical_path) {
|
||||
// highlight nodes along critical path.
|
||||
if (!$fillcolor && array_key_exists($symbol, $path)) {
|
||||
$fillcolor = ", style=filled, fillcolor=yellow";
|
||||
}
|
||||
}
|
||||
|
||||
$fontsize = ", fontsize="
|
||||
.(int)($max_fontsize / (($sizing_factor - 1) / 10 + 1));
|
||||
|
||||
$width = ", width=".sprintf("%.1f", $max_width / $sizing_factor);
|
||||
$height = ", height=".sprintf("%.1f", $max_height / $sizing_factor);
|
||||
|
||||
if ($symbol == "main()") {
|
||||
$shape = "octagon";
|
||||
$name = "Total: ".($totals["wt"] / 1000.0)." ms\\n";
|
||||
$name .= addslashes(isset($page) ? $page : $symbol);
|
||||
} else {
|
||||
$shape = "box";
|
||||
$name = addslashes($symbol)."\\nInc: ". sprintf("%.3f",$info["wt"] / 1000) .
|
||||
" ms (" . sprintf("%.1f%%", 100 * $info["wt"] / $totals["wt"]).")";
|
||||
}
|
||||
if ($left === null) {
|
||||
$label = ", label=\"".$name."\\nExcl: "
|
||||
.(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms ("
|
||||
.sprintf("%.1f%%", 100 * $info["excl_wt"] / $totals["wt"])
|
||||
. ")\\n".$info["ct"]." total calls\"";
|
||||
} else {
|
||||
if (isset($left[$symbol]) && isset($right[$symbol])) {
|
||||
$label = ", label=\"".addslashes($symbol).
|
||||
"\\nInc: ".(sprintf("%.3f",$left[$symbol]["wt"] / 1000.0))
|
||||
." ms - "
|
||||
.(sprintf("%.3f",$right[$symbol]["wt"] / 1000.0))." ms = "
|
||||
.(sprintf("%.3f",$info["wt"] / 1000.0))." ms".
|
||||
"\\nExcl: "
|
||||
.(sprintf("%.3f",$left[$symbol]["excl_wt"] / 1000.0))
|
||||
." ms - ".(sprintf("%.3f",$right[$symbol]["excl_wt"] / 1000.0))
|
||||
." ms = ".(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms".
|
||||
"\\nCalls: ".(sprintf("%.3f",$left[$symbol]["ct"]))." - "
|
||||
.(sprintf("%.3f",$right[$symbol]["ct"]))." = "
|
||||
.(sprintf("%.3f",$info["ct"]))."\"";
|
||||
} else if (isset($left[$symbol])) {
|
||||
$label = ", label=\"".addslashes($symbol).
|
||||
"\\nInc: ".(sprintf("%.3f",$left[$symbol]["wt"] / 1000.0))
|
||||
." ms - 0 ms = ".(sprintf("%.3f",$info["wt"] / 1000.0))
|
||||
." ms"."\\nExcl: "
|
||||
.(sprintf("%.3f",$left[$symbol]["excl_wt"] / 1000.0))
|
||||
." ms - 0 ms = "
|
||||
.(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms".
|
||||
"\\nCalls: ".(sprintf("%.3f",$left[$symbol]["ct"]))." - 0 = "
|
||||
.(sprintf("%.3f",$info["ct"]))."\"";
|
||||
} else {
|
||||
$label = ", label=\"".addslashes($symbol).
|
||||
"\\nInc: 0 ms - "
|
||||
.(sprintf("%.3f",$right[$symbol]["wt"] / 1000.0))
|
||||
." ms = ".(sprintf("%.3f",$info["wt"] / 1000.0))." ms".
|
||||
"\\nExcl: 0 ms - "
|
||||
.(sprintf("%.3f",$right[$symbol]["excl_wt"] / 1000.0))
|
||||
." ms = ".(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms".
|
||||
"\\nCalls: 0 - ".(sprintf("%.3f",$right[$symbol]["ct"]))
|
||||
." = ".(sprintf("%.3f",$info["ct"]))."\"";
|
||||
}
|
||||
}
|
||||
$result .= "N" . $sym_table[$symbol]["id"];
|
||||
$result .= "[shape=$shape ".$label.$width
|
||||
.$height.$fontsize.$fillcolor."];\n";
|
||||
}
|
||||
|
||||
// Generate all the edges' information.
|
||||
foreach ($raw_data as $parent_child => $info) {
|
||||
list($parent, $child) = xhprof_parse_parent_child($parent_child);
|
||||
|
||||
if (isset($sym_table[$parent]) && isset($sym_table[$child]) &&
|
||||
(empty($func) ||
|
||||
(!empty($func) && ($parent == $func || $child == $func)))) {
|
||||
|
||||
$label = $info["ct"] == 1 ? $info["ct"]." call" : $info["ct"]." calls";
|
||||
|
||||
$headlabel = $sym_table[$child]["wt"] > 0 ?
|
||||
sprintf("%.1f%%", 100 * $info["wt"]
|
||||
/ $sym_table[$child]["wt"])
|
||||
: "0.0%";
|
||||
|
||||
$taillabel = ($sym_table[$parent]["wt"] > 0) ?
|
||||
sprintf("%.1f%%",
|
||||
100 * $info["wt"] /
|
||||
($sym_table[$parent]["wt"] - $sym_table["$parent"]["excl_wt"]))
|
||||
: "0.0%";
|
||||
|
||||
$linewidth = 1;
|
||||
$arrow_size = 1;
|
||||
|
||||
if ($critical_path &&
|
||||
isset($path_edges[xhprof_build_parent_child_key($parent, $child)])) {
|
||||
$linewidth = 10; $arrow_size = 2;
|
||||
}
|
||||
|
||||
$result .= "N" . $sym_table[$parent]["id"] . " -> N"
|
||||
. $sym_table[$child]["id"];
|
||||
$result .= "[arrowsize=$arrow_size, style=\"setlinewidth($linewidth)\","
|
||||
." label=\""
|
||||
.$label."\", headlabel=\"".$headlabel
|
||||
."\", taillabel=\"".$taillabel."\" ]";
|
||||
$result .= ";\n";
|
||||
|
||||
}
|
||||
}
|
||||
$result = $result . "\n}";
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
function xhprof_render_diff_image($xhprof_runs_impl, $run1, $run2,
|
||||
$type, $threshold, $source) {
|
||||
$total1;
|
||||
$total2;
|
||||
|
||||
$raw_data1 = $xhprof_runs_impl->get_run($run1, $source, $desc_unused);
|
||||
$raw_data2 = $xhprof_runs_impl->get_run($run2, $source, $desc_unused);
|
||||
|
||||
// init_metrics($raw_data1, null, null);
|
||||
$children_table1 = xhprof_get_children_table($raw_data1);
|
||||
$children_table2 = xhprof_get_children_table($raw_data2);
|
||||
$symbol_tab1 = xhprof_compute_flat_info($raw_data1, $total1);
|
||||
$symbol_tab2 = xhprof_compute_flat_info($raw_data2, $total2);
|
||||
$run_delta = xhprof_compute_diff($raw_data1, $raw_data2);
|
||||
$script = xhprof_generate_dot_script($run_delta, $threshold, $source,
|
||||
null, null, true,
|
||||
$symbol_tab1, $symbol_tab2);
|
||||
$content = xhprof_generate_image_by_dot($script, $type);
|
||||
|
||||
xhprof_generate_mime_header($type, strlen($content));
|
||||
echo $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate image content from phprof run id.
|
||||
*
|
||||
* @param object $xhprof_runs_impl An object that implements
|
||||
* the iXHProfRuns interface
|
||||
* @param run_id, integer, the unique id for the phprof run, this is the
|
||||
* primary key for phprof database table.
|
||||
* @param type, string, one of the supported image types. See also
|
||||
* $xhprof_legal_image_types.
|
||||
* @param threshold, float, the threshold value [0,1). The functions in the
|
||||
* raw_data whose exclusive wall times ratio are below the
|
||||
* threshold will be filtered out and won't apprear in the
|
||||
* generated image.
|
||||
* @param func, string, the focus function.
|
||||
* @returns, string, the DOT script to generate image.
|
||||
*
|
||||
* @author cjiang
|
||||
*/
|
||||
function xhprof_get_content_by_run($xhprof_runs_impl, $run_id, $type,
|
||||
$threshold, $func, $source,
|
||||
$critical_path) {
|
||||
if (!$run_id)
|
||||
return "";
|
||||
|
||||
$raw_data = $xhprof_runs_impl->get_run($run_id, $source, $description);
|
||||
if (!$raw_data) {
|
||||
xhprof_error("Raw data is empty");
|
||||
return "";
|
||||
}
|
||||
|
||||
$script = xhprof_generate_dot_script($raw_data, $threshold, $source,
|
||||
$description, $func, $critical_path);
|
||||
|
||||
$content = xhprof_generate_image_by_dot($script, $type);
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate image from phprof run id and send it to client.
|
||||
*
|
||||
* @param object $xhprof_runs_impl An object that implements
|
||||
* the iXHProfRuns interface
|
||||
* @param run_id, integer, the unique id for the phprof run, this is the
|
||||
* primary key for phprof database table.
|
||||
* @param type, string, one of the supported image types. See also
|
||||
* $xhprof_legal_image_types.
|
||||
* @param threshold, float, the threshold value [0,1). The functions in the
|
||||
* raw_data whose exclusive wall times ratio are below the
|
||||
* threshold will be filtered out and won't apprear in the
|
||||
* generated image.
|
||||
* @param func, string, the focus function.
|
||||
* @param bool, does this run correspond to a PHProfLive run or a dev run?
|
||||
* @author cjiang
|
||||
*/
|
||||
function xhprof_render_image($xhprof_runs_impl, $run_id, $type, $threshold,
|
||||
$func, $source, $critical_path) {
|
||||
|
||||
$content = xhprof_get_content_by_run($xhprof_runs_impl, $run_id, $type,
|
||||
$threshold,
|
||||
$func, $source, $critical_path);
|
||||
if (!$content) {
|
||||
print "Error: either we can not find profile data for run_id ".$run_id
|
||||
." or the threshold ".$threshold." is too small or you do not"
|
||||
." have 'dot' image generation utility installed.";
|
||||
exit();
|
||||
}
|
||||
|
||||
xhprof_generate_mime_header($type, strlen($content));
|
||||
echo $content;
|
||||
}
|
||||
@@ -0,0 +1,942 @@
|
||||
<?php
|
||||
// Copyright (c) 2009 Facebook
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
//
|
||||
// This file contains various XHProf library (utility) functions.
|
||||
// Do not add any display specific code here.
|
||||
//
|
||||
|
||||
function xhprof_error($message) {
|
||||
error_log($message);
|
||||
}
|
||||
|
||||
/*
|
||||
* The list of possible metrics collected as part of XHProf that
|
||||
* require inclusive/exclusive handling while reporting.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_get_possible_metrics() {
|
||||
static $possible_metrics =
|
||||
array("wt" => array("Wall", "microsecs", "walltime"),
|
||||
"ut" => array("User", "microsecs", "user cpu time"),
|
||||
"st" => array("Sys", "microsecs", "system cpu time"),
|
||||
"cpu" => array("Cpu", "microsecs", "cpu time"),
|
||||
"mu" => array("MUse", "bytes", "memory usage"),
|
||||
"pmu" => array("PMUse", "bytes", "peak memory usage"),
|
||||
"samples" => array("Samples", "samples", "cpu time"));
|
||||
return $possible_metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the metrics we'll display based on the information
|
||||
* in the raw data.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function init_metrics($xhprof_data, $rep_symbol, $sort, $diff_report = false) {
|
||||
global $stats;
|
||||
global $pc_stats;
|
||||
global $metrics;
|
||||
global $diff_mode;
|
||||
global $sortable_columns;
|
||||
global $sort_col;
|
||||
global $display_calls;
|
||||
|
||||
$diff_mode = $diff_report;
|
||||
|
||||
if (!empty($sort)) {
|
||||
if (array_key_exists($sort, $sortable_columns)) {
|
||||
$sort_col = $sort;
|
||||
} else {
|
||||
print("Invalid Sort Key $sort specified in URL");
|
||||
}
|
||||
}
|
||||
|
||||
// For C++ profiler runs, walltime attribute isn't present.
|
||||
// In that case, use "samples" as the default sort column.
|
||||
if (!isset($xhprof_data["main()"]["wt"])) {
|
||||
|
||||
if ($sort_col == "wt") {
|
||||
$sort_col = "samples";
|
||||
}
|
||||
|
||||
// C++ profiler data doesn't have call counts.
|
||||
// ideally we should check to see if "ct" metric
|
||||
// is present for "main()". But currently "ct"
|
||||
// metric is artificially set to 1. So, relying
|
||||
// on absence of "wt" metric instead.
|
||||
$display_calls = false;
|
||||
} else {
|
||||
$display_calls = true;
|
||||
}
|
||||
|
||||
// parent/child report doesn't support exclusive times yet.
|
||||
// So, change sort hyperlinks to closest fit.
|
||||
if (!empty($rep_symbol)) {
|
||||
$sort_col = str_replace("excl_", "", $sort_col);
|
||||
}
|
||||
|
||||
if ($display_calls) {
|
||||
$stats = array("fn", "ct", "Calls%");
|
||||
} else {
|
||||
$stats = array("fn");
|
||||
}
|
||||
|
||||
$pc_stats = $stats;
|
||||
|
||||
$possible_metrics = xhprof_get_possible_metrics($xhprof_data);
|
||||
foreach ($possible_metrics as $metric => $desc) {
|
||||
if (isset($xhprof_data["main()"][$metric])) {
|
||||
$metrics[] = $metric;
|
||||
// flat (top-level reports): we can compute
|
||||
// exclusive metrics reports as well.
|
||||
$stats[] = $metric;
|
||||
$stats[] = "I" . $desc[0] . "%";
|
||||
$stats[] = "excl_" . $metric;
|
||||
$stats[] = "E" . $desc[0] . "%";
|
||||
|
||||
// parent/child report for a function: we can
|
||||
// only breakdown inclusive times correctly.
|
||||
$pc_stats[] = $metric;
|
||||
$pc_stats[] = "I" . $desc[0] . "%";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the list of metrics present in $xhprof_data as an array.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_get_metrics($xhprof_data) {
|
||||
|
||||
// get list of valid metrics
|
||||
$possible_metrics = xhprof_get_possible_metrics();
|
||||
|
||||
// return those that are present in the raw data.
|
||||
// We'll just look at the root of the subtree for this.
|
||||
$metrics = array();
|
||||
foreach ($possible_metrics as $metric => $desc) {
|
||||
if (isset($xhprof_data["main()"][$metric])) {
|
||||
$metrics[] = $metric;
|
||||
}
|
||||
}
|
||||
|
||||
return $metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a parent/child function name encoded as
|
||||
* "a==>b" and returns array("a", "b").
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_parse_parent_child($parent_child) {
|
||||
$ret = explode("==>", $parent_child);
|
||||
|
||||
// Return if both parent and child are set
|
||||
if (isset($ret[1])) {
|
||||
return $ret;
|
||||
}
|
||||
|
||||
return array(null, $ret[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given parent & child function name, composes the key
|
||||
* in the format present in the raw data.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_build_parent_child_key($parent, $child) {
|
||||
if ($parent) {
|
||||
return $parent . "==>" . $child;
|
||||
} else {
|
||||
return $child;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if XHProf raw data appears to be valid and not corrupted.
|
||||
*
|
||||
* @param int $run_id Run id of run to be pruned.
|
||||
* [Used only for reporting errors.]
|
||||
* @param array $raw_data XHProf raw data to be pruned
|
||||
* & validated.
|
||||
*
|
||||
* @return bool true on success, false on failure
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_valid_run($run_id, $raw_data) {
|
||||
|
||||
$main_info = $raw_data["main()"];
|
||||
if (empty($main_info)) {
|
||||
xhprof_error("XHProf: main() missing in raw data for Run ID: $run_id");
|
||||
return false;
|
||||
}
|
||||
|
||||
// raw data should contain either wall time or samples information...
|
||||
if (isset($main_info["wt"])) {
|
||||
$metric = "wt";
|
||||
} else if (isset($main_info["samples"])) {
|
||||
$metric = "samples";
|
||||
} else {
|
||||
xhprof_error("XHProf: Wall Time information missing from Run ID: $run_id");
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($raw_data as $info) {
|
||||
$val = $info[$metric];
|
||||
|
||||
// basic sanity checks...
|
||||
if ($val < 0) {
|
||||
xhprof_error("XHProf: $metric should not be negative: Run ID $run_id"
|
||||
. serialize($info));
|
||||
return false;
|
||||
}
|
||||
if ($val > (86400000000)) {
|
||||
xhprof_error("XHProf: $metric > 1 day found in Run ID: $run_id "
|
||||
. serialize($info));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a trimmed version of the XHProf raw data. Note that the raw
|
||||
* data contains one entry for each unique parent/child function
|
||||
* combination.The trimmed version of raw data will only contain
|
||||
* entries where either the parent or child function is in the list
|
||||
* of $functions_to_keep.
|
||||
*
|
||||
* Note: Function main() is also always kept so that overall totals
|
||||
* can still be obtained from the trimmed version.
|
||||
*
|
||||
* @param array XHProf raw data
|
||||
* @param array array of function names
|
||||
*
|
||||
* @return array Trimmed XHProf Report
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_trim_run($raw_data, $functions_to_keep) {
|
||||
|
||||
// convert list of functions to a hash with function as the key
|
||||
$function_map = array_fill_keys($functions_to_keep, 1);
|
||||
|
||||
// always keep main() as well so that overall totals can still
|
||||
// be computed if need be.
|
||||
$function_map['main()'] = 1;
|
||||
|
||||
$new_raw_data = array();
|
||||
foreach ($raw_data as $parent_child => $info) {
|
||||
list($parent, $child) = xhprof_parse_parent_child($parent_child);
|
||||
|
||||
if (isset($function_map[$parent]) || isset($function_map[$child])) {
|
||||
$new_raw_data[$parent_child] = $info;
|
||||
}
|
||||
}
|
||||
|
||||
return $new_raw_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes raw XHProf data that was aggregated over "$num_runs" number
|
||||
* of runs averages/nomalizes the data. Essentially the various metrics
|
||||
* collected are divided by $num_runs.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_normalize_metrics($raw_data, $num_runs) {
|
||||
|
||||
if (empty($raw_data) || ($num_runs == 0)) {
|
||||
return $raw_data;
|
||||
}
|
||||
|
||||
$raw_data_total = array();
|
||||
|
||||
if (isset($raw_data["==>main()"]) && isset($raw_data["main()"])) {
|
||||
xhprof_error("XHProf Error: both ==>main() and main() set in raw data...");
|
||||
}
|
||||
|
||||
foreach ($raw_data as $parent_child => $info) {
|
||||
foreach ($info as $metric => $value) {
|
||||
$raw_data_total[$parent_child][$metric] = ($value / $num_runs);
|
||||
}
|
||||
}
|
||||
|
||||
return $raw_data_total;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get raw data corresponding to specified array of runs
|
||||
* aggregated by certain weightage.
|
||||
*
|
||||
* Suppose you have run:5 corresponding to page1.php,
|
||||
* run:6 corresponding to page2.php,
|
||||
* and run:7 corresponding to page3.php
|
||||
*
|
||||
* and you want to accumulate these runs in a 2:4:1 ratio. You
|
||||
* can do so by calling:
|
||||
*
|
||||
* xhprof_aggregate_runs(array(5, 6, 7), array(2, 4, 1));
|
||||
*
|
||||
* The above will return raw data for the runs aggregated
|
||||
* in 2:4:1 ratio.
|
||||
*
|
||||
* @param object $xhprof_runs_impl An object that implements
|
||||
* the iXHProfRuns interface
|
||||
* @param array $runs run ids of the XHProf runs..
|
||||
* @param array $wts integral (ideally) weights for $runs
|
||||
* @param string $source source to fetch raw data for run from
|
||||
* @param bool $use_script_name If true, a fake edge from main() to
|
||||
* to __script::<scriptname> is introduced
|
||||
* in the raw data so that after aggregations
|
||||
* the script name is still preserved.
|
||||
*
|
||||
* @return array Return aggregated raw data
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_aggregate_runs($xhprof_runs_impl, $runs,
|
||||
$wts, $source="phprof",
|
||||
$use_script_name=false) {
|
||||
|
||||
$raw_data_total = null;
|
||||
$raw_data = null;
|
||||
$metrics = array();
|
||||
|
||||
$run_count = count($runs);
|
||||
$wts_count = count($wts);
|
||||
|
||||
if (($run_count == 0) ||
|
||||
(($wts_count > 0) && ($run_count != $wts_count))) {
|
||||
return array('description' => 'Invalid input..',
|
||||
'raw' => null);
|
||||
}
|
||||
|
||||
$bad_runs = array();
|
||||
foreach ($runs as $idx => $run_id) {
|
||||
|
||||
$raw_data = $xhprof_runs_impl->get_run($run_id, $source, $description);
|
||||
|
||||
// use the first run to derive what metrics to aggregate on.
|
||||
if ($idx == 0) {
|
||||
foreach ($raw_data["main()"] as $metric => $val) {
|
||||
if ($metric != "pmu") {
|
||||
// for now, just to keep data size small, skip "peak" memory usage
|
||||
// data while aggregating.
|
||||
// The "regular" memory usage data will still be tracked.
|
||||
if (isset($val)) {
|
||||
$metrics[] = $metric;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!xhprof_valid_run($run_id, $raw_data)) {
|
||||
$bad_runs[] = $run_id;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($use_script_name) {
|
||||
$page = $description;
|
||||
|
||||
// create a fake function '__script::$page', and have and edge from
|
||||
// main() to '__script::$page'. We will also need edges to transfer
|
||||
// all edges originating from main() to now originate from
|
||||
// '__script::$page' to all function called from main().
|
||||
//
|
||||
// We also weight main() ever so slightly higher so that
|
||||
// it shows up above the new entry in reports sorted by
|
||||
// inclusive metrics or call counts.
|
||||
if ($page) {
|
||||
foreach ($raw_data["main()"] as $metric => $val) {
|
||||
$fake_edge[$metric] = $val;
|
||||
$new_main[$metric] = $val + 0.00001;
|
||||
}
|
||||
$raw_data["main()"] = $new_main;
|
||||
$raw_data[xhprof_build_parent_child_key("main()",
|
||||
"__script::$page")]
|
||||
= $fake_edge;
|
||||
} else {
|
||||
$use_script_name = false;
|
||||
}
|
||||
}
|
||||
|
||||
// if no weights specified, use 1 as the default weightage..
|
||||
$wt = ($wts_count == 0) ? 1 : $wts[$idx];
|
||||
|
||||
// aggregate $raw_data into $raw_data_total with appropriate weight ($wt)
|
||||
foreach ($raw_data as $parent_child => $info) {
|
||||
if ($use_script_name) {
|
||||
// if this is an old edge originating from main(), it now
|
||||
// needs to be from '__script::$page'
|
||||
if (substr($parent_child, 0, 9) == "main()==>") {
|
||||
$child = substr($parent_child, 9);
|
||||
// ignore the newly added edge from main()
|
||||
if (substr($child, 0, 10) != "__script::") {
|
||||
$parent_child = xhprof_build_parent_child_key("__script::$page",
|
||||
$child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($raw_data_total[$parent_child])) {
|
||||
foreach ($metrics as $metric) {
|
||||
$raw_data_total[$parent_child][$metric] = ($wt * $info[$metric]);
|
||||
}
|
||||
} else {
|
||||
foreach ($metrics as $metric) {
|
||||
$raw_data_total[$parent_child][$metric] += ($wt * $info[$metric]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$runs_string = implode(",", $runs);
|
||||
|
||||
if (isset($wts)) {
|
||||
$wts_string = "in the ratio (" . implode(":", $wts) . ")";
|
||||
$normalization_count = array_sum($wts);
|
||||
} else {
|
||||
$wts_string = "";
|
||||
$normalization_count = $run_count;
|
||||
}
|
||||
|
||||
$run_count = $run_count - count($bad_runs);
|
||||
|
||||
$data['description'] = "Aggregated Report for $run_count runs: ".
|
||||
"$runs_string $wts_string\n";
|
||||
$data['raw'] = xhprof_normalize_metrics($raw_data_total,
|
||||
$normalization_count);
|
||||
$data['bad_runs'] = $bad_runs;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Analyze hierarchical raw data, and compute per-function (flat)
|
||||
* inclusive and exclusive metrics.
|
||||
*
|
||||
* Also, store overall totals in the 2nd argument.
|
||||
*
|
||||
* @param array $raw_data XHProf format raw profiler data.
|
||||
* @param array &$overall_totals OUT argument for returning
|
||||
* overall totals for various
|
||||
* metrics.
|
||||
* @return array Returns a map from function name to its
|
||||
* call count and inclusive & exclusive metrics
|
||||
* (such as wall time, etc.).
|
||||
*
|
||||
* @author Kannan Muthukkaruppan
|
||||
*/
|
||||
function xhprof_compute_flat_info($raw_data, &$overall_totals) {
|
||||
|
||||
global $display_calls;
|
||||
|
||||
$metrics = xhprof_get_metrics($raw_data);
|
||||
|
||||
$overall_totals = array("ct" => 0,
|
||||
"wt" => 0,
|
||||
"ut" => 0,
|
||||
"st" => 0,
|
||||
"cpu" => 0,
|
||||
"mu" => 0,
|
||||
"pmu" => 0,
|
||||
"samples" => 0
|
||||
);
|
||||
|
||||
// compute inclusive times for each function
|
||||
$symbol_tab = xhprof_compute_inclusive_times($raw_data);
|
||||
|
||||
/* total metric value is the metric value for "main()" */
|
||||
foreach ($metrics as $metric) {
|
||||
$overall_totals[$metric] = $symbol_tab["main()"][$metric];
|
||||
}
|
||||
|
||||
/*
|
||||
* initialize exclusive (self) metric value to inclusive metric value
|
||||
* to start with.
|
||||
* In the same pass, also add up the total number of function calls.
|
||||
*/
|
||||
foreach ($symbol_tab as $symbol => $info) {
|
||||
foreach ($metrics as $metric) {
|
||||
$symbol_tab[$symbol]["excl_" . $metric] = $symbol_tab[$symbol][$metric];
|
||||
}
|
||||
if ($display_calls) {
|
||||
/* keep track of total number of calls */
|
||||
$overall_totals["ct"] += $info["ct"];
|
||||
}
|
||||
}
|
||||
|
||||
/* adjust exclusive times by deducting inclusive time of children */
|
||||
foreach ($raw_data as $parent_child => $info) {
|
||||
list($parent, $child) = xhprof_parse_parent_child($parent_child);
|
||||
|
||||
if ($parent) {
|
||||
foreach ($metrics as $metric) {
|
||||
// make sure the parent exists hasn't been pruned.
|
||||
if (isset($symbol_tab[$parent])) {
|
||||
$symbol_tab[$parent]["excl_" . $metric] -= $info[$metric];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $symbol_tab;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hierarchical diff:
|
||||
* Compute and return difference of two call graphs: Run2 - Run1.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_compute_diff($xhprof_data1, $xhprof_data2) {
|
||||
global $display_calls;
|
||||
|
||||
// use the second run to decide what metrics we will do the diff on
|
||||
$metrics = xhprof_get_metrics($xhprof_data2);
|
||||
|
||||
$xhprof_delta = $xhprof_data2;
|
||||
|
||||
foreach ($xhprof_data1 as $parent_child => $info) {
|
||||
|
||||
if (!isset($xhprof_delta[$parent_child])) {
|
||||
|
||||
// this pc combination was not present in run1;
|
||||
// initialize all values to zero.
|
||||
if ($display_calls) {
|
||||
$xhprof_delta[$parent_child] = array("ct" => 0);
|
||||
} else {
|
||||
$xhprof_delta[$parent_child] = array();
|
||||
}
|
||||
foreach ($metrics as $metric) {
|
||||
$xhprof_delta[$parent_child][$metric] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ($display_calls) {
|
||||
$xhprof_delta[$parent_child]["ct"] -= $info["ct"];
|
||||
}
|
||||
|
||||
foreach ($metrics as $metric) {
|
||||
$xhprof_delta[$parent_child][$metric] -= $info[$metric];
|
||||
}
|
||||
}
|
||||
|
||||
return $xhprof_delta;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compute inclusive metrics for function. This code was factored out
|
||||
* of xhprof_compute_flat_info().
|
||||
*
|
||||
* The raw data contains inclusive metrics of a function for each
|
||||
* unique parent function it is called from. The total inclusive metrics
|
||||
* for a function is therefore the sum of inclusive metrics for the
|
||||
* function across all parents.
|
||||
*
|
||||
* @return array Returns a map of function name to total (across all parents)
|
||||
* inclusive metrics for the function.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_compute_inclusive_times($raw_data) {
|
||||
global $display_calls;
|
||||
|
||||
$metrics = xhprof_get_metrics($raw_data);
|
||||
|
||||
$symbol_tab = array();
|
||||
|
||||
/*
|
||||
* First compute inclusive time for each function and total
|
||||
* call count for each function across all parents the
|
||||
* function is called from.
|
||||
*/
|
||||
foreach ($raw_data as $parent_child => $info) {
|
||||
|
||||
list($parent, $child) = xhprof_parse_parent_child($parent_child);
|
||||
|
||||
if ($parent == $child) {
|
||||
/*
|
||||
* XHProf PHP extension should never trigger this situation any more.
|
||||
* Recursion is handled in the XHProf PHP extension by giving nested
|
||||
* calls a unique recursion-depth appended name (for example, foo@1).
|
||||
*/
|
||||
xhprof_error("Error in Raw Data: parent & child are both: $parent");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isset($symbol_tab[$child])) {
|
||||
|
||||
if ($display_calls) {
|
||||
$symbol_tab[$child] = array("ct" => $info["ct"]);
|
||||
} else {
|
||||
$symbol_tab[$child] = array();
|
||||
}
|
||||
foreach ($metrics as $metric) {
|
||||
$symbol_tab[$child][$metric] = $info[$metric];
|
||||
}
|
||||
} else {
|
||||
if ($display_calls) {
|
||||
/* increment call count for this child */
|
||||
$symbol_tab[$child]["ct"] += $info["ct"];
|
||||
}
|
||||
|
||||
/* update inclusive times/metric for this child */
|
||||
foreach ($metrics as $metric) {
|
||||
$symbol_tab[$child][$metric] += $info[$metric];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $symbol_tab;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Prunes XHProf raw data:
|
||||
*
|
||||
* Any node whose inclusive walltime accounts for less than $prune_percent
|
||||
* of total walltime is pruned. [It is possible that a child function isn't
|
||||
* pruned, but one or more of its parents get pruned. In such cases, when
|
||||
* viewing the child function's hierarchical information, the cost due to
|
||||
* the pruned parent(s) will be attributed to a special function/symbol
|
||||
* "__pruned__()".]
|
||||
*
|
||||
* @param array $raw_data XHProf raw data to be pruned & validated.
|
||||
* @param double $prune_percent Any edges that account for less than
|
||||
* $prune_percent of time will be pruned
|
||||
* from the raw data.
|
||||
*
|
||||
* @return array Returns the pruned raw data.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_prune_run($raw_data, $prune_percent) {
|
||||
|
||||
$main_info = $raw_data["main()"];
|
||||
if (empty($main_info)) {
|
||||
xhprof_error("XHProf: main() missing in raw data");
|
||||
return false;
|
||||
}
|
||||
|
||||
// raw data should contain either wall time or samples information...
|
||||
if (isset($main_info["wt"])) {
|
||||
$prune_metric = "wt";
|
||||
} else if (isset($main_info["samples"])) {
|
||||
$prune_metric = "samples";
|
||||
} else {
|
||||
xhprof_error("XHProf: for main() we must have either wt "
|
||||
."or samples attribute set");
|
||||
return false;
|
||||
}
|
||||
|
||||
// determine the metrics present in the raw data..
|
||||
$metrics = array();
|
||||
foreach ($main_info as $metric => $val) {
|
||||
if (isset($val)) {
|
||||
$metrics[] = $metric;
|
||||
}
|
||||
}
|
||||
|
||||
$prune_threshold = (($main_info[$prune_metric] * $prune_percent) / 100.0);
|
||||
|
||||
init_metrics($raw_data, null, null, false);
|
||||
$flat_info = xhprof_compute_inclusive_times($raw_data);
|
||||
|
||||
foreach ($raw_data as $parent_child => $info) {
|
||||
|
||||
list($parent, $child) = xhprof_parse_parent_child($parent_child);
|
||||
|
||||
// is this child's overall total from all parents less than threshold?
|
||||
if ($flat_info[$child][$prune_metric] < $prune_threshold) {
|
||||
unset($raw_data[$parent_child]); // prune the edge
|
||||
} else if ($parent &&
|
||||
($parent != "__pruned__()") &&
|
||||
($flat_info[$parent][$prune_metric] < $prune_threshold)) {
|
||||
|
||||
// Parent's overall inclusive metric is less than a threshold.
|
||||
// All edges to the parent node will get nuked, and this child will
|
||||
// be a dangling child.
|
||||
// So instead change its parent to be a special function __pruned__().
|
||||
$pruned_edge = xhprof_build_parent_child_key("__pruned__()", $child);
|
||||
|
||||
if (isset($raw_data[$pruned_edge])) {
|
||||
foreach ($metrics as $metric) {
|
||||
$raw_data[$pruned_edge][$metric]+=$raw_data[$parent_child][$metric];
|
||||
}
|
||||
} else {
|
||||
$raw_data[$pruned_edge] = $raw_data[$parent_child];
|
||||
}
|
||||
|
||||
unset($raw_data[$parent_child]); // prune the edge
|
||||
}
|
||||
}
|
||||
|
||||
return $raw_data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set one key in an array and return the array
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_array_set($arr, $k, $v) {
|
||||
$arr[$k] = $v;
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes/unsets one key in an array and return the array
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_array_unset($arr, $k) {
|
||||
unset($arr[$k]);
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type definitions for URL params
|
||||
*/
|
||||
define('XHPROF_STRING_PARAM', 1);
|
||||
define('XHPROF_UINT_PARAM', 2);
|
||||
define('XHPROF_FLOAT_PARAM', 3);
|
||||
define('XHPROF_BOOL_PARAM', 4);
|
||||
|
||||
|
||||
/**
|
||||
* Internal helper function used by various
|
||||
* xhprof_get_param* flavors for various
|
||||
* types of parameters.
|
||||
*
|
||||
* @param string name of the URL query string param
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_get_param_helper($param) {
|
||||
$val = null;
|
||||
if (isset($_GET[$param]))
|
||||
$val = $_GET[$param];
|
||||
else if (isset($_POST[$param])) {
|
||||
$val = $_POST[$param];
|
||||
}
|
||||
return $val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts value for string param $param from query
|
||||
* string. If param is not specified, return the
|
||||
* $default value.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_get_string_param($param, $default = '') {
|
||||
$val = xhprof_get_param_helper($param);
|
||||
|
||||
if ($val === null)
|
||||
return $default;
|
||||
|
||||
return $val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts value for unsigned integer param $param from
|
||||
* query string. If param is not specified, return the
|
||||
* $default value.
|
||||
*
|
||||
* If value is not a valid unsigned integer, logs error
|
||||
* and returns null.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_get_uint_param($param, $default = 0) {
|
||||
$val = xhprof_get_param_helper($param);
|
||||
|
||||
if ($val === null)
|
||||
$val = $default;
|
||||
|
||||
// trim leading/trailing whitespace
|
||||
$val = trim($val);
|
||||
|
||||
// if it only contains digits, then ok..
|
||||
if (ctype_digit($val)) {
|
||||
return $val;
|
||||
}
|
||||
|
||||
xhprof_error("$param is $val. It must be an unsigned integer.");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extracts value for a float param $param from
|
||||
* query string. If param is not specified, return
|
||||
* the $default value.
|
||||
*
|
||||
* If value is not a valid unsigned integer, logs error
|
||||
* and returns null.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_get_float_param($param, $default = 0) {
|
||||
$val = xhprof_get_param_helper($param);
|
||||
|
||||
if ($val === null)
|
||||
$val = $default;
|
||||
|
||||
// trim leading/trailing whitespace
|
||||
$val = trim($val);
|
||||
|
||||
// TBD: confirm the value is indeed a float.
|
||||
if (true) // for now..
|
||||
return (float)$val;
|
||||
|
||||
xhprof_error("$param is $val. It must be a float.");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts value for a boolean param $param from
|
||||
* query string. If param is not specified, return
|
||||
* the $default value.
|
||||
*
|
||||
* If value is not a valid unsigned integer, logs error
|
||||
* and returns null.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_get_bool_param($param, $default = false) {
|
||||
$val = xhprof_get_param_helper($param);
|
||||
|
||||
if ($val === null)
|
||||
$val = $default;
|
||||
|
||||
// trim leading/trailing whitespace
|
||||
$val = trim($val);
|
||||
|
||||
switch (strtolower($val)) {
|
||||
case '0':
|
||||
case '1':
|
||||
$val = (bool)$val;
|
||||
break;
|
||||
case 'true':
|
||||
case 'on':
|
||||
case 'yes':
|
||||
$val = true;
|
||||
break;
|
||||
case 'false':
|
||||
case 'off':
|
||||
case 'no':
|
||||
$val = false;
|
||||
break;
|
||||
default:
|
||||
xhprof_error("$param is $val. It must be a valid boolean string.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return $val;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize params from URL query string. The function
|
||||
* creates globals variables for each of the params
|
||||
* and if the URL query string doesn't specify a particular
|
||||
* param initializes them with the corresponding default
|
||||
* value specified in the input.
|
||||
*
|
||||
* @params array $params An array whose keys are the names
|
||||
* of URL params who value needs to
|
||||
* be retrieved from the URL query
|
||||
* string. PHP globals are created
|
||||
* with these names. The value is
|
||||
* itself an array with 2-elems (the
|
||||
* param type, and its default value).
|
||||
* If a param is not specified in the
|
||||
* query string the default value is
|
||||
* used.
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_param_init($params) {
|
||||
/* Create variables specified in $params keys, init defaults */
|
||||
foreach ($params as $k => $v) {
|
||||
switch ($v[0]) {
|
||||
case XHPROF_STRING_PARAM:
|
||||
$p = xhprof_get_string_param($k, $v[1]);
|
||||
break;
|
||||
case XHPROF_UINT_PARAM:
|
||||
$p = xhprof_get_uint_param($k, $v[1]);
|
||||
break;
|
||||
case XHPROF_FLOAT_PARAM:
|
||||
$p = xhprof_get_float_param($k, $v[1]);
|
||||
break;
|
||||
case XHPROF_BOOL_PARAM:
|
||||
$p = xhprof_get_bool_param($k, $v[1]);
|
||||
break;
|
||||
default:
|
||||
xhprof_error("Invalid param type passed to xhprof_param_init: "
|
||||
. $v[0]);
|
||||
exit();
|
||||
}
|
||||
|
||||
// create a global variable using the parameter name.
|
||||
$GLOBALS[$k] = $p;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Given a partial query string $q return matching function names in
|
||||
* specified XHProf run. This is used for the type ahead function
|
||||
* selector.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_get_matching_functions($q, $xhprof_data) {
|
||||
|
||||
$matches = array();
|
||||
|
||||
foreach ($xhprof_data as $parent_child => $info) {
|
||||
list($parent, $child) = xhprof_parse_parent_child($parent_child);
|
||||
if (stripos($parent, $q) !== false) {
|
||||
$matches[$parent] = 1;
|
||||
}
|
||||
if (stripos($child, $q) !== false) {
|
||||
$matches[$child] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
$res = array_keys($matches);
|
||||
|
||||
// sort it so the answers are in some reliable order...
|
||||
asort($res);
|
||||
|
||||
return ($res);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
//
|
||||
// Copyright (c) 2009 Facebook
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
//
|
||||
// This file defines the interface iXHProfRuns and also provides a default
|
||||
// implementation of the interface (class XHProfRuns).
|
||||
//
|
||||
|
||||
/**
|
||||
* iXHProfRuns interface for getting/saving a XHProf run.
|
||||
*
|
||||
* Clients can either use the default implementation,
|
||||
* namely XHProfRuns_Default, of this interface or define
|
||||
* their own implementation.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
interface iXHProfRuns {
|
||||
|
||||
/**
|
||||
* Returns XHProf data given a run id ($run) of a given
|
||||
* type ($type).
|
||||
*
|
||||
* Also, a brief description of the run is returned via the
|
||||
* $run_desc out parameter.
|
||||
*/
|
||||
public function get_run($run_id, $type, &$run_desc);
|
||||
|
||||
/**
|
||||
* Save XHProf data for a profiler run of specified type
|
||||
* ($type).
|
||||
*
|
||||
* The caller may optionally pass in run_id (which they
|
||||
* promise to be unique). If a run_id is not passed in,
|
||||
* the implementation of this method must generated a
|
||||
* unique run id for this saved XHProf run.
|
||||
*
|
||||
* Returns the run id for the saved XHProf run.
|
||||
*
|
||||
*/
|
||||
public function save_run($xhprof_data, $type, $run_id = null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* XHProfRuns_Default is the default implementation of the
|
||||
* iXHProfRuns interface for saving/fetching XHProf runs.
|
||||
*
|
||||
* It stores/retrieves runs to/from a filesystem directory
|
||||
* specified by the "xhprof.output_dir" ini parameter.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
class XHProfRuns_Default implements iXHProfRuns {
|
||||
|
||||
private $dir = '';
|
||||
private $suffix = 'xhprof';
|
||||
|
||||
private function gen_run_id($type) {
|
||||
return uniqid();
|
||||
}
|
||||
|
||||
private function file_name($run_id, $type) {
|
||||
|
||||
$file = "$run_id.$type." . $this->suffix;
|
||||
|
||||
if (!empty($this->dir)) {
|
||||
$file = $this->dir . "/" . $file;
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
|
||||
public function __construct($dir = null) {
|
||||
|
||||
// if user hasn't passed a directory location,
|
||||
// we use the xhprof.output_dir ini setting
|
||||
// if specified, else we default to the directory
|
||||
// in which the error_log file resides.
|
||||
|
||||
if (empty($dir)) {
|
||||
$dir = ini_get("xhprof.output_dir");
|
||||
if (empty($dir)) {
|
||||
|
||||
// some default that at least works on unix...
|
||||
$dir = "/tmp";
|
||||
|
||||
xhprof_error("Warning: Must specify directory location for XHProf runs. ".
|
||||
"Trying {$dir} as default. You can either pass the " .
|
||||
"directory location as an argument to the constructor ".
|
||||
"for XHProfRuns_Default() or set xhprof.output_dir ".
|
||||
"ini param.");
|
||||
}
|
||||
}
|
||||
$this->dir = $dir;
|
||||
}
|
||||
|
||||
public function get_run($run_id, $type, &$run_desc) {
|
||||
$file_name = $this->file_name($run_id, $type);
|
||||
|
||||
if (!file_exists($file_name)) {
|
||||
xhprof_error("Could not find file $file_name");
|
||||
$run_desc = "Invalid Run Id = $run_id";
|
||||
return null;
|
||||
}
|
||||
|
||||
$contents = file_get_contents($file_name);
|
||||
$run_desc = "XHProf Run (Namespace=$type)";
|
||||
return unserialize($contents);
|
||||
}
|
||||
|
||||
public function save_run($xhprof_data, $type, $run_id = null) {
|
||||
|
||||
// Use PHP serialize function to store the XHProf's
|
||||
// raw profiler data.
|
||||
$xhprof_data = serialize($xhprof_data);
|
||||
|
||||
if ($run_id === null) {
|
||||
$run_id = $this->gen_run_id($type);
|
||||
}
|
||||
|
||||
$file_name = $this->file_name($run_id, $type);
|
||||
$file = fopen($file_name, 'w');
|
||||
|
||||
if ($file) {
|
||||
fwrite($file, $xhprof_data);
|
||||
fclose($file);
|
||||
} else {
|
||||
xhprof_error("Could not open $file_name\n");
|
||||
}
|
||||
|
||||
// echo "Saved run in {$file_name}.\nRun id = {$run_id}.\n";
|
||||
return $run_id;
|
||||
}
|
||||
|
||||
function list_runs() {
|
||||
if (is_dir($this->dir)) {
|
||||
echo "<hr/>Existing runs:\n<ul>\n";
|
||||
foreach (glob("{$this->dir}/*.{$this->suffix}") as $file) {
|
||||
list($run,$source) = explode('.', basename($file));
|
||||
echo '<li><a href="' . htmlentities($_SERVER['SCRIPT_NAME'])
|
||||
. '?run=' . htmlentities($run) . '&source='
|
||||
. htmlentities($source) . '">'
|
||||
. htmlentities(basename($file)) . "</a><small> "
|
||||
. date("Y-m-d H:i:s", filemtime($file)) . "</small></li>\n";
|
||||
}
|
||||
echo "</ul>\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,542 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* @package core
|
||||
* @subpackage 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();
|
||||
|
||||
// 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');
|
||||
|
||||
// TODO: Change the implementation below to proper profiling class
|
||||
|
||||
/**
|
||||
* Returns if profiling is running, optionally setting it
|
||||
*/
|
||||
function profiling_is_running($value = null) {
|
||||
static $running = null;
|
||||
|
||||
if (!is_null($value)) {
|
||||
$running = (bool)$value;
|
||||
}
|
||||
|
||||
return $running;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if profiling has been saved, optionally setting it
|
||||
*/
|
||||
function profiling_is_saved($value = null) {
|
||||
static $saved = null;
|
||||
|
||||
if (!is_null($value)) {
|
||||
$saved = (bool)$value;
|
||||
}
|
||||
|
||||
return $saved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start profiling observing all the configuration
|
||||
*/
|
||||
function profiling_start() {
|
||||
global $CFG, $SESSION, $SCRIPT;
|
||||
|
||||
// If profiling isn't available, nothing to start
|
||||
if (!extension_loaded('xhprof') || !function_exists('xhprof_enable')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If profiling isn't enabled, nothing to start
|
||||
if (!$CFG->profilingenabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If profiling is already running or saved, nothing to start
|
||||
if (profiling_is_running() || profiling_is_saved()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get PGC variables
|
||||
$check = 'PROFILEME';
|
||||
$profileme = isset($_POST[$check]) || isset($_GET[$check]) || isset($_COOKIE[$check]) ? true : false;
|
||||
$profileme = $profileme && $CFG->profilingallowme;
|
||||
$check = 'DONTPROFILEME';
|
||||
$dontprofileme = isset($_POST[$check]) || isset($_GET[$check]) || isset($_COOKIE[$check]) ? true : false;
|
||||
$dontprofileme = $dontprofileme && $CFG->profilingallowme;
|
||||
$check = 'PROFILEALL';
|
||||
$profileall = isset($_POST[$check]) || isset($_GET[$check]) || isset($_COOKIE[$check]) ? true : false;
|
||||
$profileall = $profileall && $CFG->profilingallowall;
|
||||
$check = 'PROFILEALLSTOP';
|
||||
$profileallstop = isset($_POST[$check]) || isset($_GET[$check]) || isset($_COOKIE[$check]) ? true : false;
|
||||
$profileallstop = $profileallstop && $CFG->profilingallowall;
|
||||
|
||||
// DONTPROFILEME detected, nothing to start
|
||||
if ($dontprofileme) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// PROFILEALLSTOP detected, clean the mark in seesion and continue
|
||||
if ($profileallstop) {
|
||||
unset($SESSION->profileall);
|
||||
}
|
||||
|
||||
// PROFILEALL detected, set the mark in session and continue
|
||||
if ($profileall) {
|
||||
$SESSION->profileall = true;
|
||||
|
||||
// SESSION->profileall detected, set $profileall
|
||||
} else if (!empty($SESSION->profileall)) {
|
||||
$profileall = true;
|
||||
}
|
||||
|
||||
// Evaluate automatic (random) profiling if necessary
|
||||
$profileauto = false;
|
||||
if ($CFG->profilingautofrec) {
|
||||
$profileauto = (mt_rand(1, $CFG->profilingautofrec) === 1);
|
||||
}
|
||||
|
||||
// See if the $SCRIPT matches any of the included patterns
|
||||
$profileincluded = profiling_string_matches($SCRIPT, $CFG->profilingincluded);
|
||||
|
||||
// See if the $SCRIPT matches any of the excluded patterns
|
||||
$profileexcluded = profiling_string_matches($SCRIPT, $CFG->profilingexcluded);
|
||||
|
||||
// Decide if profile auto must happen (observe matchings)
|
||||
$profileauto = $profileauto && $profileincluded && !$profileexcluded;
|
||||
|
||||
// Decide if profile by match must happen (only if profileauto is disabled)
|
||||
$profilematch = $profileincluded && !$profileexcluded && !$CFG->profilingautofrec;
|
||||
|
||||
// If not auto, me, all, match have been detected, nothing to do
|
||||
if (!$profileauto && !$profileme && !$profileall && !$profilematch) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Arrived here, the script is going to be profiled, let's do it
|
||||
$ignore = array('call_user_func', 'call_user_func_array');
|
||||
xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY, array('ignored_functions' => $ignore));
|
||||
profiling_is_running(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop profiling, gathering results and storing them
|
||||
*/
|
||||
function profiling_stop() {
|
||||
global $CFG, $SCRIPT;
|
||||
|
||||
// If profiling isn't available, nothing to stop
|
||||
if (!extension_loaded('xhprof') || !function_exists('xhprof_enable')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If profiling isn't enabled, nothing to stop
|
||||
if (!$CFG->profilingenabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If profiling is not running or is already saved, nothing to stop
|
||||
if (!profiling_is_running() || profiling_is_saved()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Arrived here, profiling is running, stop and save everything
|
||||
profiling_is_running(false);
|
||||
$data = xhprof_disable();
|
||||
|
||||
$run = new moodle_xhprofrun();
|
||||
$run->prepare_run($SCRIPT);
|
||||
$runid = $run->save_run($data, null);
|
||||
profiling_is_saved(true);
|
||||
|
||||
// Prune old runs
|
||||
profiling_prune_old_runs($runid);
|
||||
}
|
||||
|
||||
function profiling_prune_old_runs($exception = 0) {
|
||||
global $CFG, $DB;
|
||||
|
||||
// Setting to 0 = no prune
|
||||
if (empty($CFG->profilinglifetime)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cuttime = time() - ($CFG->profilinglifetime * 60);
|
||||
$params = array('cuttime' => $cuttime, 'exception' => $exception);
|
||||
|
||||
$DB->delete_records_select('profiling', 'runreference = 0 AND
|
||||
timecreated < :cuttime AND
|
||||
runid != :exception', $params);
|
||||
}
|
||||
|
||||
function profiling_urls($report, $runid, $runid2 = null) {
|
||||
global $CFG;
|
||||
|
||||
$url = '';
|
||||
switch ($report) {
|
||||
case 'run':
|
||||
$url = $CFG->wwwroot . '/lib/xhprof/xhprof_html/index.php?run=' . $runid;
|
||||
break;
|
||||
case 'diff':
|
||||
$url = $CFG->wwwroot . '/lib/xhprof/xhprof_html/index.php?run1=' . $runid . '&run2=' . $runid2;
|
||||
break;
|
||||
case 'graph':
|
||||
$url = $CFG->wwwroot . '/lib/xhprof/xhprof_html/callgraph.php?run=' . $runid;
|
||||
break;
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
function profiling_print_run($run, $prevrunid = null) {
|
||||
global $CFG, $OUTPUT;
|
||||
|
||||
$output = '';
|
||||
|
||||
// Prepare the runreference/runcomment form
|
||||
$checked = $run->runreference ? ' checked=checked' : '';
|
||||
$referenceform = "<form id=\"profiling_runreference\" action=\"index.php\" method=\"GET\">" .
|
||||
"<input type=\"hidden\" name=\"sesskey\" value=\"" . sesskey() . "\"/>".
|
||||
"<input type=\"hidden\" name=\"runid\" value=\"$run->runid\"/>".
|
||||
"<input type=\"hidden\" name=\"listurl\" value=\"$run->url\"/>".
|
||||
"<input type=\"checkbox\" name=\"runreference\" value=\"1\"$checked/> ".
|
||||
"<input type=\"text\" name=\"runcomment\" value=\"$run->runcomment\"/> ".
|
||||
"<input type=\"submit\" value=\"" . get_string('savechanges') ."\"/>".
|
||||
"</form>";
|
||||
|
||||
$table = new html_table();
|
||||
$table->align = array('right', 'left');
|
||||
$table->tablealign = 'center';
|
||||
$table->attributes['class'] = 'profilingruntable';
|
||||
$table->colclasses = array('label', 'value');
|
||||
$table->data = array(
|
||||
array(get_string('runid', 'report_profiling'), $run->runid),
|
||||
array(get_string('url'), $run->url),
|
||||
array(get_string('date'), userdate($run->timecreated, '%d %B %Y, %H:%M')),
|
||||
array(get_string('executiontime', 'report_profiling'), format_float($run->totalexecutiontime / 1000, 3) . ' ms'),
|
||||
array(get_string('cputime', 'report_profiling'), format_float($run->totalcputime / 1000, 3) . ' ms'),
|
||||
array(get_string('calls', 'report_profiling'), $run->totalcalls),
|
||||
array(get_string('memory', 'report_profiling'), format_float($run->totalmemory / 1024, 0) . ' KB'),
|
||||
array(get_string('markreferencerun', 'report_profiling'), $referenceform));
|
||||
$output = $OUTPUT->box(html_writer::table($table), 'generalbox boxwidthwide boxaligncenter profilingrunbox', 'profiling_summary', true);
|
||||
// Add link to details
|
||||
$strviewdetails = get_string('viewdetails', 'report_profiling');
|
||||
$url = profiling_urls('run', $run->runid);
|
||||
$output.=$OUTPUT->heading('<a href="' . $url . '" onclick="javascript:window.open(' . "'" . $url . "'" . ');' .
|
||||
'return false;"' . ' title="">' . $strviewdetails . '</a>', 3, 'main profilinglink');
|
||||
// If there is one previous run marked as reference, add link to diff
|
||||
if ($prevrunid) {
|
||||
$strviewdiff = get_string('viewdiff', 'report_profiling');
|
||||
$url = 'index.php?runid=' . $run->runid . '&runid2=' . $prevrunid . '&listurl=' . urlencode($run->url);
|
||||
$output.=$OUTPUT->heading('<a href="' . $url . '" title="">' . $strviewdiff . '</a>', 3, 'main profilinglink');
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
function profiling_print_rundiff($run1, $run2) {
|
||||
global $CFG, $OUTPUT;
|
||||
|
||||
$output = '';
|
||||
|
||||
// Prepare the reference/comment information
|
||||
$referencetext1 = ($run1->runreference ? get_string('yes') : get_string('no')) .
|
||||
($run1->runcomment ? ' - ' . s($run1->runcomment) : '');
|
||||
$referencetext2 = ($run2->runreference ? get_string('yes') : get_string('no')) .
|
||||
($run2->runcomment ? ' - ' . s($run2->runcomment) : '');
|
||||
|
||||
// Calculate global differences
|
||||
$diffexecutiontime = profiling_get_difference($run1->totalexecutiontime, $run2->totalexecutiontime, 'ms', 1000);
|
||||
$diffcputime = profiling_get_difference($run1->totalcputime, $run2->totalcputime, 'ms', 1000);
|
||||
$diffcalls = profiling_get_difference($run1->totalcalls, $run2->totalcalls);
|
||||
$diffmemory = profiling_get_difference($run1->totalmemory, $run2->totalmemory, 'KB', 1024);
|
||||
|
||||
$table = new html_table();
|
||||
$table->align = array('right', 'left', 'left', 'left');
|
||||
$table->tablealign = 'center';
|
||||
$table->attributes['class'] = 'profilingruntable';
|
||||
$table->colclasses = array('label', 'value1', 'value2');
|
||||
$table->data = array(
|
||||
array(get_string('runid', 'report_profiling'),
|
||||
'<a href="index.php?runid=' . $run1->runid . '&listurl=' . urlencode($run1->url) . '" title="">' . $run1->runid . '</a>',
|
||||
'<a href="index.php?runid=' . $run2->runid . '&listurl=' . urlencode($run2->url) . '" title="">' . $run2->runid . '</a>'),
|
||||
array(get_string('url'), $run1->url, $run2->url),
|
||||
array(get_string('date'), userdate($run1->timecreated, '%d %B %Y, %H:%M'),
|
||||
userdate($run2->timecreated, '%d %B %Y, %H:%M')),
|
||||
array(get_string('executiontime', 'report_profiling'),
|
||||
format_float($run1->totalexecutiontime / 1000, 3) . ' ms',
|
||||
format_float($run2->totalexecutiontime / 1000, 3) . ' ms ' . $diffexecutiontime),
|
||||
array(get_string('cputime', 'report_profiling'),
|
||||
format_float($run1->totalcputime / 1000, 3) . ' ms',
|
||||
format_float($run2->totalcputime / 1000, 3) . ' ms ' . $diffcputime),
|
||||
array(get_string('calls', 'report_profiling'), $run1->totalcalls, $run2->totalcalls . ' ' . $diffcalls),
|
||||
array(get_string('memory', 'report_profiling'),
|
||||
format_float($run1->totalmemory / 1024, 0) . ' KB',
|
||||
format_float($run2->totalmemory / 1024, 0) . ' KB ' . $diffmemory),
|
||||
array(get_string('referencerun', 'report_profiling'), $referencetext1, $referencetext2));
|
||||
$output = $OUTPUT->box(html_writer::table($table), 'generalbox boxwidthwide boxaligncenter profilingrunbox', 'profiling_summary', true);
|
||||
// Add link to details
|
||||
$strviewdetails = get_string('viewdiffdetails', 'report_profiling');
|
||||
$url = profiling_urls('diff', $run1->runid, $run2->runid);
|
||||
//$url = $CFG->wwwroot . '/admin/report/profiling/index.php?run=' . $run->runid;
|
||||
$output.=$OUTPUT->heading('<a href="' . $url . '" onclick="javascript:window.open(' . "'" . $url . "'" . ');' .
|
||||
'return false;"' . ' title="">' . $strviewdetails . '</a>', 3, 'main profilinglink');
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns the HTML fragment to
|
||||
* be displayed on listing mode, it includes actions
|
||||
* like deletion/export/import...
|
||||
*/
|
||||
function profiling_list_controls($listurl) {
|
||||
global $CFG, $OUTPUT;
|
||||
|
||||
$output = '';
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that looks for matchings of one string
|
||||
* against an array of * wildchar patterns
|
||||
*/
|
||||
function profiling_string_matches($string, $patterns) {
|
||||
$patterns = explode(',', $patterns);
|
||||
foreach ($patterns as $pattern) {
|
||||
// Trim and prepare pattern
|
||||
$pattern = str_replace('\*', '.*', preg_quote(trim($pattern), '~'));
|
||||
// Don't process empty patterns
|
||||
if (empty($pattern)) {
|
||||
continue;
|
||||
}
|
||||
if (preg_match('~' . $pattern . '~', $string)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that, given to floats, returns their numerical
|
||||
* and percentual differences, propertly formated and cssstyled
|
||||
*/
|
||||
function profiling_get_difference($number1, $number2, $units = '', $factor = 1, $numdec = 2) {
|
||||
$numdiff = $number2 - $number1;
|
||||
$perdiff = 0;
|
||||
if ($number1 != $number2) {
|
||||
$perdiff = $number1 != 0 ? ($number2 * 100 / $number1) - 100 : 0;
|
||||
}
|
||||
$sign = $number2 > $number1 ? '+' : '';
|
||||
$delta = abs($perdiff) > 0.25 ? 'Δ' : '≈';
|
||||
$spanclass = $number2 > $number1 ? 'worse' : ($number1 > $number2 ? 'better' : 'same');
|
||||
$importantclass= abs($perdiff) > 1 ? ' profiling_important' : '';
|
||||
$startspan = '<span class="profiling_' . $spanclass . $importantclass . '">';
|
||||
$endspan = '</span>';
|
||||
$fnumdiff = $sign . format_float($numdiff / $factor, $numdec);
|
||||
$fperdiff = $sign . format_float($perdiff, $numdec);
|
||||
return $startspan . $delta . ' ' . $fnumdiff . ' ' . $units . ' (' . $fperdiff . '%)' . $endspan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom implementation of iXHProfRuns
|
||||
*
|
||||
* This class is one implementation of the iXHProfRuns interface, in charge
|
||||
* of storing and retrieve profiling run data to/from DB (profiling table)
|
||||
*
|
||||
* The interface only defines two methods to be defined: get_run() and
|
||||
* save_run() we'll be implementing some more in order to keep all the
|
||||
* rest of information in our runs properly handled.
|
||||
*/
|
||||
class moodle_xhprofrun implements iXHProfRuns {
|
||||
|
||||
protected $runid = null;
|
||||
protected $url = null;
|
||||
protected $totalexecutiontime = 0;
|
||||
protected $totalcputime = 0;
|
||||
protected $totalcalls = 0;
|
||||
protected $totalmemory = 0;
|
||||
protected $timecreated = 0;
|
||||
|
||||
public function __construct() {
|
||||
$this->timecreated = time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given one runid and one type, return the run data
|
||||
* and some extra info in run_desc from DB
|
||||
*
|
||||
* Note that $type is completely ignored
|
||||
*/
|
||||
public function get_run($run_id, $type, &$run_desc) {
|
||||
global $DB;
|
||||
|
||||
$rec = $DB->get_record('profiling', array('runid' => $run_id), '*', MUST_EXIST);
|
||||
|
||||
$this->runid = $rec->runid;
|
||||
$this->url = $rec->url;
|
||||
$this->totalexecutiontime = $rec->totalexecutiontime;
|
||||
$this->totalcputime = $rec->totalcputime;
|
||||
$this->totalcalls = $rec->totalcalls;
|
||||
$this->totalmemory = $rec->totalmemory;
|
||||
$this->timecreated = $rec->timecreated;
|
||||
|
||||
$run_desc = $this->url . ($rec->runreference ? ' (R) ' : ' ') . ' - ' . s($rec->runcomment);
|
||||
|
||||
return unserialize(base64_decode($rec->data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Given some run data, one type and, optionally, one runid
|
||||
* store the information in DB
|
||||
*
|
||||
* Note that $type is completely ignored
|
||||
*/
|
||||
public function save_run($xhprof_data, $type, $run_id = null) {
|
||||
global $DB;
|
||||
|
||||
if (is_null($this->url)) {
|
||||
xhprof_error("Warning: You must use the prepare_run() method before saving it");
|
||||
}
|
||||
|
||||
// Calculate runid if needed
|
||||
$this->runid = is_null($run_id) ? md5($this->url . '-' . uniqid()) : $run_id;
|
||||
|
||||
// Calculate totals
|
||||
$this->totalexecutiontime = $xhprof_data['main()']['wt'];
|
||||
$this->totalcputime = $xhprof_data['main()']['cpu'];
|
||||
$this->totalcalls = array_reduce($xhprof_data, array($this, 'sum_calls'));
|
||||
$this->totalmemory = $xhprof_data['main()']['mu'];
|
||||
|
||||
// Prepare data
|
||||
$rec = new stdClass();
|
||||
$rec->runid = $this->runid;
|
||||
$rec->url = $this->url;
|
||||
$rec->data = base64_encode(serialize($xhprof_data));
|
||||
$rec->totalexecutiontime = $this->totalexecutiontime;
|
||||
$rec->totalcputime = $this->totalcputime;
|
||||
$rec->totalcalls = $this->totalcalls;
|
||||
$rec->totalmemory = $this->totalmemory;
|
||||
$rec->timecreated = $this->timecreated;
|
||||
|
||||
$DB->insert_record('profiling', $rec);
|
||||
return $this->runid;
|
||||
}
|
||||
|
||||
public function prepare_run($url) {
|
||||
$this->url = $url;
|
||||
}
|
||||
|
||||
// Private API starts here
|
||||
|
||||
protected function sum_calls($sum, $data) {
|
||||
return $sum + $data['ct'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple subclass of {@link table_sql} that provides
|
||||
* some custom formatters for various columns, in order
|
||||
* to make the main profiles list nicer
|
||||
*/
|
||||
class xhprof_table_sql extends table_sql {
|
||||
|
||||
protected $listurlmode = false;
|
||||
|
||||
/**
|
||||
* Overwrite this method to be able to inject extra class to
|
||||
* some (reference rows). Original API doesn't seem to allow that
|
||||
*/
|
||||
function build_table(){
|
||||
if ($this->rawdata){
|
||||
foreach($this->rawdata as $row){
|
||||
$formattedrow = $this->format_row($row);
|
||||
// reference row, add 'referencerun' class
|
||||
$classname = $row->runreference ? 'referencerun' : '';
|
||||
$this->add_data_keyed($formattedrow, $classname);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define it the table is in listurlmode or not, output will
|
||||
* be different based on that
|
||||
*/
|
||||
function set_listurlmode($listurlmode) {
|
||||
$this->listurlmode = $listurlmode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format URL, so it points to last run for that url
|
||||
*/
|
||||
protected function col_url($row) {
|
||||
global $OUTPUT;
|
||||
|
||||
// Build the link to latest run for the script
|
||||
$scripturl = new moodle_url('/admin/report/profiling/index.php', array('script' => $row->url, 'listurl' => $row->url));
|
||||
$scriptaction = $OUTPUT->action_link($scripturl, $row->url);
|
||||
|
||||
// Decide, based on $this->listurlmode which actions to show
|
||||
if ($this->listurlmode) {
|
||||
$detailsaction = '';
|
||||
} else {
|
||||
// Build link icon to script details (pix + url + actionlink)
|
||||
$detailsimg = $OUTPUT->pix_icon('t/right', get_string('profilingfocusscript', 'report_profiling', $row->url));
|
||||
$detailsurl = new moodle_url('/admin/report/profiling/index.php', array('listurl' => $row->url));
|
||||
$detailsaction = $OUTPUT->action_link($detailsurl, $detailsimg);
|
||||
}
|
||||
|
||||
return $scriptaction . ' ' . $detailsaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format profiling date, human and pointing to run
|
||||
*/
|
||||
protected function col_timecreated($row) {
|
||||
global $OUTPUT;
|
||||
$fdate = userdate($row->timecreated, '%d %b %Y, %H:%M');
|
||||
$url = new moodle_url('/admin/report/profiling/index.php', array('runid' => $row->runid, 'listurl' => $row->url));
|
||||
return $OUTPUT->action_link($url, $fdate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format execution time
|
||||
*/
|
||||
protected function col_totalexecutiontime($row) {
|
||||
return format_float($row->totalexecutiontime / 1000, 3) . ' ms';
|
||||
}
|
||||
|
||||
/**
|
||||
* Format cpu time
|
||||
*/
|
||||
protected function col_totalcputime($row) {
|
||||
return format_float($row->totalcputime / 1000, 3) . ' ms';
|
||||
}
|
||||
|
||||
/**
|
||||
* Format memory
|
||||
*/
|
||||
protected function col_totalmemory($row) {
|
||||
return format_float($row->totalmemory / 1024, 3) . ' KB';
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2010121400; // YYYYMMDD = date of the last version bump
|
||||
$version = 2010121401; // YYYYMMDD = date of the last version bump
|
||||
// XX = daily increments
|
||||
|
||||
$release = '2.0 (Build: 20101214)'; // Human-friendly version name
|
||||
|
||||
Reference in New Issue
Block a user