XHProf - PHP性能分析工具

2023-08-19 14:01:12 浏览数 (2)

本文主要介绍:

  1. 在 CentOS7 中的安装 XHProf
  2. 配置 XHProf 的网页图形化界面
  3. XHProf 数据的获取和分析
  4. 优化举例
  5. XHProf 的实现原理
  6. 其他功能

简介

XHProf是一个轻量级的 PHP 性能分析工具,提供了图形化的界面展示性能参数和过程。

1. 在 CentOS7 中的安装 XHProf

代码语言:txt复制
#获取源码包
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
代码语言:txt复制
#配置 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 的网页图形化界面

代码语言:txt复制
# 将源码包中的lib和html文件夹复制到项目路径下
cp -r /root/xhprof-2.3.8/xhprof_html /wwwroot/xhprof/

cp -r /root/xhprof-2.3.8/xhprof_lib /wwwroot/xhprof/
代码语言:txt复制
#配置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"
代码语言:txt复制
#安装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 看不到数据文件
代码语言:txt复制
#确认配置目录 xhprof.output_dir 的权限,并且存在数据文件
chown -R www:www /tmp/xhprof

3. XHProf 数据的获取和分析

3.1 数据获取
代码语言:txt复制
#开启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 数据分析
代码语言:txt复制
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的方法。

代码语言:C复制
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内置函数,都统计那些指标。

三个常量如下:

代码语言:txt复制
XHPROF_FLAGS_NO_BUILTINS
设置这个常量后,将不统计PHP内置函数。毕竟PHP的内置函数性能一般都不错。没必要再消耗性能去统计。所以,建议设置。

XHPROF_FLAGS_CPU
设置这个常量后,会统计进程占用CPU时间。由于CPU时间是通过调用系统调用getrusage获取,导致性能比较差。开启这个选项后,大概性能下降一半。因此,如果对cpu耗时不是特别敏感的情况下,建议不要启用这个选项。

XHPROF_FLAGS_MEMORY
设置这个常量后,将会统计内存占用情况。由于获取内存情况,使用的是zend_memory_usage和zend_memory_peak_usage,并不是系统调用。因此,对性能影响不大。如果需要对内存使用情况进行分析的情况下,可以开启。

6. 其他功能

非侵入式监控php应用性能

xhprof_lib/utils/xhprof_lib.php文件包含额外的库函数,可用于维护、汇总XHProf运行结果。

例如:

  • xhprof_aggregate_runs() :可用于多次XHProf运行结果汇总到一个单一的运行。这可以帮助您使用XHProf来建立一个全系统“的函数级别”的性能监测工具 。 例如,您可以在生产环境中定期抽样XHProf的数据,产生小时/日 级别的报告。
  • xhprof_prune_run() :汇总大量XHProf运行结果(特别是如果它们对应不同类型的程序)将可能导致callgraph规模变得太大。您可以使用xhprof_prune_run功 能来修剪的callgraph数据中只占总运行时间中很小比例的子树。

0 人点赞