
简介Tracy性能分析器是一款开源跨平台性能分析工具以纳秒级分辨率支持CPU与GPU实时分析并且远程或嵌入式遥测可将额外开销降到最低这份中文文档面向游戏引擎、图形应用及服务端开发者帮助定位性能瓶颈并优化程序。压缩包内是一份1.26MB的docx格式用户手册文件仅1个已有228人下载学习。手册从快速概览讲起详细列出客户端初始设置、ZoneScoped与FrameMark等代码插桩标记、数据捕获与图形界面分析并覆盖锁分析、内存分析、GPU分析包括OpenGL/Vulkan/Direct3D等以及Lua/Python/Fortran API绑定高级主题则包含区域统计导出CSV、导入外部分析器数据、配置文件调整以及Visual Studio、Linux、Android、Docker等平台的注意事项和故障排除、网络端口设置、崩溃处理等实用章节。无论是首次集成Tracy还是深入使用高级特性这份完整手册都能帮助开发者快速上手节省查阅英文资料的时间成本。1. Tracy 性能分析中文文档一个被低估的 PHP 实时调试工具Tracy 是 PHP 生态里少见的装了就不想卸的调试工具它最初以错误页面出名——当你的 Laravel 或原生 PHP 项目抛异常时Tracy 会渲染一个深蓝底色的页面把调用栈、变量值、HTTP 头甚至上一帧的源码片段全部铺开比 PHP 默认的橙色错误页直观得多。但真正让 Tracy 值得投入的不是错误页而是它的阻塞检测和 Memory 面板它能精确告诉你页面卡了 3 秒这 3 秒发生在哪一行以及数组到底在哪一步吃掉了 200MB 内存。它和 Xdebug 不一样Xdebug 是采样型分析器拿的是统计样本Tracy 是钩子型工具直接在事件点上打断点做记录因此单体应用里的慢 SQL、死循环、文件写入阻塞基本一抓一个准而且生产环境也能安全挂着做日志兜底。本文写给两类人一类是被 Xdebug 的开销和配置折腾到想换方案的 PHPer另一类是搜Tracy 中文文档却发现资料零散、只能啃英文源码注释的人。我会从原理讲到配置参数再讲实际部署时最容易翻车的几个场景最后落到怎么把它当性能工具用。2. Tracy 的内部机制它凭什么能在不拖垮性能的前提下抓到瓶颈2.1 首次打开页面的蓝屏里到底藏了什么信息Tracy 渲染错误页面时并非简单打印堆栈。它的 Debugger::enable() 注册了一个自定义异常处理程序当 PHP 抛出 Throwable 时Tracy 会立即停止后续输出把异常对象转成一个专门的 BlueScreen 渲染器。这个渲染器做的事情比你想的多它会读取异常发生点对应的源码文件截取前后十几行代码并高亮出错行它会递归解析异常链上的每一个 previous 异常它还会检查所有已定义的类常量、全局变量和超全局数组——前提是你没有在生产环境关掉这个能力。普通开发者最容易忽略的是 BlueScreen 底部那个变量列表。Tracy 会把当前作用域内的所有局部变量按类型折叠展示数组和对象默认只展开前两层避免递归爆炸。字符串变量默认截断到 50 个字符但你可以通过Debugger::$maxLen调整。这个特性在排查数据从哪一步开始变脏时极其有用你不需要在代码里临时 var_dump只需在蓝屏页上点开变量看它的值在调用栈每一帧的形状。Tracy 还顺带接管了 PHP 的E_NOTICE和E_WARNING。默认情况下这些告警不会被当成异常弹出但如果开启了Debugger::$strictMode trueTracy 会把这些警告升级为异常抛出并触发蓝屏。这个开关在开发环境很爽因为 PHP 的 undefined index 这类问题在传统模式下会被数组的默认空值掩盖升级成异常后立刻现行。2.2 阻塞检测Tracy 定位页面卡了 3 秒的原理Tracy 最大的亮点其实是它的阻塞检测英文叫 stopped time detection原理非常朴素Tracy 在请求开始时记录一个时间戳然后在脚本执行的关键节点例如调用Debugger::timer()或点击 Bar 面板上的耗时信息时计算时间差。但它不是手动计时那么简单Tracy 的 Bar 右上角会显示一个总耗时数字这个数字来自$_SERVER[REQUEST_TIME_FLOAT]到当前时刻的差值但它会拆分出数据库查询、文件操作、curl 请求三类耗时。在 PHP 8 之后Tracy 还利用了 Fiber 和register_shutdown_function来做更精细的阻塞归因。具体套路是当脚本即将结束Tracy 检查总耗时是否超过 2 秒这个阈值来自Debugger::$maxExecutionTime默认 30 秒注意它是超时后告警阈值而不是脚本最大执行时间一旦超过就扫描当年注册的所有计时器把每个区间的耗时排名展示出来。这就解决了页面很慢但不知道慢在哪的核心痛点——SQL 慢你能直接看数据库日志但邮件发送阻塞、Redis 连接等待、HTTP 外部请求挂起这类问题Tracy 的阻塞检测能一张表列出来。更妙的是它生产环境的表现Xdebug 在生产环境因为性能开销太大基本不推荐开Tracy 阻塞检测的开销几乎可以忽略不计因为它只做时间戳比对和存储不采样、不堆栈回溯。所以在灰度环境开着 Tracy 做日志收集高危函数比如file_get_contents(https://...)卡了超过阈值就记一条阻塞日志是性价比很高的链路健康检查方案。2.3 内存峰值快照从哪里开始吃掉了 200MBTracy 的 Memory 面板展示的并非一个简单的memory_get_peak_usage()数值它会额外记录每次大块内存分配的位置。原理是通过memory_get_usage()在请求生命周期内以低频率采样默认是每 512KB 采样一次见Debugger::$memorySnapshotInterval把这些快照按时间排成序列最后在蓝屏或 Bar 面板里渲染成一条走势曲线。曲线图上会有几个标记点标着 memory_get_usage: 12.3MB at /app/src/Service/Export.php:45 这类信息。它的定位能力有限——因为采样只发生在 Tracy 自己调用的时刻不是 Zend MM 的完整堆分配追踪所以它不能像 Blackfire 那样告诉你具体哪个函数分配了多少字节。但在绝大多数场景下你只需要知道从第 200 行到第 300 行之间内存暴涨了 180MB就足够定位问题了。需要精确到函数级别时Tracy 还提供了Debugger::getBlueScreen()的自定义扩展但这个用得很少。3. 用 Composer 在现有 PHP 项目里落地 Tracy3.1 最小接入代码三行开启 DebuggerTracy 的安装是 Composer 单命令composer require tracy/tracy。装完后 Bootstrap 文件的接入代码极其简单但很多人因为文档不全不知道enable()的返回值该怎么处理。我先给一个最干净的最小示例?php // bootstrap.php require __DIR__ . /vendor/autoload.php; // 第二个参数是日志目录第三个参数是 IP 白名单留空表示不过滤 IP Tracy\Debugger::enable( Tracy\Detection::isDebug() ? Tracy\Debugger::Development : Tracy\Debugger::Production, __DIR__ . /logs/tracy );这段代码做了什么enable()的第一个参数传入运行模式Development模式会显示蓝屏错误页Production模式会把所有异常和错误写入日志目录并向访客展示一个风格简洁的 Server Error 静态页不是堆栈信息。第二个参数中的Detection::isDebug()是 Tracy 提供的一个探测开关它会依次检查环境变量TRACY_DEBUG、命令行参数和 IP 地址识别当前环境是本地回环地址还是局域网地址从而自动切换模式。如果你不想用这个探测完全可以写成$_SERVER[HTTP_HOST] localhost之类的条件核心就是让开发环境弹蓝屏、生产环境写日志。3.2 生产环境配置日志目录与 E_USER_NOTICE 策略很多从 Xdebug 转过来的用户会困惑Tracy 生产环境到底该怎么配。我的习惯是生产环境这样设置?php // config/tracy.php Tracy\Debugger::$logDirectory __DIR__ . /../storage/logs/tracy; Tracy\Debugger::$logSeverity E_WARNING | E_USER_NOTICE | E_DEPRECATED; Tracy\Debugger::$strictMode false; Tracy\Debugger::$email monitorexample.com; // 这个事件钩子很重要每一条日志都走这里可以接第三方告警 Tracy\Debugger::onLog[] function (array $message) { // $message [message ..., priority error] file_put_contents(/tmp/tracy-feed.log, json_encode($message) . PHP_EOL, FILE_APPEND); };$logSeverity决定哪些级别的错误会写入日志。注意E_USER_NOTICE在 PHP 8.0 之后被重新启用了7.4 时代用户级 notice 曾被弃用如果你的代码里用了trigger_error()的 E_USER_NOTICE 做业务告警必须在这里显式指定才会被记下来。onLog这个静态属性是数组你可以挂多个回调常见做法是往 Sentry 或钉钉机器人转发。这里有个关键点生产环境不要开strictMode否则 Tracy 会把所有 notice 升级成异常页面直接白屏这是新手最容易踩的坑。3.3 用命令行和日志刨析慢请求Tracy 还会接管 PHP 的register_shutdown_function当脚本因致命错误终止时自动写一条包含完整 URL、请求方法、IP 的日志。日志文件名格式是YYYY-MM-DD--error--hash.loghash 是请求摘要同一个错误重复发生时 hash 会一致方便聚合统计。排查慢请求时我一般会直接 grep 日志目录# 找出所有阻塞类日志阻塞时间单位是毫秒 grep -h execution time storage/logs/tracy/*.log | sort -t: -k4 -rn | head -20 # 查看某条日志对应的完整错误页面快照 # 每条日志头部的 hash 就是蓝屏页的文件标识重命名 .log 成 .html 后用浏览器打开最后一条命令是 Tracy 一个冷门但实用的功能Tracy\Debugger::$showBar设为 true 且处于生产模式时日志文件里不仅存文本还会附带一个序列化的 BlueScreen 快照块。你可以把 .log 复制一份改名成 .html浏览器直接打开能看到当时完整的调用栈和变量状态相当于给线上错误拍了张现场照片。4. Tracy 的必调参数清单从基础显示到性能监控的 5 个开关4.1 开发环境的参数搭配maxLen、maxDepth、showLocation刚上手 Tracy 的人最需要控制的是变量显示深度和字符串长度。默认maxDepth是 4 层maxLen是 80 个字符调试大数组或长 SQL 时明显不够用。以下是开发环境的推荐值?php Tracy\Debugger::$maxLen 4096; // 让长 SQL 和加密串完整显示 Tracy\Debugger::$maxDepth 5; // 保持默认也能用5 层足够看清嵌套结构 Tracy\Debugger::$showLocation true; // 错误页顶部显示 in /app/xxx.php:12 (called from ...) Tracy\Debugger::$sourcePath __DIR__ . /..; // 把绝对路径映射成相对路径蓝屏页更好读$showLocation配合$sourcePath是效率组合拳前者会在每条日志和错误信息旁追加调用来源后者把/var/www/html/app/src/Entity/User.php这种冗长路径压缩成app/src/Entity/User.php让蓝屏页的源码段落不再占满横向滚动条。另外$maxLen这个参数也影响dump()输出用Tracy\Debugger::dump($variable)时字符串会被截断到同样的长度。4.2 阻塞检测阈值和面板开关maxExecutionTime的玄学Debugger::$maxExecutionTime的默认值是 30 秒但这个值跟max_execution_timePHP 配置里的脚本超时完全无关它只是 Tracy 用来判断这条请求是否算慢的阈值。如果你开发的是后台报表接口30 秒很正常这时需要把这个值调高否则每个报表请求都触发记录日志日志目录会很快被塞满。?php // 对于 CLI 脚本比如队列消费阻塞检测阈值要单独照顾 if (PHP_SAPI cli) { Tracy\Debugger::$maxExecutionTime 120; } else { Tracy\Debugger::$maxExecutionTime 5; // Web 请求 5 秒没返回就是有问题 } Tracy\Debugger::$showBar true; // 控制页面底部是否显示面板默认开发环境才显示 Tracy\Debugger::$showConfig false; // 蓝屏页不显示 PHP 配置详情生产关掉避免信息泄露这里有个实践细节CLI 模式下的阻塞检测我没见过多少文档提过但它对排队列消息特别有用。队列消费的慢任务通过 Tracy 日志会精确记录是阻塞在file_get_contents还是sleep()省掉大量打点。4.3 性能面板的扩展方式自定义ITracyPanelTracy 的 Bar 面板本身提供 Console、Database、Memory、Time 等几个默认面板但它的架构允许你挂自己的面板。这个能力让它不只是调试器还能做成轻量性能看板。下面是一个记录当前请求中 Redis 调用次数和耗时的自定义面板用到的还是 Tracy 的静态属性注册方式?php use Tracy\IBarPanel; class RedisPanel implements IBarPanel { private array $queries []; private float $totalTime 0; public function log(string $command, float $ms): void { $this-queries[] [cmd $command, ms $ms]; $this-totalTime $ms; } public function getTab(): string { // 面板标签显示总耗时和次数即可 return span titleRedis QueriesRedis: . count($this-queries) . / . number_format($this-totalTime, 1) . ms/span; } public function getPanel(): string { // 展开后的长面板 $items ; foreach ($this-queries as $q) { $items . trtd . htmlspecialchars($q[cmd], ENT_QUOTES) . /td . td . $q[ms] . ms/td/tr; } return table classtracy--paneltheadtrthCommand/ththTime/th/tr/thead . tbody . $items . /tbody/table; } } // 在 bootstrap 里注册 Tracy\Debugger::getBar()-addPanel(new RedisPanel());注册方式注意是Debugger::getBar()-addPanel()不要用Debugger::addPanel()——后者已经移除。这段代码解决了 Xdebug 无法覆盖的领域Redis 这类外部调用的耗时统计比打开 Redis MONITOR 做网络抓包方便太多而且是按请求维度归类的不用自己拼时间戳。5. Tracy 部署避坑5 个最容易翻车的现场与排查办法5.1 现象页面底部不出现 Tracy Bar但错误日志正常原因Tracy 的 Bar 依赖 JavaScript而且它在/body前通过ob_start()往输出流里注入脚本。如果你的框架调用过session_start()后已经输出了 HTTP 头Tracy 还能正常工作但如果框架先调用了exit或者存在 buffer 层Tracy 的收尾脚本就来不及注入Bar 直接消失而日志和蓝屏仍然正常工作。解决确认框架没有调用ob_clean()把输出缓冲区清空或者在Debugger::enable()之前不要调用任何会 dispose buffer 的方法。常见做法是在框架的 bootstrap 文件最顶部就加载 Tracy不要等框架路由执行完再加载。5.2 现象页面上显示一大堆 JavaScript 错误Tracy 面板样式全乱了原因CSP内容安全策略头允许了self但没允许unsafe-inlineTracy 的注入是内联script和style被 CSP 拦截后面板脚本罢工错误信息页变成裸 HTML。解决开发环境的 CSP 头里临时加上unsafe-inline生产环境不开面板则没这个问题。如果你被 CSP 卡住了其实可以直接不依赖 Tracy 渲染使用Debugger::$showBar false然后通过$onLog钩子把错误推送到自己的日志系统。5.3 现象生产环境日志突然暴增每天几千个错误文件原因很多人直接把开发环境的$logSeverity复制到生产导致E_DEPRECATED这类无害提示全被记录。PHP 8 之后E_DEPRECATED在请求中很常见Tracy 默认又不做频率限制自然刷屏。解决我的生产配置只保留E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR。把E_DEPRECATED关闭。进阶做法是把E_DEPRECATED写入一个独立的 deprecated.log 文件用onLog回调区分优先级后再分发。5.4 现象Xdebug 和 Tracy 同时开页面卡死或明显变慢原因Xdebug 开启时每次函数调用都会触发 Xdebug 的堆栈记录Tracy 又额外做一次内存快照和计时两个钩子叠加导致耗时翻倍。尤其 Xdebug 的xdebug.modedebug模式对性能拖累巨大。解决用php -m检查扩展加载列表开发环境建议在 CLI 或 FPM 的 php.ini 里单独为 Xdebug 建一个配置目录php -d xdebug.modeoff启动 FPM 时才用 Tracy。我日常就是开发环境只开 Tracy需要分析性能才临时加载 Xdebug。5.5 现象Tracy 报告的错误行号与实际代码差一行原因PHP 的 opcache 缓存了已编译字节码错误行号取自 opcache 缓存但你的源文件已经被编辑器改了行号错位属于缓存陈旧。Tracy 本身没问题是 opcache 的opcache.validate_timestamps被关了。解决开发环境设置opcache.validate_timestamps1且opcache.revalidate_freq0或者直接sudo systemctl reload php-fpm清掉 opcache。如果你在用 Docker修改代码后记得重新构建容器层否则即使 reload FPM 也可能命中镜像里旧代码缓存。6. 把 Tracy 当性能工具用慢查询定位、阻塞分析和面板扩展实践6.1 用 Tracy 的数据库面板定位 N1 查询Tracy 在集成了 PDO 或 mysqli 的情况下会自动记录每条 SQL数据库面板会列出查询全文、耗时和调用来源。但这个面板默认只统计走 PDO 的查询如果框架用了查询构造器但底层没有绑定 PDO 事件Tracy 是感知不到的需要手动在数据库连接层挂钩子。这里分享一个粗糙但有效的动手方案用一个中间件包一层 PDO 实例在query()、execute()方法里调用Tracy\Debugger::timer(sql: . md5($sql))来追加耗时。?php class TracyPdo extends PDO { public function query(string $query, ?int $fetchMode null, mixed ...$fetchModeArgs): PDOStatement|false { $start microtime(true); $stmt parent::query($query, $fetchMode, ...$fetchModeArgs); Tracy\Debugger::timer(sql: . $query); // 注意 timer 名字不能重复需用 query 做 key error_log(sql . $query . took . (microtime(true) - $start) . s); return $stmt; } }这个类只是示例它不是完整的 Tracy 集成但它展示了思路Tracy 提供的是计时原语你要做的是把业务语句包裹进去。真正实践时建议直接使用框自带的 Tracy 桥接层例如 Laravel 的 Debugbar、Nette 的 Tracy 扩展因为它们已经把 SQL 采集做到了事件级。6.2 网络请求慢但后端执行快阻塞检测的另一个视角有时候用户反馈页面要 8 秒才加载完但 Tracy Bar 显示后端执行只要 500ms瓶颈在网络传输或前端渲染。Tracy 的阻塞检测也能帮忙它记录的request duration是从$_SERVER[REQUEST_TIME_FLOAT]到脚本结束的时间这个时间包含 FPM 等待时间。对比一下 Nginx access log 里的request_timeNginx 视角的完整耗时和 Tracy 的request duration两者差值就是网络传输和浏览器等待的时间——注意Nginx 的request_time到脚本结束就停了剩余的是纯网络时间。用这个差值做前后端性能拆分是一个低成本方案Nginx access log 里加log_format main $request_timeTracy 日志里取 request duration两台机器或两个文件一对就出来了。上生产环境时配合Debugger::$showBar false依然会记录堆栈所以这套方案可以在线上跑不需要关掉 Tracy。6.3 自定义错误页面与邮件告警的进阶用法生产环境 Tracy 默认展示的 Server Error 页面只能改模板文件。但很多人不知道Debugger::getBlueScreen()-addPanel()允许你往蓝屏页追加自定义区块可以塞当时的关键业务上下文。举一个真实场景支付回调接口出错时光看异常堆栈不知道是哪笔订单、金额多少。这个信息在调用栈里拿不到必须手动在异常抛出点Tracy\Debugger::log(orderId: 12345, amount: 88.00)但这样会把日志跟异常分开。更优的做法是监听onLog事件在里面检查是否出现了你的业务异常类是就把额外上下文拼到 message 里再转发出去?php Tracy\Debugger::onLog[] function (array $message) { $data $message[message]; if ($data instanceof PaymentException) { // 追加我们的上下文如订单号、用户ID $data-context [order_id $data-getOrderId()]; // 或者直接在这里推送企业微信机器人 } };这个能力把 Tracy 从错误展示工具升级成了告警路由器。配合第三方的 PHP SDK可以把线上 PHP 错误的毛刺比如 Redis 偶发连接超时汇聚进告警群而不只是被动等人反馈。我自己上线前一定会把onLog接好这样线上冒烟测试阶段就能从日志中心看到每一条异常比打开文件一个个翻日志高效得多。Tracy 这套工具我前后用了两年多最有价值的习惯是每个项目进 bootstrap 的第一件事就是配好 Tracy 的数据目录和日志切割。遇到页面慢但找不到原因时我第一个看的永远不是框架自身的 debug 工具而是 Tracy 的耗时面板——它不会撒谎。希望今天这篇能让你少走弯路把 Tracy 的潜力用透。本文还有配套的精品资源点击获取