xhprof 是 Facebook 09 年出的一个很优秀的 PHP profiler 工具,但 Facebook 后来迁移到 hhvm,早已不再维护,它在 PHP7 下有诸多 bug。
本文使用的是github上别人做的兼容PHP7的xprof项目。
安装
cd ~/source
git clone https://github.com/longxinH/xhprof
cd xhprof/extension/
查找php-config位置
which php-config
安装插件
phpize
./configure --with-php-config=/usr/local/php/bin/php-config --enable-xhprof
make
make install
修改php.ini,先用php --ini 查找到php.ini文件的位置,再在其中加入如下内容:
[xhprof]
extension=xhprof.so;
xhprof.output_dir=/var/tmp/xhprof
其中 xhprof.output_dir 是 xhprof 的输出目录,每次执行 xhprof 的 save_run 方法时都会生成一个 run_id.project_name.xhprof 文件。这个目录在哪里并不重要。
重启php-fpm
service php-fpm restart
php -m | grep xhprof
此时可以看到显示结果,xhprof扩展已安装成功
nginx配置访问
当生成 .xhprof 文件之后,我们就可以利用 xhprof_lib 来展示结果了。
server {
listen 80;
root /var/www/html/xhprof/xhprof_html;
server_name your_host;
location = / {
index index.php;
}
location ~ \.php {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
将 xhprof 中的 xhprof_lib 和 xhprof_html 两个文件夹复制到 /var/www/html/xhprof/ 下,然后访问 http://your_host/xhprof_html/index.php 即可看到如下图所示生成的数据分析文件。
文件保存的路径为上边配置的:
使用方法
方法一
使用如下代码将要检查的代码包裹起来即可:
<?php
//header
xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);
// 要检查性能的代码
//footer
$xhprof_data = xhprof_disable();
include_once '/var/www/html/xhprof/xhprof_lib/utils/xhprof_lib.php';
include_once '/var/www/html/xhprof/xhprof_lib/utils/xhprof_runs.php';
$xhprof_runs = new \XHProfRuns_Default();
$run_id = $xhprof_runs->save_run($xhprof_data, 'test');//test为生成文件内包含的一个关键字区分哪个接口的,可自定义
也可将前后代码单独分开写成PHP文件:header.php和footer.php
// header.php
<?php
if (extension_loaded('xhprof')) {
include_once 'xhprof_lib/utils/xhprof_lib.php';
include_once 'xhprof_lib/utils/xhprof_runs.php';
xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);
}
?>
// footer.php
<?php
if (extension_loaded('xhprof')) {
$profiler_namespace = 'your_project';
$xhprof_data = xhprof_disable();
$xhprof_runs = new XHProfRuns_Default();
$run_id = $xhprof_runs->save_run($xhprof_data, $profiler_namespace);
}
?>
然后用这个两个文件包裹代码如下所示:
<?php
include_once '/var/www/html/xhprof/header.php';
// 要检查性能的代码
include_once '/var/www/html/xhprof/footer.php';
示例:
include_once '/var/www/html/xhprof/header.php';
function hallo() {
}
function simpleucall($n) {
for ($i = 0; $i < $n; $i++)
hallo();
}
function simpleudcall($n) {
for ($i = 0; $i < $n; $i++)
hallo2();
}
function hallo2() {
}
function simpleicall($n) {
for ($i = 0; $i < $n; $i++)
func_num_args();
}
class Foo {
static $a = 0;
public $b = 0;
const TEST = 0;
static function read_static($n) {
for ($i = 0; $i < $n; ++$i) {
$x = self::$a;
}
}
static function write_static($n) {
for ($i = 0; $i < $n; ++$i) {
self::$a = 0;
}
}
static function isset_static($n) {
for ($i = 0; $i < $n; ++$i) {
$x = isset(self::$a);
}
}
static function empty_static($n) {
for ($i = 0; $i < $n; ++$i) {
$x = empty(self::$a);
}
}
static function f() {
}
static function call_static($n) {
for ($i = 0; $i < $n; ++$i) {
self::f();
}
}
function read_prop($n) {
for ($i = 0; $i < $n; ++$i) {
$x = $this->b;
}
}
function write_prop($n) {
for ($i = 0; $i < $n; ++$i) {
$this->b = 0;
}
}
function assign_add_prop($n) {
for ($i = 0; $i < $n; ++$i) {
$this->b += 2;
}
}
function pre_inc_prop($n) {
for ($i = 0; $i < $n; ++$i) {
++$this->b;
}
}
function pre_dec_prop($n) {
for ($i = 0; $i < $n; ++$i) {
--$this->b;
}
}
function post_inc_prop($n) {
for ($i = 0; $i < $n; ++$i) {
$this->b++;
}
}
function post_dec_prop($n) {
for ($i = 0; $i < $n; ++$i) {
$this->b--;
}
}
function isset_prop($n) {
for ($i = 0; $i < $n; ++$i) {
$x = isset($this->b);
}
}
function empty_prop($n) {
for ($i = 0; $i < $n; ++$i) {
$x = empty($this->b);
}
}
function g() {
}
function call($n) {
for ($i = 0; $i < $n; ++$i) {
$this->g();
}
}
function read_const($n) {
for ($i = 0; $i < $n; ++$i) {
$x = $this::TEST;
}
}
}
function read_static($n) {
for ($i = 0; $i < $n; ++$i) {
$x = Foo::$a;
}
}
function write_static($n) {
for ($i = 0; $i < $n; ++$i) {
Foo::$a = 0;
}
}
function isset_static($n) {
for ($i = 0; $i < $n; ++$i) {
$x = isset(Foo::$a);
}
}
function empty_static($n) {
for ($i = 0; $i < $n; ++$i) {
$x = empty(Foo::$a);
}
}
function call_static($n) {
for ($i = 0; $i < $n; ++$i) {
Foo::f();
}
}
function create_object($n) {
for ($i = 0; $i < $n; ++$i) {
$x = new Foo();
}
}
define('TEST', null);
function read_const($n) {
for ($i = 0; $i < $n; ++$i) {
$x = TEST;
}
}
function read_auto_global($n) {
for ($i = 0; $i < $n; ++$i) {
$x = $_GET;
}
}
$g_var = 0;
function read_global_var($n) {
for ($i = 0; $i < $n; ++$i) {
$x = $GLOBALS['g_var'];
}
}
function read_hash($n) {
$hash = array('test' => 0);
for ($i = 0; $i < $n; ++$i) {
$x = $hash['test'];
}
}
function read_str_offset($n) {
$str = "test";
for ($i = 0; $i < $n; ++$i) {
$x = $str[1];
}
}
function issetor($n) {
$val = array(0,1,2,3,4,5,6,7,8,9);
for ($i = 0; $i < $n; ++$i) {
$x = $val ?: null;
}
}
function issetor2($n) {
$f = false; $j = 0;
for ($i = 0; $i < $n; ++$i) {
$x = $f ?: $j + 1;
}
}
function ternary($n) {
$val = array(0,1,2,3,4,5,6,7,8,9);
$f = false;
for ($i = 0; $i < $n; ++$i) {
$x = $f ? null : $val;
}
}
function ternary2($n) {
$f = false; $j = 0;
for ($i = 0; $i < $n; ++$i) {
$x = $f ? $f : $j + 1;
}
}
/*****/
function empty_loop($n) {
for ($i = 0; $i < $n; ++$i) {
}
}
function gethrtime()
{
$hrtime = hrtime();
return (($hrtime[0]*1000000000 + $hrtime[1]) / 1000000000);
}
function start_test()
{
ob_start();
return gethrtime();
}
function end_test($start, $name, $overhead = null)
{
global $total;
global $last_time;
$end = gethrtime();
ob_end_clean();
$last_time = $end-$start;
$total += $last_time;
$num = number_format($last_time,3);
$pad = str_repeat(" ", 24-strlen($name)-strlen($num));
if (is_null($overhead)) {
echo $name.$pad.$num."\n";
} else {
$num2 = number_format($last_time - $overhead,3);
echo $name.$pad.$num." ".$num2."\n";
}
ob_start();
return gethrtime();
}
function total()
{
global $total;
$pad = str_repeat("-", 24);
echo $pad."\n";
$num = number_format($total,3);
$pad = str_repeat(" ", 24-strlen("Total")-strlen($num));
echo "Total".$pad.$num."\n";
}
const N = 5000000;
$t0 = $t = start_test();
empty_loop(N);
$t = end_test($t, 'empty_loop');
$overhead = $last_time;
simpleucall(N);
$t = end_test($t, 'func()', $overhead);
simpleudcall(N);
$t = end_test($t, 'undef_func()', $overhead);
simpleicall(N);
$t = end_test($t, 'int_func()', $overhead);
Foo::read_static(N);
$t = end_test($t, '$x = self::$x', $overhead);
Foo::write_static(N);
$t = end_test($t, 'self::$x = 0', $overhead);
Foo::isset_static(N);
$t = end_test($t, 'isset(self::$x)', $overhead);
Foo::empty_static(N);
$t = end_test($t, 'empty(self::$x)', $overhead);
read_static(N);
$t = end_test($t, '$x = Foo::$x', $overhead);
write_static(N);
$t = end_test($t, 'Foo::$x = 0', $overhead);
isset_static(N);
$t = end_test($t, 'isset(Foo::$x)', $overhead);
empty_static(N);
$t = end_test($t, 'empty(Foo::$x)', $overhead);
Foo::call_static(N);
$t = end_test($t, 'self::f()', $overhead);
call_static(N);
$t = end_test($t, 'Foo::f()', $overhead);
$x = new Foo();
$x->read_prop(N);
$t = end_test($t, '$x = $this->x', $overhead);
$x->write_prop(N);
$t = end_test($t, '$this->x = 0', $overhead);
$x->assign_add_prop(N);
$t = end_test($t, '$this->x += 2', $overhead);
$x->pre_inc_prop(N);
$t = end_test($t, '++$this->x', $overhead);
$x->pre_dec_prop(N);
$t = end_test($t, '--$this->x', $overhead);
$x->post_inc_prop(N);
$t = end_test($t, '$this->x++', $overhead);
$x->post_dec_prop(N);
$t = end_test($t, '$this->x--', $overhead);
$x->isset_prop(N);
$t = end_test($t, 'isset($this->x)', $overhead);
$x->empty_prop(N);
$t = end_test($t, 'empty($this->x)', $overhead);
$x->call(N);
$t = end_test($t, '$this->f()', $overhead);
$x->read_const(N);
$t = end_test($t, '$x = Foo::TEST', $overhead);
create_object(N);
$t = end_test($t, 'new Foo()', $overhead);
read_const(N);
$t = end_test($t, '$x = TEST', $overhead);
read_auto_global(N);
$t = end_test($t, '$x = $_GET', $overhead);
read_global_var(N);
$t = end_test($t, '$x = $GLOBALS[\'v\']', $overhead);
read_hash(N);
$t = end_test($t, '$x = $hash[\'v\']', $overhead);
read_str_offset(N);
$t = end_test($t, '$x = $str[0]', $overhead);
issetor(N);
$t = end_test($t, '$x = $a ?: null', $overhead);
issetor2(N);
$t = end_test($t, '$x = $f ?: tmp', $overhead);
ternary(N);
$t = end_test($t, '$x = $f ? $f : $a', $overhead);
ternary2(N);
$t = end_test($t, '$x = $f ? $f : tmp', $overhead);
total($t0, "Total");
include_once '/var/www/html/xhprof/footer.php';
运行:
php index.php
访问http://your_host/xhprof_html/index.php:
点击进去,是程序运行过程中各个方法所用时间和执行次数等数据的统计的表格,可进行排序查看:
选择:[View Full Callgraph]查看生成的时序图,红色的是显示用时最多的,黄色其次,其中包含有执行的方法+用时+调用次数:
方法二:在框架内使用
直接将xhprof_lib作为第三方库引入到框架lib文件夹内调用。
在框架入口文件index.php所有代码外层包裹上上边的header和footer文件的代码内容即可,并保证能正确引用到xhprof_lib.php和xhprof_runs.php。
以yaf入口为例:
<?php
//header
xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);
//code
define('APPLICATION_PATH', dirname(__FILE__)."/../");
$application = new Yaf_Application( APPLICATION_PATH . "/conf/application.ini");
$application->bootstrap()->run();
//footer
$XHPROF_PATH = APPLICATION_PATH . "library/ThirdParty/";
include_once $XHPROF_PATH . 'xhprof_lib/utils/xhprof_lib.php';
include_once $XHPROF_PATH . 'xhprof_lib/utils/xhprof_runs.php';
$profiler_namespace = 'test';
$xhprof_data = xhprof_disable();
$xhprof_runs = new XHProfRuns_Default();
$run_id = $xhprof_runs->save_run($xhprof_data, $profiler_namespace);
?>
总结
以上测试的话可在开发测试环境搞一下即可,上线的话还是要把代码删除的。
- 如果遇到错误 failed to execute cmd: " dot -Tpng". stderr: ` (process:24220): Pango-WARNING **: Invalid UTF-8 string passed to pango_layout_set_text() '。暂时不清楚怎么解决,可以选择避开它。将 xhprof_lib/utils/callgraph_utils.php 的 121,122 行的打印和 exit 注释掉。
- 如果遇到错误 Error: either we can not find profile data for run_id xxx or the threshold 0.01 is too small or you do not have 'dot' image generation utility installed,无法生成 png 图片,可能是因为生成的文件中有不能识别的字符,修复如下3:
$cmd = " dot -T".$type; // 在 cmd 之后添加一个转码工作就可以了 $dot_script = iconv("UTF-8", "ASCII//IGNORE", $dot_script);
- 如果想要更好看的 UI,可以参考以下链接1,链接2,链接3 (手动搭免费)。
- 下面是一些参数说明
性能点 | 描述 |
---|---|
Inclusive Time | 包括子函数所有执行时间 |
Exclusive Time/Self Time | 函数执行本身花费的时间,不包括子树执行时间 |
Wall Time | 花去了的时间或挂钟时间 |
CPU Time | 用户耗的时间 + 内核耗的时间 |
Inclusive CPU | 包括子函数一起所占用的 CPU |
Exclusive CPU | 函数自身所占用的 CPU |
-
xhprof issue 和某博文提到了一些替代 repo,除此之外还有 tideways 等 ↩
-
nginx 的配置参考了 某 gitbook 和 nginx wiki ↩