《让PHP扩展开拓编程前路》 之 性能分析利器 xhprof

  • Title: 《让PHP扩展开拓编程前路》 之 性能分析利器 xhprof
  • Tag: PECLPHPProfilerxhprof性能分析
  • Author: Tacks
  • Create-Date: 2023-05-16
  • Update-Date: 2023-05-18

目录

Ref

1、Intro

1.1 xhprof 的发展

PHP 扩展 xhprof 是一个轻量级的分层性能测量分析器,最开始由 Facebook 开发 facebook/xhprof,用于分析和优化 PHP WEB 网站,在2009年开源,大概2015年不再维护,facebook/xhprof 代码转到 lox/xhprof 仓库维护。后来基于 Facebook 的代码,出现了很多 XHProf 分支。从 PECL 上可以看到 xhprof 扩展,不过中途也是有更换开发者,2013 年最新版到 0.9.4 是由 phacility/xhprof 仓库维护,当时主要是 PHP5.x 系列,随着 PHP7.x 系列的更新,PHP底层数据类型实现进行优化,之前 xhprof 版本无法在 PHP7.x 上运行,于是更换成 longxinH/xhprof,截至目前 2023年05月 xhprof 最新版本是 2.3.9 。还好后继有人,xhprof 依然活跃,可谓是 xhprof 的第二世!第三世其实是 tideways_xhprof,当然本文暂且先不讨论 tideways 相关扩展。

XHProf 扩展 是 PHP 的函数级层次分析器,具有简单的基于 HTML 的可视化界面。原始数据收集组件是用 C 语言实现的(作为 PHP 扩展)。性能分析展示层全部使用 PHP。它能够报告函数级包含和独占的挂起时间、内存使用情况、CPU 时间和每个函数的调用次数,以及整个的函数调用图。

当然 xhprof 有很多衍生的版本项目,虽然他们名字可能一样,但可能不是同一套代码,因此建议直接去看 Github 上的代码。

本文讨论的 xhprof 扩展来源如下

1.2 什么是 Profiler 分析器 ?

Profiler 分析器是一种用于监控并优化程序性能的工具,它可以找出应用程序中的性能瓶颈,从而帮助开发人员提高程序的响应速度和性能。

通常来说,它可以收集程序执行期间的各种指标和性能数据,包括但不限于,CPU使用率、内存使用情况、函数执行时间等,然后生成详细的性能分析报告,以及可视化的图表展示。

  • 工具选择
    • PHP 中,比如有 xdebugxhproftideways
    • 其他语言,如 Go 中,比如 pprofdlvgo-torch
  • 工具集成
  • 应用程序执行
  • 工具收集数据
  • 可视化报告展示
    • 函数调用频次
    • 函数调用关系、调用链
    • 函数执行耗时
    • 函数内存消耗
    • 火焰图 Flame Graph
    • 堆栈图 Call Graph

2、Prepare

2.1 我的环境

  • PHP 7.3.20 没有是最新的是因为我太懒,晚点换 :bowtie:
[root@Centos7 ~]# php -v
PHP 7.3.20 (cli) (built: Jan 11 2021 17:21:40) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.20, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.3.20, Copyright (c) 1999-2018, by Zend Technologies


[root@Centos7 ~]# /php/php73/bin/pecl -V
PEAR Version: 1.10.12
PHP Version: 7.3.20
Zend Engine Version: 3.3.20
Running on: Linux Centos7 3.10.0-1160.11.1.el7.x86_64 #1 SMP Fri Dec 18 16:34:56 UTC 2020 x86_64


[root@Centos7 ~]# cat /etc/redhat-release
CentOS Linux release 7.9.2009 (Core)

2.2 安装 xhprof 扩展

  • pecl 安装 xhprof
    • 如果是针对 PHP5.x 的,需要安装 xhprof-0.9.4 版本
    • 如果是针对 PHP7.0 以上,则直接安装 xhprof 也就是最新版
[root@Centos7 ~]# /php/php73/bin/pecl install xhprof

....
Build process completed successfully
Installing '/php/php7.3.20/lib/php/extensions/no-debug-non-zts-20180731/xhprof.so'
install ok: channel://pecl.php.net/xhprof-2.3.9
configuration option "php_ini" is not set to php.ini location
You should add "extension=xhprof.so" to php.ini
  • 配置 php.ini 增加 xhprof 配置 xhprof.output_dir
# 创建目录
[root@Centos7 ~]# mkdir -p /tmp/php-xhprof/output


# 追加配置
[root@Centos7 ~]# vim  /php/php7.3.20/lib/php.ini
[xhprof]
extension=xhprof.so
xhprof.output_dir=/tmp/php-xhprof/output


# 重启
[root@Centos7 ~]# /etc/init.d/php-fpm73 restart
Gracefully shutting down php-fpm . done
Starting php-fpm  done
  • 检查 xhprof 是否被加载
[root@Centos7 ~]# php73 -m | grep xhprof
xhprof
[root@Centos7 ~]# php --ri
xhprof

xhprof support => enabled
Version => 2.3.9

Directive => Local Value => Master Value
xhprof.output_dir => /tmp/php-xhprof/output => /tmp/php-xhprof/output
xhprof.collect_additional_info => 0 => 0
xhprof.sampling_interval => 100000 => 100000
xhprof.sampling_depth => 2147483647 => 2147483647

2.3 其他准备

# 为防止需要图片相关依赖
[root@Centos7 ~]# yum install libpng -y
[root@Centos7 ~]# yum install graphviz -y


# 为性能数据采集准备
[root@Centos7 php-xhprof]# php -m | grep -E "mongodb|xhprof"
mongodb
xhprof

3、Ues Profiler

3.1 xhprof 相关函数说明

3.2 在 CLI 中使用 xhprof 进行性能分析

3.2.1 引入 longxinH/xhprof 一些类库

  • 创建一个项目目录 /code/php-xhprof
  • 拉取 longxinH/xhprof 代码
  • 创建一个测试目录 /code/php-xhprof/testcli
  • 复制 longxinH/xhprof 代码目录中的 xhprof_html xhprof_lib 代码目录到 testcli
[root@Centos7 ~]# mkdir -p /code/php-xhprof
[root@Centos7 ~]# mkdir -p /code/php-xhprof/testcli
[root@Centos7 ~]# cd /code/php-xhprof
[root@Centos7 php-xhprof]# git clone https://github.com/longxinH/xhprof


[root@Centos7 php-xhprof]# cd /code/php-xhprof/testcli
[root@Centos7 testcli]# cp -R  /code/php-xhprof/xhprof/xhprof_*  /code/php-xhprof/

3.2.2 在 testcli 目录下创建测试脚本 test.php

[root@Centos7 testcli]# touch /code/php-xhprof/testcli/test.php


[root@Centos7 php-xhprof]# tree -L 2
.
├── testcli
│   ├── inject.php
│   └── test.php
├── xhprof
│   ├── bin
│   ├── CHANGELOG
│   ├── CREDITS
│   ├── examples
│   ├── extension
│   ├── LICENSE
│   ├── package.xml
│   ├── README.md
│   ├── resource
│   ├── scripts
│   ├── support
│   ├── travis
│   ├── xhprof_html
│   └── xhprof_lib
├── xhprof_html
│   ├── callgraph.php
│   ├── css
│   ├── docs
│   ├── index.php
│   ├── jquery
│   ├── js
│   └── typeahead.php
└── xhprof_lib
    ├── display
    └── utils

3.2.3 编写测试脚本 test.php

<?php


// 开启 xhprof
xhprof_enable(XHPROF_FLAGS_NO_BUILTINS + XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);


function test($i) {
    if($i == 0) {
        return 1;
    } else {
        return $i * test($i - 1);
    }
};


$res = test(3);
var_dump($res); // 函数执行结果


// 结束 xhprof
$xhprof_data = xhprof_disable();
var_dump($xhprof_data); // xhprof 收集到的数据




// 下面与 xhprof 扩展无关,而是 PHP 序列化 xhprof_data 的数据,并生成一个文件
// 将收集到的数据 序列化存储到 xhprof.output_dir 目录中,生成一个唯一的文件,返回 文件 run_id
$XHPROF_ROOT = realpath(dirname(__FILE__) . '/..');
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();
$run_id = $xhprof_runs->save_run($xhprof_data, "xhprof_testcli");
var_dump($run_id);

3.2.4 执行测试脚本 test.php

[root@Centos7 php]# php test.php
int(6)
array(5) {
  ["test@2==>test@3"]=>
  array(5) {
    ["ct"]=>
    int(1)
    ["wt"]=>
    int(2)
    ["cpu"]=>
    int(11)
    ["mu"]=>
    int(832)
    ["pmu"]=>
    int(0)
  }
  ["test@1==>test@2"]=>
  array(5) {
    ["ct"]=>
    int(1)
    ["wt"]=>
    int(13)
    ["cpu"]=>
    int(13)
    ["mu"]=>
    int(1408)
    ["pmu"]=>
    int(0)
  }
  ["test==>test@1"]=>
  array(5) {
    ["ct"]=>
    int(1)
    ["wt"]=>
    int(16)
    ["cpu"]=>
    int(16)
    ["mu"]=>
    int(1984)
    ["pmu"]=>
    int(0)
  }
  ["main()==>test"]=>
  array(5) {
    ["ct"]=>
    int(1)
    ["wt"]=>
    int(26)
    ["cpu"]=>
    int(27)
    ["mu"]=>
    int(2560)
    ["pmu"]=>
    int(0)
  }
  ["main()"]=>
  array(5) {
    ["ct"]=>
    int(1)
    ["wt"]=>
    int(69)
    ["cpu"]=>
    int(67)
    ["mu"]=>
    int(3160)
    ["pmu"]=>
    int(0)
  }
}
string(13) "646329afc4f30"

3.2.5 查看 xhprof 收集的数据文件

[root@Centos7 php]# cat /tmp/php-xhprof/output/646329afc4f30.xhprof_testcli.xhprof
a:5:{s:15:"test@2==>test@3";a:5:{s:2:"ct";i:1;s:2:"wt";i:2;s:3:"cpu";i:11;s:2:"mu";i:832;s:3:"pmu";i:0;}s:15:"test@1==>test@2";a:5:{s:2:"ct";i:1;s:2:"wt";i:13;s:3:"cpu";i:13;s:2:"mu";i:1408;s:3:"pmu";i:0;}s:13:"test==>test@1";a:5:{s:2:"ct";i:1;s:2:"wt";i:16;s:3:"cpu";i:16;s:2:"mu";i:1984;s:3:"pmu";i:0;}s:13:"main()==>test";a:5:{s:2:"ct";i:1;s:2:"wt";i:26;s:3:"cpu";i:27;s:2:"mu";i:2560;s:3:"pmu";i:0;}s:6:"main()";a:5:{s:2:"ct";i:1;s:2:"wt";i:69;s:3:"cpu";i:67;s:2:"mu";i:3160;s:3:"pmu";i:0;}}

3.2.6 xhprof_data 数据涵义

字段 涵义
ct 函数被调用的次数
wt 函数的执行时间耗时
cpu 函数消耗的CPU时间
mu 函数方法使用的内存
pmu 函数方法使用的峰值内存

3.2.6 查看 xhprof 可视化报告分析

  • 只有数据没有图看起来很费劲,别忘记了,它有自带的可视化配置 HTML

  • Nginx 配置 WEB UI ,然后重启 Nginx, 可以访问 xhprof.tacks.com

server {
    listen  80;
    server_name xhprof.tacks.com;
    # 配置 xhprof HTML 根目录
    root /code/php-xhprof/xhprof_html/;
    index index.html index.php;
    access_log /logs/tacks/xhprof.tacks.com-access.log;
    error_log /logs/tacks/xhprof.tacks.com-error.log;
    rewrite_log on;

    location / {
            try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param SERVER_SOFTWARE nginx;
        include fastcgi_params;
    }
}
  • 针对本例,应该访问 http://xhprof.tacks.com/index.php?run=646329afc4f30&source=xhprof_testcli

646329afc4f30 表格

字段 涵义
Function Name 函数名
Calls 调用次数
Incl. Wall Time (microsec) 函数运行时间(包括子函数)
IWall% 函数运行时间(包括子函数)占比
Excl. Wall Time (microsec) 函数运行时间(不包括子函数)
EWall% 函数运行时间(不包括子函数)占比
[View Full Callgraph] 函数调用链图 (需要graphviz)

646329afc4f30 函数调用

  • 具体展示图表可以看本文 4.1 章节

3.2.7 分析所有 PHP 脚本文件

  • 编写 xhprof 通用采集脚本 inject.php
<?php
# /code/php-xhprof/testcli/inject.php
$XHPROF_ROOT = realpath(dirname(__FILE__) . '/..');
include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_lib.php";
include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_runs.php";

xhprof_enable(XHPROF_FLAGS_NO_BUILTINS + XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);

// 注册一个函数,当程序执行结束的时候去执行它。
register_shutdown_function(function () {
    // stop profiler
    $xhprof_data = xhprof_disable();

    // 冲刷
    if (function_exists('fastcgi_finish_request')) {
        fastcgi_finish_request();
    }

    // 生成文件
    $xhprof_runs = new XHProfRuns_Default();
    $xhprof_runs->save_run($xhprof_data, "xhprof_testcli_inject");
});
  • 在执行任意 PHP 脚本前,进行注入采集脚本,修改 php.ini 配置,设置 auto_prepend_file ,然后重启 php

这样所有的 PHP 脚本文件执行都会自动注入 inject.php 采集文件,非常方便,无需入侵真正的 PHP 业务代码;

[root@Centos7 php-xhprof]# vim /php/php73/lib/php.ini 
; Automatically add files before PHP document.
; http://php.net/auto-prepend-file
; auto_prepend_file =
auto_prepend_file=/code/php-xhprof/testcli/inject.php

3.3 在 PHP-FPM 中使用 xhprof 进行性能分析

3.3.1 分析特定域名的 WEB 请求

还是利用之前的 xhprof 通用采集脚本 inject.php ,可以创建一个新的 /code/php-xhprof/testfpm/inject.php

  • 针对某个站点 利用 fastcgi_param 指令设置 auto_prepend_file
    • 问题:由于 php-fpm 进程可能会复用,也就是多个网站用到同一个 php-fpm worker 进程来处理,这种情况就有可能收集到其他网站的请求
    • 解决:可以通过入侵方式,将代码写入到网站的入口文件,来进行收集数据;
# 追加 配置
# fastcgi_param PHP_VALUE "auto_prepend_file=/code/php-xhprof/testfpm/inject.php";

    location ~ \.php$ {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param SERVER_SOFTWARE nginx;
        fastcgi_param PHP_VALUE "auto_prepend_file=/code/php-xhprof/testfpm/inject.php";
        include fastcgi.conf;
    }

4、Web-UI

4.1 使用 xhprof 自带页面进行性能数据的可视化

4.1.1 xhprof Web UI 基本使用

xhprof WEB UI

可以看到采集的各种请求

  • 点击某个 Function Name 查看 父子函数的数据对比
    • 可以进入查看 父函数(Current Function) 和 子函数(Child functions) 的数据采集
  • 点击 View Callgraph
    • 可以进入查看 函数的调用链,并且根据颜色可以看到哪个函数耗时最大

xhprof Function Function

xhprof View Callgraph

4.1.2 使用 Diff Report 对比两次请求采集的数据

TIPS 使用参数 run1 run2 分别指向两次请求的 run_id ,另外可以点击页面的 Invert Diff Report 来反转两次请求的 run_id

  • 请求 A
function test() {
    $result = 0;
    for ($i=1; $i <= 10000; $i++) { 
        $result += $i;
    }
    return $result;
}
  • 请求 B
function test() {
    return 10000 * (1+10000) / 2;
}

请求B 相比 请求A 可以看到 CPU 和 内存 都大幅度降低;

两次请求数据对比

可以看到函数调用耗时之差,以及调用次数之差

两次请求调用对比

4.1.3 查看汇总请求

TIPS 使用参数 run=XXX,YYY,ZZZ 可以汇总多次请求

4.2 使用第三方工具进行性能数据的可视化

4.2.1 perftools/php-profilerperftools/xhgui 的组合拳,瞬间搭建专属的性能分析平台

前情提要

  • 性能分析利器 xhprof
    • PHP 安装此扩展
  • 通用采集分析器 profiler
    • perftools/php-profiler
    • 支持多种性能分析的扩展;类似一个通用的黑盒,使用此组件,然后选择响应的 性能采集扩展即可
    • xhprof 对应的就是 类 perftools/php-profiler/src/Profilers/XHProf.php
    • 支持采集数据的持久化;
  • 性能可视化分析UI xhgui
    • perftools/xhgui
    • 将数据持久化到 mongodb

整体流程就是 project => php-profiler => xhprof => xhgui

快速起一个 WEB 项目

可以找任意框架,或者你当前的项目,这里只是为了演示,用了这个仓库 PFC

[root@Centos7 php-xhprof]# pwd
/code/php-xhprof
# 克隆项目
[root@Centos7 php-xhprof]# git clone https://github.com/Tacks9/php-framework-by-composer pfc
[root@Centos7 php-xhprof]# ls pfc/
app  bootstrap.php  composer.json  composer.lock  composer.phar  config  data  public  readme.md  services  vendor

# 配置 Nginx
[root@Centos7 php-xhprof]# vim /etc/nginx/conf/pfc.conf
server
{
    listen 80;
    server_name pfc.tacks.com;

    index index.html index.htm index.php index.shtml index.shtm;
    root /code/php-xhprof/pfc/public;

    access_log /logs/tacks/pfc.tacks.com-access.log;
    error_log /logs/tacks/pfc.tacks.com-error.log;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param SERVER_SOFTWARE nginx;
        include fastcgi_params;
    }
}
# 重启 Nginx
[root@Centos7 php-xhprof]# systemctl restart nginx

# 设置 hosts
[root@Centos7 php-xhprof]# vim /etc/hosts
127.0.0.1 pfc.tacks.com

# 模拟请求
[root@Centos7 php-xhprof]# curl http://pfc.tacks.com/home/echotest
<h1> 控制器、方法  Home/echotest </h1>

在你的项目引入 perftools/php-profiler

[root@Centos7 pfc]# pwd
/code/php-xhprof/pfc
# composer 引入 perftools/php-profiler
[root@Centos7 pfc]# composer require perftools/php-profiler

# 增加 profiler.php 探测器启动文件
[root@Centos7 pfc]# touch config/profiler.php 

# 项目入口文件引入 profiler.php
[root@Centos7 pfc]# vim bootstrap.php 

# 在自动加载器后面
// 【探测器】
require BASE_PATH.'/config/profiler.php';
  • 探测器脚本 config/profiler.php
    • 配置 profiler 使用的扩展为 xhprof,因为本文主打就是 xhprof ,当然如果你如果安装类似其他的扩展如 tideways_xhprof ,可以自己尝试
    • 配置数据存储方式 save.handler
      • 主打通过 SAVER_STACK 的方式,可以将采集数据以多种方式存储
      • 接口上传 save.handler.upload
        • 当然你可能会疑惑这个接口哪里来,当时看文档的时候愣是不知道哪里来的接口,这就需要组合拳的另一套 perftools/xhgui 工具
      • 文件存储 save.handler.file
        • 默认以 json 的方式存储到 /tmp/xhgui.data.jsonl
// /code/php-xhprof/pfc/config/profiler.php
<?php

/**
 * Bootstrap for php-profiler. Copy and customize this file,
 * include this file inside some bootstrapper or other "early central point in execution"
 *
 * Documentation:
 * - https://github.com/perftools/php-profiler#create-profiler
 * - https://github.com/perftools/php-profiler#config
 */

use Xhgui\Profiler\Profiler;
use Xhgui\Profiler\ProfilingFlags;

// require __DIR__ . '/../vendor/perftools/php-profiler/autoload.php';

try {
    $config = array(
        // 选择性能分析器 xhprof 扩展
        'profiler' => Profiler::PROFILER_XHPROF,
        // 配置要捕获的分析数据
        'profiler.flags' => array(
            ProfilingFlags::CPU,
            ProfilingFlags::MEMORY,
            ProfilingFlags::NO_BUILTINS,
            ProfilingFlags::NO_SPANS,
        ),
        // 采集数据的保存方式
        'save.handler' => Profiler::SAVER_STACK,

        // 存储数据的接口
        // https://github.com/perftools/php-profiler#upload-saver
        'save.handler.upload' => array(
            'url' => 'http://xhgui.tacks.com/run/import',
            // 接口超时时间
            'timeout' => 3,
            // XHGui 的  upload.token
            'token' => 'token',
        ),

        // 存储数据的文件
        // https://github.com/perftools/php-profiler#file-saver
        'save.handler.file' => array(
            'filename' => '/tmp/xhgui.data.jsonl',
        ),

        // 采用堆栈的方式存储采集数据
        // https://github.com/perftools/php-profiler#stack-saver
        'save.handler.stack' => array(
            'savers' => array(
                Profiler::SAVER_UPLOAD,
                Profiler::SAVER_FILE,
            ),
            // 如果设置 true 就是全部存储,如果false,就按照顺序不成功就用下一个保存方式
            'saveAll' => false,
        ),

        // 采用Mongodb存储采集数据(作者不推荐直接存储到这里)
        // https://github.com/perftools/php-profiler#mongodb-saver
        'save.handler.mongodb' => array(
            'dsn' => 'mongodb://127.0.0.1:27017',
            'database' => 'xhprof',
            // Allows you to pass additional options like replicaSet to MongoClient.
            // 'username', 'password' and 'db' (where the user is added)
            'options' => array(),
            // Allows you to pass driver options like ca_file to MongoClient
            'driverOptions' => array(),
        ),

        // Environment variables to exclude from profiling data
        'profiler.exclude-env' => array(),
        'profiler.options' => array(),

        // 探测器采集(可以保存一定比例的请求,而非全部)
        // Profile 1 in 100 requests.
        // You can return true to profile every request.
        'profiler.enable' => function () {
            if ($_SERVER['SERVER_NAME'] == 'pfc.tacks.com') {
                return true;
                // 100%采样,默认为1%
                return rand(1, 100) === 42;
            } else {
                return false;
            }
        },

        // 请求 URL 简化
        'profiler.simple_url' => function ($url) {
            return preg_replace('/=\d+/', '', $url);
        },

        // 请求 URL 替换,移除敏感信息,例如 token query
        'profiler.replace_url' => function ($url) {
            return str_replace('token', '', $url);
        },
    );

    // 加载配置
    $profiler = new Profiler($config);

    // 启动探测器
    $profiler->start();
} catch (Exception $e) {
    error_log($e->getMessage());
}

你的PHP需要mongodb支持

[root@Centos7 php-xhprof]# php --ri mongodb

mongodb

MongoDB support => enabled
MongoDB extension version => 1.15.2
MongoDB extension stability => stable
libbson bundled version => 1.23.3
libmongoc bundled version => 1.23.3
libmongoc SSL => enabled
libmongoc SSL library => OpenSSL
libmongoc crypto => enabled
libmongoc crypto library => libcrypto
libmongoc crypto system profile => disabled
libmongoc SASL => enabled
libmongoc ICU => disabled
libmongoc compression => enabled
libmongoc compression snappy => disabled
libmongoc compression zlib => enabled
libmongoc compression zstd => disabled
libmongocrypt bundled version => 1.5.2
libmongocrypt crypto => enabled
libmongocrypt crypto library => libcrypto
crypt_shared library version => unknown

Directive => Local Value => Master Value
mongodb.debug => no value => no value

采集数据持久化需要一个 mongodb

[root@Centos7 php-xhprof]# ps -ef | grep mongo | grep -v "grep"
root      6666     1  1 May11 ?        01:30:17 /usr/bin/mongod --dbpath=/mongodb/data -f /etc/mongod.conf
[root@Centos7 php-xhprof]# netstat -ntlp | grep mongod
tcp        0      0 0.0.0.0:27017           0.0.0.0:*               LISTEN      6666/mongod 

启动一个 perftools/xhgui

[root@Centos7 php-xhprof]# pwd
/code/php-xhprof
# 克隆项目
[root@Centos7 php-xhprof]# git clone https://github.com/perftools/xhgui

# 配置 Nginx
[root@Centos7 php-xhprof]# vim /etc/nginx/conf/xhgui.conf
server
{
    listen 80;
    server_name xhgui.tacks.com;

    index index.html index.htm index.php index.shtml index.shtm;
    root /code/php-xhprof/xhgui/webroot;

    access_log /logs/tacks/xhgui.tacks.com-access.log;
    error_log /logs/tacks/xhgui.tacks.com-error.log;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param SERVER_SOFTWARE nginx;
        include fastcgi_params;
    }
}
# 重启 Nginx
[root@Centos7 php-xhprof]# systemctl restart nginx

xhgui首页-无请求

  • 访问 xhgui.tacks.com/run/import
    • 你会发现 Method not allowed ,这个就是我们在 php-profiler 中填写的配置 save.handler.upload,用来上传数据

大功告成,可以请求你的网站,然后使用性能分析xhgui工具

xhgui首页-有请求

  • 点击 Method 列,例如 某一行的 GET

xhgui-Method-Function

  • 点击 Jump to Functions

xhgui-Method-Function-Table

  • 点击 View Callgraph,查看本次请求的方法调用图

xhgui-View-Callgraph

  • 点击 Compare this run,对比两次请求

xhgui-View-compare

4.2.2 xhgui 的汉化版 laynefyc/xhgui-branch ,更贴合国人使用习惯

前情提要

  • 性能分析利器 xhprof
    • PHP 安装此扩展
  • 通用采集分析器 xhgui-chinese
    • 支持采集数据的持久化,默认存储 mongodb
  • 性能可视化分析UI xhgui-chinesewebroot
    • 将数据可视化展示,并且进行汉化,看起来直观一些

整体流程就是 project => xhgui-chinese => mongodb => webroot

快速起一个 WEB 项目

可以找任意框架,或者你当前的项目,这里只是为了演示,依然用了这个仓库 PFC

  • 初始化一个新的 WEB 项目即可,比如访问的 pfc.tacks.com ,剩下的就不演示了。
[root@Centos7 php-xhprof]# git clone https://github.com/Tacks9/php-framework-by-composer pfc
[root@Centos7 pfc]# pwd
/code/php-xhprof/pfc
[root@Centos7 pfc]# ls
app  bootstrap.php  composer.json  composer.lock  composer.phar  config  data  public  readme.md  services  vendor

部署 xhgui 汉化版

# 克隆项目
[root@Centos7 php-xhprof]# git clone https://github.com/laynefyc/xhgui-branch xhgui-chinese
[root@Centos7 xhgui-chinese]# pwd
/code/php-xhprof/xhgui-chinese
# composer 安装依赖
[root@Centos7 xhgui-chinese]# composer install
# 设置缓存目录
[root@Centos7 xhgui-chinese]# chmod -R 777 cache

[root@Centos7 xhgui-chinese]# ls
cache  CODE_OF_CONDUCT.md  composer.json  composer.lock  config  _config.yml  external  install.php  LICENSE  phpunit.xml  README.md  src  tests  vendor  webroot


# 配置 Nginx
[root@Centos7 xhgui-chinese]# vim /etc/nginx/conf/xhgui-chinese.conf
server
{
    listen 80;
    server_name xhgui-chinese.tacks.com;

    index index.html index.htm index.php index.shtml index.shtm;
    root /code/php-xhprof/xhgui-chinese/webroot;

    access_log /logs/tacks/xhgui-chinese.tacks.com-access.log;
    error_log /logs/tacks/xhgui-chinese.tacks.com-error.log;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param SERVER_SOFTWARE nginx;
        include fastcgi_params;
    }
}
# 重启 Nginx
[root@Centos7 xhgui-chinese]# systemctl restart nginx

xhgui汉化首页-无请求

设置 xhgui 汉化版的配置文件

# 自定义配置
[root@Centos7 xhgui-chinese]# cp conf/config.default.php conf/config.php
[root@Centos7 xhgui-chinese]# vim conf/config.php
  • conf/config.php 配置如下
    • 主要是对 profiler.enable 进行设置,只接收来自设定的域名请求
    • extension 扩展,依然采用 xhprof
    • save.handler 采集数据,使用 mongodb ,所以请先安装 php 的 mongodb 扩展
    • db.host 需要 mongodb 的服务端,可以本地启动一个,账号密码啥的可以在 db.options 设置在数组中
    • 数据库,默认采用 xhprof,当然可以重设一下,比如 xhprof-xhgui-chinese
<?php
return array(
    'debug' => false,
    'mode' => 'development',
    /*
     * support extension: uprofiler, tideways_xhprof, tideways, xhprof
     * default: xhprof
     */
    'extension' => 'xhprof',

    // Can be either mongodb or file.
    /*
    'save.handler' => 'file',
    'save.handler.filename' => dirname(__DIR__) . '/cache/' . 'xhgui.data.' . microtime(true) . '_' . substr(md5($url), 0, 6),
    */
    'save.handler' => 'mongodb',
    // Needed for file save handler. Beware of file locking. You can adujst this file path
    // to reduce locking problems (eg uniqid, time ...)
    //'save.handler.filename' => __DIR__.'/../data/xhgui_'.date('Ymd').'.dat',
    'db.host' => 'mongodb://127.0.0.1:27017',
    'db.db' => 'xhprof-xhgui-chinese',
    // Allows you to pass additional options like replicaSet to MongoClient.
    // 'username', 'password' and 'db' (where the user is added)
    'db.options' => array(),
    'templates.path' => dirname(__DIR__) . '/src/templates',
    'date.format' => 'Y-m-d H:i:s',
    'detail.count' => 6,
    'page.limit' => 25,
    // Profile 1 in 100 requests.
    // You can return true to profile every request.
    'profiler.enable' => function () {
        // return true;//rand(1, 100) === 42;
        if ($_SERVER['SERVER_NAME'] == 'pfc.tacks.com') {
            return true;
            // 100%采样,默认为1%
            return rand(1, 100) === 42;
        } else {
            return false;
        }
    },

    'profiler.simple_url' => function ($url) {
        return preg_replace('/\=\d+/', '', $url);
    },

    'profiler.filter_path' => array(
        //'/home/admin/www/xhgui/webroot','F:/phpPro'
    )

);

Mongodb 优化索引

这里用我本地测试的 mongodb 进行设置,如果你没有 mongodb 请先移步寻找如何安装。

至于 db 名字,看你在项目中如何配置的,如果为 xhprof-xhgui-chineseuse xhprof-xhgui-chinese ,默认的 db 名字为 xhprof

[root@Centos7 pfc]# mongo
MongoDB shell version v4.2.23
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("7b8bf1d3-507b-4c2f-aa18-c1c4615b515f") }
MongoDB server version: 4.2.23

> use xhprof-xhgui-chinese
> db.results.ensureIndex( { 'meta.SERVER.REQUEST_TIME' : -1 } )
> db.results.ensureIndex( { 'profile.main().wt' : -1 } )
> db.results.ensureIndex( { 'profile.main().mu' : -1 } )
> db.results.ensureIndex( { 'profile.main().cpu' : -1 } )
> db.results.ensureIndex( { 'meta.url' : 1 } )

设置 xhgui 需要监控的项目

  • header.php 需要引入的文件
[root@Centos7 xhgui-chinese]# ls external/header.php 
external/header.php
[root@Centos7 xhgui-chinese]# ls /code/php-xhprof/xhgui-chinese/external/header.php 
/code/php-xhprof/xhgui-chinese/external/header.php 
  • Nginx 配置,利用 fastcgi_param 设置 auto_prepend_file
# 配置 Nginx
[root@Centos7 php-xhprof]# vim /etc/nginx/conf/pfc.conf
server
{
    listen 80;
    server_name pfc.tacks.com;

    index index.html index.htm index.php index.shtml index.shtm;
    root /code/php-xhprof/pfc/public;

    access_log /logs/tacks/pfc.tacks.com-access.log;
    error_log /logs/tacks/pfc.tacks.com-error.log;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param SERVER_SOFTWARE nginx;
        fastcgi_param PHP_VALUE "auto_prepend_file=/code/php-xhprof/xhgui-chinese/external/header.php";
        include fastcgi_params;
    }
}
# 重启 Nginx
[root@Centos7 php-xhprof]# systemctl restart nginx

大功告成,可以请求你的网站,然后使用性能分析xhgui-chinese工具

xhgui-chinese 首页-有请求

  • 点击 “方法” 列,例如 某一行的 GET

xhgui-chinese-Method-Function

  • 点击 “跳转函数” 按钮

xhgui-chinese-Method-Function-Table

  • 点击 “查看调用图” 按钮

xhgui-chinese-View-Callgraph

  • 点击 “查看火焰图” 按钮

xhgui-chinese-View-flamegraph

  • 点击 “对比这次运行” 按钮

xhgui-chinese-View-Compare-list

xhgui-chinese-View-Compare-detail

本作品采用《CC 协议》,转载必须注明作者和本文链接
明天我们吃什么 悲哀藏在现实中 Tacks
本帖由系统于 1个月前 自动加精
讨论数量: 2

太好了,非常需要这个

10个月前 评论

得侵入式代码,感觉不大方便

1周前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!