mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-08-30 12:45:32 +08:00
feat(stack): XHProf 采集切换文件落盘并独立 xhgui 供应
This commit is contained in:
@@ -3,11 +3,19 @@
|
||||
* XHProf 性能分析采集器初始化脚本
|
||||
*
|
||||
* 通过 PHP auto_prepend_file 机制自动加载,零侵入业务代码。
|
||||
* 采集数据通过 HTTP Upload 发送到 XHGui 可视化服务。
|
||||
*
|
||||
* 采集数据落盘方式(环境变量 XHPROF_SAVER,默认 file):
|
||||
* - file 本地 jsonl 文件落盘(默认;按天分文件,由后台导入定时任务增量入库)
|
||||
* - upload HTTP Upload 推送到 XHGui 可视化服务(旧行为,兼容保留)
|
||||
* - stack file + upload 双写
|
||||
*
|
||||
* 环境变量控制:
|
||||
* - XHPROF_ENABLE=true 开启采集(默认关闭)
|
||||
* - XHGUI_UPLOAD_URL XHGui 上传地址(默认 http://xhgui:80/run/import)
|
||||
* - XHPROF_SAVER 落盘方式:file / upload / stack(默认 file)
|
||||
* - XHPROF_FILE_DIR jsonl 落盘目录(默认 /var/www/html/runtime/xhprof;
|
||||
* 采集端与导入任务均以 getenv 读取此变量,保持单一来源,
|
||||
* 禁止与 ThinkPHP env() 混用,两处漂移会导致导入读不到文件)
|
||||
* - XHGUI_UPLOAD_URL XHGui 上传地址(upload/stack 模式使用,默认 http://xhgui:80/run/import)
|
||||
* - XHGUI_UPLOAD_TOKEN 上传令牌(默认 ulthon-admin-xhprof)
|
||||
*
|
||||
* Cookie 手动触发:
|
||||
@@ -30,7 +38,19 @@ require_once '/opt/xhprof/vendor/autoload.php';
|
||||
use Xhgui\Profiler\Profiler;
|
||||
use Xhgui\Profiler\ProfilingFlags;
|
||||
|
||||
// 4. 配置 Profiler
|
||||
// 4. 落盘目录(单一来源:XHPROF_FILE_DIR,导入定时任务读同一变量)
|
||||
$fileDir = getenv('XHPROF_FILE_DIR') ?: '/var/www/html/runtime/xhprof';
|
||||
|
||||
// 5. 落盘方式(默认 file)
|
||||
$saver = getenv('XHPROF_SAVER') ?: Profiler::SAVER_FILE;
|
||||
|
||||
// stack 依赖上传地址:XHGUI_UPLOAD_URL 为空时降级 file 单写,防止 upload 分支静默丢数据
|
||||
if ($saver === Profiler::SAVER_STACK && !getenv('XHGUI_UPLOAD_URL')) {
|
||||
error_log('[xhprof] XHPROF_SAVER=stack 但 XHGUI_UPLOAD_URL 为空,降级为 file 单写');
|
||||
$saver = Profiler::SAVER_FILE;
|
||||
}
|
||||
|
||||
// 6. 配置 Profiler
|
||||
$config = [
|
||||
// 采集标志:CPU + 内存 + 不记录内置函数 + 不记录 span
|
||||
'profiler.flags' => [
|
||||
@@ -39,13 +59,6 @@ $config = [
|
||||
ProfilingFlags::NO_BUILTINS,
|
||||
ProfilingFlags::NO_SPANS,
|
||||
],
|
||||
// 数据上传方式:HTTP POST 到 XHGui
|
||||
'save.handler' => Profiler::SAVER_UPLOAD,
|
||||
'save.handler.upload' => [
|
||||
'url' => getenv('XHGUI_UPLOAD_URL') ?: 'http://xhgui:80/run/import',
|
||||
'timeout' => 3,
|
||||
'token' => getenv('XHGUI_UPLOAD_TOKEN') ?: 'ulthon-admin-xhprof',
|
||||
],
|
||||
// 采集控制:cookie 强制触发 或 1% 随机采样
|
||||
'profiler.enable' => function () {
|
||||
// 浏览器带 cookie _profiler 时强制采集(手动调试)
|
||||
@@ -55,9 +68,53 @@ $config = [
|
||||
// 默认 1% 采样率
|
||||
return mt_rand(1, 100) === 1;
|
||||
},
|
||||
// URL 规范化:采集时原生计算 simple_url 写入 meta(path 去 query、纯数字段替换 :id)。
|
||||
// 后台导入任务直接读 meta.simple_url,仅缺失时兜底计算,此为唯一实现点
|
||||
'profiler.simple_url' => function ($url) {
|
||||
$path = parse_url($url, PHP_URL_PATH);
|
||||
$parts = explode('/', trim($path, '/'));
|
||||
foreach ($parts as &$p) {
|
||||
if ($p !== '' && ctype_digit($p)) {
|
||||
$p = ':id';
|
||||
}
|
||||
}
|
||||
return implode('/', $parts);
|
||||
},
|
||||
];
|
||||
|
||||
// 5. 启动采集(异常不影响应用正常运行)
|
||||
// 7. 落盘方式分派(未知值回退 file)
|
||||
if ($saver === Profiler::SAVER_UPLOAD) {
|
||||
// upload:HTTP POST 到 XHGui(旧行为,兼容保留)
|
||||
$config['save.handler'] = Profiler::SAVER_UPLOAD;
|
||||
} elseif ($saver === Profiler::SAVER_STACK) {
|
||||
// stack:file + upload 双写(SaverFactory 递归创建 savers 数组中的每个 saver)
|
||||
$config['save.handler'] = Profiler::SAVER_STACK;
|
||||
$config['save.handler.stack'] = [
|
||||
'savers' => [Profiler::SAVER_FILE, Profiler::SAVER_UPLOAD],
|
||||
];
|
||||
} else {
|
||||
// file:本地 jsonl 文件落盘(默认)
|
||||
$config['save.handler'] = Profiler::SAVER_FILE;
|
||||
}
|
||||
|
||||
// FileSaver 不自建目录,start 前确保目录存在
|
||||
if ($saver !== Profiler::SAVER_UPLOAD) {
|
||||
@mkdir($fileDir, 0777, true);
|
||||
}
|
||||
|
||||
// 按天分文件(runs-{Ymd}.jsonl):filename 在每个请求求值(bootstrap 每请求执行),
|
||||
// date() 跨天自然滚动;导入任务可安全删除已导完的整天文件
|
||||
$config['save.handler.file'] = [
|
||||
'filename' => $fileDir . '/runs-' . date('Ymd') . '.jsonl',
|
||||
];
|
||||
|
||||
$config['save.handler.upload'] = [
|
||||
'url' => getenv('XHGUI_UPLOAD_URL') ?: 'http://xhgui:80/run/import',
|
||||
'timeout' => 3,
|
||||
'token' => getenv('XHGUI_UPLOAD_TOKEN') ?: 'ulthon-admin-xhprof',
|
||||
];
|
||||
|
||||
// 8. 启动采集(异常不影响应用正常运行)
|
||||
try {
|
||||
$profiler = new Profiler($config);
|
||||
$profiler->start();
|
||||
|
||||
@@ -49,38 +49,58 @@ cd source/stack/docker-dev-myproject && docker compose up -d
|
||||
|
||||
## XHProf 性能分析(可选)
|
||||
|
||||
docker-dev / docker-dev-sync / docker-serve / full 模式内置了 XHProf 性能分析能力,默认不启用,完全不影响现有行为。采集数据存储在 MySQL 中(PDO 方式),docker-dev / docker-dev-sync 开箱即用;docker-serve / full 需配置 XHGUI_PDO_DSN 环境变量指向可用的 MySQL。
|
||||
docker-dev / docker-dev-sync / docker-serve / full 模式内置了 XHProf 性能分析能力,默认不启用,完全不影响现有行为。
|
||||
|
||||
### 启用方式
|
||||
### 推荐路径:本地文件落盘 + 后台查看(默认)
|
||||
|
||||
采集数据以 jsonl 文件按天落盘到容器内目录,由后台导入定时任务增量读取写入数据库,最终在后台管理界面查看性能报告。无需启动任何额外服务。
|
||||
|
||||
```
|
||||
请求
|
||||
└─ auto_prepend_file(xhprof-bootstrap.php)
|
||||
└─ php-profiler 采集(XHPROF_ENABLE 总闸 + cookie _profiler=1 强制 + 1% 采样)
|
||||
└─ 落盘 {XHPROF_FILE_DIR}/runs-{Ymd}.jsonl(默认 /var/www/html/runtime/xhprof,按天分文件)
|
||||
└─ 后台导入定时任务增量读取入库(读同一 XHPROF_FILE_DIR,导完的整天文件自动删除)
|
||||
└─ 后台管理界面查看性能报告
|
||||
```
|
||||
|
||||
启用方式(默认即为 file 落盘,无需额外 profile):
|
||||
|
||||
```bash
|
||||
# 在模式目录中,加 --profile xhprof 启动
|
||||
cd source/stack/docker-dev
|
||||
XHPROF_ENABLE=true docker compose --profile xhprof up -d
|
||||
XHPROF_ENABLE=true docker compose up -d
|
||||
```
|
||||
|
||||
或者在 `.env` 中设置 `XHPROF_ENABLE=true`,然后:
|
||||
相关环境变量:
|
||||
|
||||
| 变量 | 默认 | 说明 |
|
||||
|------|------|------|
|
||||
| XHPROF_ENABLE | false | 采集总开关,true 开启 |
|
||||
| XHPROF_SAVER | file | 落盘方式:file(文件落盘)/ upload(推送 XHGui)/ stack(双写) |
|
||||
| XHPROF_FILE_DIR | 空 | jsonl 落盘目录,空时容器内 `/var/www/html/runtime/xhprof`;采集与导入共用此变量 |
|
||||
| XHGUI_UPLOAD_URL | 空 | XHGui 上传地址,仅 upload / stack 模式使用 |
|
||||
| XHGUI_UPLOAD_TOKEN | ulthon-admin-xhprof | 上传令牌,仅 upload / stack 模式使用 |
|
||||
|
||||
### 手动触发采集
|
||||
|
||||
默认 1% 采样率。需要强制采集某个请求时,浏览器请求带 cookie `_profiler=1`。
|
||||
|
||||
### 可选:XHGui 独立可视化服务
|
||||
|
||||
XHGui 已降级为可选服务(独立 profile),只负责实时可视化,不参与默认采集链路。需要时以 `--profile xhgui` 启动,并推荐配合 stack 双写:
|
||||
|
||||
```bash
|
||||
docker compose --profile xhprof up -d
|
||||
cd source/stack/docker-dev
|
||||
XHPROF_ENABLE=true XHPROF_SAVER=stack XHGUI_UPLOAD_URL=<XHGui上传地址> docker compose --profile xhgui up -d
|
||||
```
|
||||
|
||||
### 查看性能报告
|
||||
- `XHPROF_SAVER=stack`:文件落盘与 XHGui 推送同时写(SaverFactory 递归双写),落盘数据不丢,XHGui 实时可看
|
||||
- `XHGUI_UPLOAD_URL` 为空时不会推送;`XHPROF_SAVER=stack` 且该地址为空时,bootstrap 记录警告并自动降级为 file 单写(防静默丢数据)
|
||||
- `XHPROF_SAVER=upload`(只推送 XHGui 不落盘)为旧行为,兼容保留,不推荐
|
||||
|
||||
浏览器打开 `http://localhost:8142`,输入 Basic Auth 用户名密码(默认 `admin` / `xhgui123`)。
|
||||
浏览器打开 `http://localhost:8142` 查看,Basic Auth 默认 `admin` / `xhgui123`,通过 `XHGUI_AUTH_USER` / `XHGUI_AUTH_PASS` 覆盖。
|
||||
|
||||
### 认证配置
|
||||
|
||||
通过环境变量覆盖默认密码(在 `.env` 或 docker-compose 命令行设置):
|
||||
|
||||
```env
|
||||
XHGUI_AUTH_USER=myuser
|
||||
XHGUI_AUTH_PASS=mypassword
|
||||
```
|
||||
|
||||
### 存储配置
|
||||
|
||||
默认使用 PDO MySQL 存储(复用 docker-dev 的 MySQL)。通过环境变量自定义:
|
||||
XHGui 存储使用 PDO MySQL。docker-dev / docker-dev-sync 复用栈内 MySQL;docker-serve / full 没有内置 MySQL,需配置 `XHGUI_PDO_DSN` 指向可用的 MySQL 实例:
|
||||
|
||||
```env
|
||||
XHGUI_PDO_DSN=mysql:host=mysql;dbname=ulthon;charset=utf8mb4
|
||||
@@ -88,21 +108,27 @@ XHGUI_PDO_USER=root
|
||||
XHGUI_PDO_PASS=root
|
||||
```
|
||||
|
||||
docker-serve / full 模式没有内置 MySQL,需要设置 `XHGUI_PDO_DSN` 指向可用的 MySQL 实例。
|
||||
|
||||
### 手动触发采集
|
||||
|
||||
默认 1% 采样率。需要强制采集某个请求时,浏览器请求带 cookie `_profiler=1`。
|
||||
|
||||
### 端口说明
|
||||
|
||||
| 端口 | 服务 | 说明 |
|
||||
|------|------|------|
|
||||
| 8142 | XHGui | 性能分析报告界面 |
|
||||
| 8142 | XHGui | 可选的性能分析报告界面(需 `--profile xhgui`) |
|
||||
|
||||
### 迁移说明(profiles 更名,破坏性变更)
|
||||
|
||||
XHGui 服务的 compose profile 已由 `xhprof` 更名为 `xhgui`,采集开关与 XHGui 界面彻底解耦:
|
||||
|
||||
- 旧命令 `docker compose --profile xhprof up -d` 不再启动 XHGui,请改用 `--profile xhgui`
|
||||
- 现在只需 `XHPROF_ENABLE=true` 即可采集(默认文件落盘),无需任何 profile
|
||||
- 旧栈若依赖 upload 直推 XHGui,需显式设置 `XHPROF_SAVER=upload` 或改用推荐的 file / stack 模式
|
||||
|
||||
### 部署注记
|
||||
|
||||
bootstrap 的落盘方式切换与后台导入定时任务是配套变更,须同批上线:若采集端已切换为默认 file 落盘而后台导入任务未部署,落盘文件将无人消费、持续增长。
|
||||
|
||||
### 默认行为
|
||||
|
||||
不加 `--profile xhprof` 时,XHGui 服务不启动,行为与未集成 XHProf 完全一致。
|
||||
`XHPROF_ENABLE=false`(默认)时不采集,行为与未集成 XHProf 完全一致;不加 `--profile xhgui` 时 XHGui 服务不启动。
|
||||
|
||||
## default 目录规则(强约束)
|
||||
|
||||
|
||||
@@ -18,7 +18,9 @@ services:
|
||||
# Sync polling interval in seconds (default: 3)
|
||||
- SYNC_INTERVAL=${SYNC_INTERVAL:-3}
|
||||
- XHPROF_ENABLE=${XHPROF_ENABLE:-false}
|
||||
- XHGUI_UPLOAD_URL=http://xhgui:80/run/import
|
||||
- XHPROF_SAVER=${XHPROF_SAVER:-file}
|
||||
- XHPROF_FILE_DIR=${XHPROF_FILE_DIR:-}
|
||||
- XHGUI_UPLOAD_URL=${XHGUI_UPLOAD_URL:-}
|
||||
- XHGUI_UPLOAD_TOKEN=${XHGUI_UPLOAD_TOKEN:-ulthon-admin-xhprof}
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
@@ -64,7 +66,7 @@ services:
|
||||
xhgui:
|
||||
image: xhgui/xhgui:0.24.x
|
||||
restart: unless-stopped
|
||||
profiles: ["xhprof"]
|
||||
profiles: ["xhgui"]
|
||||
ports:
|
||||
- "8142:80"
|
||||
volumes:
|
||||
|
||||
@@ -16,7 +16,9 @@ services:
|
||||
- "host.docker.internal:host-gateway"
|
||||
environment:
|
||||
- XHPROF_ENABLE=${XHPROF_ENABLE:-false}
|
||||
- XHGUI_UPLOAD_URL=http://xhgui:80/run/import
|
||||
- XHPROF_SAVER=${XHPROF_SAVER:-file}
|
||||
- XHPROF_FILE_DIR=${XHPROF_FILE_DIR:-}
|
||||
- XHGUI_UPLOAD_URL=${XHGUI_UPLOAD_URL:-}
|
||||
- XHGUI_UPLOAD_TOKEN=${XHGUI_UPLOAD_TOKEN:-ulthon-admin-xhprof}
|
||||
depends_on:
|
||||
mysql:
|
||||
@@ -60,7 +62,7 @@ services:
|
||||
xhgui:
|
||||
image: xhgui/xhgui:0.24.x
|
||||
restart: unless-stopped
|
||||
profiles: ["xhprof"]
|
||||
profiles: ["xhgui"]
|
||||
ports:
|
||||
- "8142:80"
|
||||
volumes:
|
||||
|
||||
@@ -20,7 +20,9 @@ services:
|
||||
# - ./storage:/var/www/html/storage
|
||||
environment:
|
||||
- XHPROF_ENABLE=${XHPROF_ENABLE:-false}
|
||||
- XHGUI_UPLOAD_URL=http://xhgui:80/run/import
|
||||
- XHPROF_SAVER=${XHPROF_SAVER:-file}
|
||||
- XHPROF_FILE_DIR=${XHPROF_FILE_DIR:-}
|
||||
- XHGUI_UPLOAD_URL=${XHGUI_UPLOAD_URL:-}
|
||||
- XHGUI_UPLOAD_TOKEN=${XHGUI_UPLOAD_TOKEN:-ulthon-admin-xhprof}
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
@@ -28,7 +30,7 @@ services:
|
||||
xhgui:
|
||||
image: xhgui/xhgui:0.24.x
|
||||
restart: always
|
||||
profiles: ["xhprof"]
|
||||
profiles: ["xhgui"]
|
||||
ports:
|
||||
- "8142:80"
|
||||
volumes:
|
||||
|
||||
@@ -20,7 +20,9 @@ services:
|
||||
# - ./storage:/var/www/html/storage
|
||||
environment:
|
||||
- XHPROF_ENABLE=${XHPROF_ENABLE:-false}
|
||||
- XHGUI_UPLOAD_URL=http://xhgui:80/run/import
|
||||
- XHPROF_SAVER=${XHPROF_SAVER:-file}
|
||||
- XHPROF_FILE_DIR=${XHPROF_FILE_DIR:-}
|
||||
- XHGUI_UPLOAD_URL=${XHGUI_UPLOAD_URL:-}
|
||||
- XHGUI_UPLOAD_TOKEN=${XHGUI_UPLOAD_TOKEN:-ulthon-admin-xhprof}
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
@@ -28,7 +30,7 @@ services:
|
||||
xhgui:
|
||||
image: xhgui/xhgui:0.24.x
|
||||
restart: always
|
||||
profiles: ["xhprof"]
|
||||
profiles: ["xhgui"]
|
||||
ports:
|
||||
- "8142:80"
|
||||
volumes:
|
||||
|
||||
Reference in New Issue
Block a user