diff --git a/plugins/php/conf/php-fpm.conf b/plugins/php/conf/php-fpm.conf
index 520a62edf..433896d70 100644
--- a/plugins/php/conf/php-fpm.conf
+++ b/plugins/php/conf/php-fpm.conf
@@ -1,5 +1,4 @@
[global]
include={$SERVER_PATH}/php/{$PHP_VERSION}/etc/php-fpm.d/*.conf
-;php_value[auto_prepend_file]={$SERVER_PATH}/md_start.php
-;php_value[auto_append_file]={$SERVER_PATH}/md_end.php
\ No newline at end of file
+php_value[auto_prepend_file]={$SERVER_PATH}/php/app_start.php
\ No newline at end of file
diff --git a/plugins/php/index.html b/plugins/php/index.html
index 2243a9543..e42be69d4 100755
--- a/plugins/php/index.html
+++ b/plugins/php/index.html
@@ -15,6 +15,7 @@
diff --git a/plugins/php/index.py b/plugins/php/index.py
index d67ae2e59..a762fee5f 100755
--- a/plugins/php/index.py
+++ b/plugins/php/index.py
@@ -146,6 +146,15 @@ def makeOpenrestyConf():
mw.restartWeb()
+def phpPrependFile(version):
+ app_start = getServerDir() + '/app_start.php'
+ if not os.path.exists(app_start):
+ tpl = getPluginDir() + '/conf/app_start.php'
+ content = mw.readFile(tpl)
+ content = contentReplace(content, version)
+ mw.writeFile(app_start, content)
+
+
def phpFpmReplace(version):
desc_php_fpm = getServerDir() + '/' + version + '/etc/php-fpm.conf'
if not os.path.exists(desc_php_fpm):
@@ -194,6 +203,7 @@ def initReplace(version):
mw.writeFile(file_bin, content)
mw.execShell('chmod +x ' + file_bin)
+ phpPrependFile(version)
phpFpmWwwReplace(version)
phpFpmReplace(version)
@@ -619,6 +629,10 @@ def uninstallLib(version):
return mw.returnJson(False, '卸载信息![通道0]:' + data[0] + "[通道0]:" + data[1])
+def getConfAppStart():
+ pstart = mw.getServerDir() + '/php/app_start.php'
+ return pstart
+
if __name__ == "__main__":
if len(sys.argv) < 3:
@@ -650,6 +664,8 @@ if __name__ == "__main__":
print fpmSlowLog(version)
elif func == 'conf':
print getConf(version)
+ elif func == 'app_start':
+ print getConfAppStart()
elif func == 'get_php_conf':
print getPhpConf(version)
elif func == 'submit_php_conf':
diff --git a/plugins/php/versions/71/xhprof.sh b/plugins/php/versions/71/xhprof.sh
new file mode 100755
index 000000000..2e52e2971
--- /dev/null
+++ b/plugins/php/versions/71/xhprof.sh
@@ -0,0 +1,95 @@
+#!/bin/bash
+PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
+export PATH
+
+curPath=`pwd`
+
+rootPath=$(dirname "$curPath")
+rootPath=$(dirname "$rootPath")
+rootPath=$(dirname "$rootPath")
+rootPath=$(dirname "$rootPath")
+serverPath=$(dirname "$rootPath")
+sourcePath=${serverPath}/source/php
+
+LIBNAME=xhprof
+LIBV=2.2.0
+sysName=`uname`
+actionType=$1
+version=$2
+extFile=$serverPath/php/${version}/lib/php/extensions/no-debug-non-zts-20160303/${LIBNAME}.so
+
+if [ "$sysName" == "Darwin" ];then
+ BAK='_bak'
+else
+ BAK=''
+fi
+
+
+Install_lib()
+{
+ isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"`
+ if [ "${isInstall}" != "" ];then
+ echo "php-$version 已安装${LIBNAME},请选择其它版本!"
+ return
+ fi
+
+ if [ ! -f "$extFile" ];then
+ php_lib=$sourcePath/php_lib
+ mkdir -p $php_lib
+ if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then
+ wget -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz
+ cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz
+ fi
+ cd $php_lib/${LIBNAME}-${LIBV}/extension
+
+ $serverPath/php/$version/bin/phpize
+ ./configure --enable-xhprof \
+ --with-php-config=$serverPath/php/$version/bin/php-config
+ make && make install && make clean
+
+ fi
+
+ if [ ! -f "$extFile" ];then
+ echo "ERROR!"
+ return
+ fi
+
+
+ echo "" >> $serverPath/php/$version/etc/php.ini
+ echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini
+ echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini
+
+ $serverPath/php/init.d/php$version reload
+ echo '==========================================================='
+ echo 'successful!'
+}
+
+
+Uninstall_lib()
+{
+ if [ ! -f "$serverPath/php/$version/bin/php-config" ];then
+ echo "php-$version 未安装,请选择其它版本!"
+ return
+ fi
+
+ if [ ! -f "$extFile" ];then
+ echo "php-$version 未安装${LIBNAME},请选择其它版本!"
+ echo "php-$version not install ${LIBNAME}, Plese select other version!"
+ return
+ fi
+
+ sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini
+ sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini
+
+ rm -f $extFile
+ $serverPath/php/init.d/php$version reload
+ echo '==============================================='
+ echo 'successful!'
+}
+
+
+if [ "$actionType" == 'install' ];then
+ Install_lib
+elif [ "$actionType" == 'uninstall' ];then
+ Uninstall_lib
+fi
\ No newline at end of file
diff --git a/plugins/php/versions/phplib.conf b/plugins/php/versions/phplib.conf
index d8580e433..e77b731b3 100755
--- a/plugins/php/versions/phplib.conf
+++ b/plugins/php/versions/phplib.conf
@@ -170,7 +170,12 @@
"53",
"54",
"55",
- "56"
+ "56",
+ "70",
+ "71",
+ "72",
+ "73",
+ "74"
],
"type": "性能分析",
"msg": "不多说,不了解的不要安装!",
diff --git a/plugins/xhprof/conf/xhprof.conf b/plugins/xhprof/conf/xhprof.conf
new file mode 100755
index 000000000..bdc8095d5
--- /dev/null
+++ b/plugins/xhprof/conf/xhprof.conf
@@ -0,0 +1,27 @@
+server
+{
+ listen 5858;
+ server_name 127.0.0.1;
+ index index.html index.htm index.php;
+ root {$SERVER_PATH}/xhprof/xhprof_html;
+
+ #error_page 404 /404.html;
+ include enable-php-{$PHP_VER}.conf;
+
+ location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
+ {
+ expires 30d;
+ }
+
+ location ~ .*\.(js|css)?$
+ {
+ expires 12h;
+ }
+
+ location ~ /\.
+ {
+ deny all;
+ }
+
+ access_log {$SERVER_PATH}/xhprof/access.log main;
+}
\ No newline at end of file
diff --git a/plugins/xhprof/ico.png b/plugins/xhprof/ico.png
new file mode 100644
index 000000000..c710c5861
Binary files /dev/null and b/plugins/xhprof/ico.png differ
diff --git a/plugins/xhprof/index.html b/plugins/xhprof/index.html
new file mode 100755
index 000000000..754cf36ef
--- /dev/null
+++ b/plugins/xhprof/index.html
@@ -0,0 +1,20 @@
+
+
\ No newline at end of file
diff --git a/plugins/xhprof/index.py b/plugins/xhprof/index.py
new file mode 100755
index 000000000..a6aa10fa3
--- /dev/null
+++ b/plugins/xhprof/index.py
@@ -0,0 +1,219 @@
+# coding:utf-8
+
+import sys
+import io
+import os
+import time
+import re
+
+sys.path.append(os.getcwd() + "/class/core")
+import mw
+import site_api
+
+app_debug = False
+if mw.isAppleSystem():
+ app_debug = True
+
+
+def getPluginName():
+ return 'xhprof'
+
+
+def getPluginDir():
+ return mw.getPluginDir() + '/' + getPluginName()
+
+
+def getServerDir():
+ return mw.getServerDir() + '/' + getPluginName()
+
+
+def getArgs():
+ args = sys.argv[2:]
+ tmp = {}
+ args_len = len(args)
+
+ if args_len == 1:
+ t = args[0].strip('{').strip('}')
+ t = t.split(':')
+ tmp[t[0]] = t[1]
+ elif args_len > 1:
+ for i in range(len(args)):
+ t = args[i].split(':')
+ tmp[t[0]] = t[1]
+
+ return tmp
+
+
+def getConf():
+ return mw.getServerDir() + '/web_conf/nginx/vhost/xhprof.conf'
+
+
+def getPort():
+ file = getConf()
+ content = mw.readFile(file)
+ rep = 'listen\s*(.*);'
+ tmp = re.search(rep, content)
+ return tmp.groups()[0].strip()
+
+
+def getHomePage():
+ try:
+ port = getPort()
+ ip = '127.0.0.1'
+ if not mw.isAppleSystem():
+ ip = mw.getLocalIp()
+ url = 'http://' + ip + ':' + port + '/index.php'
+ return mw.returnJson(True, 'OK', url)
+ except Exception as e:
+ return mw.returnJson(False, '插件未启动!')
+
+
+def getPhpVer(expect=55):
+ import json
+ v = site_api.site_api().getPhpVersion()
+ v = json.loads(v)
+ for i in range(len(v)):
+ t = int(v[i]['version'])
+ if (t >= expect):
+ return str(t)
+ return str(expect)
+
+
+def getCachePhpVer():
+ cacheFile = getServerDir() + '/php.pl'
+ v = ''
+ if os.path.exists(cacheFile):
+ v = mw.readFile(cacheFile)
+ else:
+ v = getPhpVer()
+ mw.writeFile(cacheFile, v)
+ return v
+
+
+def contentReplace(content):
+ service_path = mw.getServerDir()
+ php_ver = getCachePhpVer()
+ # print php_ver
+ content = content.replace('{$ROOT_PATH}', mw.getRootDir())
+ content = content.replace('{$SERVER_PATH}', service_path)
+ content = content.replace('{$PHP_VER}', php_ver)
+ return content
+
+
+def status():
+ conf = getConf()
+ if os.path.exists(conf):
+ return 'start'
+ return 'stop'
+
+
+def start():
+ file_tpl = getPluginDir() + '/conf/xhprof.conf'
+ file_run = getConf()
+
+ if not os.path.exists(file_run):
+ centent = mw.readFile(file_tpl)
+ centent = contentReplace(centent)
+ mw.writeFile(file_run, centent)
+
+ mw.restartWeb()
+ return 'ok'
+
+
+def stop():
+ conf = getConf()
+ if os.path.exists(conf):
+ os.remove(conf)
+ mw.restartWeb()
+ return 'ok'
+
+
+def restart():
+ return start()
+
+
+def reload():
+ return start()
+
+
+def setPhpVer():
+ args = getArgs()
+
+ if not 'phpver' in args:
+ return 'phpver missing'
+
+ cacheFile = getServerDir() + '/php.pl'
+ mw.writeFile(cacheFile, args['phpver'])
+ restart()
+
+ return 'ok'
+
+
+def getSetPhpVer():
+ cacheFile = getServerDir() + '/php.pl'
+ if os.path.exists(cacheFile):
+ return mw.readFile(cacheFile).strip()
+ return ''
+
+
+def getXhPort():
+ try:
+ port = getPort()
+ return mw.returnJson(True, 'OK', port)
+ except Exception as e:
+ print e
+ return mw.returnJson(False, '插件未启动!')
+
+
+def setXhPort():
+ args = getArgs()
+ if not 'port' in args:
+ return mw.returnJson(False, 'port missing!')
+
+ port = args['port']
+ if port == '80':
+ return mw.returnJson(False, '80端不能使用!')
+
+ file = getConf()
+ if not os.path.exists(file):
+ return mw.returnJson(False, '插件未启动!')
+ content = mw.readFile(file)
+ rep = 'listen\s*(.*);'
+ content = re.sub(rep, "listen " + port + ';', content)
+ mw.writeFile(file, content)
+ mw.restartWeb()
+ return mw.returnJson(True, '修改成功!')
+
+
+def getConfAppStart():
+ pstart = mw.getServerDir() + '/php/app_start.php'
+ return pstart
+
+if __name__ == "__main__":
+ func = sys.argv[1]
+ if func == 'status':
+ print status()
+ elif func == 'start':
+ print start()
+ elif func == 'stop':
+ print stop()
+ elif func == 'restart':
+ print restart()
+ elif func == 'reload':
+ print reload()
+ elif func == 'conf':
+ print getConf()
+ elif func == 'get_home_page':
+ print getHomePage()
+ elif func == 'set_php_ver':
+ print setPhpVer()
+ elif func == 'get_set_php_ver':
+ print getSetPhpVer()
+ elif func == 'get_xhprof_port':
+ print getXhPort()
+ elif func == 'set_xhprof_port':
+ print setXhPort()
+ elif func == 'app_start':
+ print getConfAppStart()
+ else:
+ print 'error'
diff --git a/plugins/xhprof/info.json b/plugins/xhprof/info.json
new file mode 100755
index 000000000..414c73e01
--- /dev/null
+++ b/plugins/xhprof/info.json
@@ -0,0 +1,17 @@
+{
+ "title":"xhprof",
+ "tip":"soft",
+ "name":"xhprof",
+ "type":"运行环境",
+ "ps":"PHP性能瓶颈查找工具",
+ "to_ver":["1.0"],
+ "versions":["1.0"],
+ "updates":["1.0"],
+ "shell":"install.sh",
+ "checks":"server/xhprof",
+ "path": "server/xhprof",
+ "author":"midoks",
+ "home":"http://pecl.php.net/package/xhprof",
+ "date":"2020-07-12",
+ "pid": "2"
+}
\ No newline at end of file
diff --git a/plugins/xhprof/install.sh b/plugins/xhprof/install.sh
new file mode 100755
index 000000000..ca8a2c188
--- /dev/null
+++ b/plugins/xhprof/install.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
+export PATH
+
+curPath=`pwd`
+rootPath=$(dirname "$curPath")
+rootPath=$(dirname "$rootPath")
+serverPath=$(dirname "$rootPath")
+
+install_tmp=${rootPath}/tmp/mw_install.pl
+
+Install_xh()
+{
+ mkdir -p ${serverPath}/xhprof
+
+ if [ ! -d ${serverPath}/xhprof/xhprof_lib ];then
+ cp -rf $curPath/lib/ ${serverPath}/xhprof
+ fi
+
+ echo "${1}" > ${serverPath}/xhprof/version.pl
+ echo '安装完成' > $install_tmp
+
+}
+
+Uninstall_xh()
+{
+ rm -rf ${serverPath}/xhprof
+ echo '卸载完成' > $install_tmp
+}
+
+action=$1
+if [ "${1}" == 'install' ];then
+ Install_xh $2
+else
+ Uninstall_xh $2
+fi
diff --git a/plugins/xhprof/js/xhprof.js b/plugins/xhprof/js/xhprof.js
new file mode 100755
index 000000000..dae22009b
--- /dev/null
+++ b/plugins/xhprof/js/xhprof.js
@@ -0,0 +1,120 @@
+function str2Obj(str){
+ var data = {};
+ kv = str.split('&');
+ for(i in kv){
+ v = kv[i].split('=');
+ data[v[0]] = v[1];
+ }
+ return data;
+}
+
+function xhPost(method,args,callback){
+
+ var _args = null;
+ if (typeof(args) == 'string'){
+ _args = JSON.stringify(str2Obj(args));
+ } else {
+ _args = JSON.stringify(args);
+ }
+
+ var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 });
+ $.post('/plugins/run', {name:'xhprof', func:method, args:_args}, function(data) {
+ layer.close(loadT);
+ if (!data.status){
+ layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']});
+ return;
+ }
+
+ if(typeof(callback) == 'function'){
+ callback(data);
+ }
+ },'json');
+}
+
+
+function xhAsyncPost(method,args){
+
+ var _args = null;
+ if (typeof(args) == 'string'){
+ _args = JSON.stringify(str2Obj(args));
+ } else {
+ _args = JSON.stringify(args);
+ }
+ return syncPost('/plugins/run', {name:'xhprof', func:method, args:_args});
+}
+
+function homePage(){
+ xhPost('get_home_page', '', function(data){
+ var rdata = $.parseJSON(data.data);
+ if (!rdata.status){
+ layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']});
+ return;
+ }
+ var con = '
主页 ';
+ $(".soft-man-con").html(con);
+ });
+}
+
+//phpmyadmin切换php版本
+function phpVer(version) {
+
+ var _version = xhAsyncPost('get_set_php_ver','')
+ if (_version['data'] != ''){
+ version = _version['data'];
+ }
+
+ $.post('/site/get_php_version', function(rdata) {
+ // console.log(rdata);
+ var body = "
PHP版本 ";
+ var optionSelect = '';
+ for (var i = 0; i < rdata.length; i++) {
+ optionSelect = rdata[i].version == version ? 'selected' : '';
+ body += "" + rdata[i].name + " "
+ }
+ body += ' 保存
';
+ $(".soft-man-con").html(body);
+ },'json');
+}
+
+function phpVerChange(type, msg) {
+ var phpver = $("#phpver").val();
+ xhPost('set_php_ver', 'phpver='+phpver, function(data){
+ if ( data.data == 'ok' ){
+ layer.msg('设置成功!',{icon:1,time:2000,shade: [0.3, '#000']});
+ } else {
+ layer.msg('设置失败!',{icon:2,time:2000,shade: [0.3, '#000']});
+ }
+ });
+}
+
+
+//xhprf 安全设置
+function safeConf() {
+ var data = xhAsyncPost('get_xhprof_port');
+ var rdata = $.parseJSON(data.data);
+ if (!rdata.status){
+ layer.msg(rdata.msg,{icon:2,time:2000,shade: [0.3, '#000']});
+ return;
+ }
+ var con = '
\
+ 访问端口 \
+ \
+ 保存 \
+
';
+ $(".soft-man-con").html(con);
+}
+
+//修改 xhprf 端口
+function xhprofPort() {
+ var pmport = $("#pmport").val();
+ if (pmport < 80 || pmport > 65535) {
+ layer.msg('端口范围不合法!', { icon: 2 });
+ return;
+ }
+ var data = 'port=' + pmport;
+
+ xhPost('set_xhprof_port',data, function(data){
+ var rdata = $.parseJSON(data.data);
+ layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 });
+ });
+}
\ No newline at end of file
diff --git a/plugins/xhprof/lib/xhprof_html/callgraph.php b/plugins/xhprof/lib/xhprof_html/callgraph.php
new file mode 100755
index 000000000..46fc6a524
--- /dev/null
+++ b/plugins/xhprof/lib/xhprof_html/callgraph.php
@@ -0,0 +1,91 @@
+ 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.
+}
+
+$xhprof_runs_impl = new XHProfRuns_Default();
+
+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);
+}
diff --git a/plugins/xhprof/lib/xhprof_html/css/xhprof.css b/plugins/xhprof/lib/xhprof_html/css/xhprof.css
new file mode 100755
index 000000000..c3abf8622
--- /dev/null
+++ b/plugins/xhprof/lib/xhprof_html/css/xhprof.css
@@ -0,0 +1,81 @@
+/* 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.
+ */
+
+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;
+}
diff --git a/plugins/xhprof/lib/xhprof_html/index.php b/plugins/xhprof/lib/xhprof_html/index.php
new file mode 100755
index 000000000..f21d32fd5
--- /dev/null
+++ b/plugins/xhprof/lib/xhprof_html/index.php
@@ -0,0 +1,90 @@
+ 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 "";
+
+echo "
XHProf: Hierarchical Profiler Report ";
+xhprof_include_js_css();
+echo "";
+
+echo "";
+
+$vbar = ' class="vbar"';
+$vwbar = ' class="vwbar"';
+$vwlbar = ' class="vwlbar"';
+$vbbar = ' class="vbbar"';
+$vrbar = ' class="vrbar"';
+$vgbar = ' class="vgbar"';
+
+$xhprof_runs_impl = new XHProfRuns_Default();
+
+displayXHProfReport($xhprof_runs_impl, $params, $source, $run, $wts,
+ $symbol, $sort, $run1, $run2);
+
+
+echo "";
+echo "";
diff --git a/plugins/xhprof/lib/xhprof_html/jquery/indicator.gif b/plugins/xhprof/lib/xhprof_html/jquery/indicator.gif
new file mode 100755
index 000000000..9c26a717c
Binary files /dev/null and b/plugins/xhprof/lib/xhprof_html/jquery/indicator.gif differ
diff --git a/plugins/xhprof/lib/xhprof_html/jquery/jquery-1.2.6.js b/plugins/xhprof/lib/xhprof_html/jquery/jquery-1.2.6.js
new file mode 100755
index 000000000..5b8bfc2dd
--- /dev/null
+++ b/plugins/xhprof/lib/xhprof_html/jquery/jquery-1.2.6.js
@@ -0,0 +1,3549 @@
+(function(){
+/*
+ * jQuery 1.2.6 - New Wave Javascript
+ *
+ * Copyright (c) 2008 John Resig (jquery.com)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * $Date: 2009-03-17 18:35:18 $
+ * $Rev: 5685 $
+ */
+
+// Map over jQuery in case of overwrite
+var _jQuery = window.jQuery,
+// Map over the $ in case of overwrite
+ _$ = window.$;
+
+var jQuery = window.jQuery = window.$ = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.fn.init( selector, context );
+};
+
+// A simple way to check for HTML strings or ID strings
+// (both of which we optimize for)
+var quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/,
+
+// Is it a simple selector
+ isSimple = /^.[^:#\[\.]*$/,
+
+// Will speed up references to undefined, and allows munging its name.
+ undefined;
+
+jQuery.fn = jQuery.prototype = {
+ init: function( selector, context ) {
+ // Make sure that a selection was provided
+ selector = selector || document;
+
+ // Handle $(DOMElement)
+ if ( selector.nodeType ) {
+ this[0] = selector;
+ this.length = 1;
+ return this;
+ }
+ // Handle HTML strings
+ if ( typeof selector == "string" ) {
+ // Are we dealing with HTML string or an ID?
+ var match = quickExpr.exec( selector );
+
+ // Verify a match, and that no context was specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] )
+ selector = jQuery.clean( [ match[1] ], context );
+
+ // HANDLE: $("#id")
+ else {
+ var elem = document.getElementById( match[3] );
+
+ // Make sure an element was located
+ if ( elem ){
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id != match[3] )
+ return jQuery().find( selector );
+
+ // Otherwise, we inject the element directly into the jQuery object
+ return jQuery( elem );
+ }
+ selector = [];
+ }
+
+ // HANDLE: $(expr, [context])
+ // (which is just equivalent to: $(content).find(expr)
+ } else
+ return jQuery( context ).find( selector );
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) )
+ return jQuery( document )[ jQuery.fn.ready ? "ready" : "load" ]( selector );
+
+ return this.setArray(jQuery.makeArray(selector));
+ },
+
+ // The current version of jQuery being used
+ jquery: "1.2.6",
+
+ // The number of elements contained in the matched element set
+ size: function() {
+ return this.length;
+ },
+
+ // The number of elements contained in the matched element set
+ length: 0,
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num == undefined ?
+
+ // Return a 'clean' array
+ jQuery.makeArray( this ) :
+
+ // Return just the object
+ this[ num ];
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems ) {
+ // Build a new jQuery matched element set
+ var ret = jQuery( elems );
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Force the current matched set of elements to become
+ // the specified array of elements (destroying the stack in the process)
+ // You should use pushStack() in order to do this, but maintain the stack
+ setArray: function( elems ) {
+ // Resetting the length to 0, then using the native Array push
+ // is a super-fast way to populate an object with array-like properties
+ this.length = 0;
+ Array.prototype.push.apply( this, elems );
+
+ return this;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ // Determine the position of an element within
+ // the matched set of elements
+ index: function( elem ) {
+ var ret = -1;
+
+ // Locate the position of the desired element
+ return jQuery.inArray(
+ // If it receives a jQuery object, the first element is used
+ elem && elem.jquery ? elem[0] : elem
+ , this );
+ },
+
+ attr: function( name, value, type ) {
+ var options = name;
+
+ // Look for the case where we're accessing a style value
+ if ( name.constructor == String )
+ if ( value === undefined )
+ return this[0] && jQuery[ type || "attr" ]( this[0], name );
+
+ else {
+ options = {};
+ options[ name ] = value;
+ }
+
+ // Check to see if we're setting style values
+ return this.each(function(i){
+ // Set all the styles
+ for ( name in options )
+ jQuery.attr(
+ type ?
+ this.style :
+ this,
+ name, jQuery.prop( this, options[ name ], type, i, name )
+ );
+ });
+ },
+
+ css: function( key, value ) {
+ // ignore negative width and height values
+ if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 )
+ value = undefined;
+ return this.attr( key, value, "curCSS" );
+ },
+
+ text: function( text ) {
+ if ( typeof text != "object" && text != null )
+ return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
+
+ var ret = "";
+
+ jQuery.each( text || this, function(){
+ jQuery.each( this.childNodes, function(){
+ if ( this.nodeType != 8 )
+ ret += this.nodeType != 1 ?
+ this.nodeValue :
+ jQuery.fn.text( [ this ] );
+ });
+ });
+
+ return ret;
+ },
+
+ wrapAll: function( html ) {
+ if ( this[0] )
+ // The elements to wrap the target around
+ jQuery( html, this[0].ownerDocument )
+ .clone()
+ .insertBefore( this[0] )
+ .map(function(){
+ var elem = this;
+
+ while ( elem.firstChild )
+ elem = elem.firstChild;
+
+ return elem;
+ })
+ .append(this);
+
+ return this;
+ },
+
+ wrapInner: function( html ) {
+ return this.each(function(){
+ jQuery( this ).contents().wrapAll( html );
+ });
+ },
+
+ wrap: function( html ) {
+ return this.each(function(){
+ jQuery( this ).wrapAll( html );
+ });
+ },
+
+ append: function() {
+ return this.domManip(arguments, true, false, function(elem){
+ if (this.nodeType == 1)
+ this.appendChild( elem );
+ });
+ },
+
+ prepend: function() {
+ return this.domManip(arguments, true, true, function(elem){
+ if (this.nodeType == 1)
+ this.insertBefore( elem, this.firstChild );
+ });
+ },
+
+ before: function() {
+ return this.domManip(arguments, false, false, function(elem){
+ this.parentNode.insertBefore( elem, this );
+ });
+ },
+
+ after: function() {
+ return this.domManip(arguments, false, true, function(elem){
+ this.parentNode.insertBefore( elem, this.nextSibling );
+ });
+ },
+
+ end: function() {
+ return this.prevObject || jQuery( [] );
+ },
+
+ find: function( selector ) {
+ var elems = jQuery.map(this, function(elem){
+ return jQuery.find( selector, elem );
+ });
+
+ return this.pushStack( /[^+>] [^+>]/.test( selector ) || selector.indexOf("..") > -1 ?
+ jQuery.unique( elems ) :
+ elems );
+ },
+
+ clone: function( events ) {
+ // Do the clone
+ var ret = this.map(function(){
+ if ( jQuery.browser.msie && !jQuery.isXMLDoc(this) ) {
+ // IE copies events bound via attachEvent when
+ // using cloneNode. Calling detachEvent on the
+ // clone will also remove the events from the orignal
+ // In order to get around this, we use innerHTML.
+ // Unfortunately, this means some modifications to
+ // attributes in IE that are actually only stored
+ // as properties will not be copied (such as the
+ // the name attribute on an input).
+ var clone = this.cloneNode(true),
+ container = document.createElement("div");
+ container.appendChild(clone);
+ return jQuery.clean([container.innerHTML])[0];
+ } else
+ return this.cloneNode(true);
+ });
+
+ // Need to set the expando to null on the cloned set if it exists
+ // removeData doesn't work here, IE removes it from the original as well
+ // this is primarily for IE but the data expando shouldn't be copied over in any browser
+ var clone = ret.find("*").andSelf().each(function(){
+ if ( this[ expando ] != undefined )
+ this[ expando ] = null;
+ });
+
+ // Copy the events from the original to the clone
+ if ( events === true )
+ this.find("*").andSelf().each(function(i){
+ if (this.nodeType == 3)
+ return;
+ var events = jQuery.data( this, "events" );
+
+ for ( var type in events )
+ for ( var handler in events[ type ] )
+ jQuery.event.add( clone[ i ], type, events[ type ][ handler ], events[ type ][ handler ].data );
+ });
+
+ // Return the cloned set
+ return ret;
+ },
+
+ filter: function( selector ) {
+ return this.pushStack(
+ jQuery.isFunction( selector ) &&
+ jQuery.grep(this, function(elem, i){
+ return selector.call( elem, i );
+ }) ||
+
+ jQuery.multiFilter( selector, this ) );
+ },
+
+ not: function( selector ) {
+ if ( selector.constructor == String )
+ // test special case where just one selector is passed in
+ if ( isSimple.test( selector ) )
+ return this.pushStack( jQuery.multiFilter( selector, this, true ) );
+ else
+ selector = jQuery.multiFilter( selector, this );
+
+ var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType;
+ return this.filter(function() {
+ return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector;
+ });
+ },
+
+ add: function( selector ) {
+ return this.pushStack( jQuery.unique( jQuery.merge(
+ this.get(),
+ typeof selector == 'string' ?
+ jQuery( selector ) :
+ jQuery.makeArray( selector )
+ )));
+ },
+
+ is: function( selector ) {
+ return !!selector && jQuery.multiFilter( selector, this ).length > 0;
+ },
+
+ hasClass: function( selector ) {
+ return this.is( "." + selector );
+ },
+
+ val: function( value ) {
+ if ( value == undefined ) {
+
+ if ( this.length ) {
+ var elem = this[0];
+
+ // We need to handle select boxes special
+ if ( jQuery.nodeName( elem, "select" ) ) {
+ var index = elem.selectedIndex,
+ values = [],
+ options = elem.options,
+ one = elem.type == "select-one";
+
+ // Nothing was selected
+ if ( index < 0 )
+ return null;
+
+ // Loop through all the selected options
+ for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
+ var option = options[ i ];
+
+ if ( option.selected ) {
+ // Get the specifc value for the option
+ value = jQuery.browser.msie && !option.attributes.value.specified ? option.text : option.value;
+
+ // We don't need an array for one selects
+ if ( one )
+ return value;
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ return values;
+
+ // Everything else, we just grab the value
+ } else
+ return (this[0].value || "").replace(/\r/g, "");
+
+ }
+
+ return undefined;
+ }
+
+ if( value.constructor == Number )
+ value += '';
+
+ return this.each(function(){
+ if ( this.nodeType != 1 )
+ return;
+
+ if ( value.constructor == Array && /radio|checkbox/.test( this.type ) )
+ this.checked = (jQuery.inArray(this.value, value) >= 0 ||
+ jQuery.inArray(this.name, value) >= 0);
+
+ else if ( jQuery.nodeName( this, "select" ) ) {
+ var values = jQuery.makeArray(value);
+
+ jQuery( "option", this ).each(function(){
+ this.selected = (jQuery.inArray( this.value, values ) >= 0 ||
+ jQuery.inArray( this.text, values ) >= 0);
+ });
+
+ if ( !values.length )
+ this.selectedIndex = -1;
+
+ } else
+ this.value = value;
+ });
+ },
+
+ html: function( value ) {
+ return value == undefined ?
+ (this[0] ?
+ this[0].innerHTML :
+ null) :
+ this.empty().append( value );
+ },
+
+ replaceWith: function( value ) {
+ return this.after( value ).remove();
+ },
+
+ eq: function( i ) {
+ return this.slice( i, i + 1 );
+ },
+
+ slice: function() {
+ return this.pushStack( Array.prototype.slice.apply( this, arguments ) );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function(elem, i){
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ andSelf: function() {
+ return this.add( this.prevObject );
+ },
+
+ data: function( key, value ){
+ var parts = key.split(".");
+ parts[1] = parts[1] ? "." + parts[1] : "";
+
+ if ( value === undefined ) {
+ var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
+
+ if ( data === undefined && this.length )
+ data = jQuery.data( this[0], key );
+
+ return data === undefined && parts[1] ?
+ this.data( parts[0] ) :
+ data;
+ } else
+ return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){
+ jQuery.data( this, key, value );
+ });
+ },
+
+ removeData: function( key ){
+ return this.each(function(){
+ jQuery.removeData( this, key );
+ });
+ },
+
+ domManip: function( args, table, reverse, callback ) {
+ var clone = this.length > 1, elems;
+
+ return this.each(function(){
+ if ( !elems ) {
+ elems = jQuery.clean( args, this.ownerDocument );
+
+ if ( reverse )
+ elems.reverse();
+ }
+
+ var obj = this;
+
+ if ( table && jQuery.nodeName( this, "table" ) && jQuery.nodeName( elems[0], "tr" ) )
+ obj = this.getElementsByTagName("tbody")[0] || this.appendChild( this.ownerDocument.createElement("tbody") );
+
+ var scripts = jQuery( [] );
+
+ jQuery.each(elems, function(){
+ var elem = clone ?
+ jQuery( this ).clone( true )[0] :
+ this;
+
+ // execute all scripts after the elements have been injected
+ if ( jQuery.nodeName( elem, "script" ) )
+ scripts = scripts.add( elem );
+ else {
+ // Remove any inner scripts for later evaluation
+ if ( elem.nodeType == 1 )
+ scripts = scripts.add( jQuery( "script", elem ).remove() );
+
+ // Inject the elements into the document
+ callback.call( obj, elem );
+ }
+ });
+
+ scripts.each( evalScript );
+ });
+ }
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+function evalScript( i, elem ) {
+ if ( elem.src )
+ jQuery.ajax({
+ url: elem.src,
+ async: false,
+ dataType: "script"
+ });
+
+ else
+ jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );
+
+ if ( elem.parentNode )
+ elem.parentNode.removeChild( elem );
+}
+
+function now(){
+ return +new Date;
+}
+
+jQuery.extend = jQuery.fn.extend = function() {
+ // copy reference to target object
+ var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;
+
+ // Handle a deep copy situation
+ if ( target.constructor == Boolean ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target != "object" && typeof target != "function" )
+ target = {};
+
+ // extend jQuery itself if only one argument is passed
+ if ( length == i ) {
+ target = this;
+ --i;
+ }
+
+ for ( ; i < length; i++ )
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null )
+ // Extend the base object
+ for ( var name in options ) {
+ var src = target[ name ], copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy )
+ continue;
+
+ // Recurse if we're merging object values
+ if ( deep && copy && typeof copy == "object" && !copy.nodeType )
+ target[ name ] = jQuery.extend( deep,
+ // Never move original objects, clone them
+ src || ( copy.length != null ? [ ] : { } )
+ , copy );
+
+ // Don't bring in undefined values
+ else if ( copy !== undefined )
+ target[ name ] = copy;
+
+ }
+
+ // Return the modified object
+ return target;
+};
+
+var expando = "jQuery" + now(), uuid = 0, windowData = {},
+ // exclude the following css properties to add px
+ exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i,
+ // cache defaultView
+ defaultView = document.defaultView || {};
+
+jQuery.extend({
+ noConflict: function( deep ) {
+ window.$ = _$;
+
+ if ( deep )
+ window.jQuery = _jQuery;
+
+ return jQuery;
+ },
+
+ // See test/unit/core.js for details concerning this function.
+ isFunction: function( fn ) {
+ return !!fn && typeof fn != "string" && !fn.nodeName &&
+ fn.constructor != Array && /^[\s[]?function/.test( fn + "" );
+ },
+
+ // check if an element is in a (or is an) XML document
+ isXMLDoc: function( elem ) {
+ return elem.documentElement && !elem.body ||
+ elem.tagName && elem.ownerDocument && !elem.ownerDocument.body;
+ },
+
+ // Evalulates a script in a global context
+ globalEval: function( data ) {
+ data = jQuery.trim( data );
+
+ if ( data ) {
+ // Inspired by code by Andrea Giammarchi
+ // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
+ var head = document.getElementsByTagName("head")[0] || document.documentElement,
+ script = document.createElement("script");
+
+ script.type = "text/javascript";
+ if ( jQuery.browser.msie )
+ script.text = data;
+ else
+ script.appendChild( document.createTextNode( data ) );
+
+ // Use insertBefore instead of appendChild to circumvent an IE6 bug.
+ // This arises when a base node is used (#2709).
+ head.insertBefore( script, head.firstChild );
+ head.removeChild( script );
+ }
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase();
+ },
+
+ cache: {},
+
+ data: function( elem, name, data ) {
+ elem = elem == window ?
+ windowData :
+ elem;
+
+ var id = elem[ expando ];
+
+ // Compute a unique ID for the element
+ if ( !id )
+ id = elem[ expando ] = ++uuid;
+
+ // Only generate the data cache if we're
+ // trying to access or manipulate it
+ if ( name && !jQuery.cache[ id ] )
+ jQuery.cache[ id ] = {};
+
+ // Prevent overriding the named cache with undefined values
+ if ( data !== undefined )
+ jQuery.cache[ id ][ name ] = data;
+
+ // Return the named cache data, or the ID for the element
+ return name ?
+ jQuery.cache[ id ][ name ] :
+ id;
+ },
+
+ removeData: function( elem, name ) {
+ elem = elem == window ?
+ windowData :
+ elem;
+
+ var id = elem[ expando ];
+
+ // If we want to remove a specific section of the element's data
+ if ( name ) {
+ if ( jQuery.cache[ id ] ) {
+ // Remove the section of cache data
+ delete jQuery.cache[ id ][ name ];
+
+ // If we've removed all the data, remove the element's cache
+ name = "";
+
+ for ( name in jQuery.cache[ id ] )
+ break;
+
+ if ( !name )
+ jQuery.removeData( elem );
+ }
+
+ // Otherwise, we want to remove all of the element's data
+ } else {
+ // Clean up the element expando
+ try {
+ delete elem[ expando ];
+ } catch(e){
+ // IE has trouble directly removing the expando
+ // but it's ok with using removeAttribute
+ if ( elem.removeAttribute )
+ elem.removeAttribute( expando );
+ }
+
+ // Completely remove the data cache
+ delete jQuery.cache[ id ];
+ }
+ },
+
+ // args is for internal usage only
+ each: function( object, callback, args ) {
+ var name, i = 0, length = object.length;
+
+ if ( args ) {
+ if ( length == undefined ) {
+ for ( name in object )
+ if ( callback.apply( object[ name ], args ) === false )
+ break;
+ } else
+ for ( ; i < length; )
+ if ( callback.apply( object[ i++ ], args ) === false )
+ break;
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( length == undefined ) {
+ for ( name in object )
+ if ( callback.call( object[ name ], name, object[ name ] ) === false )
+ break;
+ } else
+ for ( var value = object[0];
+ i < length && callback.call( value, i, value ) !== false; value = object[++i] ){}
+ }
+
+ return object;
+ },
+
+ prop: function( elem, value, type, i, name ) {
+ // Handle executable functions
+ if ( jQuery.isFunction( value ) )
+ value = value.call( elem, i );
+
+ // Handle passing in a number to a CSS property
+ return value && value.constructor == Number && type == "curCSS" && !exclude.test( name ) ?
+ value + "px" :
+ value;
+ },
+
+ className: {
+ // internal only, use addClass("class")
+ add: function( elem, classNames ) {
+ jQuery.each((classNames || "").split(/\s+/), function(i, className){
+ if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) )
+ elem.className += (elem.className ? " " : "") + className;
+ });
+ },
+
+ // internal only, use removeClass("class")
+ remove: function( elem, classNames ) {
+ if (elem.nodeType == 1)
+ elem.className = classNames != undefined ?
+ jQuery.grep(elem.className.split(/\s+/), function(className){
+ return !jQuery.className.has( classNames, className );
+ }).join(" ") :
+ "";
+ },
+
+ // internal only, use hasClass("class")
+ has: function( elem, className ) {
+ return jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1;
+ }
+ },
+
+ // A method for quickly swapping in/out CSS properties to get correct calculations
+ swap: function( elem, options, callback ) {
+ var old = {};
+ // Remember the old values, and insert the new ones
+ for ( var name in options ) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
+ }
+
+ callback.call( elem );
+
+ // Revert the old values
+ for ( var name in options )
+ elem.style[ name ] = old[ name ];
+ },
+
+ css: function( elem, name, force ) {
+ if ( name == "width" || name == "height" ) {
+ var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ];
+
+ function getWH() {
+ val = name == "width" ? elem.offsetWidth : elem.offsetHeight;
+ var padding = 0, border = 0;
+ jQuery.each( which, function() {
+ padding += parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0;
+ border += parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0;
+ });
+ val -= Math.round(padding + border);
+ }
+
+ if ( jQuery(elem).is(":visible") )
+ getWH();
+ else
+ jQuery.swap( elem, props, getWH );
+
+ return Math.max(0, val);
+ }
+
+ return jQuery.curCSS( elem, name, force );
+ },
+
+ curCSS: function( elem, name, force ) {
+ var ret, style = elem.style;
+
+ // A helper method for determining if an element's values are broken
+ function color( elem ) {
+ if ( !jQuery.browser.safari )
+ return false;
+
+ // defaultView is cached
+ var ret = defaultView.getComputedStyle( elem, null );
+ return !ret || ret.getPropertyValue("color") == "";
+ }
+
+ // We need to handle opacity special in IE
+ if ( name == "opacity" && jQuery.browser.msie ) {
+ ret = jQuery.attr( style, "opacity" );
+
+ return ret == "" ?
+ "1" :
+ ret;
+ }
+ // Opera sometimes will give the wrong display answer, this fixes it, see #2037
+ if ( jQuery.browser.opera && name == "display" ) {
+ var save = style.outline;
+ style.outline = "0 solid black";
+ style.outline = save;
+ }
+
+ // Make sure we're using the right name for getting the float value
+ if ( name.match( /float/i ) )
+ name = styleFloat;
+
+ if ( !force && style && style[ name ] )
+ ret = style[ name ];
+
+ else if ( defaultView.getComputedStyle ) {
+
+ // Only "float" is needed here
+ if ( name.match( /float/i ) )
+ name = "float";
+
+ name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase();
+
+ var computedStyle = defaultView.getComputedStyle( elem, null );
+
+ if ( computedStyle && !color( elem ) )
+ ret = computedStyle.getPropertyValue( name );
+
+ // If the element isn't reporting its values properly in Safari
+ // then some display: none elements are involved
+ else {
+ var swap = [], stack = [], a = elem, i = 0;
+
+ // Locate all of the parent display: none elements
+ for ( ; a && color(a); a = a.parentNode )
+ stack.unshift(a);
+
+ // Go through and make them visible, but in reverse
+ // (It would be better if we knew the exact display type that they had)
+ for ( ; i < stack.length; i++ )
+ if ( color( stack[ i ] ) ) {
+ swap[ i ] = stack[ i ].style.display;
+ stack[ i ].style.display = "block";
+ }
+
+ // Since we flip the display style, we have to handle that
+ // one special, otherwise get the value
+ ret = name == "display" && swap[ stack.length - 1 ] != null ?
+ "none" :
+ ( computedStyle && computedStyle.getPropertyValue( name ) ) || "";
+
+ // Finally, revert the display styles back
+ for ( i = 0; i < swap.length; i++ )
+ if ( swap[ i ] != null )
+ stack[ i ].style.display = swap[ i ];
+ }
+
+ // We should always get a number back from opacity
+ if ( name == "opacity" && ret == "" )
+ ret = "1";
+
+ } else if ( elem.currentStyle ) {
+ var camelCase = name.replace(/\-(\w)/g, function(all, letter){
+ return letter.toUpperCase();
+ });
+
+ ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ];
+
+ // From the awesome hack by Dean Edwards
+ // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+ // If we're not dealing with a regular pixel number
+ // but a number that has a weird ending, we need to convert it to pixels
+ if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) {
+ // Remember the original values
+ var left = style.left, rsLeft = elem.runtimeStyle.left;
+
+ // Put in the new values to get a computed value out
+ elem.runtimeStyle.left = elem.currentStyle.left;
+ style.left = ret || 0;
+ ret = style.pixelLeft + "px";
+
+ // Revert the changed values
+ style.left = left;
+ elem.runtimeStyle.left = rsLeft;
+ }
+ }
+
+ return ret;
+ },
+
+ clean: function( elems, context ) {
+ var ret = [];
+ context = context || document;
+ // !context.createElement fails in IE with an error but returns typeof 'object'
+ if (typeof context.createElement == 'undefined')
+ context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+
+ jQuery.each(elems, function(i, elem){
+ if ( !elem )
+ return;
+
+ if ( elem.constructor == Number )
+ elem += '';
+
+ // Convert html string into DOM nodes
+ if ( typeof elem == "string" ) {
+ // Fix "XHTML"-style tags in all browsers
+ elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){
+ return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ?
+ all :
+ front + ">" + tag + ">";
+ });
+
+ // Trim whitespace, otherwise indexOf won't work as expected
+ var tags = jQuery.trim( elem ).toLowerCase(), div = context.createElement("div");
+
+ var wrap =
+ // option or optgroup
+ !tags.indexOf("
", "" ] ||
+
+ !tags.indexOf("", "" ] ||
+
+ tags.match(/^<(thead|tbody|tfoot|colg|cap)/) &&
+ [ 1, "" ] ||
+
+ !tags.indexOf("", " " ] ||
+
+ // matched above
+ (!tags.indexOf(" ", " " ] ||
+
+ !tags.indexOf("", " " ] ||
+
+ // IE can't serialize and ";
+ echo "";
+ echo "";
+ echo "";
+}
+
+
+/*
+ * Formats call counts for XHProf reports.
+ *
+ * Description:
+ * Call counts in single-run reports are integer values.
+ * However, call counts for aggregated reports can be
+ * fractional. This function will print integer values
+ * without decimal point, but with commas etc.
+ *
+ * 4000 ==> 4,000
+ *
+ * It'll round fractional values to decimal precision of 3
+ * 4000.1212 ==> 4,000.121
+ * 4000.0001 ==> 4,000
+ *
+ */
+function xhprof_count_format($num) {
+ $num = round($num, 3);
+ if (round($num) == $num) {
+ return number_format($num);
+ } else {
+ return number_format($num, 3);
+ }
+}
+
+function xhprof_percent_format($s, $precision = 1) {
+ return sprintf('%.'.$precision.'f%%', 100 * $s);
+}
+
+/**
+ * Implodes the text for a bunch of actions (such as links, forms,
+ * into a HTML list and returns the text.
+ */
+function xhprof_render_actions($actions) {
+ $out = array();
+
+ if (count($actions)) {
+ $out[] = '';
+ foreach ($actions as $action) {
+ $out[] = ''.$action.' ';
+ }
+ $out[] = ' ';
+ }
+
+ return implode('', $out);
+}
+
+
+/**
+ * @param html-str $content the text/image/innerhtml/whatever for the link
+ * @param raw-str $href
+ * @param raw-str $class
+ * @param raw-str $id
+ * @param raw-str $title
+ * @param raw-str $target
+ * @param raw-str $onclick
+ * @param raw-str $style
+ * @param raw-str $access
+ * @param raw-str $onmouseover
+ * @param raw-str $onmouseout
+ * @param raw-str $onmousedown
+ * @param raw-str $dir
+ * @param raw-str $rel
+ */
+function xhprof_render_link($content, $href, $class='', $id='', $title='',
+ $target='',
+ $onclick='', $style='', $access='', $onmouseover='',
+ $onmouseout='', $onmousedown='') {
+
+ if (!$content) {
+ return '';
+ }
+
+ if ($href) {
+ $link = ' 1,
+ "ct" => 1,
+ "wt" => 1,
+ "excl_wt" => 1,
+ "ut" => 1,
+ "excl_ut" => 1,
+ "st" => 1,
+ "excl_st" => 1,
+ "mu" => 1,
+ "excl_mu" => 1,
+ "pmu" => 1,
+ "excl_pmu" => 1,
+ "cpu" => 1,
+ "excl_cpu" => 1,
+ "samples" => 1,
+ "excl_samples" => 1
+ );
+
+// Textual descriptions for column headers in "single run" mode
+$descriptions = array(
+ "fn" => "Function Name",
+ "ct" => "Calls",
+ "Calls%" => "Calls%",
+
+ "wt" => "Incl. Wall Time (microsec)",
+ "IWall%" => "IWall%",
+ "excl_wt" => "Excl. Wall Time (microsec)",
+ "EWall%" => "EWall%",
+
+ "ut" => "Incl. User (microsecs)",
+ "IUser%" => "IUser%",
+ "excl_ut" => "Excl. User (microsec)",
+ "EUser%" => "EUser%",
+
+ "st" => "Incl. Sys (microsec)",
+ "ISys%" => "ISys%",
+ "excl_st" => "Excl. Sys (microsec)",
+ "ESys%" => "ESys%",
+
+ "cpu" => "Incl. CPU (microsecs)",
+ "ICpu%" => "ICpu%",
+ "excl_cpu" => "Excl. CPU (microsec)",
+ "ECpu%" => "ECPU%",
+
+ "mu" => "Incl. MemUse (bytes)",
+ "IMUse%" => "IMemUse%",
+ "excl_mu" => "Excl. MemUse (bytes)",
+ "EMUse%" => "EMemUse%",
+
+ "pmu" => "Incl. PeakMemUse (bytes)",
+ "IPMUse%" => "IPeakMemUse%",
+ "excl_pmu" => "Excl. PeakMemUse (bytes)",
+ "EPMUse%" => "EPeakMemUse%",
+
+ "samples" => "Incl. Samples",
+ "ISamples%" => "ISamples%",
+ "excl_samples" => "Excl. Samples",
+ "ESamples%" => "ESamples%",
+ );
+
+// Formatting Callback Functions...
+$format_cbk = array(
+ "fn" => "",
+ "ct" => "xhprof_count_format",
+ "Calls%" => "xhprof_percent_format",
+
+ "wt" => "number_format",
+ "IWall%" => "xhprof_percent_format",
+ "excl_wt" => "number_format",
+ "EWall%" => "xhprof_percent_format",
+
+ "ut" => "number_format",
+ "IUser%" => "xhprof_percent_format",
+ "excl_ut" => "number_format",
+ "EUser%" => "xhprof_percent_format",
+
+ "st" => "number_format",
+ "ISys%" => "xhprof_percent_format",
+ "excl_st" => "number_format",
+ "ESys%" => "xhprof_percent_format",
+
+ "cpu" => "number_format",
+ "ICpu%" => "xhprof_percent_format",
+ "excl_cpu" => "number_format",
+ "ECpu%" => "xhprof_percent_format",
+
+ "mu" => "number_format",
+ "IMUse%" => "xhprof_percent_format",
+ "excl_mu" => "number_format",
+ "EMUse%" => "xhprof_percent_format",
+
+ "pmu" => "number_format",
+ "IPMUse%" => "xhprof_percent_format",
+ "excl_pmu" => "number_format",
+ "EPMUse%" => "xhprof_percent_format",
+
+ "samples" => "number_format",
+ "ISamples%" => "xhprof_percent_format",
+ "excl_samples" => "number_format",
+ "ESamples%" => "xhprof_percent_format",
+ );
+
+
+// Textual descriptions for column headers in "diff" mode
+$diff_descriptions = array(
+ "fn" => "Function Name",
+ "ct" => "Calls Diff",
+ "Calls%" => "Calls Diff%",
+
+ "wt" => "Incl. Wall Diff (microsec)",
+ "IWall%" => "IWall Diff%",
+ "excl_wt" => "Excl. Wall Diff (microsec)",
+ "EWall%" => "EWall Diff%",
+
+ "ut" => "Incl. User Diff (microsec)",
+ "IUser%" => "IUser Diff%",
+ "excl_ut" => "Excl. User Diff (microsec)",
+ "EUser%" => "EUser Diff%",
+
+ "cpu" => "Incl. CPU Diff (microsec)",
+ "ICpu%" => "ICpu Diff%",
+ "excl_cpu" => "Excl. CPU Diff (microsec)",
+ "ECpu%" => "ECpu Diff%",
+
+ "st" => "Incl. Sys Diff (microsec)",
+ "ISys%" => "ISys Diff%",
+ "excl_st" => "Excl. Sys Diff (microsec)",
+ "ESys%" => "ESys Diff%",
+
+ "mu" => "Incl. MemUse Diff (bytes)",
+ "IMUse%" => "IMemUse Diff%",
+ "excl_mu" => "Excl. MemUse Diff (bytes)",
+ "EMUse%" => "EMemUse Diff%",
+
+ "pmu" => "Incl. PeakMemUse Diff (bytes)",
+ "IPMUse%" => "IPeakMemUse Diff%",
+ "excl_pmu" => "Excl. PeakMemUse Diff (bytes)",
+ "EPMUse%" => "EPeakMemUse Diff%",
+
+ "samples" => "Incl. Samples Diff",
+ "ISamples%" => "ISamples Diff%",
+ "excl_samples" => "Excl. Samples Diff",
+ "ESamples%" => "ESamples Diff%",
+ );
+
+// columns that'll be displayed in a top-level report
+$stats = array();
+
+// columns that'll be displayed in a function's parent/child report
+$pc_stats = array();
+
+// Various total counts
+$totals = 0;
+$totals_1 = 0;
+$totals_2 = 0;
+
+/*
+ * The subset of $possible_metrics that is present in the raw profile data.
+ */
+$metrics = null;
+
+/**
+ * Callback comparison operator (passed to usort() for sorting array of
+ * tuples) that compares array elements based on the sort column
+ * specified in $sort_col (global parameter).
+ *
+ * @author Kannan
+ */
+function sort_cbk($a, $b) {
+ global $sort_col;
+ global $diff_mode;
+
+ if ($sort_col == "fn") {
+
+ // case insensitive ascending sort for function names
+ $left = strtoupper($a["fn"]);
+ $right = strtoupper($b["fn"]);
+
+ if ($left == $right)
+ return 0;
+ return ($left < $right) ? -1 : 1;
+
+ } else {
+
+ // descending sort for all others
+ $left = $a[$sort_col];
+ $right = $b[$sort_col];
+
+ // if diff mode, sort by absolute value of regression/improvement
+ if ($diff_mode) {
+ $left = abs($left);
+ $right = abs($right);
+ }
+
+ if ($left == $right)
+ return 0;
+ return ($left > $right) ? -1 : 1;
+ }
+}
+
+/**
+ * Get the appropriate description for a statistic
+ * (depending upon whether we are in diff report mode
+ * or single run report mode).
+ *
+ * @author Kannan
+ */
+function stat_description($stat) {
+ global $descriptions;
+ global $diff_descriptions;
+ global $diff_mode;
+
+ if ($diff_mode) {
+ return $diff_descriptions[$stat];
+ } else {
+ return $descriptions[$stat];
+ }
+}
+
+
+/**
+ * Analyze raw data & generate the profiler report
+ * (common for both single run mode and diff mode).
+ *
+ * @author: Kannan
+ */
+function profiler_report ($url_params,
+ $rep_symbol,
+ $sort,
+ $run1,
+ $run1_desc,
+ $run1_data,
+ $run2 = 0,
+ $run2_desc = "",
+ $run2_data = array()) {
+ global $totals;
+ global $totals_1;
+ global $totals_2;
+ global $stats;
+ global $pc_stats;
+ global $diff_mode;
+ global $base_path;
+
+ // if we are reporting on a specific function, we can trim down
+ // the report(s) to just stuff that is relevant to this function.
+ // That way compute_flat_info()/compute_diff() etc. do not have
+ // to needlessly work hard on churning irrelevant data.
+ if (!empty($rep_symbol)) {
+ $run1_data = xhprof_trim_run($run1_data, array($rep_symbol));
+ if ($diff_mode) {
+ $run2_data = xhprof_trim_run($run2_data, array($rep_symbol));
+ }
+ }
+
+ if ($diff_mode) {
+ $run_delta = xhprof_compute_diff($run1_data, $run2_data);
+ $symbol_tab = xhprof_compute_flat_info($run_delta, $totals);
+ $symbol_tab1 = xhprof_compute_flat_info($run1_data, $totals_1);
+ $symbol_tab2 = xhprof_compute_flat_info($run2_data, $totals_2);
+ } else {
+ $symbol_tab = xhprof_compute_flat_info($run1_data, $totals);
+ }
+
+ $run1_txt = sprintf("Run #%s: %s",
+ $run1, $run1_desc);
+
+ $base_url_params = xhprof_array_unset(xhprof_array_unset($url_params,
+ 'symbol'),
+ 'all');
+
+ $top_link_query_string = "$base_path/?" . http_build_query($base_url_params);
+
+ if ($diff_mode) {
+ $diff_text = "Diff";
+ $base_url_params = xhprof_array_unset($base_url_params, 'run1');
+ $base_url_params = xhprof_array_unset($base_url_params, 'run2');
+ $run1_link = xhprof_render_link('View Run #' . $run1,
+ "$base_path/?" .
+ http_build_query(xhprof_array_set($base_url_params,
+ 'run',
+ $run1)));
+ $run2_txt = sprintf("Run #%s: %s",
+ $run2, $run2_desc);
+
+ $run2_link = xhprof_render_link('View Run #' . $run2,
+ "$base_path/?" .
+ http_build_query(xhprof_array_set($base_url_params,
+ 'run',
+ $run2)));
+ } else {
+ $diff_text = "Run";
+ }
+
+ // set up the action links for operations that can be done on this report
+ $links = array();
+ $links [] = xhprof_render_link("View Top Level $diff_text Report",
+ $top_link_query_string);
+
+ if ($diff_mode) {
+ $inverted_params = $url_params;
+ $inverted_params['run1'] = $url_params['run2'];
+ $inverted_params['run2'] = $url_params['run1'];
+
+ // view the different runs or invert the current diff
+ $links [] = $run1_link;
+ $links [] = $run2_link;
+ $links [] = xhprof_render_link('Invert ' . $diff_text . ' Report',
+ "$base_path/?".
+ http_build_query($inverted_params));
+ }
+
+ // lookup function typeahead form
+ $links [] = ' ';
+
+ echo xhprof_render_actions($links);
+
+
+ echo
+ '' .
+ ' ' . $diff_text . ' Report ' .
+ ' ' . ($diff_mode ?
+ $run1_txt . 'vs. ' . $run2_txt :
+ $run1_txt) .
+ ' ' .
+ ' Tip ' .
+ ' Click a function name below to drill down. ' .
+ ' ' .
+ '
';
+
+ // data tables
+ if (!empty($rep_symbol)) {
+ if (!isset($symbol_tab[$rep_symbol])) {
+ echo " Symbol $rep_symbol not found in XHProf run ";
+ return;
+ }
+
+ /* single function report with parent/child information */
+ if ($diff_mode) {
+ $info1 = isset($symbol_tab1[$rep_symbol]) ?
+ $symbol_tab1[$rep_symbol] : null;
+ $info2 = isset($symbol_tab2[$rep_symbol]) ?
+ $symbol_tab2[$rep_symbol] : null;
+ symbol_report($url_params, $run_delta, $symbol_tab[$rep_symbol],
+ $sort, $rep_symbol,
+ $run1, $info1,
+ $run2, $info2);
+ } else {
+ symbol_report($url_params, $run1_data, $symbol_tab[$rep_symbol],
+ $sort, $rep_symbol, $run1);
+ }
+ } else {
+ /* flat top-level report of all functions */
+ full_report($url_params, $symbol_tab, $sort, $run1, $run2);
+ }
+}
+
+/**
+ * Computes percentage for a pair of values, and returns it
+ * in string format.
+ */
+function pct($a, $b) {
+ if ($b == 0) {
+ return "N/A";
+ } else {
+ $res = (round(($a * 1000 / $b)) / 10);
+ return $res;
+ }
+}
+
+/**
+ * Given a number, returns the td class to use for display.
+ *
+ * For instance, negative numbers in diff reports comparing two runs (run1 & run2)
+ * represent improvement from run1 to run2. We use green to display those deltas,
+ * and red for regression deltas.
+ */
+function get_print_class($num, $bold) {
+ global $vbar;
+ global $vbbar;
+ global $vrbar;
+ global $vgbar;
+ global $diff_mode;
+
+ if ($bold) {
+ if ($diff_mode) {
+ if ($num <= 0) {
+ $class = $vgbar; // green (improvement)
+ } else {
+ $class = $vrbar; // red (regression)
+ }
+ } else {
+ $class = $vbbar; // blue
+ }
+ }
+ else {
+ $class = $vbar; // default (black)
+ }
+
+ return $class;
+}
+
+/**
+ * Prints a element with a numeric value.
+ */
+function print_td_num($num, $fmt_func, $bold=false, $attributes=null) {
+
+ $class = get_print_class($num, $bold);
+
+ if (!empty($fmt_func) && is_numeric($num) ) {
+ $num = call_user_func($fmt_func, $num);
+ }
+
+ print(" $num \n");
+}
+
+/**
+ * Prints a element with a pecentage.
+ */
+function print_td_pct($numer, $denom, $bold=false, $attributes=null) {
+ global $vbar;
+ global $vbbar;
+ global $diff_mode;
+
+ $class = get_print_class($numer, $bold);
+
+ if ($denom == 0) {
+ $pct = "N/A%";
+ } else {
+ $pct = xhprof_percent_format($numer / abs($denom));
+ }
+
+ print(" $pct \n");
+}
+
+/**
+ * Print "flat" data corresponding to one function.
+ *
+ * @author Kannan
+ */
+function print_function_info($url_params, $info, $sort, $run1, $run2) {
+ static $odd_even = 0;
+
+ global $totals;
+ global $sort_col;
+ global $metrics;
+ global $format_cbk;
+ global $display_calls;
+ global $base_path;
+
+ // Toggle $odd_or_even
+ $odd_even = 1 - $odd_even;
+
+ if ($odd_even) {
+ print("");
+ }
+ else {
+ print(' ');
+ }
+
+ $href = "$base_path/?" .
+ http_build_query(xhprof_array_set($url_params,
+ 'symbol', $info["fn"]));
+
+ print('');
+ print(xhprof_render_link($info["fn"], $href));
+ print_source_link($info);
+ print(" \n");
+
+ if ($display_calls) {
+ // Call Count..
+ print_td_num($info["ct"], $format_cbk["ct"], ($sort_col == "ct"));
+ print_td_pct($info["ct"], $totals["ct"], ($sort_col == "ct"));
+ }
+
+ // Other metrics..
+ foreach ($metrics as $metric) {
+ // Inclusive metric
+ print_td_num($info[$metric], $format_cbk[$metric],
+ ($sort_col == $metric));
+ print_td_pct($info[$metric], $totals[$metric],
+ ($sort_col == $metric));
+
+ // Exclusive Metric
+ print_td_num($info["excl_" . $metric],
+ $format_cbk["excl_" . $metric],
+ ($sort_col == "excl_" . $metric));
+ print_td_pct($info["excl_" . $metric],
+ $totals[$metric],
+ ($sort_col == "excl_" . $metric));
+ }
+
+ print(" \n");
+}
+
+/**
+ * Print non-hierarchical (flat-view) of profiler data.
+ *
+ * @author Kannan
+ */
+function print_flat_data($url_params, $title, $flat_data, $sort, $run1, $run2, $limit) {
+
+ global $stats;
+ global $sortable_columns;
+ global $vwbar;
+ global $base_path;
+
+ $size = count($flat_data);
+ if (!$limit) { // no limit
+ $limit = $size;
+ $display_link = "";
+ } else {
+ $display_link = xhprof_render_link(" [ display all ]",
+ "$base_path/?" .
+ http_build_query(xhprof_array_set($url_params,
+ 'all', 1)));
+ }
+
+ print("$title $display_link ");
+
+ print('');
+ print('');
+
+ foreach ($stats as $stat) {
+ $desc = stat_description($stat);
+ if (array_key_exists($stat, $sortable_columns)) {
+ $href = "$base_path/?"
+ . http_build_query(xhprof_array_set($url_params, 'sort', $stat));
+ $header = xhprof_render_link($desc, $href);
+ } else {
+ $header = $desc;
+ }
+
+ if ($stat == "fn")
+ print("$header ");
+ else print("$header ");
+ }
+ print(" \n");
+
+ if ($limit >= 0) {
+ $limit = min($size, $limit);
+ for ($i = 0; $i < $limit; $i++) {
+ print_function_info($url_params, $flat_data[$i], $sort, $run1, $run2);
+ }
+ } else {
+ // if $limit is negative, print abs($limit) items starting from the end
+ $limit = min($size, abs($limit));
+ for ($i = 0; $i < $limit; $i++) {
+ print_function_info($url_params, $flat_data[$size - $i - 1], $sort, $run1, $run2);
+ }
+ }
+ print("
");
+
+ // let's print the display all link at the bottom as well...
+ if ($display_link) {
+ echo '' . $display_link . '
';
+ }
+
+}
+
+/**
+ * Generates a tabular report for all functions. This is the top-level report.
+ *
+ * @author Kannan
+ */
+function full_report($url_params, $symbol_tab, $sort, $run1, $run2) {
+ global $vwbar;
+ global $vbar;
+ global $totals;
+ global $totals_1;
+ global $totals_2;
+ global $metrics;
+ global $diff_mode;
+ global $descriptions;
+ global $sort_col;
+ global $format_cbk;
+ global $display_calls;
+ global $base_path;
+
+ $possible_metrics = xhprof_get_possible_metrics();
+
+ if ($diff_mode) {
+
+ $base_url_params = xhprof_array_unset(xhprof_array_unset($url_params,
+ 'run1'),
+ 'run2');
+ $href1 = "$base_path/?" .
+ http_build_query(xhprof_array_set($base_url_params,
+ 'run', $run1));
+ $href2 = "$base_path/?" .
+ http_build_query(xhprof_array_set($base_url_params,
+ 'run', $run2));
+
+ print("Overall Diff Summary ");
+ print('' . "\n");
+ print('');
+ print(" ");
+ print("" . xhprof_render_link("Run #$run1", $href1) . " ");
+ print("" . xhprof_render_link("Run #$run2", $href2) . " ");
+ print("Diff ");
+ print("Diff% ");
+ print(' ');
+
+ if ($display_calls) {
+ print('');
+ print("Number of Function Calls ");
+ print_td_num($totals_1["ct"], $format_cbk["ct"]);
+ print_td_num($totals_2["ct"], $format_cbk["ct"]);
+ print_td_num($totals_2["ct"] - $totals_1["ct"], $format_cbk["ct"], true);
+ print_td_pct($totals_2["ct"] - $totals_1["ct"], $totals_1["ct"], true);
+ print(' ');
+ }
+
+ foreach ($metrics as $metric) {
+ $m = $metric;
+ print('');
+ print("" . str_replace(" ", " ", $descriptions[$m]) . " ");
+ print_td_num($totals_1[$m], $format_cbk[$m]);
+ print_td_num($totals_2[$m], $format_cbk[$m]);
+ print_td_num($totals_2[$m] - $totals_1[$m], $format_cbk[$m], true);
+ print_td_pct($totals_2[$m] - $totals_1[$m], $totals_1[$m], true);
+ print(' ');
+ }
+ print('
');
+
+ $callgraph_report_title = '[View Regressions/Improvements using Callgraph Diff]';
+
+ } else {
+ print("
\n");
+
+ print('' . "\n");
+ echo "";
+ echo "Overall Summary ";
+ echo " ";
+ echo " ";
+
+ foreach ($metrics as $metric) {
+ echo "";
+ echo "Total "
+ . str_replace(" ", " ", stat_description($metric)) . ": ";
+ echo "" . number_format($totals[$metric]) . " "
+ . $possible_metrics[$metric][1] . " ";
+ echo " ";
+ }
+
+ if ($display_calls) {
+ echo "";
+ echo "Number of Function Calls: ";
+ echo "" . number_format($totals['ct']) . " ";
+ echo " ";
+ }
+
+ echo "
";
+ print(" \n");
+
+ $callgraph_report_title = '[View Full Callgraph]';
+ }
+
+ print("" .
+ xhprof_render_link($callgraph_report_title,
+ "$base_path/callgraph.php" . "?" . http_build_query($url_params))
+ . " ");
+
+
+ $flat_data = array();
+ foreach ($symbol_tab as $symbol => $info) {
+ $tmp = $info;
+ $tmp["fn"] = $symbol;
+ $flat_data[] = $tmp;
+ }
+ usort($flat_data, 'sort_cbk');
+
+ print(" ");
+
+ if (!empty($url_params['all'])) {
+ $all = true;
+ $limit = 0; // display all rows
+ } else {
+ $all = false;
+ $limit = 100; // display only limited number of rows
+ }
+
+ $desc = str_replace(" ", " ", $descriptions[$sort_col]);
+
+ if ($diff_mode) {
+ if ($all) {
+ $title = "Total Diff Report: '
+ .'Sorted by absolute value of regression/improvement in $desc";
+ } else {
+ $title = "Top 100 Regressions /"
+ . "Improvements : "
+ . "Sorted by $desc Diff";
+ }
+ } else {
+ if ($all) {
+ $title = "Sorted by $desc";
+ } else {
+ $title = "Displaying top $limit functions: Sorted by $desc";
+ }
+ }
+ print_flat_data($url_params, $title, $flat_data, $sort, $run1, $run2, $limit);
+}
+
+
+/**
+ * Return attribute names and values to be used by javascript tooltip.
+ */
+function get_tooltip_attributes($type, $metric) {
+ return "type='$type' metric='$metric'";
+}
+
+/**
+ * Print info for a parent or child function in the
+ * parent & children report.
+ *
+ * @author Kannan
+ */
+function pc_info($info, $base_ct, $base_info, $parent) {
+ global $sort_col;
+ global $metrics;
+ global $format_cbk;
+ global $display_calls;
+
+ if ($parent)
+ $type = "Parent";
+ else $type = "Child";
+
+ if ($display_calls) {
+ $mouseoverct = get_tooltip_attributes($type, "ct");
+ /* call count */
+ print_td_num($info["ct"], $format_cbk["ct"], ($sort_col == "ct"), $mouseoverct);
+ print_td_pct($info["ct"], $base_ct, ($sort_col == "ct"), $mouseoverct);
+ }
+
+ /* Inclusive metric values */
+ foreach ($metrics as $metric) {
+ print_td_num($info[$metric], $format_cbk[$metric],
+ ($sort_col == $metric),
+ get_tooltip_attributes($type, $metric));
+ print_td_pct($info[$metric], $base_info[$metric], ($sort_col == $metric),
+ get_tooltip_attributes($type, $metric));
+ }
+}
+
+function print_pc_array($url_params, $results, $base_ct, $base_info, $parent,
+ $run1, $run2) {
+ global $base_path;
+
+ // Construct section title
+ if ($parent) {
+ $title = 'Parent function';
+ }
+ else {
+ $title = 'Child function';
+ }
+ if (count($results) > 1) {
+ $title .= 's';
+ }
+
+ print("");
+ print("" . $title . " ");
+ print(" ");
+
+ $odd_even = 0;
+ foreach ($results as $info) {
+ $href = "$base_path/?" .
+ http_build_query(xhprof_array_set($url_params,
+ 'symbol', $info["fn"]));
+
+ $odd_even = 1 - $odd_even;
+
+ if ($odd_even) {
+ print('');
+ }
+ else {
+ print(' ');
+ }
+
+ print("" . xhprof_render_link($info["fn"], $href));
+ print_source_link($info);
+ print(" ");
+ pc_info($info, $base_ct, $base_info, $parent);
+ print(" ");
+ }
+}
+
+function print_source_link($info) {
+ if (strncmp($info['fn'], 'run_init', 8) && $info['fn'] !== 'main()') {
+ if (defined('XHPROF_SYMBOL_LOOKUP_URL')) {
+ $link = xhprof_render_link(
+ 'source',
+ XHPROF_SYMBOL_LOOKUP_URL . '?symbol='.rawurlencode($info["fn"]));
+ print(' ('.$link.')');
+ }
+ }
+}
+
+
+function print_symbol_summary($symbol_info, $stat, $base) {
+
+ $val = $symbol_info[$stat];
+ $desc = str_replace(" ", " ", stat_description($stat));
+
+ print("$desc: ");
+ print(number_format($val));
+ print(" (" . pct($val, $base) . "% of overall)");
+ if (substr($stat, 0, 4) == "excl") {
+ $func_base = $symbol_info[str_replace("excl_", "", $stat)];
+ print(" (" . pct($val, $func_base) . "% of this function)");
+ }
+ print(" ");
+}
+
+/**
+ * Generates a report for a single function/symbol.
+ *
+ * @author Kannan
+ */
+function symbol_report($url_params,
+ $run_data, $symbol_info, $sort, $rep_symbol,
+ $run1,
+ $symbol_info1 = null,
+ $run2 = 0,
+ $symbol_info2 = null) {
+ global $vwbar;
+ global $vbar;
+ global $totals;
+ global $pc_stats;
+ global $sortable_columns;
+ global $metrics;
+ global $diff_mode;
+ global $descriptions;
+ global $format_cbk;
+ global $sort_col;
+ global $display_calls;
+ global $base_path;
+
+ $possible_metrics = xhprof_get_possible_metrics();
+
+ if ($diff_mode) {
+ $diff_text = "Diff ";
+ $regr_impr = "Regression /Improvement ";
+ } else {
+ $diff_text = "";
+ $regr_impr = "";
+ }
+
+ if ($diff_mode) {
+
+ $base_url_params = xhprof_array_unset(xhprof_array_unset($url_params,
+ 'run1'),
+ 'run2');
+ $href1 = "$base_path?"
+ . http_build_query(xhprof_array_set($base_url_params, 'run', $run1));
+ $href2 = "$base_path?"
+ . http_build_query(xhprof_array_set($base_url_params, 'run', $run2));
+
+ print("$regr_impr summary for $rep_symbol ");
+ print('' . "\n");
+ print('');
+ print("$rep_symbol ");
+ print("Run #$run1 ");
+ print("Run #$run2 ");
+ print("Diff ");
+ print("Diff% ");
+ print(' ');
+ print('');
+
+ if ($display_calls) {
+ print("Number of Function Calls ");
+ print_td_num($symbol_info1["ct"], $format_cbk["ct"]);
+ print_td_num($symbol_info2["ct"], $format_cbk["ct"]);
+ print_td_num($symbol_info2["ct"] - $symbol_info1["ct"],
+ $format_cbk["ct"], true);
+ print_td_pct($symbol_info2["ct"] - $symbol_info1["ct"],
+ $symbol_info1["ct"], true);
+ print(' ');
+ }
+
+
+ foreach ($metrics as $metric) {
+ $m = $metric;
+
+ // Inclusive stat for metric
+ print('');
+ print("" . str_replace(" ", " ", $descriptions[$m]) . " ");
+ print_td_num($symbol_info1[$m], $format_cbk[$m]);
+ print_td_num($symbol_info2[$m], $format_cbk[$m]);
+ print_td_num($symbol_info2[$m] - $symbol_info1[$m], $format_cbk[$m], true);
+ print_td_pct($symbol_info2[$m] - $symbol_info1[$m], $symbol_info1[$m], true);
+ print(' ');
+
+ // AVG (per call) Inclusive stat for metric
+ print('');
+ print("" . str_replace(" ", " ", $descriptions[$m]) . " per call ");
+ $avg_info1 = 'N/A';
+ $avg_info2 = 'N/A';
+ if ($symbol_info1['ct'] > 0) {
+ $avg_info1 = ($symbol_info1[$m] / $symbol_info1['ct']);
+ }
+ if ($symbol_info2['ct'] > 0) {
+ $avg_info2 = ($symbol_info2[$m] / $symbol_info2['ct']);
+ }
+ print_td_num($avg_info1, $format_cbk[$m]);
+ print_td_num($avg_info2, $format_cbk[$m]);
+ print_td_num($avg_info2 - $avg_info1, $format_cbk[$m], true);
+ print_td_pct($avg_info2 - $avg_info1, $avg_info1, true);
+ print(' ');
+
+ // Exclusive stat for metric
+ $m = "excl_" . $metric;
+ print('');
+ print("" . str_replace(" ", " ", $descriptions[$m]) . " ");
+ print_td_num($symbol_info1[$m], $format_cbk[$m]);
+ print_td_num($symbol_info2[$m], $format_cbk[$m]);
+ print_td_num($symbol_info2[$m] - $symbol_info1[$m], $format_cbk[$m], true);
+ print_td_pct($symbol_info2[$m] - $symbol_info1[$m], $symbol_info1[$m], true);
+ print(' ');
+ }
+
+ print('
');
+ }
+
+ print("");
+ print("Parent/Child $regr_impr report for $rep_symbol ");
+
+ $callgraph_href = "$base_path/callgraph.php?"
+ . http_build_query(xhprof_array_set($url_params, 'func', $rep_symbol));
+
+ print(" [View Callgraph $diff_text] ");
+
+ print(" ");
+
+ print('' . "\n");
+ print('');
+
+ foreach ($pc_stats as $stat) {
+ $desc = stat_description($stat);
+ if (array_key_exists($stat, $sortable_columns)) {
+
+ $href = "$base_path/?" .
+ http_build_query(xhprof_array_set($url_params,
+ 'sort', $stat));
+ $header = xhprof_render_link($desc, $href);
+ } else {
+ $header = $desc;
+ }
+
+ if ($stat == "fn")
+ print("$header ");
+ else print("$header ");
+ }
+ print(" ");
+
+ print("");
+ print("Current Function ");
+ print(" ");
+
+ print("");
+ // make this a self-reference to facilitate copy-pasting snippets to e-mails
+ print("$rep_symbol ");
+ print_source_link(array('fn' => $rep_symbol));
+ print(" ");
+
+ if ($display_calls) {
+ // Call Count
+ print_td_num($symbol_info["ct"], $format_cbk["ct"]);
+ print_td_pct($symbol_info["ct"], $totals["ct"]);
+ }
+
+ // Inclusive Metrics for current function
+ foreach ($metrics as $metric) {
+ print_td_num($symbol_info[$metric], $format_cbk[$metric], ($sort_col == $metric));
+ print_td_pct($symbol_info[$metric], $totals[$metric], ($sort_col == $metric));
+ }
+ print(" ");
+
+ print("");
+ print(""
+ ."Exclusive Metrics $diff_text for Current Function ");
+
+ if ($display_calls) {
+ // Call Count
+ print(" ");
+ print(" ");
+ }
+
+ // Exclusive Metrics for current function
+ foreach ($metrics as $metric) {
+ print_td_num($symbol_info["excl_" . $metric], $format_cbk["excl_" . $metric],
+ ($sort_col == $metric),
+ get_tooltip_attributes("Child", $metric));
+ print_td_pct($symbol_info["excl_" . $metric], $symbol_info[$metric],
+ ($sort_col == $metric),
+ get_tooltip_attributes("Child", $metric));
+ }
+ print(" ");
+
+ // list of callers/parent functions
+ $results = array();
+ if ($display_calls) {
+ $base_ct = $symbol_info["ct"];
+ } else {
+ $base_ct = 0;
+ }
+ foreach ($metrics as $metric) {
+ $base_info[$metric] = $symbol_info[$metric];
+ }
+ foreach ($run_data as $parent_child => $info) {
+ list($parent, $child) = xhprof_parse_parent_child($parent_child);
+ if (($child == $rep_symbol) && ($parent)) {
+ $info_tmp = $info;
+ $info_tmp["fn"] = $parent;
+ $results[] = $info_tmp;
+ }
+ }
+ usort($results, 'sort_cbk');
+
+ if (count($results) > 0) {
+ print_pc_array($url_params, $results, $base_ct, $base_info, true,
+ $run1, $run2);
+ }
+
+ // list of callees/child functions
+ $results = array();
+ $base_ct = 0;
+ foreach ($run_data as $parent_child => $info) {
+ list($parent, $child) = xhprof_parse_parent_child($parent_child);
+ if ($parent == $rep_symbol) {
+ $info_tmp = $info;
+ $info_tmp["fn"] = $child;
+ $results[] = $info_tmp;
+ if ($display_calls) {
+ $base_ct += $info["ct"];
+ }
+ }
+ }
+ usort($results, 'sort_cbk');
+
+ if (count($results)) {
+ print_pc_array($url_params, $results, $base_ct, $base_info, false,
+ $run1, $run2);
+ }
+
+ print("
");
+
+ // These will be used for pop-up tips/help.
+ // Related javascript code is in: xhprof_report.js
+ print("\n");
+ print('');
+ print("\n");
+
+}
+
+/**
+ * Generate the profiler report for a single run.
+ *
+ * @author Kannan
+ */
+function profiler_single_run_report ($url_params,
+ $xhprof_data,
+ $run_desc,
+ $rep_symbol,
+ $sort,
+ $run) {
+
+ init_metrics($xhprof_data, $rep_symbol, $sort, false);
+
+ profiler_report($url_params, $rep_symbol, $sort, $run, $run_desc,
+ $xhprof_data);
+}
+
+
+
+/**
+ * Generate the profiler report for diff mode (delta between two runs).
+ *
+ * @author Kannan
+ */
+function profiler_diff_report($url_params,
+ $xhprof_data1,
+ $run1_desc,
+ $xhprof_data2,
+ $run2_desc,
+ $rep_symbol,
+ $sort,
+ $run1,
+ $run2) {
+
+
+ // Initialize what metrics we'll display based on data in Run2
+ init_metrics($xhprof_data2, $rep_symbol, $sort, true);
+
+ profiler_report($url_params,
+ $rep_symbol,
+ $sort,
+ $run1,
+ $run1_desc,
+ $xhprof_data1,
+ $run2,
+ $run2_desc,
+ $xhprof_data2);
+}
+
+
+/**
+ * Generate a XHProf Display View given the various URL parameters
+ * as arguments. The first argument is an object that implements
+ * the iXHProfRuns interface.
+ *
+ * @param object $xhprof_runs_impl An object that implements
+ * the iXHProfRuns interface
+ *.
+ * @param array $url_params Array of non-default URL params.
+ *
+ * @param string $source Category/type of the run. The source in
+ * combination with the run id uniquely
+ * determines a profiler run.
+ *
+ * @param string $run run id, or comma separated sequence of
+ * run ids. The latter is used if an aggregate
+ * report of the runs is desired.
+ *
+ * @param string $wts Comma separate list of integers.
+ * Represents the weighted ratio in
+ * which which a set of runs will be
+ * aggregated. [Used only for aggregate
+ * reports.]
+ *
+ * @param string $symbol Function symbol. If non-empty then the
+ * parent/child view of this function is
+ * displayed. If empty, a flat-profile view
+ * of the functions is displayed.
+ *
+ * @param string $run1 Base run id (for diff reports)
+ *
+ * @param string $run2 New run id (for diff reports)
+ *
+ */
+function displayXHProfReport($xhprof_runs_impl, $url_params, $source,
+ $run, $wts, $symbol, $sort, $run1, $run2) {
+
+ if ($run) { // specific run to display?
+
+ // run may be a single run or a comma separate list of runs
+ // that'll be aggregated. If "wts" (a comma separated list
+ // of integral weights is specified), the runs will be
+ // aggregated in that ratio.
+ //
+ $runs_array = explode(",", $run);
+
+ if (count($runs_array) == 1) {
+ $xhprof_data = $xhprof_runs_impl->get_run($runs_array[0],
+ $source,
+ $description);
+ } else {
+ if (!empty($wts)) {
+ $wts_array = explode(",", $wts);
+ } else {
+ $wts_array = null;
+ }
+ $data = xhprof_aggregate_runs($xhprof_runs_impl,
+ $runs_array, $wts_array, $source, false);
+ $xhprof_data = $data['raw'];
+ $description = $data['description'];
+ }
+
+
+ profiler_single_run_report($url_params,
+ $xhprof_data,
+ $description,
+ $symbol,
+ $sort,
+ $run);
+
+ } else if ($run1 && $run2) { // diff report for two runs
+
+ $xhprof_data1 = $xhprof_runs_impl->get_run($run1, $source, $description1);
+ $xhprof_data2 = $xhprof_runs_impl->get_run($run2, $source, $description2);
+
+ profiler_diff_report($url_params,
+ $xhprof_data1,
+ $description1,
+ $xhprof_data2,
+ $description2,
+ $symbol,
+ $sort,
+ $run1,
+ $run2);
+
+ } else {
+ echo "No XHProf runs specified in the URL.";
+ if (method_exists($xhprof_runs_impl, 'list_runs')) {
+ $xhprof_runs_impl->list_runs();
+ }
+ }
+}
diff --git a/plugins/xhprof/lib/xhprof_lib/utils/callgraph_utils.php b/plugins/xhprof/lib/xhprof_lib/utils/callgraph_utils.php
new file mode 100755
index 000000000..90f4c31a2
--- /dev/null
+++ b/plugins/xhprof/lib/xhprof_lib/utils/callgraph_utils.php
@@ -0,0 +1,486 @@
+ 1,
+ "gif" => 1,
+ "png" => 1,
+ "svg" => 1, // support scalable vector graphic
+ "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 'svg':
+ $mime = 'image/svg+xml'; // content type for scalable vector graphic
+ 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")
+ );
+
+ $cmd = " dot -T".$type;
+
+ $process = proc_open( $cmd, $descriptorspec, $pipes, sys_get_temp_dir(), array( 'PATH' => getenv( 'PATH' ) ) );
+ 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, color=grey, 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;
+}
diff --git a/plugins/xhprof/lib/xhprof_lib/utils/xhprof_lib.php b/plugins/xhprof/lib/xhprof_lib/utils/xhprof_lib.php
new file mode 100755
index 000000000..4a07e900f
--- /dev/null
+++ b/plugins/xhprof/lib/xhprof_lib/utils/xhprof_lib.php
@@ -0,0 +1,945 @@
+ 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();
+ 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:: 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();
+ }
+
+ if ($k === 'run') {
+ $p = implode(',', array_filter(explode(',', $p), 'ctype_xdigit'));
+ }
+
+ // 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);
+}
diff --git a/plugins/xhprof/lib/xhprof_lib/utils/xhprof_runs.php b/plugins/xhprof/lib/xhprof_lib/utils/xhprof_runs.php
new file mode 100755
index 000000000..01e9d069f
--- /dev/null
+++ b/plugins/xhprof/lib/xhprof_lib/utils/xhprof_runs.php
@@ -0,0 +1,164 @@
+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)) {
+
+ $dir = sys_get_temp_dir();
+
+ 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 " Existing runs:\n\n";
+ $files = glob("{$this->dir}/*.{$this->suffix}");
+ usort($files, function($a, $b) {return filemtime($b) - filemtime($a);});
+ foreach ($files as $file) {
+ list($run,$source) = explode('.', basename($file));
+ echo ''
+ . htmlentities(basename($file)) . " "
+ . date("Y-m-d H:i:s", filemtime($file)) . " \n";
+ }
+ echo " \n";
+ }
+ }
+}