2012 年刚开始学习 PHP,那个时候的 PHP 应用很简单,没有太多复杂的设计模式,像依赖注入,工厂模式这些还几乎没有,Reflection API 那时也才刚出来,一个 PHP 应用就是一些包了前端代码的脚本文件,正是因为 PHP 的简单可靠,学习成本极低,那几年 PHP 红极一时。你可以很快速的看懂别人写的 PHP 代码,不管他写的到底有多烂。我那个时候刚毕业凭借对 PHP 的熟练掌握可以轻松在三四线城市拿到七八千工资,加上对当时各大框架(Tp、Ci、Yii、Cphalcon)源码研究十分透彻,自认为已经摸到 PHP 的天花板。后面换城市写 PHP 逐渐少了很多,用的比较多的是自己基于 Swoole 写的一套框架,得心应手。
不过现在所在的公司 PHP 占据了 90% 的份额,基本都是 Laravel/Lumen 应用,这让持续在做性能优化的我变得很难。Laravel 中使用了大量的设计模式、动态注入,很多时候只有在运行时才能知道能知道具体的对象实例,想要仅通过看源码确定服务运行状态有一定的难度。所以在做性能分析时火焰图和可视化调用栈很重要,可以非常直观准确看到各种耗时。
在 PHP 中我们可以关注两个层次的火焰图:PHP 内核与 PHP Zend 调用栈。前者是抓取系统调用的耗时火焰图,后者是 Zend VM 的耗时火焰图,下面就这两种火焰图生成做分享。
运行环境
操作系统(Arch Linux)
# neofech
-` russell@T14
.o+` -----------
`ooo/ OS: Arch Linux x86_64
`+oooo: Host: NBLK-WAX9X M1040
`+oooooo: Kernel: 5.14.14-arch1-1
-+oooooo+: Uptime: 1 hour, 15 mins
`/:-:++oooo+: Packages: 852 (pacman)
`/++++/+++++++: Shell: zsh 5.8
`/++++++++++++++: Resolution: 1920x1080
`/+++ooooooooooooo/` DE: Plasma 5.23.2
./ooosssso++osssssso+` WM: KWin
.oossssso-````/ossssss+` WM Theme: Breeze
-osssssso. :ssssssso. Theme: Breeze Light [Plasma], Breeze [GTK2/3]
:osssssss/ osssso+++. Icons: Uos-fulldistro-icons [Plasma], Uos-fulldi
/ossssssss/ +ssssooo/- Terminal: konsole
`/ossssso+/:- -:/+osssso+- Terminal Font: Hack 10
`+sso+:-` `.-/+oso: CPU: AMD Ryzen 5 3500U with Radeon Vega Mobile G
`++:. `-/+/ GPU: AMD ATI 03:00.0 Picasso
.` `/ Memory: 1928MiB / 6880MiB
PHP 版本
# php -v
PHP 7.4.23 (cli) (built: Sep 19 2021 12:07:04) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
with Xdebug v3.1.1, Copyright (c) 2002-2021, by Derick Rethans
这个没啥要求,只要不是 5.6 版本以下就行。
Laravel 版本
# php artisan -V
Laravel Framework 8.61.0
perf 版本
# perf -v
perf version 5.14.g7d2a07b76933
下载 flamegraph
git clone https://github.com/brendangregg/FlameGraph.git
cd FlameGraph
# 这一步是将 FlameGraph 注入到可执行文件系统路径中,下面就可以直接执行里面的工具了
export PATH=`pwd`:$PATH
这个工具是关键,通过 perf 抓取系统调用耗时然后生成火焰图。
抓取 PHP 内核火焰图
首先找到 PHP 进程 ID,然后通过以下命令进行抓取
# 抓取 PID 为 7959 进程 60 秒
perf record -F 99 -p 7959 -g -- sleep 30
导出堆栈
perf script > out.perf
生成 Fold stacks 文件
stackcollapse-perf.pl out.perf > out.folded
生成火焰图
flamegraph.pl out.folded > php-zend-flame-graph.svg
看一下效果,svg 格式图片,可以点击火焰条进行过滤(打开新 tab 查看):
抓取 Zend VM 耗时火焰图
该火焰图生成依赖 xdebug,不同的 xdebug 版本使用的配置不一样.配置说明可以直接看 xdebug 包配置文件或者上 Github 看。
Xdebug3 配置:
zend_extension=xdebug
xdebug.mode=trace
xdebug.start_with_request=trigger
xdebug.trigger_value=StartProfileForMe
xdebug.trace_output_name = xdebug.trace.%t.%s
xdebug.output_dir = /tmp
xdebug.trace_format=1
Xdebug2 配置:
xdebug.trace_output_name = xdebug.trace.%t.%s
xdebug.trace_enable_trigger = 1
xdebug.trace_output_dir = /tmp
xdebug.trace_enable_trigger_value = ""
xdebug.trace_format=1
请求接口url,获取php堆栈数据 curl http://youdomain.com?XDEBUG_TRACE=
。trace 文件在 /tmp
目录下,以 xdebug.tarce 开头。
转换堆栈数据(如果 stackcollapse-xdebug.php 不存在,注意上面 下载 flamegraph 介绍):
stackcollapse-xdebug.php /tmp/xdebug.trace.1635786267._data_workspaces_study_laravel-framework_laravel_server_php.xt
如果 xdebug trace 文件为 gz
格式,转换一下:
# 参数 `-dk` 表示解压并保留源文件。
gzip -dk /tmp/xdebug.trace.1635786267._data_workspaces_study_laravel-framework_laravel_server_php.xt.gz
转换堆栈数据
stackcollapse-xdebug.php xdebug.trace.1635786267._data_workspaces_study_laravel-framework_laravel_server_php.xt > out.folded
生成火焰图
flamegraph.pl out.folded > laravel-flame-graph.svg
看一下效果,svg 格式图片,可以点击火焰条进行过滤(打开新 tab 查看):