本文主要介绍:
- 在 CentOS7 中的安装 XHProf
- 配置 XHProf 的网页图形化界面
- XHProf 数据的获取和分析
- 优化举例
- XHProf 的实现原理
- 其他功能
简介
XHProf是一个轻量级的 PHP 性能分析工具,提供了图形化的界面展示性能参数和过程。
1. 在 CentOS7 中的安装 XHProf
#获取源码包
wget http://pecl.php.net/get/xhprof-2.3.8.tgz
tar xvf xhprof-2.3.8.tgz
cd xhprof/extension
#编译安装
/opt/remi/php74/root/usr/bin/phpize
./configure --with-php-config=/opt/remi/php74/root/usr/bin/php-config
make && make install
#配置 xhprof 的扩展及运行数据路径
vim /etc/opt/remi/php74/php.d/20-xhprof.ini
; Enable xhprof extension module
extension=xhprof.so
#储存 XHProf 运行数据的默认目录,默认值是 /tmp
xhprof.output_dir=/tmp/xhprof
#重启 php-fpm
systemctl restart php74-php-fpm
#检查是否安装成功
php -m | grep xhprof
2. 配置 XHProf 的网页图形化界面
# 将源码包中的lib和html文件夹复制到项目路径下
cp -r /root/xhprof-2.3.8/xhprof_html /wwwroot/xhprof/
cp -r /root/xhprof-2.3.8/xhprof_lib /wwwroot/xhprof/
#配置Nginx
server {
listen 1025;
server_name your_ip_or_domain;
root /wwwroot/xhprof/xhprof_html;
index index.php index.html index.htm;
access_log /var/log/nginx/xhprof_access.log;
error_log /var/log/nginx/xhprof_error.log;
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi.conf;
}
}
#重新加载nginx配置
nginx -s reload
#访问你的IP或域名
2.1 可能的问题
2.1.1 failed to execute cmd \" dot -Tpng\"
#安装libpng
wget https://sourceforge.net/projects/libpng/files/libpng16/1.6.28/libpng-1.6.28.tar.gz/download
mv download libpng-1.6.28.tar.gz
tar -zxvf libpng-1.6.28.tar.gz
cd libpng-1.6.28
./configure
make && make install
#安装Graphviz
wget http://www.graphviz.org/pub/graphviz/stable/SOURCES/graphviz-2.24.0.tar.gz
tar -zxvf graphviz-2.24.0.tar.gz
cd graphviz-2.24.0
./configure
make && make install
#重启php-fpm
2.1.2 看不到数据文件
#确认配置目录 xhprof.output_dir 的权限,并且存在数据文件
chown -R www:www /tmp/xhprof
3. XHProf 数据的获取和分析
3.1 数据获取
#开启xhprof
xhprof_enable(XHPROF_FLAGS_NO_BUILTINS | XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY, []);
#停止性能分析,并返回此次运行的 xhprof 数据
$xhprof_data = xhprof_disable();
#将数据写入文件中
file_put_contents('/tmp/xhprof/' . uniqid() . '.xhprof', serialize($data));
3.2 数据分析
Incl. 表示Including(包含)的缩写
Excl. 表示Excluding(不包含)的缩写
Function Name: 函数名
Calls: 调用次数
Calls%: 调用次数的百分比
Incl. Wall Time: 包含子函数执行的所有花费时间。单位:微秒(下同)
Excl. Wall Time: 函数本身执行所花费的时间。
Incl. CPU: 包含子函数执行的所花费的CPU时间。
Excl. CPU: 函数本身执行所花费的CPU时间。
Incl.MemUse: 包含子函数执行的所占用的内存。单位:字节(下同)
Excl.MemUse: 函数本身执行所占用的内存。
Incl.PeakMemUse: 包含子函数执行,所占用内存的峰值。
Excl.PeakMemUse: 函数本身执行所占用内存的峰值。
即:
- 包含子函数的程序执行时间、CPU时间和内存消耗
- 函数本身的程序执行时间、CPU时间和内存消耗
- 所占程序执行时间、CPU时间或者内存消耗的百分比
4. 优化举例
- 找一个较慢的API的运行数据,打开执行报告(Run Report),按照函数本身执行时间(Excl. Wall Time),倒序排序。
- 可以看到排在上面的就是占用时间最多的函数,点击函数名称进入该函数的调用关系表中,往父级函数(Parent function)或子级函数(Parent function)找到自己开发的函数。
- 查看函数代码,找出可优化点,比如循环查数据库是否可改为批量查,sql 是否使用了索引等等。
- 实施代码优化方案,比如代码结构,数据缓存,MySQL索引
- 同理,根据内存排序,可以找到是否使用了
select * from table_name
这样的查询语句,改成获取具体的字段,也可以用Redis,ES等手段去优化。
5. XHProf 的实现原理
5.1 PHP 是如何加载第三方扩展的?
加载流程:
- 扩展会提供一个 get_module(void) 的方法拿到扩展的 zend_module_entry 结构体的定义
- 扩展被编译成so文件后,在php.ini文件中配置 xxx.so, 表示加载扩展
- php 启动的时候会读取 php.ini 文件,并做解析
- 在 Linux 下 通过 dlopen() 打开扩展的 xxx.so 库文件
- 通过系统的 dlsym() 获取动态库中 get_module() 函数的地址,执行每个扩展的 get_module 方法拿到 zend_module_entry 结构体
- 把zend_module_entry 结构体注册到php的 extension_lists 扩展列表中。extension_lists是一个链表,保存着根据php.ini中定义的extension=xxx.so取到的全部扩展名称,其中engine是zend扩展,functions为php扩展
- 在 php 的生命周期中执行各个扩展定义的 PHP_MINIT
5.2 如何收集性能数据的?
起始外部扩展就是相当于内核一个模块,都是 zend_module_entry 结构体。
收集性能数据的原理,就是在模块初始化的时候代理了这个,代理了这个编译和执行OPCODE 的函数,覆盖了一层,加了自己的处理,自己的处理就是C代码的执行时间和PHP申请堆内存的计算。
收集数据的过程其实针对函数嵌套调用递归收集的过程。在调用xhprof_enable方法时,会把默认的方法替换为xhprof的方法。
static void hp_begin(long level, long xhprof_flags)
{
if (!hp_globals.enabled)
{
int hp_profile_flag = 1;
hp_globals.enabled = 1;
hp_globals.xhprof_flags = (uint32) xhprof_flags;
/* Replace zend_compile with our proxy */
/* 处理加载PHP文件 */
/* 先把zend引擎默认处理方法保存到_zend_compile_file变量中。*/
_zend_compile_file = zend_compile_file;
/* 在把xhprof相对应的方法赋值给zend_compile_file。
这样,每次加载PHP文件时,就会执行xhprof相应的方法。*/
zend_compile_file = hp_compile_file;
/* Replace zend_compile_string with our proxy */
/* 处理eval代码的执行 */
_zend_compile_string = zend_compile_string;
zend_compile_string = hp_compile_string;
/*init the execute pointer*/
/* 处理 函数方法的执行 */
_zend_execute_ex = zend_execute_ex;
zend_execute_ex = hp_execute_ex;
// .........
}
}
/*那我们看下,hp_compile_file方法,又是如何实现的*/
ZEND_DLEXPORT zend_op_array* hp_compile_file(zend_file_handle *file_handle, int type)
{
const char *filename;
char *func;
int len;
zend_op_array *ret;
int hp_profile_flag = 1;
filename = hp_get_base_filename(file_handle->filename);
len = sizeof("load") - 1 + strlen(filename) + 3;
func = (char *) emalloc(len);
snprintf(func, len, "load::%s", filename);
//方法执行前记录当前各项性能如数,如cpu 内存等
BEGIN_PROFILING(&hp_globals.entries, func, hp_profile_flag);
//开始zend引擎相应的方法,加载文件
ret = _zend_compile_file(file_handle, type);
if (hp_globals.entries)
{
//加载文件完毕后,再次记录当前各项性能数据。以便以后计算差值。
END_PROFILING(&hp_globals.entries, hp_profile_flag);
}
efree(func);
return ret;
xhprof_enable开启的这一行解析成 OPCODE,然后执行之前代理的 hp_execute_ex 函数,初始化main节点,来存当前调用PHP函数的性能数据 ct wt mu pmu等数据。
在xhprof_enable 和 xhprof_disable 之前的PHP代码,每一行都会被翻译成OPCODE,都会执行hp_execute_ex 函数。
$xhprof_data = xhprof_disable();
结束收集数据,返回已经收集到的数据。
数据格式在内核里面是一个二级HASHTABLE,对应PHP是一个二维数组
这个结束过程是对应这之前xhprof_enable的执行过程,是对main虚拟的节点做回源处理。
xhprof_enable提供了三个常量,用于设置你是否需要统计PHP内置函数,都统计那些指标。
三个常量如下:
XHPROF_FLAGS_NO_BUILTINS
设置这个常量后,将不统计PHP内置函数。毕竟PHP的内置函数性能一般都不错。没必要再消耗性能去统计。所以,建议设置。
XHPROF_FLAGS_CPU
设置这个常量后,会统计进程占用CPU时间。由于CPU时间是通过调用系统调用getrusage获取,导致性能比较差。开启这个选项后,大概性能下降一半。因此,如果对cpu耗时不是特别敏感的情况下,建议不要启用这个选项。
XHPROF_FLAGS_MEMORY
设置这个常量后,将会统计内存占用情况。由于获取内存情况,使用的是zend_memory_usage和zend_memory_peak_usage,并不是系统调用。因此,对性能影响不大。如果需要对内存使用情况进行分析的情况下,可以开启。
6. 其他功能
xhprof_lib/utils/xhprof_lib.php文件包含额外的库函数,可用于维护、汇总XHProf运行结果。
例如:
- xhprof_aggregate_runs() :可用于多次XHProf运行结果汇总到一个单一的运行。这可以帮助您使用XHProf来建立一个全系统“的函数级别”的性能监测工具 。 例如,您可以在生产环境中定期抽样XHProf的数据,产生小时/日 级别的报告。
- xhprof_prune_run() :汇总大量XHProf运行结果(特别是如果它们对应不同类型的程序)将可能导致callgraph规模变得太大。您可以使用xhprof_prune_run功 能来修剪的callgraph数据中只占总运行时间中很小比例的子树。