前两天遇到一个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个可选值:
- XHPROF_FLAGS_NO_BUILTINS:使得跳过所有内置(内部)函数
- XHPROF_FLAGS_CPU:使输出的性能数据中添加 CPU 数据
- 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_enable
和xhprof_disable
,还有另一对函数xhprof_sample_enable
和xhprof_sample_disable
,相比前一对,后者是以采样模式启动xhprof性能分析,更适合生产环境。
问题
“原生” xhprof 有两个小问题:
- 需要在应用程序中注入xhprof的函数调用,在某些情况下,这并不太合适
- 每次收集到的性能数据是以单个文件的方式存储,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 展示的性能数据时,会遇到以下几个术语,其含义对应如下:
- Calls / Call Count:函数/方法被调用的次数
- Incl. Wall Time / Wall Time:执行该函数/方法实际耗费的时间
- Incl. MemUse / Memory Usage:该函数/方法当前占用的内存
- Incl. PeakMemUse / Peak Memory Usage:函数/方法占用内存的峰值(注:我也不知道这个峰值是怎么算的)
- Incl. CPU / CPU:执行该函数/方法,花费的CPU时间
- Excl. Wall Time / Exclusive Wall Time
- Excl. MemUse / Exclusive Memory Usage
- Excl. PeakMemUse / Exclusive Peak Memory Usage
- Exclusive CPU
术语中的Incl
表示Inclusive
,Excl
表示Exclusive
。Inclusive
表示测量到的数据是函数/方法本身及所有调用的子函数/方法总共耗费占用的资源,Exclusive
则表示不包含调用的子函数/方法耗费占用的资源。另外,所有测量值都是每个函数/方法调用在次数上的叠加。