mirror of
https://github.com/midoks/mdserver-web.git
synced 2026-10-18 23:45:52 +08:00
xhprof
This commit is contained in:
@@ -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
|
||||
php_value[auto_prepend_file]={$SERVER_PATH}/php/app_start.php
|
||||
@@ -15,6 +15,7 @@
|
||||
<p onclick="pluginLogs('php',$('.plugin_version').attr('version'),'fpm_log');">FPM日志</p>
|
||||
<p onclick="pluginLogs('php',$('.plugin_version').attr('version'),'fpm_slow_log');">慢日志</p>
|
||||
<p onclick="getPhpinfo($('.plugin_version').attr('version'));">PHPIFNO</p>
|
||||
<p onclick="pluginConfig('php',$('.plugin_version').attr('version'),'app_start');">预加载脚本</p>
|
||||
</div>
|
||||
<div class="bt-w-con pd15">
|
||||
<div class="soft-man-con"></div>
|
||||
|
||||
@@ -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':
|
||||
|
||||
Executable
+95
@@ -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
|
||||
@@ -170,7 +170,12 @@
|
||||
"53",
|
||||
"54",
|
||||
"55",
|
||||
"56"
|
||||
"56",
|
||||
"70",
|
||||
"71",
|
||||
"72",
|
||||
"73",
|
||||
"74"
|
||||
],
|
||||
"type": "性能分析",
|
||||
"msg": "不多说,不了解的不要安装!",
|
||||
|
||||
Executable
+27
@@ -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;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1015 B |
Executable
+20
@@ -0,0 +1,20 @@
|
||||
<div class="bt-form">
|
||||
<div class="bt-w-main">
|
||||
<div class="bt-w-menu">
|
||||
<p class="bgw" onclick="pluginService('xhprof');">服务</p>
|
||||
<p onclick="pluginConfig('xhprof');">重写模版</p>
|
||||
<p onclick="homePage()">主页</p>
|
||||
<p onclick="pluginConfig('xhprof','','app_start')">预加载脚本</p>
|
||||
<p onclick="phpVer(56)">PHP版本</p>
|
||||
<p onclick="safeConf();">安全设置</p>
|
||||
</div>
|
||||
<div class="bt-w-con pd15">
|
||||
<div class="soft-man-con"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$.getScript( "/plugins/file?name=xhprof&f=js/xhprof.js");
|
||||
pluginService('xhprof');
|
||||
</script>
|
||||
Executable
+219
@@ -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'
|
||||
Executable
+17
@@ -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"
|
||||
}
|
||||
Executable
+36
@@ -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
|
||||
Executable
+120
@@ -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 = '<button class="btn btn-default btn-sm" onclick="window.open(\'' + rdata.data + '\')">主页</button>';
|
||||
$(".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 = "<div class='ver line'><span class='tname'>PHP版本</span><select id='phpver' class='bt-input-text mr20' name='phpVersion' style='width:110px'>";
|
||||
var optionSelect = '';
|
||||
for (var i = 0; i < rdata.length; i++) {
|
||||
optionSelect = rdata[i].version == version ? 'selected' : '';
|
||||
body += "<option value='" + rdata[i].version + "' " + optionSelect + ">" + rdata[i].name + "</option>"
|
||||
}
|
||||
body += '</select><button class="btn btn-success btn-sm" onclick="phpVerChange(\'phpversion\',\'get\')">保存</button></div>';
|
||||
$(".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 = '<div class="ver line">\
|
||||
<span style="margin-right:10px">访问端口</span>\
|
||||
<input class="bt-input-text mr20" name="Name" id="pmport" value="' + rdata['data'] + '" placeholder="phpmyadmin访问端口" maxlength="5" type="number">\
|
||||
<button class="btn btn-success btn-sm" onclick="xhprofPort()">保存</button>\
|
||||
</div>';
|
||||
$(".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 });
|
||||
});
|
||||
}
|
||||
Executable
+91
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
// Copyright (c) 2009 Facebook
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
/**
|
||||
*
|
||||
* A callgraph generator for XHProf.
|
||||
*
|
||||
* * This file is part of the UI/reporting component,
|
||||
* used for viewing results of XHProf runs from a
|
||||
* browser.
|
||||
*
|
||||
* Modification History:
|
||||
* 02/15/2008 - cjiang - The first version of callgraph visualizer
|
||||
* based on Graphviz's DOT tool.
|
||||
*
|
||||
* @author Changhao Jiang (cjiang@facebook.com)
|
||||
*/
|
||||
|
||||
// by default assume that xhprof_html & xhprof_lib directories
|
||||
// are at the same level.
|
||||
$GLOBALS['XHPROF_LIB_ROOT'] = dirname(__FILE__) . '/../xhprof_lib';
|
||||
|
||||
require_once $GLOBALS['XHPROF_LIB_ROOT'].'/display/xhprof.php';
|
||||
|
||||
ini_set('max_execution_time', 100);
|
||||
|
||||
$params = array(// run id param
|
||||
'run' => array(XHPROF_STRING_PARAM, ''),
|
||||
|
||||
// source/namespace/type of run
|
||||
'source' => array(XHPROF_STRING_PARAM, 'xhprof'),
|
||||
|
||||
// the focus function, if it is set, only directly
|
||||
// parents/children functions of it will be shown.
|
||||
'func' => array(XHPROF_STRING_PARAM, ''),
|
||||
|
||||
// image type, can be 'jpg', 'gif', 'ps', 'png'
|
||||
'type' => array(XHPROF_STRING_PARAM, 'png'),
|
||||
|
||||
// only functions whose exclusive time over the total time
|
||||
// is larger than this threshold will be shown.
|
||||
// default is 0.01.
|
||||
'threshold' => array(XHPROF_FLOAT_PARAM, 0.01),
|
||||
|
||||
// whether to show critical_path
|
||||
'critical' => array(XHPROF_BOOL_PARAM, true),
|
||||
|
||||
// first run in diff mode.
|
||||
'run1' => array(XHPROF_STRING_PARAM, ''),
|
||||
|
||||
// second run in diff mode.
|
||||
'run2' => array(XHPROF_STRING_PARAM, '')
|
||||
);
|
||||
|
||||
// pull values of these params, and create named globals for each param
|
||||
xhprof_param_init($params);
|
||||
|
||||
// if invalid value specified for threshold, then use the default
|
||||
if ($threshold < 0 || $threshold > 1) {
|
||||
$threshold = $params['threshold'][1];
|
||||
}
|
||||
|
||||
// if invalid value specified for type, use the default
|
||||
if (!array_key_exists($type, $xhprof_legal_image_types)) {
|
||||
$type = $params['type'][1]; // default image type.
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
+81
@@ -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;
|
||||
}
|
||||
Executable
+90
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
// Copyright (c) 2009 Facebook
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
//
|
||||
// XHProf: A Hierarchical Profiler for PHP
|
||||
//
|
||||
// XHProf has two components:
|
||||
//
|
||||
// * This module is the UI/reporting component, used
|
||||
// for viewing results of XHProf runs from a browser.
|
||||
//
|
||||
// * Data collection component: This is implemented
|
||||
// as a PHP extension (XHProf).
|
||||
//
|
||||
//
|
||||
//
|
||||
// @author(s) Kannan Muthukkaruppan
|
||||
// Changhao Jiang
|
||||
//
|
||||
|
||||
// by default assume that xhprof_html & xhprof_lib directories
|
||||
// are at the same level.
|
||||
$GLOBALS['XHPROF_LIB_ROOT'] = dirname(__FILE__) . '/../xhprof_lib';
|
||||
|
||||
require_once $GLOBALS['XHPROF_LIB_ROOT'].'/display/xhprof.php';
|
||||
|
||||
// param name, its type, and default value
|
||||
$params = array('run' => array(XHPROF_STRING_PARAM, ''),
|
||||
'wts' => array(XHPROF_STRING_PARAM, ''),
|
||||
'symbol' => array(XHPROF_STRING_PARAM, ''),
|
||||
'sort' => array(XHPROF_STRING_PARAM, 'wt'), // wall time
|
||||
'run1' => array(XHPROF_STRING_PARAM, ''),
|
||||
'run2' => array(XHPROF_STRING_PARAM, ''),
|
||||
'source' => array(XHPROF_STRING_PARAM, 'xhprof'),
|
||||
'all' => array(XHPROF_UINT_PARAM, 0),
|
||||
);
|
||||
|
||||
// pull values of these params, and create named globals for each param
|
||||
xhprof_param_init($params);
|
||||
|
||||
/* reset params to be a array of variable names to values
|
||||
by the end of this page, param should only contain values that need
|
||||
to be preserved for the next page. unset all unwanted keys in $params.
|
||||
*/
|
||||
foreach ($params as $k => $v) {
|
||||
$params[$k] = $$k;
|
||||
|
||||
// unset key from params that are using default values. So URLs aren't
|
||||
// ridiculously long.
|
||||
if ($params[$k] == $v[1]) {
|
||||
unset($params[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
echo "<html>";
|
||||
|
||||
echo "<head><title>XHProf: Hierarchical Profiler Report</title>";
|
||||
xhprof_include_js_css();
|
||||
echo "</head>";
|
||||
|
||||
echo "<body>";
|
||||
|
||||
$vbar = ' class="vbar"';
|
||||
$vwbar = ' class="vwbar"';
|
||||
$vwlbar = ' class="vwlbar"';
|
||||
$vbbar = ' class="vbbar"';
|
||||
$vrbar = ' class="vrbar"';
|
||||
$vgbar = ' class="vgbar"';
|
||||
|
||||
$xhprof_runs_impl = new XHProfRuns_Default();
|
||||
|
||||
displayXHProfReport($xhprof_runs_impl, $params, $source, $run, $wts,
|
||||
$symbol, $sort, $run1, $run2);
|
||||
|
||||
|
||||
echo "</body>";
|
||||
echo "</html>";
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
+3549
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Autocomplete - jQuery plugin 1.0.2
|
||||
*
|
||||
* Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
|
||||
*
|
||||
* Dual licensed under the MIT and GPL licenses:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*
|
||||
* Revision: $Id$
|
||||
*
|
||||
*/
|
||||
|
||||
.ac_results {
|
||||
padding: 0px;
|
||||
border: 1px solid black;
|
||||
background-color: white;
|
||||
overflow: hidden;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
.ac_results ul {
|
||||
width: 100%;
|
||||
list-style-position: outside;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ac_results li {
|
||||
margin: 0px;
|
||||
padding: 2px 5px;
|
||||
cursor: default;
|
||||
display: block;
|
||||
/*
|
||||
if width will be 100% horizontal scrollbar will apear
|
||||
when scroll mode will be used
|
||||
*/
|
||||
/*width: 100%;*/
|
||||
font: menu;
|
||||
font-size: 12px;
|
||||
/*
|
||||
it is very important, if line-height not setted or setted
|
||||
in relative units scroll will be broken in firefox
|
||||
*/
|
||||
line-height: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ac_loading {
|
||||
background: white url('indicator.gif') right center no-repeat;
|
||||
}
|
||||
|
||||
.ac_odd {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.ac_over {
|
||||
background-color: #0A246A;
|
||||
color: white;
|
||||
}
|
||||
@@ -0,0 +1,759 @@
|
||||
/*
|
||||
* Autocomplete - jQuery plugin 1.0.2
|
||||
*
|
||||
* Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
|
||||
*
|
||||
* Dual licensed under the MIT and GPL licenses:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*
|
||||
* Revision: $Id: jquery.autocomplete.js,v 1.1.1.1 2009-03-17 18:35:18 kannan Exp $
|
||||
*
|
||||
*/
|
||||
|
||||
;(function($) {
|
||||
|
||||
$.fn.extend({
|
||||
autocomplete: function(urlOrData, options) {
|
||||
var isUrl = typeof urlOrData == "string";
|
||||
options = $.extend({}, $.Autocompleter.defaults, {
|
||||
url: isUrl ? urlOrData : null,
|
||||
data: isUrl ? null : urlOrData,
|
||||
delay: isUrl ? $.Autocompleter.defaults.delay : 10,
|
||||
max: options && !options.scroll ? 10 : 150
|
||||
}, options);
|
||||
|
||||
// if highlight is set to false, replace it with a do-nothing function
|
||||
options.highlight = options.highlight || function(value) { return value; };
|
||||
|
||||
// if the formatMatch option is not specified, then use formatItem for backwards compatibility
|
||||
options.formatMatch = options.formatMatch || options.formatItem;
|
||||
|
||||
return this.each(function() {
|
||||
new $.Autocompleter(this, options);
|
||||
});
|
||||
},
|
||||
result: function(handler) {
|
||||
return this.bind("result", handler);
|
||||
},
|
||||
search: function(handler) {
|
||||
return this.trigger("search", [handler]);
|
||||
},
|
||||
flushCache: function() {
|
||||
return this.trigger("flushCache");
|
||||
},
|
||||
setOptions: function(options){
|
||||
return this.trigger("setOptions", [options]);
|
||||
},
|
||||
unautocomplete: function() {
|
||||
return this.trigger("unautocomplete");
|
||||
}
|
||||
});
|
||||
|
||||
$.Autocompleter = function(input, options) {
|
||||
|
||||
var KEY = {
|
||||
UP: 38,
|
||||
DOWN: 40,
|
||||
DEL: 46,
|
||||
TAB: 9,
|
||||
RETURN: 13,
|
||||
ESC: 27,
|
||||
COMMA: 188,
|
||||
PAGEUP: 33,
|
||||
PAGEDOWN: 34,
|
||||
BACKSPACE: 8
|
||||
};
|
||||
|
||||
// Create $ object for input element
|
||||
var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
|
||||
|
||||
var timeout;
|
||||
var previousValue = "";
|
||||
var cache = $.Autocompleter.Cache(options);
|
||||
var hasFocus = 0;
|
||||
var lastKeyPressCode;
|
||||
var config = {
|
||||
mouseDownOnSelect: false
|
||||
};
|
||||
var select = $.Autocompleter.Select(options, input, selectCurrent, config);
|
||||
|
||||
var blockSubmit;
|
||||
|
||||
// prevent form submit in opera when selecting with return key
|
||||
$.browser.opera && $(input.form).bind("submit.autocomplete", function() {
|
||||
if (blockSubmit) {
|
||||
blockSubmit = false;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
|
||||
$input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
|
||||
// track last key pressed
|
||||
lastKeyPressCode = event.keyCode;
|
||||
switch(event.keyCode) {
|
||||
|
||||
case KEY.UP:
|
||||
event.preventDefault();
|
||||
if ( select.visible() ) {
|
||||
select.prev();
|
||||
} else {
|
||||
onChange(0, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.DOWN:
|
||||
event.preventDefault();
|
||||
if ( select.visible() ) {
|
||||
select.next();
|
||||
} else {
|
||||
onChange(0, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.PAGEUP:
|
||||
event.preventDefault();
|
||||
if ( select.visible() ) {
|
||||
select.pageUp();
|
||||
} else {
|
||||
onChange(0, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.PAGEDOWN:
|
||||
event.preventDefault();
|
||||
if ( select.visible() ) {
|
||||
select.pageDown();
|
||||
} else {
|
||||
onChange(0, true);
|
||||
}
|
||||
break;
|
||||
|
||||
// matches also semicolon
|
||||
case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
|
||||
case KEY.TAB:
|
||||
case KEY.RETURN:
|
||||
if( selectCurrent() ) {
|
||||
// stop default to prevent a form submit, Opera needs special handling
|
||||
event.preventDefault();
|
||||
blockSubmit = true;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.ESC:
|
||||
select.hide();
|
||||
break;
|
||||
|
||||
default:
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(onChange, options.delay);
|
||||
break;
|
||||
}
|
||||
}).focus(function(){
|
||||
// track whether the field has focus, we shouldn't process any
|
||||
// results if the field no longer has focus
|
||||
hasFocus++;
|
||||
}).blur(function() {
|
||||
hasFocus = 0;
|
||||
if (!config.mouseDownOnSelect) {
|
||||
hideResults();
|
||||
}
|
||||
}).click(function() {
|
||||
// show select when clicking in a focused field
|
||||
if ( hasFocus++ > 1 && !select.visible() ) {
|
||||
onChange(0, true);
|
||||
}
|
||||
}).bind("search", function() {
|
||||
// TODO why not just specifying both arguments?
|
||||
var fn = (arguments.length > 1) ? arguments[1] : null;
|
||||
function findValueCallback(q, data) {
|
||||
var result;
|
||||
if( data && data.length ) {
|
||||
for (var i=0; i < data.length; i++) {
|
||||
if( data[i].result.toLowerCase() == q.toLowerCase() ) {
|
||||
result = data[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( typeof fn == "function" ) fn(result);
|
||||
else $input.trigger("result", result && [result.data, result.value]);
|
||||
}
|
||||
$.each(trimWords($input.val()), function(i, value) {
|
||||
request(value, findValueCallback, findValueCallback);
|
||||
});
|
||||
}).bind("flushCache", function() {
|
||||
cache.flush();
|
||||
}).bind("setOptions", function() {
|
||||
$.extend(options, arguments[1]);
|
||||
// if we've updated the data, repopulate
|
||||
if ( "data" in arguments[1] )
|
||||
cache.populate();
|
||||
}).bind("unautocomplete", function() {
|
||||
select.unbind();
|
||||
$input.unbind();
|
||||
$(input.form).unbind(".autocomplete");
|
||||
});
|
||||
|
||||
|
||||
function selectCurrent() {
|
||||
var selected = select.selected();
|
||||
if( !selected )
|
||||
return false;
|
||||
|
||||
var v = selected.result;
|
||||
previousValue = v;
|
||||
|
||||
if ( options.multiple ) {
|
||||
var words = trimWords($input.val());
|
||||
if ( words.length > 1 ) {
|
||||
v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v;
|
||||
}
|
||||
v += options.multipleSeparator;
|
||||
}
|
||||
|
||||
$input.val(v);
|
||||
hideResultsNow();
|
||||
$input.trigger("result", [selected.data, selected.value]);
|
||||
return true;
|
||||
}
|
||||
|
||||
function onChange(crap, skipPrevCheck) {
|
||||
if( lastKeyPressCode == KEY.DEL ) {
|
||||
select.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
var currentValue = $input.val();
|
||||
|
||||
if ( !skipPrevCheck && currentValue == previousValue )
|
||||
return;
|
||||
|
||||
previousValue = currentValue;
|
||||
|
||||
currentValue = lastWord(currentValue);
|
||||
if ( currentValue.length >= options.minChars) {
|
||||
$input.addClass(options.loadingClass);
|
||||
if (!options.matchCase)
|
||||
currentValue = currentValue.toLowerCase();
|
||||
request(currentValue, receiveData, hideResultsNow);
|
||||
} else {
|
||||
stopLoading();
|
||||
select.hide();
|
||||
}
|
||||
};
|
||||
|
||||
function trimWords(value) {
|
||||
if ( !value ) {
|
||||
return [""];
|
||||
}
|
||||
var words = value.split( options.multipleSeparator );
|
||||
var result = [];
|
||||
$.each(words, function(i, value) {
|
||||
if ( $.trim(value) )
|
||||
result[i] = $.trim(value);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function lastWord(value) {
|
||||
if ( !options.multiple )
|
||||
return value;
|
||||
var words = trimWords(value);
|
||||
return words[words.length - 1];
|
||||
}
|
||||
|
||||
// fills in the input box w/the first match (assumed to be the best match)
|
||||
// q: the term entered
|
||||
// sValue: the first matching result
|
||||
function autoFill(q, sValue){
|
||||
// autofill in the complete box w/the first match as long as the user hasn't entered in more data
|
||||
// if the last user key pressed was backspace, don't autofill
|
||||
if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
|
||||
// fill in the value (keep the case the user has typed)
|
||||
$input.val($input.val() + sValue.substring(lastWord(previousValue).length));
|
||||
// select the portion of the value not typed by the user (so the next character will erase)
|
||||
$.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length);
|
||||
}
|
||||
};
|
||||
|
||||
function hideResults() {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(hideResultsNow, 200);
|
||||
};
|
||||
|
||||
function hideResultsNow() {
|
||||
var wasVisible = select.visible();
|
||||
select.hide();
|
||||
clearTimeout(timeout);
|
||||
stopLoading();
|
||||
if (options.mustMatch) {
|
||||
// call search and run callback
|
||||
$input.search(
|
||||
function (result){
|
||||
// if no value found, clear the input box
|
||||
if( !result ) {
|
||||
if (options.multiple) {
|
||||
var words = trimWords($input.val()).slice(0, -1);
|
||||
$input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
|
||||
}
|
||||
else
|
||||
$input.val( "" );
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
if (wasVisible)
|
||||
// position cursor at end of input field
|
||||
$.Autocompleter.Selection(input, input.value.length, input.value.length);
|
||||
};
|
||||
|
||||
function receiveData(q, data) {
|
||||
if ( data && data.length && hasFocus ) {
|
||||
stopLoading();
|
||||
select.display(data, q);
|
||||
autoFill(q, data[0].value);
|
||||
select.show();
|
||||
} else {
|
||||
hideResultsNow();
|
||||
}
|
||||
};
|
||||
|
||||
function request(term, success, failure) {
|
||||
if (!options.matchCase)
|
||||
term = term.toLowerCase();
|
||||
var data = cache.load(term);
|
||||
// recieve the cached data
|
||||
if (data && data.length) {
|
||||
success(term, data);
|
||||
// if an AJAX url has been supplied, try loading the data now
|
||||
} else if( (typeof options.url == "string") && (options.url.length > 0) ){
|
||||
|
||||
var extraParams = {
|
||||
timestamp: +new Date()
|
||||
};
|
||||
$.each(options.extraParams, function(key, param) {
|
||||
extraParams[key] = typeof param == "function" ? param() : param;
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
// try to leverage ajaxQueue plugin to abort previous requests
|
||||
mode: "abort",
|
||||
// limit abortion to this input
|
||||
port: "autocomplete" + input.name,
|
||||
dataType: options.dataType,
|
||||
url: options.url,
|
||||
data: $.extend({
|
||||
q: lastWord(term),
|
||||
limit: options.max
|
||||
}, extraParams),
|
||||
success: function(data) {
|
||||
var parsed = options.parse && options.parse(data) || parse(data);
|
||||
cache.add(term, parsed);
|
||||
success(term, parsed);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
|
||||
select.emptyList();
|
||||
failure(term);
|
||||
}
|
||||
};
|
||||
|
||||
function parse(data) {
|
||||
var parsed = [];
|
||||
var rows = data.split("\n");
|
||||
for (var i=0; i < rows.length; i++) {
|
||||
var row = $.trim(rows[i]);
|
||||
if (row) {
|
||||
row = row.split("|");
|
||||
parsed[parsed.length] = {
|
||||
data: row,
|
||||
value: row[0],
|
||||
result: options.formatResult && options.formatResult(row, row[0]) || row[0]
|
||||
};
|
||||
}
|
||||
}
|
||||
return parsed;
|
||||
};
|
||||
|
||||
function stopLoading() {
|
||||
$input.removeClass(options.loadingClass);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
$.Autocompleter.defaults = {
|
||||
inputClass: "ac_input",
|
||||
resultsClass: "ac_results",
|
||||
loadingClass: "ac_loading",
|
||||
minChars: 1,
|
||||
delay: 400,
|
||||
matchCase: false,
|
||||
matchSubset: true,
|
||||
matchContains: false,
|
||||
cacheLength: 10,
|
||||
max: 100,
|
||||
mustMatch: false,
|
||||
extraParams: {},
|
||||
selectFirst: true,
|
||||
formatItem: function(row) { return row[0]; },
|
||||
formatMatch: null,
|
||||
autoFill: false,
|
||||
width: 0,
|
||||
multiple: false,
|
||||
multipleSeparator: ", ",
|
||||
highlight: function(value, term) {
|
||||
return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
|
||||
},
|
||||
scroll: true,
|
||||
scrollHeight: 180
|
||||
};
|
||||
|
||||
$.Autocompleter.Cache = function(options) {
|
||||
|
||||
var data = {};
|
||||
var length = 0;
|
||||
|
||||
function matchSubset(s, sub) {
|
||||
if (!options.matchCase)
|
||||
s = s.toLowerCase();
|
||||
var i = s.indexOf(sub);
|
||||
if (i == -1) return false;
|
||||
return i == 0 || options.matchContains;
|
||||
};
|
||||
|
||||
function add(q, value) {
|
||||
if (length > options.cacheLength){
|
||||
flush();
|
||||
}
|
||||
if (!data[q]){
|
||||
length++;
|
||||
}
|
||||
data[q] = value;
|
||||
}
|
||||
|
||||
function populate(){
|
||||
if( !options.data ) return false;
|
||||
// track the matches
|
||||
var stMatchSets = {},
|
||||
nullData = 0;
|
||||
|
||||
// no url was specified, we need to adjust the cache length to make sure it fits the local data store
|
||||
if( !options.url ) options.cacheLength = 1;
|
||||
|
||||
// track all options for minChars = 0
|
||||
stMatchSets[""] = [];
|
||||
|
||||
// loop through the array and create a lookup structure
|
||||
for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
|
||||
var rawValue = options.data[i];
|
||||
// if rawValue is a string, make an array otherwise just reference the array
|
||||
rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
|
||||
|
||||
var value = options.formatMatch(rawValue, i+1, options.data.length);
|
||||
if ( value === false )
|
||||
continue;
|
||||
|
||||
var firstChar = value.charAt(0).toLowerCase();
|
||||
// if no lookup array for this character exists, look it up now
|
||||
if( !stMatchSets[firstChar] )
|
||||
stMatchSets[firstChar] = [];
|
||||
|
||||
// if the match is a string
|
||||
var row = {
|
||||
value: value,
|
||||
data: rawValue,
|
||||
result: options.formatResult && options.formatResult(rawValue) || value
|
||||
};
|
||||
|
||||
// push the current match into the set list
|
||||
stMatchSets[firstChar].push(row);
|
||||
|
||||
// keep track of minChars zero items
|
||||
if ( nullData++ < options.max ) {
|
||||
stMatchSets[""].push(row);
|
||||
}
|
||||
};
|
||||
|
||||
// add the data items to the cache
|
||||
$.each(stMatchSets, function(i, value) {
|
||||
// increase the cache size
|
||||
options.cacheLength++;
|
||||
// add to the cache
|
||||
add(i, value);
|
||||
});
|
||||
}
|
||||
|
||||
// populate any existing data
|
||||
setTimeout(populate, 25);
|
||||
|
||||
function flush(){
|
||||
data = {};
|
||||
length = 0;
|
||||
}
|
||||
|
||||
return {
|
||||
flush: flush,
|
||||
add: add,
|
||||
populate: populate,
|
||||
load: function(q) {
|
||||
if (!options.cacheLength || !length)
|
||||
return null;
|
||||
/*
|
||||
* if dealing w/local data and matchContains than we must make sure
|
||||
* to loop through all the data collections looking for matches
|
||||
*/
|
||||
if( !options.url && options.matchContains ){
|
||||
// track all matches
|
||||
var csub = [];
|
||||
// loop through all the data grids for matches
|
||||
for( var k in data ){
|
||||
// don't search through the stMatchSets[""] (minChars: 0) cache
|
||||
// this prevents duplicates
|
||||
if( k.length > 0 ){
|
||||
var c = data[k];
|
||||
$.each(c, function(i, x) {
|
||||
// if we've got a match, add it to the array
|
||||
if (matchSubset(x.value, q)) {
|
||||
csub.push(x);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return csub;
|
||||
} else
|
||||
// if the exact item exists, use it
|
||||
if (data[q]){
|
||||
return data[q];
|
||||
} else
|
||||
if (options.matchSubset) {
|
||||
for (var i = q.length - 1; i >= options.minChars; i--) {
|
||||
var c = data[q.substr(0, i)];
|
||||
if (c) {
|
||||
var csub = [];
|
||||
$.each(c, function(i, x) {
|
||||
if (matchSubset(x.value, q)) {
|
||||
csub[csub.length] = x;
|
||||
}
|
||||
});
|
||||
return csub;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
$.Autocompleter.Select = function (options, input, select, config) {
|
||||
var CLASSES = {
|
||||
ACTIVE: "ac_over"
|
||||
};
|
||||
|
||||
var listItems,
|
||||
active = -1,
|
||||
data,
|
||||
term = "",
|
||||
needsInit = true,
|
||||
element,
|
||||
list;
|
||||
|
||||
// Create results
|
||||
function init() {
|
||||
if (!needsInit)
|
||||
return;
|
||||
element = $("<div/>")
|
||||
.hide()
|
||||
.addClass(options.resultsClass)
|
||||
.css("position", "absolute")
|
||||
.appendTo(document.body);
|
||||
|
||||
list = $("<ul/>").appendTo(element).mouseover( function(event) {
|
||||
if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
|
||||
active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
|
||||
$(target(event)).addClass(CLASSES.ACTIVE);
|
||||
}
|
||||
}).click(function(event) {
|
||||
$(target(event)).addClass(CLASSES.ACTIVE);
|
||||
select();
|
||||
// TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
|
||||
input.focus();
|
||||
return false;
|
||||
}).mousedown(function() {
|
||||
config.mouseDownOnSelect = true;
|
||||
}).mouseup(function() {
|
||||
config.mouseDownOnSelect = false;
|
||||
});
|
||||
|
||||
if( options.width > 0 )
|
||||
element.css("width", options.width);
|
||||
|
||||
needsInit = false;
|
||||
}
|
||||
|
||||
function target(event) {
|
||||
var element = event.target;
|
||||
while(element && element.tagName != "LI")
|
||||
element = element.parentNode;
|
||||
// more fun with IE, sometimes event.target is empty, just ignore it then
|
||||
if(!element)
|
||||
return [];
|
||||
return element;
|
||||
}
|
||||
|
||||
function moveSelect(step) {
|
||||
listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
|
||||
movePosition(step);
|
||||
var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
|
||||
if(options.scroll) {
|
||||
var offset = 0;
|
||||
listItems.slice(0, active).each(function() {
|
||||
offset += this.offsetHeight;
|
||||
});
|
||||
if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
|
||||
list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
|
||||
} else if(offset < list.scrollTop()) {
|
||||
list.scrollTop(offset);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function movePosition(step) {
|
||||
active += step;
|
||||
if (active < 0) {
|
||||
active = listItems.size() - 1;
|
||||
} else if (active >= listItems.size()) {
|
||||
active = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function limitNumberOfItems(available) {
|
||||
return options.max && options.max < available
|
||||
? options.max
|
||||
: available;
|
||||
}
|
||||
|
||||
function fillList() {
|
||||
list.empty();
|
||||
var max = limitNumberOfItems(data.length);
|
||||
for (var i=0; i < max; i++) {
|
||||
if (!data[i])
|
||||
continue;
|
||||
var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
|
||||
if ( formatted === false )
|
||||
continue;
|
||||
var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
|
||||
$.data(li, "ac_data", data[i]);
|
||||
}
|
||||
listItems = list.find("li");
|
||||
if ( options.selectFirst ) {
|
||||
listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
|
||||
active = 0;
|
||||
}
|
||||
// apply bgiframe if available
|
||||
if ( $.fn.bgiframe )
|
||||
list.bgiframe();
|
||||
}
|
||||
|
||||
return {
|
||||
display: function(d, q) {
|
||||
init();
|
||||
data = d;
|
||||
term = q;
|
||||
fillList();
|
||||
},
|
||||
next: function() {
|
||||
moveSelect(1);
|
||||
},
|
||||
prev: function() {
|
||||
moveSelect(-1);
|
||||
},
|
||||
pageUp: function() {
|
||||
if (active != 0 && active - 8 < 0) {
|
||||
moveSelect( -active );
|
||||
} else {
|
||||
moveSelect(-8);
|
||||
}
|
||||
},
|
||||
pageDown: function() {
|
||||
if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
|
||||
moveSelect( listItems.size() - 1 - active );
|
||||
} else {
|
||||
moveSelect(8);
|
||||
}
|
||||
},
|
||||
hide: function() {
|
||||
element && element.hide();
|
||||
listItems && listItems.removeClass(CLASSES.ACTIVE);
|
||||
active = -1;
|
||||
},
|
||||
visible : function() {
|
||||
return element && element.is(":visible");
|
||||
},
|
||||
current: function() {
|
||||
return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
|
||||
},
|
||||
show: function() {
|
||||
var offset = $(input).offset();
|
||||
element.css({
|
||||
width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
|
||||
top: offset.top + input.offsetHeight,
|
||||
left: offset.left
|
||||
}).show();
|
||||
if(options.scroll) {
|
||||
list.scrollTop(0);
|
||||
list.css({
|
||||
maxHeight: options.scrollHeight,
|
||||
overflow: 'auto'
|
||||
});
|
||||
|
||||
if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
|
||||
var listHeight = 0;
|
||||
listItems.each(function() {
|
||||
listHeight += this.offsetHeight;
|
||||
});
|
||||
var scrollbarsVisible = listHeight > options.scrollHeight;
|
||||
list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
|
||||
if (!scrollbarsVisible) {
|
||||
// IE doesn't recalculate width when scrollbar disappears
|
||||
listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
selected: function() {
|
||||
var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
|
||||
return selected && selected.length && $.data(selected[0], "ac_data");
|
||||
},
|
||||
emptyList: function (){
|
||||
list && list.empty();
|
||||
},
|
||||
unbind: function() {
|
||||
element && element.remove();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
$.Autocompleter.Selection = function(field, start, end) {
|
||||
if( field.createTextRange ){
|
||||
var selRange = field.createTextRange();
|
||||
selRange.collapse(true);
|
||||
selRange.moveStart("character", start);
|
||||
selRange.moveEnd("character", end);
|
||||
selRange.select();
|
||||
} else if( field.setSelectionRange ){
|
||||
field.setSelectionRange(start, end);
|
||||
} else {
|
||||
if( field.selectionStart ){
|
||||
field.selectionStart = start;
|
||||
field.selectionEnd = end;
|
||||
}
|
||||
}
|
||||
field.focus();
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* jQuery Tooltip plugin 1.3
|
||||
*
|
||||
* http://bassistance.de/jquery-plugins/jquery-plugin-tooltip/
|
||||
* http://docs.jquery.com/Plugins/Tooltip
|
||||
*
|
||||
* Copyright (c) 2006 - 2008 Jörn Zaefferer
|
||||
*
|
||||
* $Id$
|
||||
*
|
||||
* Dual licensed under the MIT and GPL licenses:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
#tooltip {
|
||||
position: absolute;
|
||||
z-index: 3000;
|
||||
border: 1px solid #111;
|
||||
background-color: lightyellow;
|
||||
padding: 5px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
#tooltip h3, #tooltip div { margin: 0; }
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
/*
|
||||
* jQuery Tooltip plugin 1.3
|
||||
*
|
||||
* http://bassistance.de/jquery-plugins/jquery-plugin-tooltip/
|
||||
* http://docs.jquery.com/Plugins/Tooltip
|
||||
*
|
||||
* Copyright (c) 2006 - 2008 Jörn Zaefferer
|
||||
*
|
||||
* $Id: jquery.tooltip.js,v 1.1.1.1 2009-03-17 18:35:18 kannan Exp $
|
||||
*
|
||||
* Dual licensed under the MIT and GPL licenses:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
;(function($) {
|
||||
|
||||
// the tooltip element
|
||||
var helper = {},
|
||||
// the current tooltipped element
|
||||
current,
|
||||
// the title of the current element, used for restoring
|
||||
title,
|
||||
// timeout id for delayed tooltips
|
||||
tID,
|
||||
// IE 5.5 or 6
|
||||
IE = $.browser.msie && /MSIE\s(5\.5|6\.)/.test(navigator.userAgent),
|
||||
// flag for mouse tracking
|
||||
track = false;
|
||||
|
||||
$.tooltip = {
|
||||
blocked: false,
|
||||
defaults: {
|
||||
delay: 200,
|
||||
fade: false,
|
||||
showURL: true,
|
||||
extraClass: "",
|
||||
top: 15,
|
||||
left: 15,
|
||||
id: "tooltip"
|
||||
},
|
||||
block: function() {
|
||||
$.tooltip.blocked = !$.tooltip.blocked;
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.extend({
|
||||
tooltip: function(settings) {
|
||||
settings = $.extend({}, $.tooltip.defaults, settings);
|
||||
createHelper(settings);
|
||||
return this.each(function() {
|
||||
$.data(this, "tooltip", settings);
|
||||
this.tOpacity = helper.parent.css("opacity");
|
||||
// copy tooltip into its own expando and remove the title
|
||||
this.tooltipText = this.title;
|
||||
$(this).removeAttr("title");
|
||||
// also remove alt attribute to prevent default tooltip in IE
|
||||
this.alt = "";
|
||||
})
|
||||
.mouseover(save)
|
||||
.mouseout(hide)
|
||||
.click(hide);
|
||||
},
|
||||
fixPNG: IE ? function() {
|
||||
return this.each(function () {
|
||||
var image = $(this).css('backgroundImage');
|
||||
if (image.match(/^url\(["']?(.*\.png)["']?\)$/i)) {
|
||||
image = RegExp.$1;
|
||||
$(this).css({
|
||||
'backgroundImage': 'none',
|
||||
'filter': "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='" + image + "')"
|
||||
}).each(function () {
|
||||
var position = $(this).css('position');
|
||||
if (position != 'absolute' && position != 'relative')
|
||||
$(this).css('position', 'relative');
|
||||
});
|
||||
}
|
||||
});
|
||||
} : function() { return this; },
|
||||
unfixPNG: IE ? function() {
|
||||
return this.each(function () {
|
||||
$(this).css({'filter': '', backgroundImage: ''});
|
||||
});
|
||||
} : function() { return this; },
|
||||
hideWhenEmpty: function() {
|
||||
return this.each(function() {
|
||||
$(this)[ $(this).html() ? "show" : "hide" ]();
|
||||
});
|
||||
},
|
||||
url: function() {
|
||||
return this.attr('href') || this.attr('src');
|
||||
}
|
||||
});
|
||||
|
||||
function createHelper(settings) {
|
||||
// there can be only one tooltip helper
|
||||
if( helper.parent )
|
||||
return;
|
||||
// create the helper, h3 for title, div for url
|
||||
helper.parent = $('<div id="' + settings.id + '"><h3></h3><div class="body"></div><div class="url"></div></div>')
|
||||
// add to document
|
||||
.appendTo(document.body)
|
||||
// hide it at first
|
||||
.hide();
|
||||
|
||||
// apply bgiframe if available
|
||||
if ( $.fn.bgiframe )
|
||||
helper.parent.bgiframe();
|
||||
|
||||
// save references to title and url elements
|
||||
helper.title = $('h3', helper.parent);
|
||||
helper.body = $('div.body', helper.parent);
|
||||
helper.url = $('div.url', helper.parent);
|
||||
}
|
||||
|
||||
function settings(element) {
|
||||
return $.data(element, "tooltip");
|
||||
}
|
||||
|
||||
// main event handler to start showing tooltips
|
||||
function handle(event) {
|
||||
// show helper, either with timeout or on instant
|
||||
if( settings(this).delay )
|
||||
tID = setTimeout(show, settings(this).delay);
|
||||
else
|
||||
show();
|
||||
|
||||
// if selected, update the helper position when the mouse moves
|
||||
track = !!settings(this).track;
|
||||
$(document.body).bind('mousemove', update);
|
||||
|
||||
// update at least once
|
||||
update(event);
|
||||
}
|
||||
|
||||
// save elements title before the tooltip is displayed
|
||||
function save() {
|
||||
// if this is the current source, or it has no title (occurs with click event), stop
|
||||
if ( $.tooltip.blocked || this == current || (!this.tooltipText && !settings(this).bodyHandler) )
|
||||
return;
|
||||
|
||||
// save current
|
||||
current = this;
|
||||
title = this.tooltipText;
|
||||
|
||||
if ( settings(this).bodyHandler ) {
|
||||
helper.title.hide();
|
||||
var bodyContent = settings(this).bodyHandler.call(this);
|
||||
if (bodyContent.nodeType || bodyContent.jquery) {
|
||||
helper.body.empty().append(bodyContent)
|
||||
} else {
|
||||
helper.body.html( bodyContent );
|
||||
}
|
||||
helper.body.show();
|
||||
} else if ( settings(this).showBody ) {
|
||||
var parts = title.split(settings(this).showBody);
|
||||
helper.title.html(parts.shift()).show();
|
||||
helper.body.empty();
|
||||
for(var i = 0, part; (part = parts[i]); i++) {
|
||||
if(i > 0)
|
||||
helper.body.append("<br/>");
|
||||
helper.body.append(part);
|
||||
}
|
||||
helper.body.hideWhenEmpty();
|
||||
} else {
|
||||
helper.title.html(title).show();
|
||||
helper.body.hide();
|
||||
}
|
||||
|
||||
// if element has href or src, add and show it, otherwise hide it
|
||||
if( settings(this).showURL && $(this).url() )
|
||||
helper.url.html( $(this).url().replace('http://', '') ).show();
|
||||
else
|
||||
helper.url.hide();
|
||||
|
||||
// add an optional class for this tip
|
||||
helper.parent.addClass(settings(this).extraClass);
|
||||
|
||||
// fix PNG background for IE
|
||||
if (settings(this).fixPNG )
|
||||
helper.parent.fixPNG();
|
||||
|
||||
handle.apply(this, arguments);
|
||||
}
|
||||
|
||||
// delete timeout and show helper
|
||||
function show() {
|
||||
tID = null;
|
||||
if ((!IE || !$.fn.bgiframe) && settings(current).fade) {
|
||||
if (helper.parent.is(":animated"))
|
||||
helper.parent.stop().show().fadeTo(settings(current).fade, current.tOpacity);
|
||||
else
|
||||
helper.parent.is(':visible') ? helper.parent.fadeTo(settings(current).fade, current.tOpacity) : helper.parent.fadeIn(settings(current).fade);
|
||||
} else {
|
||||
helper.parent.show();
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* callback for mousemove
|
||||
* updates the helper position
|
||||
* removes itself when no current element
|
||||
*/
|
||||
function update(event) {
|
||||
if($.tooltip.blocked)
|
||||
return;
|
||||
|
||||
if (event && event.target.tagName == "OPTION") {
|
||||
return;
|
||||
}
|
||||
|
||||
// stop updating when tracking is disabled and the tooltip is visible
|
||||
if ( !track && helper.parent.is(":visible")) {
|
||||
$(document.body).unbind('mousemove', update)
|
||||
}
|
||||
|
||||
// if no current element is available, remove this listener
|
||||
if( current == null ) {
|
||||
$(document.body).unbind('mousemove', update);
|
||||
return;
|
||||
}
|
||||
|
||||
// remove position helper classes
|
||||
helper.parent.removeClass("viewport-right").removeClass("viewport-bottom");
|
||||
|
||||
var left = helper.parent[0].offsetLeft;
|
||||
var top = helper.parent[0].offsetTop;
|
||||
if (event) {
|
||||
// position the helper 15 pixel to bottom right, starting from mouse position
|
||||
left = event.pageX + settings(current).left;
|
||||
top = event.pageY + settings(current).top;
|
||||
var right='auto';
|
||||
if (settings(current).positionLeft) {
|
||||
right = $(window).width() - left;
|
||||
left = 'auto';
|
||||
}
|
||||
helper.parent.css({
|
||||
left: left,
|
||||
right: right,
|
||||
top: top
|
||||
});
|
||||
}
|
||||
|
||||
var v = viewport(),
|
||||
h = helper.parent[0];
|
||||
// check horizontal position
|
||||
if (v.x + v.cx < h.offsetLeft + h.offsetWidth) {
|
||||
left -= h.offsetWidth + 20 + settings(current).left;
|
||||
helper.parent.css({left: left + 'px'}).addClass("viewport-right");
|
||||
}
|
||||
// check vertical position
|
||||
if (v.y + v.cy < h.offsetTop + h.offsetHeight) {
|
||||
top -= h.offsetHeight + 20 + settings(current).top;
|
||||
helper.parent.css({top: top + 'px'}).addClass("viewport-bottom");
|
||||
}
|
||||
}
|
||||
|
||||
function viewport() {
|
||||
return {
|
||||
x: $(window).scrollLeft(),
|
||||
y: $(window).scrollTop(),
|
||||
cx: $(window).width(),
|
||||
cy: $(window).height()
|
||||
};
|
||||
}
|
||||
|
||||
// hide helper and restore added classes and the title
|
||||
function hide(event) {
|
||||
if($.tooltip.blocked)
|
||||
return;
|
||||
// clear timeout if possible
|
||||
if(tID)
|
||||
clearTimeout(tID);
|
||||
// no more current element
|
||||
current = null;
|
||||
|
||||
var tsettings = settings(this);
|
||||
function complete() {
|
||||
helper.parent.removeClass( tsettings.extraClass ).hide().css("opacity", "");
|
||||
}
|
||||
if ((!IE || !$.fn.bgiframe) && tsettings.fade) {
|
||||
if (helper.parent.is(':animated'))
|
||||
helper.parent.stop().fadeTo(tsettings.fade, 0, complete);
|
||||
else
|
||||
helper.parent.stop().fadeOut(tsettings.fade, complete);
|
||||
} else
|
||||
complete();
|
||||
|
||||
if( settings(this).fixPNG )
|
||||
helper.parent.unfixPNG();
|
||||
}
|
||||
|
||||
})(jQuery);
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
/* Copyright (c) 2009 Facebook
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helper javascript functions for XHProf report tooltips.
|
||||
*
|
||||
* @author Kannan Muthukkaruppan
|
||||
*/
|
||||
|
||||
// Take a string which is actually a number in comma separated format
|
||||
// and return a string representing the absolute value of the number.
|
||||
function stringAbs(x) {
|
||||
return x.replace("-", "");
|
||||
}
|
||||
|
||||
// Takes a number in comma-separated string format, and
|
||||
// returns a boolean to indicate if the number is negative
|
||||
// or not.
|
||||
function isNegative(x) {
|
||||
|
||||
return (x.indexOf("-") == 0);
|
||||
|
||||
}
|
||||
|
||||
function addCommas(nStr)
|
||||
{
|
||||
nStr += '';
|
||||
x = nStr.split('.');
|
||||
x1 = x[0];
|
||||
x2 = x.length > 1 ? '.' + x[1] : '';
|
||||
var rgx = /(\d+)(\d{3})/;
|
||||
while (rgx.test(x1)) {
|
||||
x1 = x1.replace(rgx, '$1' + ',' + '$2');
|
||||
}
|
||||
return x1 + x2;
|
||||
}
|
||||
|
||||
// Mouseover tips for parent rows in parent/child report..
|
||||
function ParentRowToolTip(cell, metric)
|
||||
{
|
||||
var metric_val;
|
||||
var parent_metric_val;
|
||||
var parent_metric_pct_val;
|
||||
var col_index;
|
||||
var diff_text;
|
||||
|
||||
row = cell.parentNode;
|
||||
tds = row.getElementsByTagName("td");
|
||||
|
||||
parent_func = tds[0].innerHTML; // name
|
||||
|
||||
if (diff_mode) {
|
||||
diff_text = " diff ";
|
||||
} else {
|
||||
diff_text = "";
|
||||
}
|
||||
|
||||
s = '<center>';
|
||||
|
||||
if (metric == "ct") {
|
||||
parent_ct = tds[1].innerHTML; // calls
|
||||
parent_ct_pct = tds[2].innerHTML;
|
||||
|
||||
func_ct = addCommas(func_ct);
|
||||
|
||||
if (diff_mode) {
|
||||
s += 'There are ' + stringAbs(parent_ct) +
|
||||
(isNegative(parent_ct) ? ' fewer ' : ' more ') +
|
||||
' calls to ' + func_name + ' from ' + parent_func + '<br>';
|
||||
|
||||
text = " of diff in calls ";
|
||||
} else {
|
||||
text = " of calls ";
|
||||
}
|
||||
|
||||
s += parent_ct_pct + text + '(' + parent_ct + '/' + func_ct + ') to '
|
||||
+ func_name + ' are from ' + parent_func + '<br>';
|
||||
} else {
|
||||
|
||||
// help for other metrics such as wall time, user cpu time, memory usage
|
||||
col_index = metrics_col[metric];
|
||||
parent_metric_val = tds[col_index].innerHTML;
|
||||
parent_metric_pct_val = tds[col_index+1].innerHTML;
|
||||
|
||||
metric_val = addCommas(func_metrics[metric]);
|
||||
|
||||
s += parent_metric_pct_val + '(' + parent_metric_val + '/' + metric_val
|
||||
+ ') of ' + metrics_desc[metric] +
|
||||
(diff_mode ? ((isNegative(parent_metric_val) ?
|
||||
" decrease" : " increase")) : "") +
|
||||
' in ' + func_name + ' is due to calls from ' + parent_func + '<br>';
|
||||
}
|
||||
|
||||
s += '</center>';
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
// Mouseover tips for child rows in parent/child report..
|
||||
function ChildRowToolTip(cell, metric)
|
||||
{
|
||||
var metric_val;
|
||||
var child_metric_val;
|
||||
var child_metric_pct_val;
|
||||
var col_index;
|
||||
var diff_text;
|
||||
|
||||
row = cell.parentNode;
|
||||
tds = row.getElementsByTagName("td");
|
||||
|
||||
child_func = tds[0].innerHTML; // name
|
||||
|
||||
if (diff_mode) {
|
||||
diff_text = " diff ";
|
||||
} else {
|
||||
diff_text = "";
|
||||
}
|
||||
|
||||
s = '<center>';
|
||||
|
||||
if (metric == "ct") {
|
||||
|
||||
child_ct = tds[1].innerHTML; // calls
|
||||
child_ct_pct = tds[2].innerHTML;
|
||||
|
||||
s += func_name + ' called ' + child_func + ' ' + stringAbs(child_ct) +
|
||||
(diff_mode ? (isNegative(child_ct) ? " fewer" : " more") : "" )
|
||||
+ ' times.<br>';
|
||||
s += 'This accounts for ' + child_ct_pct + ' (' + child_ct
|
||||
+ '/' + total_child_ct
|
||||
+ ') of function calls made by ' + func_name + '.';
|
||||
|
||||
} else {
|
||||
|
||||
// help for other metrics such as wall time, user cpu time, memory usage
|
||||
col_index = metrics_col[metric];
|
||||
child_metric_val = tds[col_index].innerHTML;
|
||||
child_metric_pct_val = tds[col_index+1].innerHTML;
|
||||
|
||||
metric_val = addCommas(func_metrics[metric]);
|
||||
|
||||
if (child_func.indexOf("Exclusive Metrics") != -1) {
|
||||
s += 'The exclusive ' + metrics_desc[metric] + diff_text
|
||||
+ ' for ' + func_name
|
||||
+ ' is ' + child_metric_val + " <br>";
|
||||
|
||||
s += "which is " + child_metric_pct_val + " of the inclusive "
|
||||
+ metrics_desc[metric]
|
||||
+ diff_text + " for " + func_name + " (" + metric_val + ").";
|
||||
|
||||
} else {
|
||||
|
||||
s += child_func + ' when called from ' + func_name
|
||||
+ ' takes ' + stringAbs(child_metric_val)
|
||||
+ (diff_mode ? (isNegative(child_metric_val) ? " less" : " more") : "")
|
||||
+ " of " + metrics_desc[metric] + " <br>";
|
||||
|
||||
s += "which is " + child_metric_pct_val + " of the inclusive "
|
||||
+ metrics_desc[metric]
|
||||
+ diff_text + " for " + func_name + " (" + metric_val + ").";
|
||||
}
|
||||
}
|
||||
|
||||
s += '</center>';
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$('td[@metric]').tooltip(
|
||||
{ bodyHandler: function() {
|
||||
var type = $(this).attr('type');
|
||||
var metric = $(this).attr('metric');
|
||||
if (type == 'Parent') {
|
||||
return ParentRowToolTip(this, metric);
|
||||
} else if (type == 'Child') {
|
||||
return ChildRowToolTip(this, metric);
|
||||
}
|
||||
},
|
||||
showURL : false
|
||||
});
|
||||
var cur_params = {} ;
|
||||
$.each(location.search.replace('?','').split('&'), function(i, x) {
|
||||
var y = x.split('='); cur_params[y[0]] = y[1];
|
||||
});
|
||||
$('input.function_typeahead')
|
||||
.autocomplete('typeahead.php', { extraParams : cur_params })
|
||||
.result(function(event, item) {
|
||||
cur_params['symbol'] = item;
|
||||
location.search = '?' + jQuery.param(cur_params);
|
||||
});
|
||||
});
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
// Copyright (c) 2009 Facebook
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
/**
|
||||
* AJAX endpoint for XHProf function name typeahead.
|
||||
*
|
||||
* @author(s) Kannan Muthukkaruppan
|
||||
* Changhao Jiang
|
||||
*/
|
||||
|
||||
// by default assume that xhprof_html & xhprof_lib directories
|
||||
// are at the same level.
|
||||
$GLOBALS['XHPROF_LIB_ROOT'] = dirname(__FILE__) . '/../xhprof_lib';
|
||||
|
||||
require_once $GLOBALS['XHPROF_LIB_ROOT'].'/display/xhprof.php';
|
||||
|
||||
$xhprof_runs_impl = new XHProfRuns_Default();
|
||||
|
||||
require_once $GLOBALS['XHPROF_LIB_ROOT'].'/display/typeahead_common.php';
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
// Copyright (c) 2009 Facebook
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
/**
|
||||
* AJAX endpoint for XHProf function name typeahead is implemented
|
||||
* as a thin wrapper around this file. The wrapper must set up
|
||||
* the global $xhprof_runs_impl to correspond to an object that
|
||||
* implements the iXHProfRuns interface.
|
||||
*
|
||||
* @author(s) Kannan Muthukkaruppan
|
||||
* Changhao Jiang
|
||||
*/
|
||||
|
||||
|
||||
require_once $GLOBALS['XHPROF_LIB_ROOT'].'/utils/xhprof_lib.php';
|
||||
|
||||
// param name, its type, and default value
|
||||
$params = array('q' => array(XHPROF_STRING_PARAM, ''),
|
||||
'run' => array(XHPROF_STRING_PARAM, ''),
|
||||
'run1' => array(XHPROF_STRING_PARAM, ''),
|
||||
'run2' => array(XHPROF_STRING_PARAM, ''),
|
||||
'source' => array(XHPROF_STRING_PARAM, 'xhprof'),
|
||||
);
|
||||
|
||||
// pull values of these params, and create named globals for each param
|
||||
xhprof_param_init($params);
|
||||
|
||||
if (!empty($run)) {
|
||||
|
||||
// single run mode
|
||||
$raw_data = $xhprof_runs_impl->get_run($run, $source, $desc_unused);
|
||||
$functions = xhprof_get_matching_functions($q, $raw_data);
|
||||
|
||||
} else if (!empty($run1) && !empty($run2)) {
|
||||
|
||||
// diff mode
|
||||
$raw_data = $xhprof_runs_impl->get_run($run1, $source, $desc_unused);
|
||||
$functions1 = xhprof_get_matching_functions($q, $raw_data);
|
||||
|
||||
$raw_data = $xhprof_runs_impl->get_run($run2, $source, $desc_unused);
|
||||
$functions2 = xhprof_get_matching_functions($q, $raw_data);
|
||||
|
||||
|
||||
$functions = array_unique(array_merge($functions1, $functions2));
|
||||
asort($functions);
|
||||
} else {
|
||||
xhprof_error("no valid runs specified to typeahead endpoint");
|
||||
$functions = array();
|
||||
}
|
||||
|
||||
// If exact match is present move it to the front
|
||||
if (in_array($q, $functions)) {
|
||||
$old_functions = $functions;
|
||||
|
||||
$functions = array($q);
|
||||
foreach ($old_functions as $f) {
|
||||
// exact match case has already been added to the front
|
||||
if ($f != $q) {
|
||||
$functions[] = $f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($functions as $f) {
|
||||
echo $f."\n";
|
||||
}
|
||||
+1439
File diff suppressed because it is too large
Load Diff
+486
@@ -0,0 +1,486 @@
|
||||
<?php
|
||||
// Copyright (c) 2009 Facebook
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
/*
|
||||
* This file contains callgraph image generation related XHProf utility
|
||||
* functions
|
||||
*
|
||||
*/
|
||||
|
||||
// Supported ouput format
|
||||
$xhprof_legal_image_types = array(
|
||||
"jpg" => 1,
|
||||
"gif" => 1,
|
||||
"png" => 1,
|
||||
"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;
|
||||
}
|
||||
+945
@@ -0,0 +1,945 @@
|
||||
<?php
|
||||
// Copyright (c) 2009 Facebook
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
//
|
||||
// This file contains various XHProf library (utility) functions.
|
||||
// Do not add any display specific code here.
|
||||
//
|
||||
|
||||
function xhprof_error($message) {
|
||||
error_log($message);
|
||||
}
|
||||
|
||||
/*
|
||||
* The list of possible metrics collected as part of XHProf that
|
||||
* require inclusive/exclusive handling while reporting.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_get_possible_metrics() {
|
||||
static $possible_metrics =
|
||||
array("wt" => array("Wall", "microsecs", "walltime"),
|
||||
"ut" => array("User", "microsecs", "user cpu time"),
|
||||
"st" => array("Sys", "microsecs", "system cpu time"),
|
||||
"cpu" => array("Cpu", "microsecs", "cpu time"),
|
||||
"mu" => array("MUse", "bytes", "memory usage"),
|
||||
"pmu" => array("PMUse", "bytes", "peak memory usage"),
|
||||
"samples" => array("Samples", "samples", "cpu time"));
|
||||
return $possible_metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the metrics we'll display based on the information
|
||||
* in the raw data.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function init_metrics($xhprof_data, $rep_symbol, $sort, $diff_report = false) {
|
||||
global $stats;
|
||||
global $pc_stats;
|
||||
global $metrics;
|
||||
global $diff_mode;
|
||||
global $sortable_columns;
|
||||
global $sort_col;
|
||||
global $display_calls;
|
||||
|
||||
$diff_mode = $diff_report;
|
||||
|
||||
if (!empty($sort)) {
|
||||
if (array_key_exists($sort, $sortable_columns)) {
|
||||
$sort_col = $sort;
|
||||
} else {
|
||||
print("Invalid Sort Key $sort specified in URL");
|
||||
}
|
||||
}
|
||||
|
||||
// For C++ profiler runs, walltime attribute isn't present.
|
||||
// In that case, use "samples" as the default sort column.
|
||||
if (!isset($xhprof_data["main()"]["wt"])) {
|
||||
|
||||
if ($sort_col == "wt") {
|
||||
$sort_col = "samples";
|
||||
}
|
||||
|
||||
// C++ profiler data doesn't have call counts.
|
||||
// ideally we should check to see if "ct" metric
|
||||
// is present for "main()". But currently "ct"
|
||||
// metric is artificially set to 1. So, relying
|
||||
// on absence of "wt" metric instead.
|
||||
$display_calls = false;
|
||||
} else {
|
||||
$display_calls = true;
|
||||
}
|
||||
|
||||
// parent/child report doesn't support exclusive times yet.
|
||||
// So, change sort hyperlinks to closest fit.
|
||||
if (!empty($rep_symbol)) {
|
||||
$sort_col = str_replace("excl_", "", $sort_col);
|
||||
}
|
||||
|
||||
if ($display_calls) {
|
||||
$stats = array("fn", "ct", "Calls%");
|
||||
} else {
|
||||
$stats = array("fn");
|
||||
}
|
||||
|
||||
$pc_stats = $stats;
|
||||
|
||||
$possible_metrics = xhprof_get_possible_metrics();
|
||||
foreach ($possible_metrics as $metric => $desc) {
|
||||
if (isset($xhprof_data["main()"][$metric])) {
|
||||
$metrics[] = $metric;
|
||||
// flat (top-level reports): we can compute
|
||||
// exclusive metrics reports as well.
|
||||
$stats[] = $metric;
|
||||
$stats[] = "I" . $desc[0] . "%";
|
||||
$stats[] = "excl_" . $metric;
|
||||
$stats[] = "E" . $desc[0] . "%";
|
||||
|
||||
// parent/child report for a function: we can
|
||||
// only breakdown inclusive times correctly.
|
||||
$pc_stats[] = $metric;
|
||||
$pc_stats[] = "I" . $desc[0] . "%";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the list of metrics present in $xhprof_data as an array.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_get_metrics($xhprof_data) {
|
||||
|
||||
// get list of valid metrics
|
||||
$possible_metrics = xhprof_get_possible_metrics();
|
||||
|
||||
// return those that are present in the raw data.
|
||||
// We'll just look at the root of the subtree for this.
|
||||
$metrics = array();
|
||||
foreach ($possible_metrics as $metric => $desc) {
|
||||
if (isset($xhprof_data["main()"][$metric])) {
|
||||
$metrics[] = $metric;
|
||||
}
|
||||
}
|
||||
|
||||
return $metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a parent/child function name encoded as
|
||||
* "a==>b" and returns array("a", "b").
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_parse_parent_child($parent_child) {
|
||||
$ret = explode("==>", $parent_child);
|
||||
|
||||
// Return if both parent and child are set
|
||||
if (isset($ret[1])) {
|
||||
return $ret;
|
||||
}
|
||||
|
||||
return array(null, $ret[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given parent & child function name, composes the key
|
||||
* in the format present in the raw data.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_build_parent_child_key($parent, $child) {
|
||||
if ($parent) {
|
||||
return $parent . "==>" . $child;
|
||||
} else {
|
||||
return $child;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if XHProf raw data appears to be valid and not corrupted.
|
||||
*
|
||||
* @param int $run_id Run id of run to be pruned.
|
||||
* [Used only for reporting errors.]
|
||||
* @param array $raw_data XHProf raw data to be pruned
|
||||
* & validated.
|
||||
*
|
||||
* @return bool true on success, false on failure
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_valid_run($run_id, $raw_data) {
|
||||
|
||||
$main_info = $raw_data["main()"];
|
||||
if (empty($main_info)) {
|
||||
xhprof_error("XHProf: main() missing in raw data for Run ID: $run_id");
|
||||
return false;
|
||||
}
|
||||
|
||||
// raw data should contain either wall time or samples information...
|
||||
if (isset($main_info["wt"])) {
|
||||
$metric = "wt";
|
||||
} else if (isset($main_info["samples"])) {
|
||||
$metric = "samples";
|
||||
} else {
|
||||
xhprof_error("XHProf: Wall Time information missing from Run ID: $run_id");
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($raw_data as $info) {
|
||||
$val = $info[$metric];
|
||||
|
||||
// basic sanity checks...
|
||||
if ($val < 0) {
|
||||
xhprof_error("XHProf: $metric should not be negative: Run ID $run_id"
|
||||
. serialize($info));
|
||||
return false;
|
||||
}
|
||||
if ($val > (86400000000)) {
|
||||
xhprof_error("XHProf: $metric > 1 day found in Run ID: $run_id "
|
||||
. serialize($info));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a trimmed version of the XHProf raw data. Note that the raw
|
||||
* data contains one entry for each unique parent/child function
|
||||
* combination.The trimmed version of raw data will only contain
|
||||
* entries where either the parent or child function is in the list
|
||||
* of $functions_to_keep.
|
||||
*
|
||||
* Note: Function main() is also always kept so that overall totals
|
||||
* can still be obtained from the trimmed version.
|
||||
*
|
||||
* @param array XHProf raw data
|
||||
* @param array array of function names
|
||||
*
|
||||
* @return array Trimmed XHProf Report
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_trim_run($raw_data, $functions_to_keep) {
|
||||
|
||||
// convert list of functions to a hash with function as the key
|
||||
$function_map = array_fill_keys($functions_to_keep, 1);
|
||||
|
||||
// always keep main() as well so that overall totals can still
|
||||
// be computed if need be.
|
||||
$function_map['main()'] = 1;
|
||||
|
||||
$new_raw_data = array();
|
||||
foreach ($raw_data as $parent_child => $info) {
|
||||
list($parent, $child) = xhprof_parse_parent_child($parent_child);
|
||||
|
||||
if (isset($function_map[$parent]) || isset($function_map[$child])) {
|
||||
$new_raw_data[$parent_child] = $info;
|
||||
}
|
||||
}
|
||||
|
||||
return $new_raw_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes raw XHProf data that was aggregated over "$num_runs" number
|
||||
* of runs averages/nomalizes the data. Essentially the various metrics
|
||||
* collected are divided by $num_runs.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_normalize_metrics($raw_data, $num_runs) {
|
||||
|
||||
if (empty($raw_data) || ($num_runs == 0)) {
|
||||
return $raw_data;
|
||||
}
|
||||
|
||||
$raw_data_total = array();
|
||||
|
||||
if (isset($raw_data["==>main()"]) && isset($raw_data["main()"])) {
|
||||
xhprof_error("XHProf Error: both ==>main() and main() set in raw data...");
|
||||
}
|
||||
|
||||
foreach ($raw_data as $parent_child => $info) {
|
||||
foreach ($info as $metric => $value) {
|
||||
$raw_data_total[$parent_child][$metric] = ($value / $num_runs);
|
||||
}
|
||||
}
|
||||
|
||||
return $raw_data_total;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get raw data corresponding to specified array of runs
|
||||
* aggregated by certain weightage.
|
||||
*
|
||||
* Suppose you have run:5 corresponding to page1.php,
|
||||
* run:6 corresponding to page2.php,
|
||||
* and run:7 corresponding to page3.php
|
||||
*
|
||||
* and you want to accumulate these runs in a 2:4:1 ratio. You
|
||||
* can do so by calling:
|
||||
*
|
||||
* xhprof_aggregate_runs(array(5, 6, 7), array(2, 4, 1));
|
||||
*
|
||||
* The above will return raw data for the runs aggregated
|
||||
* in 2:4:1 ratio.
|
||||
*
|
||||
* @param object $xhprof_runs_impl An object that implements
|
||||
* the iXHProfRuns interface
|
||||
* @param array $runs run ids of the XHProf runs..
|
||||
* @param array $wts integral (ideally) weights for $runs
|
||||
* @param string $source source to fetch raw data for run from
|
||||
* @param bool $use_script_name If true, a fake edge from main() to
|
||||
* to __script::<scriptname> is introduced
|
||||
* in the raw data so that after aggregations
|
||||
* the script name is still preserved.
|
||||
*
|
||||
* @return array Return aggregated raw data
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_aggregate_runs($xhprof_runs_impl, $runs,
|
||||
$wts, $source="phprof",
|
||||
$use_script_name=false) {
|
||||
|
||||
$raw_data_total = null;
|
||||
$raw_data = null;
|
||||
$metrics = array();
|
||||
|
||||
$run_count = count($runs);
|
||||
$wts_count = count($wts);
|
||||
|
||||
if (($run_count == 0) ||
|
||||
(($wts_count > 0) && ($run_count != $wts_count))) {
|
||||
return array('description' => 'Invalid input..',
|
||||
'raw' => null);
|
||||
}
|
||||
|
||||
$bad_runs = array();
|
||||
foreach ($runs as $idx => $run_id) {
|
||||
|
||||
$raw_data = $xhprof_runs_impl->get_run($run_id, $source, $description);
|
||||
|
||||
// use the first run to derive what metrics to aggregate on.
|
||||
if ($idx == 0) {
|
||||
foreach ($raw_data["main()"] as $metric => $val) {
|
||||
if ($metric != "pmu") {
|
||||
// for now, just to keep data size small, skip "peak" memory usage
|
||||
// data while aggregating.
|
||||
// The "regular" memory usage data will still be tracked.
|
||||
if (isset($val)) {
|
||||
$metrics[] = $metric;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!xhprof_valid_run($run_id, $raw_data)) {
|
||||
$bad_runs[] = $run_id;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($use_script_name) {
|
||||
$page = $description;
|
||||
|
||||
// create a fake function '__script::$page', and have and edge from
|
||||
// main() to '__script::$page'. We will also need edges to transfer
|
||||
// all edges originating from main() to now originate from
|
||||
// '__script::$page' to all function called from main().
|
||||
//
|
||||
// We also weight main() ever so slightly higher so that
|
||||
// it shows up above the new entry in reports sorted by
|
||||
// inclusive metrics or call counts.
|
||||
if ($page) {
|
||||
foreach ($raw_data["main()"] as $metric => $val) {
|
||||
$fake_edge[$metric] = $val;
|
||||
$new_main[$metric] = $val + 0.00001;
|
||||
}
|
||||
$raw_data["main()"] = $new_main;
|
||||
$raw_data[xhprof_build_parent_child_key("main()",
|
||||
"__script::$page")]
|
||||
= $fake_edge;
|
||||
} else {
|
||||
$use_script_name = false;
|
||||
}
|
||||
}
|
||||
|
||||
// if no weights specified, use 1 as the default weightage..
|
||||
$wt = ($wts_count == 0) ? 1 : $wts[$idx];
|
||||
|
||||
// aggregate $raw_data into $raw_data_total with appropriate weight ($wt)
|
||||
foreach ($raw_data as $parent_child => $info) {
|
||||
if ($use_script_name) {
|
||||
// if this is an old edge originating from main(), it now
|
||||
// needs to be from '__script::$page'
|
||||
if (substr($parent_child, 0, 9) == "main()==>") {
|
||||
$child = substr($parent_child, 9);
|
||||
// ignore the newly added edge from main()
|
||||
if (substr($child, 0, 10) != "__script::") {
|
||||
$parent_child = xhprof_build_parent_child_key("__script::$page",
|
||||
$child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($raw_data_total[$parent_child])) {
|
||||
foreach ($metrics as $metric) {
|
||||
$raw_data_total[$parent_child][$metric] = ($wt * $info[$metric]);
|
||||
}
|
||||
} else {
|
||||
foreach ($metrics as $metric) {
|
||||
$raw_data_total[$parent_child][$metric] += ($wt * $info[$metric]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$runs_string = implode(",", $runs);
|
||||
|
||||
if (isset($wts)) {
|
||||
$wts_string = "in the ratio (" . implode(":", $wts) . ")";
|
||||
$normalization_count = array_sum($wts);
|
||||
} else {
|
||||
$wts_string = "";
|
||||
$normalization_count = $run_count;
|
||||
}
|
||||
|
||||
$run_count = $run_count - count($bad_runs);
|
||||
|
||||
$data['description'] = "Aggregated Report for $run_count runs: ".
|
||||
"$runs_string $wts_string\n";
|
||||
$data['raw'] = xhprof_normalize_metrics($raw_data_total,
|
||||
$normalization_count);
|
||||
$data['bad_runs'] = $bad_runs;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Analyze hierarchical raw data, and compute per-function (flat)
|
||||
* inclusive and exclusive metrics.
|
||||
*
|
||||
* Also, store overall totals in the 2nd argument.
|
||||
*
|
||||
* @param array $raw_data XHProf format raw profiler data.
|
||||
* @param array &$overall_totals OUT argument for returning
|
||||
* overall totals for various
|
||||
* metrics.
|
||||
* @return array Returns a map from function name to its
|
||||
* call count and inclusive & exclusive metrics
|
||||
* (such as wall time, etc.).
|
||||
*
|
||||
* @author Kannan Muthukkaruppan
|
||||
*/
|
||||
function xhprof_compute_flat_info($raw_data, &$overall_totals) {
|
||||
|
||||
global $display_calls;
|
||||
|
||||
$metrics = xhprof_get_metrics($raw_data);
|
||||
|
||||
$overall_totals = array("ct" => 0,
|
||||
"wt" => 0,
|
||||
"ut" => 0,
|
||||
"st" => 0,
|
||||
"cpu" => 0,
|
||||
"mu" => 0,
|
||||
"pmu" => 0,
|
||||
"samples" => 0
|
||||
);
|
||||
|
||||
// compute inclusive times for each function
|
||||
$symbol_tab = xhprof_compute_inclusive_times($raw_data);
|
||||
|
||||
/* total metric value is the metric value for "main()" */
|
||||
foreach ($metrics as $metric) {
|
||||
$overall_totals[$metric] = $symbol_tab["main()"][$metric];
|
||||
}
|
||||
|
||||
/*
|
||||
* initialize exclusive (self) metric value to inclusive metric value
|
||||
* to start with.
|
||||
* In the same pass, also add up the total number of function calls.
|
||||
*/
|
||||
foreach ($symbol_tab as $symbol => $info) {
|
||||
foreach ($metrics as $metric) {
|
||||
$symbol_tab[$symbol]["excl_" . $metric] = $symbol_tab[$symbol][$metric];
|
||||
}
|
||||
if ($display_calls) {
|
||||
/* keep track of total number of calls */
|
||||
$overall_totals["ct"] += $info["ct"];
|
||||
}
|
||||
}
|
||||
|
||||
/* adjust exclusive times by deducting inclusive time of children */
|
||||
foreach ($raw_data as $parent_child => $info) {
|
||||
list($parent, $child) = xhprof_parse_parent_child($parent_child);
|
||||
|
||||
if ($parent) {
|
||||
foreach ($metrics as $metric) {
|
||||
// make sure the parent exists hasn't been pruned.
|
||||
if (isset($symbol_tab[$parent])) {
|
||||
$symbol_tab[$parent]["excl_" . $metric] -= $info[$metric];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $symbol_tab;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hierarchical diff:
|
||||
* Compute and return difference of two call graphs: Run2 - Run1.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_compute_diff($xhprof_data1, $xhprof_data2) {
|
||||
global $display_calls;
|
||||
|
||||
// use the second run to decide what metrics we will do the diff on
|
||||
$metrics = xhprof_get_metrics($xhprof_data2);
|
||||
|
||||
$xhprof_delta = $xhprof_data2;
|
||||
|
||||
foreach ($xhprof_data1 as $parent_child => $info) {
|
||||
|
||||
if (!isset($xhprof_delta[$parent_child])) {
|
||||
|
||||
// this pc combination was not present in run1;
|
||||
// initialize all values to zero.
|
||||
if ($display_calls) {
|
||||
$xhprof_delta[$parent_child] = array("ct" => 0);
|
||||
} else {
|
||||
$xhprof_delta[$parent_child] = array();
|
||||
}
|
||||
foreach ($metrics as $metric) {
|
||||
$xhprof_delta[$parent_child][$metric] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ($display_calls) {
|
||||
$xhprof_delta[$parent_child]["ct"] -= $info["ct"];
|
||||
}
|
||||
|
||||
foreach ($metrics as $metric) {
|
||||
$xhprof_delta[$parent_child][$metric] -= $info[$metric];
|
||||
}
|
||||
}
|
||||
|
||||
return $xhprof_delta;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compute inclusive metrics for function. This code was factored out
|
||||
* of xhprof_compute_flat_info().
|
||||
*
|
||||
* The raw data contains inclusive metrics of a function for each
|
||||
* unique parent function it is called from. The total inclusive metrics
|
||||
* for a function is therefore the sum of inclusive metrics for the
|
||||
* function across all parents.
|
||||
*
|
||||
* @return array Returns a map of function name to total (across all parents)
|
||||
* inclusive metrics for the function.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_compute_inclusive_times($raw_data) {
|
||||
global $display_calls;
|
||||
|
||||
$metrics = xhprof_get_metrics($raw_data);
|
||||
|
||||
$symbol_tab = array();
|
||||
|
||||
/*
|
||||
* First compute inclusive time for each function and total
|
||||
* call count for each function across all parents the
|
||||
* function is called from.
|
||||
*/
|
||||
foreach ($raw_data as $parent_child => $info) {
|
||||
|
||||
list($parent, $child) = xhprof_parse_parent_child($parent_child);
|
||||
|
||||
if ($parent == $child) {
|
||||
/*
|
||||
* XHProf PHP extension should never trigger this situation any more.
|
||||
* Recursion is handled in the XHProf PHP extension by giving nested
|
||||
* calls a unique recursion-depth appended name (for example, foo@1).
|
||||
*/
|
||||
xhprof_error("Error in Raw Data: parent & child are both: $parent");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isset($symbol_tab[$child])) {
|
||||
|
||||
if ($display_calls) {
|
||||
$symbol_tab[$child] = array("ct" => $info["ct"]);
|
||||
} else {
|
||||
$symbol_tab[$child] = array();
|
||||
}
|
||||
foreach ($metrics as $metric) {
|
||||
$symbol_tab[$child][$metric] = $info[$metric];
|
||||
}
|
||||
} else {
|
||||
if ($display_calls) {
|
||||
/* increment call count for this child */
|
||||
$symbol_tab[$child]["ct"] += $info["ct"];
|
||||
}
|
||||
|
||||
/* update inclusive times/metric for this child */
|
||||
foreach ($metrics as $metric) {
|
||||
$symbol_tab[$child][$metric] += $info[$metric];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $symbol_tab;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Prunes XHProf raw data:
|
||||
*
|
||||
* Any node whose inclusive walltime accounts for less than $prune_percent
|
||||
* of total walltime is pruned. [It is possible that a child function isn't
|
||||
* pruned, but one or more of its parents get pruned. In such cases, when
|
||||
* viewing the child function's hierarchical information, the cost due to
|
||||
* the pruned parent(s) will be attributed to a special function/symbol
|
||||
* "__pruned__()".]
|
||||
*
|
||||
* @param array $raw_data XHProf raw data to be pruned & validated.
|
||||
* @param double $prune_percent Any edges that account for less than
|
||||
* $prune_percent of time will be pruned
|
||||
* from the raw data.
|
||||
*
|
||||
* @return array Returns the pruned raw data.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_prune_run($raw_data, $prune_percent) {
|
||||
|
||||
$main_info = $raw_data["main()"];
|
||||
if (empty($main_info)) {
|
||||
xhprof_error("XHProf: main() missing in raw data");
|
||||
return false;
|
||||
}
|
||||
|
||||
// raw data should contain either wall time or samples information...
|
||||
if (isset($main_info["wt"])) {
|
||||
$prune_metric = "wt";
|
||||
} else if (isset($main_info["samples"])) {
|
||||
$prune_metric = "samples";
|
||||
} else {
|
||||
xhprof_error("XHProf: for main() we must have either wt "
|
||||
."or samples attribute set");
|
||||
return false;
|
||||
}
|
||||
|
||||
// determine the metrics present in the raw data..
|
||||
$metrics = array();
|
||||
foreach ($main_info as $metric => $val) {
|
||||
if (isset($val)) {
|
||||
$metrics[] = $metric;
|
||||
}
|
||||
}
|
||||
|
||||
$prune_threshold = (($main_info[$prune_metric] * $prune_percent) / 100.0);
|
||||
|
||||
init_metrics($raw_data, null, null, false);
|
||||
$flat_info = xhprof_compute_inclusive_times($raw_data);
|
||||
|
||||
foreach ($raw_data as $parent_child => $info) {
|
||||
|
||||
list($parent, $child) = xhprof_parse_parent_child($parent_child);
|
||||
|
||||
// is this child's overall total from all parents less than threshold?
|
||||
if ($flat_info[$child][$prune_metric] < $prune_threshold) {
|
||||
unset($raw_data[$parent_child]); // prune the edge
|
||||
} else if ($parent &&
|
||||
($parent != "__pruned__()") &&
|
||||
($flat_info[$parent][$prune_metric] < $prune_threshold)) {
|
||||
|
||||
// Parent's overall inclusive metric is less than a threshold.
|
||||
// All edges to the parent node will get nuked, and this child will
|
||||
// be a dangling child.
|
||||
// So instead change its parent to be a special function __pruned__().
|
||||
$pruned_edge = xhprof_build_parent_child_key("__pruned__()", $child);
|
||||
|
||||
if (isset($raw_data[$pruned_edge])) {
|
||||
foreach ($metrics as $metric) {
|
||||
$raw_data[$pruned_edge][$metric]+=$raw_data[$parent_child][$metric];
|
||||
}
|
||||
} else {
|
||||
$raw_data[$pruned_edge] = $raw_data[$parent_child];
|
||||
}
|
||||
|
||||
unset($raw_data[$parent_child]); // prune the edge
|
||||
}
|
||||
}
|
||||
|
||||
return $raw_data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set one key in an array and return the array
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_array_set($arr, $k, $v) {
|
||||
$arr[$k] = $v;
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes/unsets one key in an array and return the array
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_array_unset($arr, $k) {
|
||||
unset($arr[$k]);
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type definitions for URL params
|
||||
*/
|
||||
define('XHPROF_STRING_PARAM', 1);
|
||||
define('XHPROF_UINT_PARAM', 2);
|
||||
define('XHPROF_FLOAT_PARAM', 3);
|
||||
define('XHPROF_BOOL_PARAM', 4);
|
||||
|
||||
|
||||
/**
|
||||
* Internal helper function used by various
|
||||
* xhprof_get_param* flavors for various
|
||||
* types of parameters.
|
||||
*
|
||||
* @param string name of the URL query string param
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_get_param_helper($param) {
|
||||
$val = null;
|
||||
if (isset($_GET[$param]))
|
||||
$val = $_GET[$param];
|
||||
else if (isset($_POST[$param])) {
|
||||
$val = $_POST[$param];
|
||||
}
|
||||
return $val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts value for string param $param from query
|
||||
* string. If param is not specified, return the
|
||||
* $default value.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_get_string_param($param, $default = '') {
|
||||
$val = xhprof_get_param_helper($param);
|
||||
|
||||
if ($val === null)
|
||||
return $default;
|
||||
|
||||
return $val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts value for unsigned integer param $param from
|
||||
* query string. If param is not specified, return the
|
||||
* $default value.
|
||||
*
|
||||
* If value is not a valid unsigned integer, logs error
|
||||
* and returns null.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_get_uint_param($param, $default = 0) {
|
||||
$val = xhprof_get_param_helper($param);
|
||||
|
||||
if ($val === null)
|
||||
$val = $default;
|
||||
|
||||
// trim leading/trailing whitespace
|
||||
$val = trim($val);
|
||||
|
||||
// if it only contains digits, then ok..
|
||||
if (ctype_digit($val)) {
|
||||
return $val;
|
||||
}
|
||||
|
||||
xhprof_error("$param is $val. It must be an unsigned integer.");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extracts value for a float param $param from
|
||||
* query string. If param is not specified, return
|
||||
* the $default value.
|
||||
*
|
||||
* If value is not a valid unsigned integer, logs error
|
||||
* and returns null.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_get_float_param($param, $default = 0) {
|
||||
$val = xhprof_get_param_helper($param);
|
||||
|
||||
if ($val === null)
|
||||
$val = $default;
|
||||
|
||||
// trim leading/trailing whitespace
|
||||
$val = trim($val);
|
||||
|
||||
// TBD: confirm the value is indeed a float.
|
||||
if (true) // for now..
|
||||
return (float)$val;
|
||||
|
||||
xhprof_error("$param is $val. It must be a float.");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts value for a boolean param $param from
|
||||
* query string. If param is not specified, return
|
||||
* the $default value.
|
||||
*
|
||||
* If value is not a valid unsigned integer, logs error
|
||||
* and returns null.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_get_bool_param($param, $default = false) {
|
||||
$val = xhprof_get_param_helper($param);
|
||||
|
||||
if ($val === null)
|
||||
$val = $default;
|
||||
|
||||
// trim leading/trailing whitespace
|
||||
$val = trim($val);
|
||||
|
||||
switch (strtolower($val)) {
|
||||
case '0':
|
||||
case '1':
|
||||
$val = (bool)$val;
|
||||
break;
|
||||
case 'true':
|
||||
case 'on':
|
||||
case 'yes':
|
||||
$val = true;
|
||||
break;
|
||||
case 'false':
|
||||
case 'off':
|
||||
case 'no':
|
||||
$val = false;
|
||||
break;
|
||||
default:
|
||||
xhprof_error("$param is $val. It must be a valid boolean string.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return $val;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize params from URL query string. The function
|
||||
* creates globals variables for each of the params
|
||||
* and if the URL query string doesn't specify a particular
|
||||
* param initializes them with the corresponding default
|
||||
* value specified in the input.
|
||||
*
|
||||
* @params array $params An array whose keys are the names
|
||||
* of URL params who value needs to
|
||||
* be retrieved from the URL query
|
||||
* string. PHP globals are created
|
||||
* with these names. The value is
|
||||
* itself an array with 2-elems (the
|
||||
* param type, and its default value).
|
||||
* If a param is not specified in the
|
||||
* query string the default value is
|
||||
* used.
|
||||
* @author Kannan
|
||||
*/
|
||||
function xhprof_param_init($params) {
|
||||
/* Create variables specified in $params keys, init defaults */
|
||||
foreach ($params as $k => $v) {
|
||||
switch ($v[0]) {
|
||||
case XHPROF_STRING_PARAM:
|
||||
$p = xhprof_get_string_param($k, $v[1]);
|
||||
break;
|
||||
case XHPROF_UINT_PARAM:
|
||||
$p = xhprof_get_uint_param($k, $v[1]);
|
||||
break;
|
||||
case XHPROF_FLOAT_PARAM:
|
||||
$p = xhprof_get_float_param($k, $v[1]);
|
||||
break;
|
||||
case XHPROF_BOOL_PARAM:
|
||||
$p = xhprof_get_bool_param($k, $v[1]);
|
||||
break;
|
||||
default:
|
||||
xhprof_error("Invalid param type passed to xhprof_param_init: "
|
||||
. $v[0]);
|
||||
exit();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
//
|
||||
// Copyright (c) 2009 Facebook
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
//
|
||||
// This file defines the interface iXHProfRuns and also provides a default
|
||||
// implementation of the interface (class XHProfRuns).
|
||||
//
|
||||
|
||||
/**
|
||||
* iXHProfRuns interface for getting/saving a XHProf run.
|
||||
*
|
||||
* Clients can either use the default implementation,
|
||||
* namely XHProfRuns_Default, of this interface or define
|
||||
* their own implementation.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
interface iXHProfRuns {
|
||||
|
||||
/**
|
||||
* Returns XHProf data given a run id ($run) of a given
|
||||
* type ($type).
|
||||
*
|
||||
* Also, a brief description of the run is returned via the
|
||||
* $run_desc out parameter.
|
||||
*/
|
||||
public function get_run($run_id, $type, &$run_desc);
|
||||
|
||||
/**
|
||||
* Save XHProf data for a profiler run of specified type
|
||||
* ($type).
|
||||
*
|
||||
* The caller may optionally pass in run_id (which they
|
||||
* promise to be unique). If a run_id is not passed in,
|
||||
* the implementation of this method must generated a
|
||||
* unique run id for this saved XHProf run.
|
||||
*
|
||||
* Returns the run id for the saved XHProf run.
|
||||
*
|
||||
*/
|
||||
public function save_run($xhprof_data, $type, $run_id = null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* XHProfRuns_Default is the default implementation of the
|
||||
* iXHProfRuns interface for saving/fetching XHProf runs.
|
||||
*
|
||||
* It stores/retrieves runs to/from a filesystem directory
|
||||
* specified by the "xhprof.output_dir" ini parameter.
|
||||
*
|
||||
* @author Kannan
|
||||
*/
|
||||
class XHProfRuns_Default implements iXHProfRuns {
|
||||
|
||||
private $dir = '';
|
||||
private $suffix = 'xhprof';
|
||||
|
||||
private function gen_run_id($type) {
|
||||
return uniqid();
|
||||
}
|
||||
|
||||
private function file_name($run_id, $type) {
|
||||
|
||||
$file = "$run_id.$type." . $this->suffix;
|
||||
|
||||
if (!empty($this->dir)) {
|
||||
$file = $this->dir . "/" . $file;
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
|
||||
public function __construct($dir = null) {
|
||||
|
||||
// if user hasn't passed a directory location,
|
||||
// we use the xhprof.output_dir ini setting
|
||||
// if specified, else we default to the directory
|
||||
// in which the error_log file resides.
|
||||
|
||||
if (empty($dir)) {
|
||||
$dir = ini_get("xhprof.output_dir");
|
||||
if (empty($dir)) {
|
||||
|
||||
$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 "<hr/>Existing runs:\n<ul>\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 '<li><a href="' . htmlentities($_SERVER['SCRIPT_NAME'])
|
||||
. '?run=' . htmlentities($run) . '&source='
|
||||
. htmlentities($source) . '">'
|
||||
. htmlentities(basename($file)) . "</a><small> "
|
||||
. date("Y-m-d H:i:s", filemtime($file)) . "</small></li>\n";
|
||||
}
|
||||
echo "</ul>\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user