XHProf + XHGui:搭建PHP性能分析系统

2026-04-16 奥古斯宏 #XHProf #XHGui #性能分析 #PHP
PHP 应用哪里慢?XHProf 采集性能数据,XHGui 可视化展示调用链和耗时,Docker 五分钟搭好完整的性能分析系统,不用改业务代码

你的接口响应慢,但不知道慢在哪里。你在代码里到处加 var_dump 和日志,结果还是找不到瓶颈。你需要一个能透视代码执行过程的工具——XHProf + XHGui,完全开源、自托管的 PHP 性能分析方案。

XHGui 运行列表


为什么选它

对比项 XHProf + XHGui Blackfire New Relic
价格 免费 $199/月起 $99/月起
数据存储 自己服务器 Blackfire 云 New Relic 云
PHP 版本 5.x - 8.x 7.0+ 7.0+
函数级分析 支持 支持 支持
调用图可视化 支持 支持 支持
部署方式 Docker / 手动 SaaS / 自托管 SaaS

核心优势:免费、自托管、函数级精度、调用图可视化。数据不出服务器,适合对数据安全有要求的团队。


安装部署

第一步:安装 XHProf 扩展

XHProf 是 Facebook 开源的 PHP 性能分析扩展,能精确记录每个函数的执行时间、CPU 时间和内存占用。

# 从源码编译(支持 PHP 8.x)
cd /tmp
git clone --depth=1 https://github.com/longxinH/xh.git xhprof-src
cd xhprof-src/extension

phpize
./configure
make -j$(nproc)
sudo make install

# 创建配置文件
echo "extension=xhprof.so" | sudo tee /etc/php/8.4/cli/conf.d/20-xhprof.ini
echo "extension=xhprof.so" | sudo tee /etc/php/8.4/fpm/conf.d/20-xhprof.ini

# 验证安装
php -m | grep xhprof
# 输出:xhprof

注意:官方 phacility/xhprof 已停止维护,PHP 8.x 请用 longxinH/xh(fork 版本,持续维护)。

第二步:部署 XHGui

XHGui 是 XHProf 的 Web 可视化界面。不需要 MongoDB,可以直接用 SQLite:

# 克隆项目
cd /opt  # 或你喜欢的目录
git clone --depth=1 https://github.com/perftools/xhgui.git
cd xhgui

# 安装依赖
composer install --no-dev

# 安装 PDO SQLite 驱动
sudo apt-get install -y php-sqlite3  # Debian/Ubuntu

第三步:配置存储

编辑 config/config.php

<?php
return [
    'save.handler' => 'pdo',
    'pdo' => [
        'dsn' => 'sqlite:' . __DIR__ . '/../data/xhgui.sqlite',
        'user' => null,
        'pass' => null,
        'table' => 'results',
        'tableWatch' => 'watches',
        'initSchema' => 'true',
    ],
    'timezone' => 'Asia/Shanghai',
];

创建数据目录并初始化:

版权声明:本文由phpreturn.com(PHP武器库官网)原创和首发,所有权利归phpreturn(PHP武器库)所有,本站允许任何形式的转载/引用文章,但必须同时注明出处。

mkdir -p data
touch data/xhgui.sqlite

首次访问时 XHGui 会自动创建表结构。

第四步:启动服务

cd /opt/xhgui

# 方式一:开发测试(PHP 内置服务器)
php -S localhost:8180 -t webroot/

# 方式二:生产环境(Nginx + PHP-FPM)
# Nginx 配置 webroot 指向 /opt/xhgui/webroot/

打开浏览器访问 http://localhost:8180,看到空白的运行列表就说明部署成功。


接入你的项目

最简接入(3行代码)

在你的应用入口文件添加:

<?php
// public/index.php 或入口文件最前面

xhprof_enable(XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY);

register_shutdown_function(function () {
    $data = xhprof_disable();
    
    // 保存到 XHGui
    require '/opt/xhgui/src/autoload.php';
    $app = new \XHGui\Application();
    $saver = $app->getSaver();
    
    $now = microtime(true);
    $saver->save([
        'meta' => [
            'url' => $_SERVER['REQUEST_URI'] ?? '/cli',
            'SERVER' => $_SERVER,
            'get' => $_GET,
            'env' => $_ENV,
            'simple_url' => preg_replace('#\?.*$#', '', $_SERVER['REQUEST_URI'] ?? '/cli'),
            'request_ts_micro' => [
                'sec' => (int)$now,
                'usec' => ($now - (int)$now) * 1000000,
            ],
        ],
        'profile' => $data,
    ]);
});

// 你的框架启动代码...
$app = run();

生产环境采样

不要每个请求都采集,采样即可:

// 只采集 1% 的请求(生产环境推荐)
if (rand(1, 100) === 1) {
    xhprof_enable(XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY);
    
    register_shutdown_function(function () {
        // ... 同上
    });
}

采样模式下性能开销 < 5%,几乎无感知。

框架集成示例

ThinkPHP:

版权声明:本文由phpreturn.com(PHP武器库官网)原创和首发,所有权利归phpreturn(PHP武器库)所有,本站允许任何形式的转载/引用文章,但必须同时注明出处。

// app/ExceptionHandle.php 或 public/index.php 入口处
if (env('APP_DEBUG') || rand(1, 100) === 1) {
    xhprof_enable(XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY);
    register_shutdown_function(function () {
        $data = xhprof_disable();
        if (!$data) return;
        
        require '/opt/xhgui/src/autoload.php';
        $app = new \XHGui\Application();
        $saver = $app->getSaver();
        
        $now = microtime(true);
        $saver->save([
            'meta' => [
                'url' => $_SERVER['REQUEST_URI'] ?? '/cli',
                'SERVER' => $_SERVER,
                'get' => $_GET,
                'env' => $_ENV,
                'simple_url' => preg_replace('#\?.*$#', '', $_SERVER['REQUEST_URI'] ?? '/cli'),
                'request_ts_micro' => ['sec' => (int)$now, 'usec' => ($now - (int)$now) * 1000000],
            ],
            'profile' => $data,
        ]);
    });
}

Laravel:

// app/Providers/AppServiceProvider.php boot() 方法
if (config('app.env') !== 'production' || rand(1, 100) === 1) {
    xhprof_enable(XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY);
    register_shutdown_function(function () {
        // 同上
    });
}

使用指南

运行列表

访问 XHGui 首页,你会看到所有已记录的请求:

运行列表

每条记录显示:

  • URL — 请求地址
  • Time — 发生时间
  • wt — 壁钟时间(Wall Time,实际耗时)
  • cpu — CPU 时间
  • mu — 内存使用量
  • pmu — 峰值内存

顶部导航栏提供多种排序方式:

  • Recent — 最近记录
  • Longest wall time — 最耗时请求
  • Most CPU — CPU 密集型请求
  • Most memory — 内存密集型请求
  • Custom View — 自定义筛选
  • Watch Functions — 监控指定函数
  • Waterfall — 瀑布图视图

详情分析

点击任意一条记录进入详情页:

版权声明:本文由phpreturn.com(PHP武器库官网)原创和首发,所有权利归phpreturn(PHP武器库)所有,本站允许任何形式的转载/引用文章,但必须同时注明出处。

运行详情

左侧显示本次运行的摘要信息:

  • URL、时间戳、ID
  • Wall Time / CPU Time / Memory Usage / Peak Memory
  • GET 参数、SERVER 信息

右侧是两个关键图表:

Self Wall Time(自身耗时排行)

  • 显示每个函数自身的耗时(不含子调用)
  • 图表越长表示该函数越耗时
  • 快速定位性能热点

Memory Hogs(内存消耗排行)

  • 显示每个函数的内存分配量
  • 发现内存泄漏嫌疑

点击 Jump to functions 查看完整的函数调用列表,包含每个函数的调用次数、耗时、内存等详细数据。

版权声明:本文由phpreturn.com(PHP武器库官网)原创和首发,所有权利归phpreturn(PHP武器库)所有,本站允许任何形式的转载/引用文章,但必须同时注明出处。

调用图

点击 View Callgraph 查看函数调用关系图:

调用图

调用图直观展示:

  • 函数之间的调用关系(谁调用了谁)
  • 每个函数的耗时占比(节点大小)
  • 调用次数(箭头标注)
  • 瓶颈一目了然(最大的红色节点)

提示:占比低于 1% 的函数会被自动省略,保持图表清晰。


实战案例

案例1:定位慢接口

场景/api/orders 接口平均响应 2 秒

接入 XHProf 后,在详情页发现:

Self Wall Time 排行:
1. DB::query()     - 1,800ms  ← 瓶颈!
2. OrderService::getOrders() - 1,850ms
3. UserController::auth()     - 80ms
4. View::render()             - 70ms

原因orders 表缺少索引,全表扫描 10 万条记录。

版权声明:本文由phpreturn.com(PHP武器库官网)原创和首发,所有权利归phpreturn(PHP武器库)所有,本站允许任何形式的转载/引用文章,但必须同时注明出处。

修复

ALTER TABLE orders ADD INDEX idx_user_created (user_id, created_at);

效果:1,800ms → 15ms,提升 99%

案例2:发现内存泄漏

场景:后台任务运行后内存持续增长

Memory Hogs 排行显示:

1. ImageProcessor::resize()   - 15MB × 50次调用
2. FontLoader::loadFont()      - 12MB × 50次调用  
3. Template::render()          - 2MB

问题代码

class ImageProcessor {
    private static $cache = [];  // 静态数组永不释放
    
    public function resize($image) {
        self::$cache[] = $image;  // 每次调用都追加!
        return $this->doResize($image);
    }
}

修复

版权声明:本文由phpreturn.com(PHP武器库官网)原创和首发,所有权利归phpreturn(PHP武器库)所有,本站允许任何形式的转载/引用文章,但必须同时注明出处。

public function resize($image) {
    $result = $this->doResize($image);
    unset($image);  // 及时释放
    return $result;
}

案例3:优化第三方库

场景:PDF 生成接口很慢

调用图显示:

main() wt=3200ms
└─ PDF::generate() wt=3000ms
   └─ Font::load() wt=2500ms  ← 每次都重新加载字体!

修复:加缓存

class Font {
    private static $fonts = [];
    
    public static function load($name) {
        if (!isset(self::$fonts[$name])) {
            self::$fonts[$name] = self::loadFromFile($name);
        }
        return self::$fonts[$name];
    }
}

效果:3,000ms → 500ms。


最佳实践

1. 采样策略

// 开发环境:全量采集
xhprof_enable();

// 测试环境:10% 采样
if (rand(1, 10) === 1) { xhprof_enable(); }

// 生产环境:1% 采样
if (rand(1, 100) === 1) { xhprof_enable(); }

// 高流量:0.1% 采样
if (rand(1, 1000) === 1) { xhprof_enable(); }

2. 忽略噪音

只关注业务代码,过滤内置函数:

xhprof_enable(
    XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY | XHPROF_FLAGS_NO_BUILTINS
);

// 或者只监控 API 请求
if (strpos($_SERVER['REQUEST_URI'], '/api/') === 0) {
    xhprof_enable();
}

3. Watch Functions(监控关键函数)

在 XHGui 界面设置 Watch Functions,当某个函数超过阈值时自动标记:

版权声明:本文由phpreturn.com(PHP武器库官网)原创和首发,所有权利归phpreturn(PHP武器库)所有,本站允许任何形式的转载/引用文章,但必须同时注明出处。

// config/config.php 中配置
return [
    // ... 其他配置
    'watch_functions' => [
        ['name' => 'DB::query', 'threshold' => 100],  // 超过100ms告警
        ['name' => 'curl_exec', 'threshold' => 500],    // 超过500ms告警
        ['name' => 'file_get_contents', 'threshold' => 200],
    ],
];

4. 定期清理数据

SQLite 文件会持续增长,建议定期清理:

# crontab 每周清理30天前的数据
0 3 * * 0 find /opt/xhgui/data -name "*.sqlite" -exec sqlite3 {} "DELETE FROM results WHERE request_date < date('now', '-30 days');" \;

或者直接删除重建:

# 更简单的方式:每月重置
0 3 1 * * rm -f /opt/xhgui/data/xhgui.sqlite && touch /opt/xhgui/data/xhgui.sqlite

存储方案对比

方案 适用场景 优点 缺点
SQLite 个人/小团队 零配置、无需额外服务 不支持分布式写入
MySQL 中型团队 成熟稳定 需要额外数据库实例
MongoDB 大流量 写入性能高 资源占用大

本文使用 SQLite 方案,零依赖,适合大多数场景。如果需要切换到 MySQL 或 MongoDB,只需修改 config/config.php 中的 save.handler 和对应连接配置即可,代码不用改。


常见问题

Q: XHProf 支持 PHP 8 吗?

支持。但要用兼容版本:

# 官方版(phacility)已停更,不支持 PHP 8
# 用 fork 版本:
git clone https://github.com/longxinH/xh.git

Q: 性能开销大吗?

很小。采样模式下:

模式 开销
全量采集 ~10-15%
1% 采样 < 1%
0.1% 采样 < 0.1%

生产环境推荐 1% 采样,几乎无感知。

版权声明:本文由phpreturn.com(PHP武器库官网)原创和首发,所有权利归phpreturn(PHP武器库)所有,本站允许任何形式的转载/引用文章,但必须同时注明出处。

Q: 能监控 CLI 脚本吗?

可以。在脚本开头加同样的代码即可:

#!/usr/bin/env php
<?php
xhprof_enable(XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY);

// 你的脚本逻辑...

$data = xhprof_disable();
// 保存到 XHGui(同上)

Q: 和 XDebug 冲突吗?

不冲突。两者用途不同:

  • XDebug — 断点调试、堆栈跟踪
  • XHProf — 性能分析、函数级耗时统计

可以同时启用,但不建议在生产环境同时开启(都有一定开销)。

Q: 数据存哪里?

取决于你的配置:

# SQLite(本文方案)
/opt/xhgui/data/xhgui.sqlite

# MySQL
mysql> SELECT * FROM xhprof.results LIMIT 1;

# MongoDB
mongo> use xhprof; db.results.find().pretty()

总结

XHProf + XHGui 让 PHP 性能问题无处遁形:

  • 免费开源,零成本
  • 自托管,数据安全
  • 函数级精度分析
  • 调用图可视化
  • 支持所有主流 PHP 框架
  • SQLite 零配置起步

快速上手清单:

版权声明:本文由phpreturn.com(PHP武器库官网)原创和首发,所有权利归phpreturn(PHP武器库)所有,本站允许任何形式的转载/引用文章,但必须同时注明出处。

  1. 编译安装 XHProf 扩展(5 分钟)
  2. 克隆 XHGui 并 composer install(3 分钟)
  3. 配置 SQLite 存储(1 分钟)
  4. 入口文件加 3 行代码(1 分钟)
  5. 打开浏览器查看第一个性能报告

本文基于 XHProf v2.3 (longxinH/xh) + XHGui (perftools/xhgui) + PHP 8.4 实测编写,所有代码均通过验证。

最近浏览
累计浏览次数:1
评论
点击登录
phpreturn,PHP武器库,专注PHP领域的项目和资讯,收录和介绍PHP相关项目。

本站所有权利归 phpreturn.com 所有

举报/反馈/投稿邮箱:phpreturn@ulthon.com

鲁ICP备19027671号-2