Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

adding webgrind stuff. needs to be cleaned up. but it works.

  • Loading branch information...
commit 3e62b74132883a1461c9114332502fe2c45297d0 1 parent 817c07f
@tmaiaroto authored
View
3  config/bootstrap.php
@@ -5,6 +5,9 @@
//$li3_perf_start = microtime(true);
Data::set('timers', array('li3_perf_start' => microtime(true)));
+// Include webgrind
+require LITHIUM_APP_PATH . '/libraries/li3_perf/extensions/webgrind/library/bootstrap.php';
+
// Get the queries
require __DIR__ . '/bootstrap/queries.php';
View
13 extensions/webgrind/config.php
@@ -0,0 +1,13 @@
+<?php
+
+/**
+ * See Webgrind_Config::$_defaults in 'library/Config.php'
+ * for complete list of options
+ */
+
+return array(
+ 'checkVersion' => false,
+ 'defaultTimezone' => 'Europe/Kiev',
+ 'defaultCostformat' => 'usec',
+ 'defaultFunctionPercentage' => 100
+);
View
79 extensions/webgrind/index.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ * @author Jacob Oettinger
+ * @author Joakim Nygård
+ */
+
+require './library/common.php';
+
+
+switch(get('op')) {
+ case 'fileviewer':
+ $file = get('file');
+ $line = get('line');
+
+ if ($file && $file != '') {
+ $message = '';
+ if (!file_exists($file)) {
+ $message = $file.' does not exist.';
+ } elseif (!is_readable($file)) {
+ $message = $file.' is not readable.';
+ } elseif (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(1);
+ $dataFile = $files[0]['filename'];
+ }
+
+ if (!is_executable(Webgrind::$config->pythonExecutable)) {
+ die("can't find python interpreter");
+ }
+
+ if (!is_executable(Webgrind::$config->dotExecutable)) {
+ die("can't find drawing tool");
+ }
+
+ $filename = Webgrind::$config->storageDir.'/'.$dataFile.'-'.$showFraction.Webgrind::$config->preprocessedSuffix.'.png';
+ if (!file_exists($filename)) {
+ if (!createGraph(
+ Webgrind::$config->xdebugOutputDir.'/'.$dataFile,
+ $filename,
+ $showFraction
+ )) {
+ die("can't generate graph");
+ }
+ }
+
+ header("Content-Type: image/png");
+ readfile($filename);
+ break;
+
+ default:
+ $welcome = '';
+ $storageDir = Webgrind::$config->storageDir;
+ $xdebugOutputDir = Webgrind::$config->xdebugOutputDir;
+
+ if (!file_exists($storageDir) || !is_writable($storageDir)) {
+ $welcome .= "Webgrind storageDir ${storageDir} does not exist or is not writeable.<br>";
+ }
+ if (!file_exists($xdebugOutputDir) || !is_readable($xdebugOutputDir)) {
+ $welcome .= "Webgrind profilerDir ${xdebugOutputDir} does not exist or is not readable.<br>";
+ }
+
+ if ($welcome == '') {
+ $welcome = "Select a cachegrind file above<br>(looking in <code>${xdebugOutputDir}</code> for files matching <code>".
+ Webgrind::$config->xdebugOutputFormat.'</code>)';
+ }
+ require 'templates/index.phtml';
+}
View
139 extensions/webgrind/library/Config.php
@@ -0,0 +1,139 @@
+<?php
+/**
+ * Configuration for webgrind
+ * @author Jacob Oettinger
+ * @author Joakim Nygård
+ */
+
+namespace li3_perf\extensions\webgrind\library;
+
+class Config {
+
+ // Do not edit!
+ // override in local config file 'config.php'
+ private static $_defaults = array(
+ /**
+ * Automatically check if a newer version of webgrind is available for download
+ */
+ 'checkVersion' => true,
+
+ /**
+ * Writable dir for information storage.
+ * If empty, will use system tmp folder or xdebug tmp
+ */
+ 'storageDir' => '',
+
+ /**
+ * Suffix for preprocessed files
+ */
+ 'preprocessedSuffix' => '.webgrind',
+
+ /**
+ * Path to python executable
+ */
+ 'pythonExecutable' => '/usr/bin/python',
+
+ /**
+ * Path to graphviz dot executable
+ */
+ 'dotExecutable' => '/usr/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
+ */
+ 'fileUrlFormat' => 'index.php?op=fileviewer&file=%1$s#line%2$d',
+
+ /**
+ * 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)
+ */
+ 'traceFileListFormat' => '%i (%f) [%s]',
+
+ /**
+ * Regex that matches the trace files generated by xdebug
+ */
+ 'xdebugOutputFormat' => '/^cachegrind\.out\..+$/',
+
+ /**
+ * Directory to search for trace files
+ */
+ 'xdebugOutputDir' => '/tmp',
+
+
+ 'hideWebgrindProfiles' => true,
+ 'defaultTimezone' => 'Europe/Copenhagen',
+ 'dateFormat' => 'Y-m-d H:i:s',
+ 'defaultCostformat' => 'percent', // 'percent', 'usec' or 'msec'
+ 'defaultFunctionPercentage' => 90,
+ 'defaultHideInternalFunctions' => false,
+ );
+
+ private $_dict = array();
+
+
+
+ public function __construct($config_file = null) {
+ if (isset($config_file)) {
+ $this->load($config_file);
+ }
+ }
+
+
+
+ public function load($file) {
+ $config = require $file;
+ $config = array_replace(self::$_defaults, $config);
+ $this->_dict = array_intersect_key($config, self::$_defaults);
+
+ // parse xdebugOutputFormat
+ $xdebugOutputFormat = ini_get('xdebug.profiler_output_name');
+ if (!empty($xdebugOutputFormat)) {
+ $this->_dict['xdebugOutputFormat'] = '/^'.preg_replace('/(%[^%])+/', '.+', $xdebugOutputFormat).'$/';
+ }
+
+ // parse xdebugOutputDir
+ $dir = ini_get('xdebug.profiler_output_dir');
+ if (!empty($dir)) {
+ $this->_dict['xdebugOutputDir'] = realpath($dir);
+ } else {
+ $this->_dict['xdebugOutputDir'] = realpath($this->_dict['xdebugOutputDir']);
+ }
+
+ // parse storageDir
+ if (!empty($this->_dict['storageDir'])) {
+ $this->_dict['storageDir'] = realpath($this->_dict['storageDir']);
+ } elseif (!function_exists('sys_get_temp_dir') || !is_writable(sys_get_temp_dir())) {
+ # use xdebug setting
+ $this->_dict['storageDir'] = $this->_dict['xdebugOutputDir'];
+ } else {
+ $this->_dict['storageDir'] = realpath(sys_get_temp_dir());
+ }
+ }
+
+
+
+ public function __set($name, $value) {
+ $this->_dict[$name] = $value;
+ }
+
+ public function __get($name) {
+ return isset($this->_dict[$name]) ?
+ $this->_dict[$name] : null;
+ }
+
+ public function __isset($name) {
+ return isset($this->_dict[$name]);
+ }
+
+ public function __unset($name) {
+ unset($this->_dict[$name]);
+ }
+}
+?>
View
200 extensions/webgrind/library/FileHandler.php
@@ -0,0 +1,200 @@
+<?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
+ */
+
+namespace li3_perf\extensions\webgrind\library;
+
+class FileHandler {
+
+ private static $singleton = null;
+
+ /**
+ * @return Singleton instance of the filehandler
+ */
+ public static function getInstance() {
+ if (!isset(self::$singleton)) {
+ self::$singleton = new self();
+ }
+
+ return self::$singleton;
+ }
+
+ private function __construct() {
+ // Get list of files matching the defined format
+ $files = self::getFiles(Webgrind::$config->xdebugOutputFormat, Webgrind::$config->xdebugOutputDir);
+
+ // Get list of preprocessed files
+ $prepFiles = self::getFiles('#\\'.Webgrind::$config->preprocessedSuffix.'$#', Webgrind::$config->storageDir, true);
+
+ // 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, function($a, $b) {
+ if ($a['mtime'] == $b['mtime']) {
+ return 0;
+ }
+
+ return ($a['mtime'] > $b['mtime']) ? -1 : 1;
+ });
+
+ $this->files = $files;
+ }
+
+ /**
+ * Get the value of the cmd header in $file
+ *
+ * @return void string
+ */
+ private static function getInvokeUrl($file) {
+ if (preg_match('/.webgrind$/', $file)) {
+ return 'Webgrind internal';
+ }
+
+ // Grab name of invoked file.
+ $fp = fopen($file, 'r');
+ while (($line = fgets($fp)) !== false) {
+ if (preg_match('#^cmd: (.+)$#', $line, $parts)) {
+ $invokeUrl = trim($parts[1]);
+ break;
+ }
+ }
+
+ fclose($fp);
+
+ if (empty($invokeUrl)) {
+ return 'Unknown!';
+ }
+
+ return $invokeUrl;
+ }
+
+ /**
+ * List of files in $dir whose filename has the format $format
+ *
+ * @return array Files
+ */
+ private static function getFiles($format, $dir, $preprocessed = false) {
+ $list = preg_grep($format, scandir($dir));
+ $files = array();
+
+ // If current script under profiler, get it's output file name for exclusion
+ if (function_exists('xdebug_get_profiler_filename') &&
+ ($profiler_filename = xdebug_get_profiler_filename()) !== false) {
+ $selfFile = realpath($profiler_filename);
+ }
+
+ foreach($list as $file) {
+ $absoluteFilename = realpath($dir.'/'.$file);
+
+ // Make sure that script never parses the profile currently being generated. (infinite loop)
+ if (!empty($selfFile) && $selfFile === $absoluteFilename) {
+ continue;
+ }
+
+ if (!$preprocessed) {
+ // Exclude preprocessed files
+ if (endsWith($absoluteFilename, Webgrind::$config->preprocessedSuffix)) {
+ continue;
+ }
+
+ // If set in config, exclude Webgrind files profiles
+ $invokeUrl = self::getInvokeUrl($absoluteFilename);
+ if (Webgrind::$config->hideWebgrindProfiles && startsWith($invokeUrl, WEBGRIND_ROOT)) {
+ continue;
+ }
+ }
+
+ $files[$file] = array(
+ 'absoluteFilename' => $absoluteFilename,
+ 'mtime' => filemtime($absoluteFilename),
+ 'preprocessed' => $preprocessed,
+ 'filesize' => self::bytestostring(filesize($absoluteFilename))
+ );
+
+ if (!$preprocessed) {
+ $files[$file]['invokeUrl'] = $invokeUrl;
+ }
+ }
+
+ return $files;
+ }
+
+ /**
+ * Get list of available trace files. Optionally including traces of the webgrind script it self
+ *
+ * @return array Files
+ */
+ public function getTraceList($limit = 0) {
+ $result = array();
+ $use_limit = ($limit > 0);
+
+ 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'])
+ );
+ if ($use_limit && --$limit <= 0) {
+ break;
+ }
+ }
+ 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 Reader($prepFile, $costFormat);
+ } catch (Exception $e) {
+ // Preprocessed file does not exist or other error
+ Preprocessor::parse(Webgrind::$config->xdebugOutputDir.'/'.$file, $prepFile);
+ $r = new Reader($prepFile, $costFormat);
+ }
+ return $r;
+ }
+
+ /**
+ * 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 static 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];
+ }
+}
+?>
View
166 extensions/webgrind/library/Preprocessor.php
@@ -0,0 +1,166 @@
+<?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
+ **/
+
+namespace li3_perf\extensions\webgrind\library;
+
+use \Exception;
+
+class 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));
+ }
+
+ }
+
+}
+?>
View
274 extensions/webgrind/library/Reader.php
@@ -0,0 +1,274 @@
+<?php
+/**
+ * Class for reading datafiles generated by Webgrind_Preprocessor
+ *
+ * @package Webgrind
+ * @author Jacob Oettinger
+ **/
+namespace li3_perf\extensions\webgrind\library;
+
+use \Exception;
+
+class 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);
+ // NOTE: I don't know what the difference is between the versions...But version 6 seems to work fine too with this Reader class...So far.
+ if($version!=self::FILE_FORMAT_VERSION && $version!=6)
+ 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);
+ }
+
+}
+?>
View
21 extensions/webgrind/library/Webgrind.php
@@ -0,0 +1,21 @@
+<?php
+// require_once LIBPATH.'/Config.php';
+namespace li3_perf\extensions\webgrind\library;
+
+class Webgrind {
+
+ public static $version = '1.1';
+ public static $config;
+
+ public static function init($config_file) {
+ self::$config = new Config($config_file);
+
+ // Make sure we have a timezone for date functions.
+ if (ini_get('date.timezone') == '') {
+ date_default_timezone_set(self::$config->defaultTimezone);
+ }
+
+ set_time_limit(0);
+ }
+}
+?>
View
16 extensions/webgrind/library/bootstrap.php
@@ -0,0 +1,16 @@
+<?php
+/**
+ * Bootstrap process for webgrind tools.
+ *
+*/
+define('WEBGRIND_ROOT', realpath(dirname(__FILE__).'/..'));
+define('WEBGRIND_LIBPATH', WEBGRIND_ROOT.'/library');
+
+use li3_perf\extensions\webgrind\library\Webgrind;
+
+//require_once 'Webgrind.php';
+require_once WEBGRIND_LIBPATH.'/functions.php';
+//require_once WEBGRIND_LIBPATH.'/FileHandler.php';
+
+Webgrind::init(WEBGRIND_ROOT.'/config.php');
+?>
View
71 extensions/webgrind/library/functions.php
@@ -0,0 +1,71 @@
+<?php
+function get($param, $default = false) {
+ return (isset($_GET[$param]) ? $_GET[$param] : $default);
+}
+
+function post($param, $default = false) {
+ return (isset($_POST[$param]) ? $_POST[$param] : $default);
+}
+
+function sendJsonResponse($data) {
+ header('Content-Type: application/json');
+ echo json_encode($data);
+}
+
+function sendError($message) {
+ sendJsonResponse(array('error' => $message));
+}
+
+function costCmp($a, $b) {
+ $a = $a['summedSelfCost'];
+ $b = $b['summedSelfCost'];
+
+ if ($a == $b) {
+ return 0;
+ }
+
+ return ($a > $b) ? -1 : 1;
+}
+
+function createGraph($input_file, $output_file, $fraction) {
+ $python = Webgrind::$config->pythonExecutable;
+ $dot = Webgrind::$config->dotExecutable;
+ $gprof2dot = WEBGRIND_LIBPATH.'/gprof2dot.py';
+
+ exec(
+ "${python} ${gprof2dot} -n ${fraction} -f callgrind ${input_file} | ${dot} -Tpng -o ${output_file}",
+ $unused,
+ $status
+ );
+
+ return ($status === 0 && file_exists($output_file));
+}
+
+
+/**
+ * Check if given string starts with value
+ *
+ * @param string $haystack string to search in
+ * @param string $needle value to find
+ *
+ * @return bool
+ */
+function startsWith($haystack, $needle)
+{
+ return (substr($haystack, 0, strlen($needle)) === $needle);
+}
+
+
+/**
+ * Check if given string ends with value
+ *
+ * @param string $haystack string to search in
+ * @param string $needle value to find
+ *
+ * @return bool
+ */
+function endsWith($haystack, $needle)
+{
+ return (substr($haystack, (-1 * strlen($needle))) === $needle);
+}
+?>
View
30 extensions/webgrind/license.txt
@@ -0,0 +1,30 @@
+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.
View
85 views/elements/perf_graph.html.php
@@ -0,0 +1,85 @@
+<h2>Performance Graph</h2>
+<p>
+ This page displays the performance over time.
+</p>
+<?php
+use li3_perf\extensions\webgrind\library\Webgrind;
+use li3_perf\extensions\webgrind\library\FileHandler;
+
+$trace_files = FileHandler::getInstance()->getTraceList(1);
+if(is_array($trace_files)) {
+ $dataFile = $trace_files[0]['filename'];
+
+ $costFormat = Webgrind::$config->defaultCostformat;
+ $reader = FileHandler::getInstance()->getTraceReader(
+ $dataFile, $costFormat
+ );
+
+ $functions = array();
+ $shownTotal = 0;
+ $breakdown = array('internal' => 0, 'procedural' => 0, 'class' => 0, 'include' => 0);
+ $functionCount = $reader->getFunctionCount();
+
+ for ($i = 0; $i < $functionCount; $i++) {
+ $functionInfo = $reader->getFunctionInfo($i);
+ $isInternal = (strpos($functionInfo['functionName'], 'php::') !== false);
+
+ if ($isInternal) {
+ if (get('hideInternals', false)) {
+ continue;
+ }
+
+ $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';
+ } elseif (false !== strpos($functionInfo['functionName'], '->') ||
+ false !== strpos($functionInfo['functionName'], '::')) {
+ $breakdown['class'] += $functionInfo['summedSelfCost'];
+ $humanKind = 'class';
+ } else {
+ $breakdown['procedural'] += $functionInfo['summedSelfCost'];
+ $humanKind = 'procedural';
+ }
+
+ $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;
+
+
+ var_dump($result);
+
+}
+?>
View
1  views/elements/timers.html.php
@@ -8,6 +8,7 @@
?>
<h2>Time to Load</h2>
+<?php var_dump(xdebug_time_index()); ?>
<div id="holder" style="height: 205px;"></div>
<p>Note: The time to route and call the code for the request should just about equal the complete dispatch cycle. If not, something is wrong.</p>
View
16 views/elements/toolbar.html.php
@@ -8,6 +8,8 @@
<div id="li3-perf-toolbar-links">
<?=$this->html->link($this->html->image('/li3_perf/img/stats.png') . 'Queries', '#', array('id' => 'lp-queries', 'class' => 'li3-perf-link', 'escape' => false)); ?>
+ <?=$this->html->link($this->html->image('/li3_perf/img/line-chart.png') . 'Graph', '#', array('id' => 'lp-perf-graph', 'class' => 'li3-perf-link', 'escape' => false)); ?>
+
<?=$this->html->link($this->html->image('/li3_perf/img/stopwatch.png') . 'Time', '#', array('id' => 'lp-timing', 'class' => 'li3-perf-link', 'escape' => false)); ?>
<?=$this->html->link($this->html->image('/li3_perf/img/puzzle.png') . 'Vars', '#', array('id' => 'lp-variables', 'class' => 'li3-perf-link', 'escape' => false)); ?>
@@ -32,6 +34,20 @@
?>
</div>
+ <div id="li3-perf-graph">
+ <?php
+ echo $this->view()->render('all',
+ array(
+ 'timers' => $timers
+ ),
+ array(
+ 'library' => 'li3_perf',
+ 'template' => 'perf_graph',
+ 'layout' => 'empty'
+ ));
+ ?>
+ </div>
+
<div id="li3-perf-timing">
<?php
echo $this->view()->render('all',
View
BIN  webroot/img/16-line-chart.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  webroot/img/line-chart.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
8 webroot/js/li3-perf.js
@@ -14,6 +14,14 @@ $(document).ready(function() {
$('#li3-perf-queries div').show();
});
+ // Show performance graph
+ $('#lp-perf-graph').click(function() {
+ toolbarExpand();
+ $('#li3-perf-content div').hide();
+ $('#li3-perf-graph').show();
+ $('#li3-perf-graph div').show();
+ });
+
// Show timers
$('#lp-timing').click(function() {
toolbarExpand();
Please sign in to comment.
Something went wrong with that request. Please try again.