diff --git a/source/docker/xhgui-config.php b/source/docker/xhgui-config.php new file mode 100644 index 0000000..7e09774 --- /dev/null +++ b/source/docker/xhgui-config.php @@ -0,0 +1,58 @@ + 'pdo', + + 'pdo' => [ + 'dsn' => $pdoDsn, + 'user' => getenv('XHGUI_PDO_USER') ?: 'root', + 'pass' => getenv('XHGUI_PDO_PASS') ?: 'root', + 'table' => getenv('XHGUI_PDO_TABLE') ?: 'xhgui_results', + 'tableWatch' => getenv('XHGUI_PDO_TABLE_WATCHES') ?: 'xhgui_watches', + 'initSchema' => getenv('XHGUI_PDO_INITSCHEMA') ?: 'true', + ], + + 'authentication' => function () { + $user = getenv('XHGUI_AUTH_USER'); + $pass = getenv('XHGUI_AUTH_PASS'); + + // 未配置认证则直接放行(兼容内网/开发环境不设密码的场景) + if (empty($user)) { + return true; + } + + // 验证 HTTP Basic Auth(hash_equals 防止时序攻击) + $sentUser = $_SERVER['PHP_AUTH_USER'] ?? ''; + $sentPass = $_SERVER['PHP_AUTH_PW'] ?? ''; + + if (hash_equals($user, $sentUser) && hash_equals($pass, $sentPass)) { + return true; + } + + // 认证失败,发送 401 响应 + header('WWW-Authenticate: Basic realm="XHGui Performance Analyzer"'); + http_response_code(401); + echo 'Authentication required.'; + return false; + }, +]; diff --git a/source/docker/xhprof-bootstrap.php b/source/docker/xhprof-bootstrap.php new file mode 100644 index 0000000..35f39e6 --- /dev/null +++ b/source/docker/xhprof-bootstrap.php @@ -0,0 +1,66 @@ + [ + ProfilingFlags::CPU, + ProfilingFlags::MEMORY, + 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 时强制采集(手动调试) + if (!empty($_COOKIE['_profiler'])) { + return true; + } + // 默认 1% 采样率 + return mt_rand(1, 100) === 1; + }, +]; + +// 5. 启动采集(异常不影响应用正常运行) +try { + $profiler = new Profiler($config); + $profiler->start(); +} catch (\Throwable $e) { + error_log('[xhprof] ' . $e->getMessage()); +} diff --git a/source/stack/README.md b/source/stack/README.md index 3250a6f..9b6b598 100644 --- a/source/stack/README.md +++ b/source/stack/README.md @@ -39,14 +39,71 @@ cd source/stack/docker-dev-myproject && docker compose up -d | 模式 | 说明 | |------|------| | `default` | 默认基线,仓库根目录 `php think run`,无 Docker 文件 | -| `docker-serve` | Docker 部署模式(基于基础镜像,nginx + php-fpm) | -| `docker-dev` | Docker 开发模式(nginx + php-fpm + MySQL + Redis + phpMyAdmin + Xdebug) | -| `docker-dev-sync` | Docker 开发模式 - Windows I/O 优化(rsync 同步,避免 bind mount 慢速问题) | -| `full` | 完整服务栈(规划中),从 PHP 镜像从头构建 | +| `docker-serve` | Docker 部署模式(基于基础镜像,nginx + php-fpm + XHProf) | +| `docker-dev` | Docker 开发模式(nginx + php-fpm + MySQL + Redis + phpMyAdmin + Xdebug + XHProf) | +| `docker-dev-sync` | Docker 开发模式 - Windows I/O 优化(rsync 同步,避免 bind mount 慢速问题) + XHProf | +| `full` | 完整服务栈(规划中),从 PHP 镜像从头构建 + XHProf | | `author` | 框架作者维护工具(基础镜像构建、发布脚本等) | > `author` 为框架作者专用,使用者一般不需要。 +## XHProf 性能分析(可选) + +docker-dev / docker-dev-sync / docker-serve / full 模式内置了 XHProf 性能分析能力,默认不启用,完全不影响现有行为。采集数据存储在 MySQL 中(PDO 方式),docker-dev / docker-dev-sync 开箱即用;docker-serve / full 需配置 XHGUI_PDO_DSN 环境变量指向可用的 MySQL。 + +### 启用方式 + +```bash +# 在模式目录中,加 --profile xhprof 启动 +cd source/stack/docker-dev +XHPROF_ENABLE=true docker compose --profile xhprof up -d +``` + +或者在 `.env` 中设置 `XHPROF_ENABLE=true`,然后: + +```bash +docker compose --profile xhprof up -d +``` + +### 查看性能报告 + +浏览器打开 `http://localhost:8142`,输入 Basic Auth 用户名密码(默认 `admin` / `xhgui123`)。 + +### 认证配置 + +通过环境变量覆盖默认密码(在 `.env` 或 docker-compose 命令行设置): + +```env +XHGUI_AUTH_USER=myuser +XHGUI_AUTH_PASS=mypassword +``` + +### 存储配置 + +默认使用 PDO MySQL 存储(复用 docker-dev 的 MySQL)。通过环境变量自定义: + +```env +XHGUI_PDO_DSN=mysql:host=mysql;dbname=ulthon;charset=utf8mb4 +XHGUI_PDO_USER=root +XHGUI_PDO_PASS=root +``` + +docker-serve / full 模式没有内置 MySQL,需要设置 `XHGUI_PDO_DSN` 指向可用的 MySQL 实例。 + +### 手动触发采集 + +默认 1% 采样率。需要强制采集某个请求时,浏览器请求带 cookie `_profiler=1`。 + +### 端口说明 + +| 端口 | 服务 | 说明 | +|------|------|------| +| 8142 | XHGui | 性能分析报告界面 | + +### 默认行为 + +不加 `--profile xhprof` 时,XHGui 服务不启动,行为与未集成 XHProf 完全一致。 + ## default 目录规则(强约束) - `source/stack/default/` 必须与代码库默认行为一致(`php think run`,不依赖 Docker) diff --git a/source/stack/author/docker/Dockerfile.base b/source/stack/author/docker/Dockerfile.base index 5182cf4..5112d00 100644 --- a/source/stack/author/docker/Dockerfile.base +++ b/source/stack/author/docker/Dockerfile.base @@ -26,6 +26,7 @@ RUN install-php-extensions event RUN install-php-extensions imagick RUN install-php-extensions zip RUN install-php-extensions pcntl +RUN install-php-extensions xhprof # 清理默认 Nginx 配置 RUN rm /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default diff --git a/source/stack/docker-dev-sync/Dockerfile b/source/stack/docker-dev-sync/Dockerfile index 76a3afc..bb0625b 100644 --- a/source/stack/docker-dev-sync/Dockerfile +++ b/source/stack/docker-dev-sync/Dockerfile @@ -27,6 +27,7 @@ RUN install-php-extensions imagick RUN install-php-extensions zip RUN install-php-extensions pcntl RUN install-php-extensions xdebug +RUN install-php-extensions xhprof # Clean default Nginx config RUN rm /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default @@ -62,6 +63,17 @@ RUN rm -rf /var/www/html/source/docker && ln -sf /var/www/html/source/stack/dock # Generate autoloader (without --no-dev optimizations) RUN composer dump-autoload +# XHProf: 安装 php-profiler 到独立目录(避免 rsync 同步覆盖) +RUN mkdir -p /opt/xhprof \ + && cd /opt/xhprof \ + && composer require perftools/php-profiler + +# XHProf: 复制采集器 bootstrap(全局配置,不受 rsync 同步影响) +COPY source/docker/xhprof-bootstrap.php /opt/xhprof/xhprof-bootstrap.php + +# XHProf: 配置 auto_prepend_file(zzz 前缀确保最后加载) +RUN echo 'auto_prepend_file = /opt/xhprof/xhprof-bootstrap.php' > /usr/local/etc/php/conf.d/zzz-xhprof.ini + # Internal install composer and dependencies, comment out if not needed # RUN install-php-extensions @composer diff --git a/source/stack/docker-dev-sync/docker-compose.yaml b/source/stack/docker-dev-sync/docker-compose.yaml index 87c2aed..5b0904c 100644 --- a/source/stack/docker-dev-sync/docker-compose.yaml +++ b/source/stack/docker-dev-sync/docker-compose.yaml @@ -17,6 +17,9 @@ services: environment: # 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 + - XHGUI_UPLOAD_TOKEN=${XHGUI_UPLOAD_TOKEN:-ulthon-admin-xhprof} extra_hosts: - "host.docker.internal:host-gateway" depends_on: @@ -57,3 +60,21 @@ services: depends_on: mysql: condition: service_healthy + + xhgui: + image: xhgui/xhgui:0.24.x + restart: unless-stopped + profiles: ["xhprof"] + ports: + - "8142:80" + volumes: + # 挂载全局认证配置(Basic Auth) + - ../../../source/docker/xhgui-config.php:/var/www/xhgui/config/config.php + environment: + - XHGUI_UPLOAD_TOKEN=${XHGUI_UPLOAD_TOKEN:-ulthon-admin-xhprof} + # Basic Auth 认证(默认 admin/xhgui123,通过 .env 或环境变量覆盖) + - XHGUI_AUTH_USER=${XHGUI_AUTH_USER:-admin} + - XHGUI_AUTH_PASS=${XHGUI_AUTH_PASS:-xhgui123} + depends_on: + mysql: + condition: service_healthy diff --git a/source/stack/docker-dev/Dockerfile b/source/stack/docker-dev/Dockerfile index c7eb45d..3afa085 100644 --- a/source/stack/docker-dev/Dockerfile +++ b/source/stack/docker-dev/Dockerfile @@ -27,6 +27,7 @@ RUN install-php-extensions imagick RUN install-php-extensions zip RUN install-php-extensions pcntl RUN install-php-extensions xdebug +RUN install-php-extensions xhprof # Clean default Nginx config RUN rm /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default @@ -65,6 +66,17 @@ RUN rm -rf /var/www/html/source/docker \ # Generate autoloader (without --no-dev optimizations) RUN composer dump-autoload +# XHProf: 安装 php-profiler 到独立目录(避免 bind mount 覆盖 /var/www/html) +RUN mkdir -p /opt/xhprof \ + && cd /opt/xhprof \ + && composer require perftools/php-profiler + +# XHProf: 复制采集器 bootstrap(全局配置,不受 bind mount 影响) +COPY source/docker/xhprof-bootstrap.php /opt/xhprof/xhprof-bootstrap.php + +# XHProf: 配置 auto_prepend_file(zzz 前缀确保最后加载) +RUN echo 'auto_prepend_file = /opt/xhprof/xhprof-bootstrap.php' > /usr/local/etc/php/conf.d/zzz-xhprof.ini + # Internal install composer and dependencies, comment out if not needed # RUN install-php-extensions @composer diff --git a/source/stack/docker-dev/docker-compose.yaml b/source/stack/docker-dev/docker-compose.yaml index 06eb02b..9d75b44 100644 --- a/source/stack/docker-dev/docker-compose.yaml +++ b/source/stack/docker-dev/docker-compose.yaml @@ -14,6 +14,10 @@ services: - ../../..:/var/www/html extra_hosts: - "host.docker.internal:host-gateway" + environment: + - XHPROF_ENABLE=${XHPROF_ENABLE:-false} + - XHGUI_UPLOAD_URL=http://xhgui:80/run/import + - XHGUI_UPLOAD_TOKEN=${XHGUI_UPLOAD_TOKEN:-ulthon-admin-xhprof} depends_on: mysql: condition: service_healthy @@ -52,3 +56,21 @@ services: depends_on: mysql: condition: service_healthy + + xhgui: + image: xhgui/xhgui:0.24.x + restart: unless-stopped + profiles: ["xhprof"] + ports: + - "8142:80" + volumes: + # 挂载全局认证配置(Basic Auth) + - ../../../source/docker/xhgui-config.php:/var/www/xhgui/config/config.php + environment: + - XHGUI_UPLOAD_TOKEN=${XHGUI_UPLOAD_TOKEN:-ulthon-admin-xhprof} + # Basic Auth 认证(默认 admin/xhgui123,通过 .env 或环境变量覆盖) + - XHGUI_AUTH_USER=${XHGUI_AUTH_USER:-admin} + - XHGUI_AUTH_PASS=${XHGUI_AUTH_PASS:-xhgui123} + depends_on: + mysql: + condition: service_healthy diff --git a/source/stack/docker-serve/Dockerfile b/source/stack/docker-serve/Dockerfile index 1ccd8da..80d3192 100644 --- a/source/stack/docker-serve/Dockerfile +++ b/source/stack/docker-serve/Dockerfile @@ -1,6 +1,9 @@ ARG BASE_IMAGE=ulthon/ulthon_admin-base:latest FROM ${BASE_IMAGE} +# XHProf 扩展(若基础镜像已含则跳过) +RUN install-php-extensions xhprof + # 设置工作目录 WORKDIR /var/www/html @@ -17,6 +20,17 @@ RUN rm -rf /var/www/html/source/docker && ln -sf /var/www/html/source/stack/dock # 生成自动加载文件 RUN composer dump-autoload --optimize --no-dev --classmap-authoritative +# XHProf: 安装 php-profiler 到独立目录 +RUN mkdir -p /opt/xhprof \ + && cd /opt/xhprof \ + && composer require perftools/php-profiler --no-dev + +# XHProf: 复制采集器 bootstrap(全局配置,不受 bind mount 影响) +COPY source/docker/xhprof-bootstrap.php /opt/xhprof/xhprof-bootstrap.php + +# XHProf: 配置 auto_prepend_file(zzz 前缀确保最后加载) +RUN echo 'auto_prepend_file = /opt/xhprof/xhprof-bootstrap.php' > /usr/local/etc/php/conf.d/zzz-xhprof.ini + VOLUME /var/www/html/runtime VOLUME /var/www/html/public/storage VOLUME /var/www/html/public/build diff --git a/source/stack/docker-serve/docker-compose.yaml b/source/stack/docker-serve/docker-compose.yaml index 84025ea..15f6dfc 100644 --- a/source/stack/docker-serve/docker-compose.yaml +++ b/source/stack/docker-serve/docker-compose.yaml @@ -18,5 +18,25 @@ services: # - ./public/storage:/var/www/html/public/storage # - ./public/build:/var/www/html/public/build # - ./storage:/var/www/html/storage + environment: + - XHPROF_ENABLE=${XHPROF_ENABLE:-false} + - XHGUI_UPLOAD_URL=http://xhgui:80/run/import + - XHGUI_UPLOAD_TOKEN=${XHGUI_UPLOAD_TOKEN:-ulthon-admin-xhprof} extra_hosts: - "host.docker.internal:host-gateway" + + xhgui: + image: xhgui/xhgui:0.24.x + restart: always + profiles: ["xhprof"] + ports: + - "8142:80" + volumes: + # 挂载全局认证配置(Basic Auth) + - ../../../source/docker/xhgui-config.php:/var/www/xhgui/config/config.php + environment: + - XHGUI_UPLOAD_TOKEN=${XHGUI_UPLOAD_TOKEN:-ulthon-admin-xhprof} + # Basic Auth 认证(默认 admin/xhgui123,通过 .env 或环境变量覆盖) + - XHGUI_AUTH_USER=${XHGUI_AUTH_USER:-admin} + - XHGUI_AUTH_PASS=${XHGUI_AUTH_PASS:-xhgui123} + # PDO 存储:需配置 XHGUI_PDO_DSN 指向 MySQL(docker-serve 无内置 MySQL) diff --git a/source/stack/full/Dockerfile b/source/stack/full/Dockerfile index cc77380..c445de2 100644 --- a/source/stack/full/Dockerfile +++ b/source/stack/full/Dockerfile @@ -26,6 +26,7 @@ RUN install-php-extensions event RUN install-php-extensions imagick RUN install-php-extensions zip RUN install-php-extensions pcntl +RUN install-php-extensions xhprof # 清理默认 Nginx 配置 RUN rm /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default @@ -61,6 +62,17 @@ RUN rm -rf /var/www/html/source/docker && ln -sf /var/www/html/source/stack/full # 生成自动加载文件 RUN composer dump-autoload --optimize --no-dev --classmap-authoritative +# XHProf: 安装 php-profiler 到独立目录 +RUN mkdir -p /opt/xhprof \ + && cd /opt/xhprof \ + && composer require perftools/php-profiler --no-dev + +# XHProf: 复制采集器 bootstrap(全局配置,不受 bind mount 影响) +COPY source/docker/xhprof-bootstrap.php /opt/xhprof/xhprof-bootstrap.php + +# XHProf: 配置 auto_prepend_file(zzz 前缀确保最后加载) +RUN echo 'auto_prepend_file = /opt/xhprof/xhprof-bootstrap.php' > /usr/local/etc/php/conf.d/zzz-xhprof.ini + # 内部安装compsoer并安装依赖,如果不需要可以注释掉 # RUN install-php-extensions @composer diff --git a/source/stack/full/docker-compose.yaml b/source/stack/full/docker-compose.yaml index fbb5c97..bbf3943 100644 --- a/source/stack/full/docker-compose.yaml +++ b/source/stack/full/docker-compose.yaml @@ -18,5 +18,25 @@ services: # - ./public/storage:/var/www/html/public/storage # - ./public/build:/var/www/html/public/build # - ./storage:/var/www/html/storage + environment: + - XHPROF_ENABLE=${XHPROF_ENABLE:-false} + - XHGUI_UPLOAD_URL=http://xhgui:80/run/import + - XHGUI_UPLOAD_TOKEN=${XHGUI_UPLOAD_TOKEN:-ulthon-admin-xhprof} extra_hosts: - "host.docker.internal:host-gateway" + + xhgui: + image: xhgui/xhgui:0.24.x + restart: always + profiles: ["xhprof"] + ports: + - "8142:80" + volumes: + # 挂载全局认证配置(Basic Auth) + - ../../../source/docker/xhgui-config.php:/var/www/xhgui/config/config.php + environment: + - XHGUI_UPLOAD_TOKEN=${XHGUI_UPLOAD_TOKEN:-ulthon-admin-xhprof} + # Basic Auth 认证(默认 admin/xhgui123,通过 .env 或环境变量覆盖) + - XHGUI_AUTH_USER=${XHGUI_AUTH_USER:-admin} + - XHGUI_AUTH_PASS=${XHGUI_AUTH_PASS:-xhgui123} + # PDO 存储:需配置 XHGUI_PDO_DSN 指向 MySQL(docker-serve 无内置 MySQL)