Xhprof安装与使用

前两天遇到一个PHP代码的bug,分析的结果是:因为要处理的数据量过大,内存分配超出了限制(php.ini中配置项memory_limit,默认是128M)。长期使用Python/PHP做Web开发,对于内存使用关注较少,这个事情让我重新关注起代码的内存占用问题,所以为工作中使用的测试开发环境配置Xhprof,进行性能数据收集分析(注:我们项目是用PHP开发的)。之所以选择Xhprof,是因为比较轻量,对性能影响较小,甚至可以一定方式用于生产环境,安装使用也方便。

安装

Xhprof是一个PHP扩展,安装方式与一般PHP扩展一致。

1.从这里下载最新的源码包。假设解压缩后的文件夹为xhprof

2.编译安装

cd xhprof/extension
/path/to/php/bin/phpize
./configure --with-php-config=/path/to/php/bin/php-config
make
make install

3.配置php.ini,添加以下几行:

[xhprof]
extension=xhprof.so
xhprof.output_dir="/path/to/output/dir"

4.执行/path/to/php/bin/php -i | grep "xhprof",确认xhprof扩展能成功加载

5.xhprof会将收集到的性能数据按文件(一次数据收集存放一个文件)存放在xhprof.output_dir指定的目录(注意添加写权限)下,并通过xhprof/xhprof_html目录下的PHP脚本程序提供网页形式的展示,所以还需配置web服务提供对该脚本程序的访问,以Nginx为例:

server {
    listen 8080;
    server_name localhost;

    root /path/to/xhprof/xhprof_html;
    location / {
        index index.html index.php;
        try_files $uri /index.php?$query_string;
    }
    location ~ \.php$ {
        fastcgi_split_path_info ^(.+.php)(/.+)$;
        fastcgi_pass unix:/var/run/php5-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_index index.php;
        include fastcgi_params;
    }
}

6.重启PHP-FPM与Nginx

7.xhprof_html提供的Web展示功能可以提供代码调用逻辑图,但依赖于工具dot(即该逻辑图是使用dot语言写的),所以需要额外安装依赖graphviz,例如在CentOS上:yum install graphviz

8.要对目标PHP程序进行性能分析,需要在程序中注入xhprof提供的方法调用,来收集性能数据,然后通过命令行执行或HTTP请求来触发

使用

以Yii应用为例,xhprof方法调用注入示例如下:

<?php
// 启动xhprof性能数据收集
xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);


// 加载Yii应用代码,处理请求
defined('YII_DEBUG') or define('YII_DEBUG',true);
require_once('path/to/yii/framework/yii.php');
$configFile='path/to/config/file.php';
Yii::createWebApplication($configFile)->run();


// 结束收集
$xhprof_data = xhprof_disable();

$XHPROF_ROOT = "/path/to/xhprof/";
include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_lib.php";
include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_runs.php";

$xhprof_runs = new XHProfRuns_Default();
// 将此次收集的性能数据存放到xhprof.output_dir目录
$run_id = $xhprof_runs->save_run($xhprof_data, "xhprof_yii");

其中函数xhprof_enable有两个可选参数$flags$options

$flags有3个可选值:

  1. XHPROF_FLAGS_NO_BUILTINS:使得跳过所有内置(内部)函数
  2. XHPROF_FLAGS_CPU:使输出的性能数据中添加 CPU 数据
  3. XHPROF_FLAGS_MEMORY:使输出的性能数据中添加内存数据

可以组合使用三个可选值:xhprof_enable(XHPROF_FLAGS_NO_BUILTINS | XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY);

$options是数组类型,可传入键为ignored_functions的值,表示忽略性能分析中的某些函数,如:

xhprof_enable(0, array(
    'ignored_functions' =>  array(
                                'call_user_func',
                                'call_user_func_array'
                            )
    )
);

除了xhprof_enablexhprof_disable,还有另一对函数xhprof_sample_enablexhprof_sample_disable,相比前一对,后者是以采样模式启动xhprof性能分析,更适合生产环境。

问题

“原生” xhprof 有两个小问题:

  1. 需要在应用程序中注入xhprof的函数调用,在某些情况下,这并不太合适
  2. 每次收集到的性能数据是以单个文件的方式存储,xhprof_html提供的Web展示功能每次只能分析展示单个文件的内容,无法关联多次收集的性能数据进行分析

所以开源社区有一些xhprof的衍生版本,相比”原生”xhprof,区别主要在数据存储与分析。

XHProf UI

针对上述的两点问题,我选择 XHProf UI 进行额外部署,步骤如下:

1.从 GitHub 上克隆代码库:git clone https://github.com/preinheimer/xhprof.git xhprof-ui(注:这里指定了克隆的目标目录为xhprof-ui,以区分原来部署的原生xhprof)

2.依照原生XHprof安装步骤的2-7操作

3.因为XHProf UI默认使用MySQL来存储性能数据,所以需要进行数据库配置。将文件/path/to/xhprof-ui/xhprof_lib/目录下的config.sample.php重命名为config.php,然后修改如下配置项:

// Change these:
$_xhprof['dbtype'] = 'mysql'; // Only relevant for PDO
$_xhprof['dbhost'] = 'localhost';
$_xhprof['dbuser'] = 'root';
$_xhprof['dbpass'] = 'password';
$_xhprof['dbname'] = 'xhprof';
$_xhprof['dbadapter'] = 'Pdo';
// 注意这里有个坑$_xhprof['servername']的值不能超过三个字符这个配置项即对应数据表details的server_id字段
$_xhprof['servername'] = 'myserver';
$_xhprof['namespace'] = 'myapp';
$_xhprof['url'] = 'http://url/to/xhprof/xhprof_html';

//These are good for linux and its derivatives.
/*
$_xhprof['dot_binary']  = '/usr/bin/dot';
$_xhprof['dot_tempdir'] = '/tmp';
$_xhprof['dot_errfile'] = '/tmp/xh_dot.err';
*/

// $controlIPs = false; //Disables access controlls completely. 
$controlIPs = array();
$controlIPs[] = "127.0.0.1";   // localhost, you'll want to add your own ip here
$controlIPs[] = "::1";         // localhost IP v6

并创建相应的数据库xhprof和数据表details,其中创建数据表details的SQL语句见文件/path/to/xhprof-ui/xhprof_lib/utils/xhprof_runs.php,也可见 这里XHProf UI 代码的README.markdown有文档说明。


前面说过“原生”xhprof的问题之一是:“需要在应用程序中注入xhprof的函数调用,在某些情况下,这并不太合适”。那么XHProf UI是怎么解决这个问题的呢?

配置文件php.ini中有对配置项:

  • auto_prepend_file:指定的脚本文件会在目标脚本执行之前自动解析执行
  • auto_append_file:指定的脚本文件会在目标脚本执行之后自动解析执行

XHProf UI提供脚本/path/to/xhprof-ui/external/header.php用于配置auto_prepend_file,即在所有目标PHP程序运行之前自动注入xhprof性能数据收集能力。

配置auto_prepend_file的方式有两种:一种是直接修改php.ini的auto_prepend_file配置项,另一种是通过Nginx/Apache等HTTP服务器传送配置指令给PHP-FPM进程,如在Nginx的目标server配置块中添加一行:fastcgi_param PHP_VALUE "auto_prepend_file=/path/to/xhprof-ui/external/header.php";

默认XHProf UI不会对PHP Web应用收集性能分析数据,可以在请求Web应用的任意URL时,添加GET参数_profile=1来启用,/path/to/xhprof-ui/external/header.php脚本会检查_profile参数,并将参数值写到cookie中setcookie('_profile', $_GET['_profile']);(这样就不用每次请求都带GET参数_profile=1,并且cookie是针对域名的,这样也就同域名下的其他URL请求启用了性能分析),然后对目标URL去掉参数_profile后发起重定向;对于不带GET参数_profile的URL请求,header.php会继续检查是否存在名为_profile的cookie,如果存在且值为布尔真,则设置条件变量启用性能分析,否则不启用。若想要对已启用性能分析的域名禁用性能分析,则可以通过对URL请求添加GET参数_profile=0来禁用,因为header.php在检查cookie时发现_profile值为布尔假(0),所以不会启用性能分析。

术语

在查看 Xhprof 或 XHProf UI 展示的性能数据时,会遇到以下几个术语,其含义对应如下:

  1. Calls / Call Count:函数/方法被调用的次数
  2. Incl. Wall Time / Wall Time:执行该函数/方法实际耗费的时间
  3. Incl. MemUse / Memory Usage:该函数/方法当前占用的内存
  4. Incl. PeakMemUse / Peak Memory Usage:函数/方法占用内存的峰值(注:我也不知道这个峰值是怎么算的)
  5. Incl. CPU / CPU:执行该函数/方法,花费的CPU时间
  6. Excl. Wall Time / Exclusive Wall Time
  7. Excl. MemUse / Exclusive Memory Usage
  8. Excl. PeakMemUse / Exclusive Peak Memory Usage
  9. Exclusive CPU

术语中的Incl表示InclusiveExcl表示ExclusiveInclusive表示测量到的数据是函数/方法本身及所有调用的子函数/方法总共耗费占用的资源Exclusive则表示不包含调用的子函数/方法耗费占用的资源。另外,所有测量值都是每个函数/方法调用在次数上的叠加。

参考资料