Commit 7953e21f authored by Ad Schellevis's avatar Ad Schellevis

add XDebug support switch and debugger for development

When using webgrind, use the optional graphviz package for callgraph support
parent 76329592
......@@ -893,13 +893,20 @@ function system_generate_lighty_config(
} else {
$captiveportal = ",\"mod_cgi\"";
$http_rewrite_rules = <<<EOD
### Phalcon ui and api routing
# Phalcon ui and api routing
alias.url += ( "/ui/" => "/usr/local/opnsense/www/" )
alias.url += ( "/api/" => "/usr/local/opnsense/www/" )
url.rewrite-if-not-file = ( "^/ui/(.*)$" => "/ui/index.php?_url=/$1" ,
"^/api/(.*)$" => "/api/api.php?_url=/$1"
)
EOD;
if (isset($config['system']['webgui']['enable_xdebug'])) {
$http_rewrite_rules .= <<<EOD
# Enable webgrind to analyse profiler results
alias.url += ( "/webgrind/" => "/usr/local/opnsense/contrib/webgrind/" )
EOD;
}
$captive_portal_mod_evasive = "";
$server_upload_dirs = "server.upload-dirs = ( \"{$g['upload_path']}/\", \"/tmp/\", \"/var/\" )\n";
......
......@@ -163,6 +163,20 @@ for EXT in $PHPMODULES; do
fi
done
# Enable XDebug if enabled in config
if [ ! -z `/usr/bin/grep "<enable_xdebug>1</enable_xdebug>" /conf/config.xml` ]; then
/bin/cat >>/usr/local/lib/php.ini <<EOF
extension=xdebug.so
[xdebug]
xdebug.profiler_enable_trigger = 1
xdebug.profiler_output_name = cachegrind.out.%t.%p
EOF
fi
/bin/cat >>/usr/local/lib/php.ini <<EOF
[suhosin]
suhosin.get.max_array_depth = 5000
......
Webgrind
========
Webgrind is a [Xdebug](http://www.xdebug.org) profiling web frontend in PHP5. It implements a subset of the features of [kcachegrind](http://kcachegrind.sourceforge.net/cgi-bin/show.cgi) and installs in seconds and works on all platforms. For quick'n'dirty optimizations it does the job. Here's a screenshot showing the output from profiling:
[![](http://jokke.dk/media/2008-webgrind/webgrind_small.png)](http://jokke.dk/media/2008-webgrind/webgrind_large.png)
It is possible that a larger number of kcachegrind features will be implemented in the future, bringing webgrind closer to completing one of the [suggested PHP Google Summer of Code 2008](http://wiki.php.net/gsoc/2008#xdebug_profiling_web_frontend). At this point nothing has been planned, though.
Features
--------
* Super simple, cross platform installation - obviously :)
* Track time spent in functions by self cost or inclusive cost. Inclusive cost is time inside function + calls to other functions.
* See if time is spent in internal or user functions.
* See where any function was called from and which functions it calls.
* Generate a call graph using [gprof2dot.py](https://github.com/jrfonseca/gprof2dot)
Suggestions for improvements and new features are more than welcome - this is just a start.
Mailing list is available through the [webgrind google group](http://groups.google.com/group/webgrind-general/topics).
Installation
------------
1. Download webgrind
2. Unzip package to favourite path accessible by webserver.
3. Load webgrind in browser and start profiling
See the [Installation Wiki page](https://github.com/jokkedk/webgrind/wiki/Installation) for more
Credits
-------
Webgrind is written by [Joakim Nygård](http://jokke.dk) and [Jacob Oettinger](http://oettinger.dk). It would not have been possible without the great tool that Xdebug is thanks to [Derick Rethans](http://www.derickrethans.nl).
<?php
/**
* Configuration for webgrind
* @author Jacob Oettinger
* @author Joakim Nygård
*/
class Webgrind_Config extends Webgrind_MasterConfig {
/**
* Automatically check if a newer version of webgrind is available for download
*/
static $checkVersion = true;
static $hideWebgrindProfiles = true;
/**
* Writable dir for information storage.
* If empty, will use system tmp folder or xdebug tmp
*/
static $storageDir = '';
static $profilerDir = '/tmp';
/**
* Suffix for preprocessed files
*/
static $preprocessedSuffix = '.webgrind';
static $defaultTimezone = 'Europe/Copenhagen';
static $dateFormat = 'Y-m-d H:i:s';
static $defaultCostformat = 'percent'; // 'percent', 'usec' or 'msec'
static $defaultFunctionPercentage = 90;
static $defaultHideInternalFunctions = false;
/**
* Path to python executable
*/
static $pythonExecutable = '/usr/local/bin/python2.7';
/**
* Path to graphviz dot executable
*/
static $dotExecutable = '/usr/local/bin/dot';
/**
* sprintf compatible format for generating links to source files.
* %1$s will be replaced by the full path name of the file
* %2$d will be replaced by the linenumber
*/
static $fileUrlFormat = 'index.php?op=fileviewer&file=%1$s#line%2$d'; // Built in fileviewer
//static $fileUrlFormat = 'txmt://open/?url=file://%1$s&line=%2$d'; // Textmate
//static $fileUrlFormat = 'file://%1$s'; // ?
/**
* format of the trace drop down list
* default is: invokeurl (tracefile_name) [tracefile_size]
* the following options will be replaced:
* %i - invoked url
* %f - trace file name
* %s - size of trace file
* %m - modified time of file name (in dateFormat specified above)
*/
static $traceFileListFormat = '%i (%f) [%s]';
#########################
# BELOW NOT FOR EDITING #
#########################
/**
* Regex that matches the trace files generated by xdebug
*/
static function xdebugOutputFormat() {
$outputName = ini_get('xdebug.profiler_output_name');
if($outputName=='') // Ini value not defined
$outputName = '/^cachegrind\.out\..+$/';
else
$outputName = '/^'.preg_replace('/(%[^%])+/', '.+', $outputName).'$/';
return $outputName;
}
/**
* Directory to search for trace files
*/
static function xdebugOutputDir() {
$dir = ini_get('xdebug.profiler_output_dir');
if($dir=='') // Ini value not defined
return realpath(Webgrind_Config::$profilerDir).'/';
return realpath($dir).'/';
}
/**
* Writable dir for information storage
*/
static function storageDir() {
if (!empty(Webgrind_Config::$storageDir))
return realpath(Webgrind_Config::$storageDir).'/';
if (!function_exists('sys_get_temp_dir') || !is_writable(sys_get_temp_dir())) {
# use xdebug setting
return Webgrind_Config::xdebugOutputDir();
}
return realpath(sys_get_temp_dir()).'/';
}
}
<?php
/**
* @author Jacob Oettinger
* @author Joakim Nygård
*/
class Webgrind_MasterConfig
{
static $webgrindVersion = '1.1';
}
require './config.php';
require './library/FileHandler.php';
// TODO: Errorhandling:
// No files, outputdir not writable
set_time_limit(0);
// Make sure we have a timezone for date functions.
if (ini_get('date.timezone') == '')
date_default_timezone_set( Webgrind_Config::$defaultTimezone );
try {
switch(get('op')){
case 'file_list':
echo json_encode(Webgrind_FileHandler::getInstance()->getTraceList());
break;
case 'function_list':
$dataFile = get('dataFile');
if($dataFile=='0'){
$files = Webgrind_FileHandler::getInstance()->getTraceList();
$dataFile = $files[0]['filename'];
}
$reader = Webgrind_FileHandler::getInstance()->getTraceReader($dataFile, get('costFormat', Webgrind_Config::$defaultCostformat));
$functions = array();
$shownTotal = 0;
$breakdown = array('internal' => 0, 'procedural' => 0, 'class' => 0, 'include' => 0);
for($i=0;$i<$reader->getFunctionCount();$i++) {
$functionInfo = $reader->getFunctionInfo($i);
if (false !== strpos($functionInfo['functionName'], 'php::')) {
$breakdown['internal'] += $functionInfo['summedSelfCost'];
$humanKind = 'internal';
} elseif (false !== strpos($functionInfo['functionName'], 'require_once::') ||
false !== strpos($functionInfo['functionName'], 'require::') ||
false !== strpos($functionInfo['functionName'], 'include_once::') ||
false !== strpos($functionInfo['functionName'], 'include::')) {
$breakdown['include'] += $functionInfo['summedSelfCost'];
$humanKind = 'include';
} else {
if (false !== strpos($functionInfo['functionName'], '->') || false !== strpos($functionInfo['functionName'], '::')) {
$breakdown['class'] += $functionInfo['summedSelfCost'];
$humanKind = 'class';
} else {
$breakdown['procedural'] += $functionInfo['summedSelfCost'];
$humanKind = 'procedural';
}
}
if (!(int)get('hideInternals', 0) || strpos($functionInfo['functionName'], 'php::') === false) {
$shownTotal += $functionInfo['summedSelfCost'];
$functions[$i] = $functionInfo;
$functions[$i]['nr'] = $i;
$functions[$i]['humanKind'] = $humanKind;
}
}
usort($functions,'costCmp');
$remainingCost = $shownTotal*get('showFraction');
$result['functions'] = array();
foreach($functions as $function){
$remainingCost -= $function['summedSelfCost'];
$function['file'] = urlencode($function['file']);
$result['functions'][] = $function;
if($remainingCost<0)
break;
}
$result['summedInvocationCount'] = $reader->getFunctionCount();
$result['summedRunTime'] = $reader->formatCost($reader->getHeader('summary'), 'msec');
$result['dataFile'] = $dataFile;
$result['invokeUrl'] = $reader->getHeader('cmd');
$result['runs'] = $reader->getHeader('runs');
$result['breakdown'] = $breakdown;
$result['mtime'] = date(Webgrind_Config::$dateFormat,filemtime(Webgrind_Config::xdebugOutputDir().$dataFile));
$creator = preg_replace('/[^0-9\.]/', '', $reader->getHeader('creator'));
$result['linkToFunctionLine'] = version_compare($creator, '2.1') > 0;
echo json_encode($result);
break;
case 'callinfo_list':
$reader = Webgrind_FileHandler::getInstance()->getTraceReader(get('file'), get('costFormat', Webgrind_Config::$defaultCostformat));
$functionNr = get('functionNr');
$function = $reader->getFunctionInfo($functionNr);
$result = array('calledFrom'=>array(), 'subCalls'=>array());
$foundInvocations = 0;
for($i=0;$i<$function['calledFromInfoCount'];$i++){
$invo = $reader->getCalledFromInfo($functionNr, $i);
$foundInvocations += $invo['callCount'];
$callerInfo = $reader->getFunctionInfo($invo['functionNr']);
$invo['file'] = urlencode($callerInfo['file']);
$invo['callerFunctionName'] = $callerInfo['functionName'];
$result['calledFrom'][] = $invo;
}
$result['calledByHost'] = ($foundInvocations<$function['invocationCount']);
for($i=0;$i<$function['subCallInfoCount'];$i++){
$invo = $reader->getSubCallInfo($functionNr, $i);
$callInfo = $reader->getFunctionInfo($invo['functionNr']);
$invo['file'] = urlencode($function['file']); // Sub call to $callInfo['file'] but from $function['file']
$invo['callerFunctionName'] = $callInfo['functionName'];
$result['subCalls'][] = $invo;
}
echo json_encode($result);
break;
case 'fileviewer':
$file = get('file');
$line = get('line');
if($file && $file!=''){
$message = '';
if(!file_exists($file)){
$message = $file.' does not exist.';
} else if(!is_readable($file)){
$message = $file.' is not readable.';
} else if(is_dir($file)){
$message = $file.' is a directory.';
}
} else {
$message = 'No file to view';
}
require 'templates/fileviewer.phtml';
break;
case 'function_graph':
$dataFile = get('dataFile');
$showFraction = 100 - intval(get('showFraction') * 100);
if($dataFile == '0'){
$files = Webgrind_FileHandler::getInstance()->getTraceList();
$dataFile = $files[0]['filename'];
}
header("Content-Type: image/png");
$filename = Webgrind_Config::storageDir().$dataFile.'-'.$showFraction.Webgrind_Config::$preprocessedSuffix.'.png';
if (!file_exists($filename)) {
shell_exec(Webgrind_Config::$pythonExecutable.' library/gprof2dot.py -n '.$showFraction.' -f callgrind '.Webgrind_Config::xdebugOutputDir().''.$dataFile.' | '.Webgrind_Config::$dotExecutable.' -Tpng -o ' . $filename);
}
readfile($filename);
break;
case 'version_info':
$response = @file_get_contents('http://jokke.dk/webgrindupdate.json?version='.Webgrind_Config::$webgrindVersion);
echo $response;
break;
default:
$welcome = '';
if (!file_exists(Webgrind_Config::storageDir()) || !is_writable(Webgrind_Config::storageDir())) {
$welcome .= 'Webgrind $storageDir does not exist or is not writeable: <code>'.Webgrind_Config::storageDir().'</code><br>';
}
if (!file_exists(Webgrind_Config::xdebugOutputDir()) || !is_readable(Webgrind_Config::xdebugOutputDir())) {
$welcome .= 'Webgrind $profilerDir does not exist or is not readable: <code>'.Webgrind_Config::xdebugOutputDir().'</code><br>';
}
if ($welcome == '') {
$welcome = 'Select a cachegrind file above<br>(looking in <code>'.Webgrind_Config::xdebugOutputDir().'</code> for files matching <code>'.Webgrind_Config::xdebugOutputFormat().'</code>)';
}
require 'templates/index.phtml';
}
} catch (Exception $e) {
echo json_encode(array('error' => $e->getMessage().'<br>'.$e->getFile().', line '.$e->getLine()));
return;
}
function get($param, $default=false){
return (isset($_GET[$param])? $_GET[$param] : $default);
}
function costCmp($a, $b){
$a = $a['summedSelfCost'];
$b = $b['summedSelfCost'];
if ($a == $b) {
return 0;
}
return ($a > $b) ? -1 : 1;
}
This diff is collapsed.
This diff is collapsed.
/**
* jQuery.ScrollTo - Easy element scrolling using jQuery.
* Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
* Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
* and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
* Date: 2/19/2008
* @author Ariel Flesler
* @version 1.3.3
*/
;(function($){var o=$.scrollTo=function(a,b,c){o.window().scrollTo(a,b,c)};o.defaults={axis:'y',duration:1};o.window=function(){return $($.browser.safari?'body':'html')};$.fn.scrollTo=function(l,m,n){if(typeof m=='object'){n=m;m=0}n=$.extend({},o.defaults,n);m=m||n.speed||n.duration;n.queue=n.queue&&n.axis.length>1;if(n.queue)m/=2;n.offset=j(n.offset);n.over=j(n.over);return this.each(function(){var a=this,b=$(a),t=l,c,d={},w=b.is('html,body');switch(typeof t){case'number':case'string':if(/^([+-]=)?\d+(px)?$/.test(t)){t=j(t);break}t=$(t,this);case'object':if(t.is||t.style)c=(t=$(t)).offset()}$.each(n.axis.split(''),function(i,f){var P=f=='x'?'Left':'Top',p=P.toLowerCase(),k='scroll'+P,e=a[k],D=f=='x'?'Width':'Height';if(c){d[k]=c[p]+(w?0:e-b.offset()[p]);if(n.margin){d[k]-=parseInt(t.css('margin'+P))||0;d[k]-=parseInt(t.css('border'+P+'Width'))||0}d[k]+=n.offset[p]||0;if(n.over[p])d[k]+=t[D.toLowerCase()]()*n.over[p]}else d[k]=t[p];if(/^\d+$/.test(d[k]))d[k]=d[k]<=0?0:Math.min(d[k],h(D));if(!i&&n.queue){if(e!=d[k])g(n.onAfterFirst);delete d[k]}});g(n.onAfter);function g(a){b.animate(d,m,n.easing,a&&function(){a.call(this,l)})};function h(D){var b=w?$.browser.opera?document.body:document.documentElement:a;return b['scroll'+D]-b['client'+D]}})};function j(a){return typeof a=='object'?a:{top:a,left:a}}})(jQuery);
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
/**
* sprintf() for JavaScript v.0.4
*
* Copyright (c) 2007 Alexandru Marasteanu <http://alexei.417.ro/>
* Thanks to David Baird (unit test and patch).
*
* This program 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 2 of the License, or (at your option) any later
* version.
*
* This program 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
* this program; if not, write to the Free Software Foundation, Inc., 59 Temple
* Place, Suite 330, Boston, MA 02111-1307 USA
*/
function str_repeat(i, m) { for (var o = []; m > 0; o[--m] = i); return(o.join('')); }
function sprintf () {
var i = 0, a, f = arguments[i++], o = [], m, p, c, x;
while (f) {
if (m = /^[^\x25]+/.exec(f)) o.push(m[0]);
else if (m = /^\x25{2}/.exec(f)) o.push('%');
else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) {
if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) throw("Too few arguments.");
if (/[^s]/.test(m[7]) && (typeof(a) != 'number'))
throw("Expecting number but found " + typeof(a));
switch (m[7]) {
case 'b': a = a.toString(2); break;
case 'c': a = String.fromCharCode(a); break;
case 'd': a = parseInt(a); break;
case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break;
case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break;
case 'o': a = a.toString(8); break;
case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break;
case 'u': a = Math.abs(a); break;
case 'x': a = a.toString(16); break;
case 'X': a = a.toString(16).toUpperCase(); break;
}
a = (/[def]/.test(m[7]) && m[2] && a > 0 ? '+' + a : a);
c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' ';
x = m[5] - String(a).length;
p = m[5] ? str_repeat(c, x) : '';
o.push(m[4] ? a + p : p + a);
}
else throw ("Huh ?!");
f = f.substring(m[0].length);
}
return o.join('');
}
<?php
require 'Reader.php';
require 'Preprocessor.php';
/**
* Class handling access to data-files(original and preprocessed) for webgrind.
* @author Jacob Oettinger
* @author Joakim Nygård
*/
class Webgrind_FileHandler{
private static $singleton = null;
/**
* @return Singleton instance of the filehandler
*/
public static function getInstance(){
if(self::$singleton==null)
self::$singleton = new self();
return self::$singleton;
}
private function __construct(){
// Get list of files matching the defined format
$files = $this->getFiles(Webgrind_Config::xdebugOutputFormat(), Webgrind_Config::xdebugOutputDir());
// Get list of preprocessed files
$prepFiles = $this->getPrepFiles('/\\'.Webgrind_Config::$preprocessedSuffix.'$/', Webgrind_Config::storageDir());
// Loop over the preprocessed files.
foreach($prepFiles as $fileName=>$prepFile){
$fileName = str_replace(Webgrind_Config::$preprocessedSuffix,'',$fileName);
// If it is older than its corrosponding original: delete it.
// If it's original does not exist: delete it
if(!isset($files[$fileName]) || $files[$fileName]['mtime']>$prepFile['mtime'] )
unlink($prepFile['absoluteFilename']);
else
$files[$fileName]['preprocessed'] = true;
}
// Sort by mtime
uasort($files,array($this,'mtimeCmp'));
$this->files = $files;
}
/**
* Get the value of the cmd header in $file
*
* @return void string
*/
private function getInvokeUrl($file){
if (preg_match('/.webgrind$/', $file))
return 'Webgrind internal';
// Grab name of invoked file.
$fp = fopen($file, 'r');
$invokeUrl = '';
while ((($line = fgets($fp)) !== FALSE) && !strlen($invokeUrl)){
if (preg_match('/^cmd: (.*)$/', $line, $parts)){
$invokeUrl = isset($parts[1]) ? $parts[1] : '';
}
}
fclose($fp);
if (!strlen($invokeUrl))
$invokeUrl = 'Unknown!';
return $invokeUrl;
}
/**
* List of files in $dir whose filename has the format $format
*
* @return array Files
*/
private function getFiles($format, $dir){
$list = preg_grep($format,scandir($dir));
$files = array();
$scriptFilename = $_SERVER['SCRIPT_FILENAME'];
# Moved this out of loop to run faster
if (function_exists('xdebug_get_profiler_filename'))
$selfFile = realpath(xdebug_get_profiler_filename());
else
$selfFile = '';
foreach($list as $file){
$absoluteFilename = $dir.$file;
// Exclude webgrind preprocessed files
if (false !== strstr($absoluteFilename, Webgrind_Config::$preprocessedSuffix))
continue;
// Make sure that script never parses the profile currently being generated. (infinite loop)
if ($selfFile == realpath($absoluteFilename))
continue;
$invokeUrl = rtrim($this->getInvokeUrl($absoluteFilename));
if (Webgrind_Config::$hideWebgrindProfiles && $invokeUrl == dirname(dirname(__FILE__)).DIRECTORY_SEPARATOR.'index.php')
continue;
$files[$file] = array('absoluteFilename' => $absoluteFilename,
'mtime' => filemtime($absoluteFilename),
'preprocessed' => false,
'invokeUrl' => $invokeUrl,
'filesize' => $this->bytestostring(filesize($absoluteFilename))
);
}
return $files;
}
/**
* List of files in $dir whose filename has the format $format
*
* @return array Files
*/
private function getPrepFiles($format, $dir){
$list = preg_grep($format,scandir($dir));
$files = array();
$scriptFilename = $_SERVER['SCRIPT_FILENAME'];
foreach($list as $file){
$absoluteFilename = $dir.$file;
// Make sure that script does not include the profile currently being generated. (infinite loop)
if (function_exists('xdebug_get_profiler_filename') && realpath(xdebug_get_profiler_filename())==realpath($absoluteFilename))
continue;
$files[$file] = array('absoluteFilename' => $absoluteFilename,
'mtime' => filemtime($absoluteFilename),
'preprocessed' => true,
'filesize' => $this->bytestostring(filesize($absoluteFilename))
);
}
return $files;
}
/**
* Get list of available trace files. Optionally including traces of the webgrind script it self
*
* @return array Files
*/
public function getTraceList(){
$result = array();
foreach($this->files as $fileName=>$file){
$result[] = array('filename' => $fileName,
'invokeUrl' => str_replace($_SERVER['DOCUMENT_ROOT'].'/', '', $file['invokeUrl']),
'filesize' => $file['filesize'],
'mtime' => date(Webgrind_Config::$dateFormat, $file['mtime'])
);
}
return $result;
}
/**
* Get a trace reader for the specific file.
*
* If the file has not been preprocessed yet this will be done first.
*
* @param string File to read
* @param Cost format for the reader
* @return Webgrind_Reader Reader for $file
*/
public function getTraceReader($file, $costFormat){
$prepFile = Webgrind_Config::storageDir().$file.Webgrind_Config::$preprocessedSuffix;
try{
$r = new Webgrind_Reader($prepFile, $costFormat);
} catch (Exception $e){
// Preprocessed file does not exist or other error
Webgrind_Preprocessor::parse(Webgrind_Config::xdebugOutputDir().$file, $prepFile);
$r = new Webgrind_Reader($prepFile, $costFormat);
}
return $r;
}
/**
* Comparison function for sorting
*
* @return boolean
*/
private function mtimeCmp($a, $b){
if ($a['mtime'] == $b['mtime'])
return 0;
return ($a['mtime'] > $b['mtime']) ? -1 : 1;
}
/**
* Present a size (in bytes) as a human-readable value
*
* @param int $size size (in bytes)
* @param int $precision number of digits after the decimal point
* @return string
*/
private function bytestostring($size, $precision = 0) {
$sizes = array('YB', 'ZB', 'EB', 'PB', 'TB', 'GB', 'MB', 'KB', 'B');
$total = count($sizes);
while($total-- && $size > 1024) {
$size /= 1024;
}
return round($size, $precision).$sizes[$total];
}
}
\ No newline at end of file
<?php
/**
* Class for preprocessing callgrind files.
*
* Information from the callgrind file is extracted and written in a binary format for
* fast random access.
*
* @see http://code.google.com/p/webgrind/wiki/PreprocessedFormat
* @see http://valgrind.org/docs/manual/cl-format.html
* @package Webgrind
* @author Jacob Oettinger
**/
class Webgrind_Preprocessor
{
/**
* Fileformat version. Embedded in the output for parsers to use.
*/
const FILE_FORMAT_VERSION = 7;
/**
* Binary number format used.
* @see http://php.net/pack
*/
const NR_FORMAT = 'V';
/**
* Size, in bytes, of the above number format
*/
const NR_SIZE = 4;
/**
* String name of main function
*/
const ENTRY_POINT = '{main}';
/**
* Extract information from $inFile and store in preprocessed form in $outFile
*
* @param string $inFile Callgrind file to read
* @param string $outFile File to write preprocessed data to
* @return void
**/
static function parse($inFile, $outFile)
{
$in = @fopen($inFile, 'rb');
if(!$in)
throw new Exception('Could not open '.$inFile.' for reading.');
$out = @fopen($outFile, 'w+b');
if(!$out)
throw new Exception('Could not open '.$outFile.' for writing.');
$nextFuncNr = 0;
$functions = array();
$headers = array();
$calls = array();
// Read information into memory
while(($line = fgets($in))){
if(substr($line,0,3)==='fl='){
// Found invocation of function. Read functionname
list($function) = fscanf($in,"fn=%[^\n\r]s");
if(!isset($functions[$function])){
$functions[$function] = array(
'filename' => substr(trim($line),3),
'invocationCount' => 0,
'nr' => $nextFuncNr++,
'count' => 0,
'summedSelfCost' => 0,
'summedInclusiveCost' => 0,
'calledFromInformation' => array(),
'subCallInformation' => array()
);
}
$functions[$function]['invocationCount']++;
// Special case for ENTRY_POINT - it contains summary header
if(self::ENTRY_POINT == $function){
fgets($in);
$headers[] = fgets($in);
fgets($in);
}
// Cost line
list($lnr, $cost) = fscanf($in,"%d %d");
$functions[$function]['line'] = $lnr;
$functions[$function]['summedSelfCost'] += $cost;
$functions[$function]['summedInclusiveCost'] += $cost;
} else if(substr($line,0,4)==='cfn=') {
// Found call to function. ($function should contain function call originates from)
$calledFunctionName = substr(trim($line),4);
// Skip call line
fgets($in);
// Cost line
list($lnr, $cost) = fscanf($in,"%d %d");
$functions[$function]['summedInclusiveCost'] += $cost;
if(!isset($functions[$calledFunctionName]['calledFromInformation'][$function.':'.$lnr]))
$functions[$calledFunctionName]['calledFromInformation'][$function.':'.$lnr] = array('functionNr'=>$functions[$function]['nr'],'line'=>$lnr,'callCount'=>0,'summedCallCost'=>0);
$functions[$calledFunctionName]['calledFromInformation'][$function.':'.$lnr]['callCount']++;
$functions[$calledFunctionName]['calledFromInformation'][$function.':'.$lnr]['summedCallCost'] += $cost;
if(!isset($functions[$function]['subCallInformation'][$calledFunctionName.':'.$lnr])){
$functions[$function]['subCallInformation'][$calledFunctionName.':'.$lnr] = array('functionNr'=>$functions[$calledFunctionName]['nr'],'line'=>$lnr,'callCount'=>0,'summedCallCost'=>0);
}
$functions[$function]['subCallInformation'][$calledFunctionName.':'.$lnr]['callCount']++;
$functions[$function]['subCallInformation'][$calledFunctionName.':'.$lnr]['summedCallCost'] += $cost;
} else if(strpos($line,': ')!==false){
// Found header
$headers[] = $line;
}
}
// Write output
$functionCount = sizeof($functions);
fwrite($out, pack(self::NR_FORMAT.'*', self::FILE_FORMAT_VERSION, 0, $functionCount));
// Make room for function addresses
fseek($out,self::NR_SIZE*$functionCount, SEEK_CUR);
$functionAddresses = array();
foreach($functions as $functionName => $function){
$functionAddresses[] = ftell($out);
$calledFromCount = sizeof($function['calledFromInformation']);
$subCallCount = sizeof($function['subCallInformation']);
fwrite($out, pack(self::NR_FORMAT.'*', $function['line'], $function['summedSelfCost'], $function['summedInclusiveCost'], $function['invocationCount'], $calledFromCount, $subCallCount));
// Write called from information
foreach((array)$function['calledFromInformation'] as $call){
fwrite($out, pack(self::NR_FORMAT.'*', $call['functionNr'], $call['line'], $call['callCount'], $call['summedCallCost']));
}
// Write sub call information
foreach((array)$function['subCallInformation'] as $call){
fwrite($out, pack(self::NR_FORMAT.'*', $call['functionNr'], $call['line'], $call['callCount'], $call['summedCallCost']));
}
fwrite($out, $function['filename']."\n".$functionName."\n");
}
$headersPos = ftell($out);
// Write headers
foreach($headers as $header){
fwrite($out,$header);
}
// Write addresses
fseek($out,self::NR_SIZE, SEEK_SET);
fwrite($out, pack(self::NR_FORMAT, $headersPos));
// Skip function count
fseek($out,self::NR_SIZE, SEEK_CUR);
// Write function addresses
foreach($functionAddresses as $address){
fwrite($out, pack(self::NR_FORMAT, $address));
}
}
}
<?php
/**
* Class for reading datafiles generated by Webgrind_Preprocessor
*
* @package Webgrind
* @author Jacob Oettinger
**/
class Webgrind_Reader
{
/**
* File format version that this reader understands
*/
const FILE_FORMAT_VERSION = 7;
/**
* Binary number format used.
* @see http://php.net/pack
*/
const NR_FORMAT = 'V';
/**
* Size, in bytes, of the above number format
*/
const NR_SIZE = 4;
/**
* Length of a call information block
*/
const CALLINFORMATION_LENGTH = 4;
/**
* Length of a function information block
*/
const FUNCTIONINFORMATION_LENGTH = 6;
/**
* Address of the headers in the data file
*
* @var int
*/
private $headersPos;
/**
* Array of addresses pointing to information about functions
*
* @var array
*/
private $functionPos;
/**
* Array of headers
*
* @var array
*/
private $headers=null;
/**
* Format to return costs in
*
* @var string
*/
private $costFormat;
/**
* Constructor
* @param string Data file to read
* @param string Format to return costs in
**/
function __construct($dataFile, $costFormat){
$this->fp = @fopen($dataFile,'rb');
if(!$this->fp)
throw new Exception('Error opening file!');
$this->costFormat = $costFormat;
$this->init();
}
/**
* Initializes the parser by reading initial information.
*
* Throws an exception if the file version does not match the readers version
*
* @return void
* @throws Exception
*/
private function init(){
list($version, $this->headersPos, $functionCount) = $this->read(3);
if($version!=self::FILE_FORMAT_VERSION)
throw new Exception('Datafile not correct version. Found '.$version.' expected '.self::FILE_FORMAT_VERSION);
$this->functionPos = $this->read($functionCount);
}
/**
* Returns number of functions
* @return int
*/
function getFunctionCount(){
return count($this->functionPos);
}
/**
* Returns information about function with nr $nr
*
* @param $nr int Function number
* @return array Function information
*/
function getFunctionInfo($nr){
$this->seek($this->functionPos[$nr]);
list($line, $summedSelfCost, $summedInclusiveCost, $invocationCount, $calledFromCount, $subCallCount) = $this->read(self::FUNCTIONINFORMATION_LENGTH);
$this->seek(self::NR_SIZE*self::CALLINFORMATION_LENGTH*($calledFromCount+$subCallCount), SEEK_CUR);
$file = $this->readLine();
$function = $this->readLine();
$result = array(
'file'=>$file,
'line'=>$line,
'functionName'=>$function,
'summedSelfCost'=>$summedSelfCost,
'summedInclusiveCost'=>$summedInclusiveCost,
'invocationCount'=>$invocationCount,
'calledFromInfoCount'=>$calledFromCount,
'subCallInfoCount'=>$subCallCount
);
$result['summedSelfCost'] = $this->formatCost($result['summedSelfCost']);
$result['summedInclusiveCost'] = $this->formatCost($result['summedInclusiveCost']);
return $result;
}
/**
* Returns information about positions where a function has been called from
*
* @param $functionNr int Function number
* @param $calledFromNr int Called from position nr
* @return array Called from information
*/
function getCalledFromInfo($functionNr, $calledFromNr){
$this->seek(
$this->functionPos[$functionNr]
+ self::NR_SIZE
* (self::CALLINFORMATION_LENGTH * $calledFromNr + self::FUNCTIONINFORMATION_LENGTH)
);
$data = $this->read(self::CALLINFORMATION_LENGTH);
$result = array(
'functionNr'=>$data[0],
'line'=>$data[1],
'callCount'=>$data[2],
'summedCallCost'=>$data[3]
);
$result['summedCallCost'] = $this->formatCost($result['summedCallCost']);
return $result;
}
/**
* Returns information about functions called by a function
*
* @param $functionNr int Function number
* @param $subCallNr int Sub call position nr
* @return array Sub call information
*/
function getSubCallInfo($functionNr, $subCallNr){
// Sub call count is the second last number in the FUNCTION_INFORMATION block
$this->seek($this->functionPos[$functionNr] + self::NR_SIZE * (self::FUNCTIONINFORMATION_LENGTH - 2));
$calledFromInfoCount = $this->read();
$this->seek( ( ($calledFromInfoCount+$subCallNr) * self::CALLINFORMATION_LENGTH + 1 ) * self::NR_SIZE,SEEK_CUR);
$data = $this->read(self::CALLINFORMATION_LENGTH);
$result = array(
'functionNr'=>$data[0],
'line'=>$data[1],
'callCount'=>$data[2],
'summedCallCost'=>$data[3]
);
$result['summedCallCost'] = $this->formatCost($result['summedCallCost']);
return $result;
}
/**
* Returns array of defined headers
*
* @return array Headers in format array('header name'=>'header value')
*/
function getHeaders(){
if($this->headers==null){ // Cache headers
$this->seek($this->headersPos);
$this->headers['runs'] = 0;
while($line=$this->readLine()){
$parts = explode(': ',$line);
if ($parts[0] == 'summary') {
$this->headers['runs']++;
if(isset($this->headers['summary']))
$this->headers['summary'] += $parts[1];
else
$this->headers['summary'] = $parts[1];
} else {
$this->headers[$parts[0]] = $parts[1];
}
}
}
return $this->headers;
}
/**
* Returns value of a single header
*
* @return string Header value
*/
function getHeader($header){
$headers = $this->getHeaders();
return isset($headers[$header]) ? $headers[$header] : '';
}
/**
* Formats $cost using the format in $this->costFormat or optionally the format given as input
*
* @param int $cost Cost
* @param string $format 'percent', 'msec' or 'usec'
* @return int Formatted cost
*/
function formatCost($cost, $format=null)
{
if($format==null)
$format = $this->costFormat;
if ($format == 'percent') {
$total = $this->getHeader('summary');
$result = ($total==0) ? 0 : ($cost*100)/$total;
return number_format($result, 2, '.', '');
}
if ($format == 'msec') {
return round($cost/1000, 0);
}
// Default usec
return $cost;
}
private function read($numbers=1){
$values = unpack(self::NR_FORMAT.$numbers,fread($this->fp,self::NR_SIZE*$numbers));
if($numbers==1)
return $values[1];
else
return array_values($values); // reindex and return
}
private function readLine(){
$result = fgets($this->fp);
if($result)
return trim($result);
else
return $result;
}
private function seek($offset, $whence=SEEK_SET){
return fseek($this->fp, $offset, $whence);
}
}
This diff is collapsed.
Software License Agreement (BSD License)
Copyright (c) 2008-2011, Jacob Oettinger & Joakim Nygård
All rights reserved.
Redistribution and use of this software in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of Jacob Oettinger and Joakim Nygård nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of Jacob Oettinger and Joakim Nygård.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
#!/bin/sh
if [ "$1" == "" ]
then
echo "Usage: package.sh <tag>"
else
mkdir package_tmp
rm webgrind-$1.zip
cd package_tmp
svn export https://webgrind.googlecode.com/svn/tags/$1 webgrind
svn export https://webgrind.googlecode.com/svn/wiki webgrind/docs
for i in webgrind/docs/*.wiki; do mv "$i" "${i%.wiki}.txt"; done
rm webgrind/package.sh
zip -r ../webgrind-$1.zip webgrind
cd ..
rm -rf package_tmp
fi
body {
font-family : Helvetica, Verdana, Arial, sans-serif;
font-size : 12px;
color : #000000;
margin:0px;
}
a {
color:#000000;
text-decoration:none;
}
a:hover {
text-decoration:underline;
}
#footer a {
text-decoration:underline;
}
img {
border: 0;
}
h2 {
font-size:16px;
margin:0px 0 5px 0;
font-weight:normal;
}
#head {
padding:5px 10px 10px 10px;
border-bottom:1px solid #404040;
background:url(../img/head.png) repeat-x;
}
#logo {
float:left;
}
#logo h1 {
font:normal 30px "Futura", Helvetica, Verdana;
padding:0px;
margin:0px;
}
#logo p {
font:normal 11px "Verdana";
margin:0px;
padding:0px;
}
#options {
float:right;
width:580px;
padding:10px 0 0 0;
}
#options form {
margin:0px;
}
#main {
margin:10px;
}
#hello_message {
text-align: center;
margin: 30px 0;
}
#trace_view {
display:none;
}
#runtime_sum,
#invocation_sum,
#shown_sum {
font-weight: bold;
}
#footer {
text-align: center;
font: normal 11px;
}
div.hr {
border-top: 1px solid black;
margin:10px 5px;
}
div.callinfo_area {
display: none;
margin: 5px 5px;
}
table.tablesorter {
border-width: 1px 0 1px 1px;
border-style: solid;
border-color: #D9D9D9;
font-family:arial;
margin:10px 0pt 15px;
padding:0px;
font-size: 8pt;
width: 100%;
text-align: left;
}
table.tablesorter thead tr th, table.tablesorter tfoot tr th {
background-color: #D7DDE4;
border-right: 1px solid #CDCDCD;
border-bottom: 1px solid #CDCDCD;
font-size: 8pt;
padding: 4px;
}
table.tablesorter thead tr .header {
background-image: url(../img/bg.gif);
background-repeat: no-repeat;
background-position: center right;
cursor: pointer;
}
table.tablesorter tbody td {
color: #000000;
border-right:1px solid #D9D9D9;
padding: 4px;
vertical-align: top;
}
table.tablesorter tbody tr.odd {
background-color:#EAEEF2;
}
table.tablesorter tbody tr.even {
background-color:#FFFFFF;
}
table.tablesorter thead tr .headerSortUp {
background-image: url(../img/asc.gif);
}
table.tablesorter thead tr .headerSortDown {
background-image: url(../img/desc.gif);
}
table.tablesorter thead tr .headerSortDown, table.tablesorter thead tr .headerSortUp {
background-color: #9BA8C6;
}
td.nr {
text-align: right;
font-family: "Lucida Console", "Andale Mono", "Monaco", monospace;
}
th span{
margin-right: 13px;
}
img.list_reload {
margin:0px 5px;
}
.block_box {
margin: 10px 10px;
}
.num {
display:block;
clear:left;
color: gray;
text-align: right;
margin-right: 6pt;
padding-right: 6pt;
border-right: 1px solid gray;
}
.line_emph {
background-color: #bbbbff;
}
a.load_invocations {
display: none;
background-color: #999;
border: 1px solid #333;
padding: 2px;
}
\ No newline at end of file
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<script src="js/jquery.js" type="text/javascript" charset="utf-8"></script>
<script src="js/jquery.scrollTo.js" type="text/javascript" charset="utf-8"></script>
<link rel="stylesheet" type="text/css" href="styles/style.css">
<title>
webgrind - fileviewer: <?php echo $file?>
</title>
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
$('div#'+location.hash.substr(1)).addClass('line_emph');
});
</script>
</head>
<body>
<div id="head">
<div id="logo">
<h1>webgrind<sup style="font-size:10px">v<?php echo Webgrind_Config::$webgrindVersion?></sup></h1>
<p>profiling in the browser</p>
</div>
<div style="clear:both;"></div>
</div>
<div id="main">
<h2><?php echo $file?></h2>
<br>
<?php if ($message==''):?>
<?php $source = highlight_file($file, true); ?>
<table border="0">
<tr>
<td align="right" valign="top"><code>
<?php
foreach ($lines = explode('<br />', $source) as $num => $line) {
$num++;
echo "<span class='num' name='line$num' id='line$num'>$num</span>";
}
?>
</code></td>
<td valign="top" nowrap="nowrap"><?php echo $source; ?></td>
</tr>
</table>
<?php else:?>
<p><b><?php echo $message?></b></p>
<?php endif?>
</div>
</body>
</html>
\ No newline at end of file
This diff is collapsed.
......@@ -44,6 +44,7 @@ $pconfig['noantilockout'] = isset($config['system']['webgui']['noantilockout']);
$pconfig['nodnsrebindcheck'] = isset($config['system']['webgui']['nodnsrebindcheck']);
$pconfig['nohttpreferercheck'] = isset($config['system']['webgui']['nohttpreferercheck']);
$pconfig['beast_protection'] = isset($config['system']['webgui']['beast_protection']);
$pconfig['enable_xdebug'] = isset($config['system']['webgui']['enable_xdebug']) ;
$pconfig['loginautocomplete'] = isset($config['system']['webgui']['loginautocomplete']);
$pconfig['althostnames'] = $config['system']['webgui']['althostnames'];
$pconfig['enableserial'] = $config['system']['enableserial'];
......@@ -167,6 +168,12 @@ if ($_POST) {
else
unset($config['system']['webgui']['beast_protection']);
if ($_POST['enable_xdebug'] == "yes") {
$config['system']['webgui']['enable_xdebug'] = true;
} else {
unset($config['system']['webgui']['enable_xdebug']);
}
if ($_POST['loginautocomplete'] == "yes")
$config['system']['webgui']['loginautocomplete'] = true;
else
......@@ -478,6 +485,17 @@ include("head.inc");
"More information on BEAST is available from <a target='_blank' href='https://en.wikipedia.org/wiki/Transport_Layer_Security#BEAST_attack'>Wikipedia</a>."); ?>
</td>
</tr>
<tr>
<td width="22%" valign="top" class="vncell"><?=gettext("Enable XDebug"); ?></td>
<td width="78%" class="vtable">
<input name="enable_xdebug" type="checkbox" id="enable_xdebug" value="yes" <?php if ($pconfig['enable_xdebug']) echo "checked=\"checked\""; ?> />
<strong><?=gettext("Enable debugger / profiler (developer mode, do not enable in production environment)"); ?></strong>
<br />
<?php echo gettext("When this is checked, php XDebug will be enabled and profiling output can be analysed using webgrind which will be available at [this-url]/webgrind/"); ?>
<br />
<?php echo gettext("For more information about XDebug profiling and how to enable it for your requests, please visit http://www.xdebug.org/docs/all_settings#profiler_enable_trigger"); ?>
</td>
</tr>
<tr>
<th colspan="2" valign="top" class="listtopic"><?=gettext("Secure Shell"); ?></th>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment