Reli 是一个用PHP编写的采样分析器(或VM状态检查器)。它可以从进程外部读取有关运行PHP脚本的信息。它是一个独立的CLI工具,因此目标程序不需要任何修改。
能做什么?
- 检测和可视化PHP脚本中的瓶颈。它不仅提供函数级的分析,而且还提供行级或操作码级的解析
- 即使调用了很多快速函数,也不会产生累积开销的分析,因为这是一个采样分析器
- 调查bug或性能故障的原因。即使PHP脚本处于无法解释的无响应状态,您也可以使用它来找出它在内部做什么。
- 查找内存瓶颈或内存泄漏
如何工作?
- 解析解释器的ELF二进制文件
- 从 /proc//maps读取内存映射
- 通过FFI使用ptrace(2)和process_vm_readv(2)来读取外部进程的内存
- 分析PHP虚拟机(又名Zend Engine)中的内部数据结构
需求
- PHP 8.1+(NTS/ZTS)
- 64bit Linux x86_64
- 必须启用FFI扩展(编译增加配置–with-ffi)
- 必须启用PCNTL扩展
安装
1 2 3 4 | git clone git@github.com:reliforp /reli-prof .git cd reli-prof /usr/local/php-8 .2.14 /bin/php /home/www/build/composer .phar install /usr/local/php-8 .2.14 /bin/php . /reli |
使用
1、获取跟踪配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | www@ShaoBoWan:~ /build/reli-prof $ /usr/local/php-8 .2.14 /bin/php . /reli inspector:trace --help Description: periodically get call trace from an outer process or thread Usage: inspector:trace [options] [--] [ [...]] Arguments: cmd command to execute as a target: either pid (via -p /--pid ) or cmd must be specified args command line arguments for cmd Options: -p, --pid=PID process id -d, --depth[=DEPTH] max depth -s, -- sleep -ns[=SLEEP-NS] nanoseconds between traces (default: 1000 * 1000 * 10) -r, --max-retries[=MAX-RETRIES] max retries on contiguous errors of read (default: 10) -S, --stop-process[=STOP-PROCESS] stop the target process while reading its trace (default: off) --php-regex[=PHP-REGEX] regex to find the php binary loaded in the target process --libpthread-regex[=LIBPTHREAD-REGEX] regex to find the libpthread.so loaded in the target process --php-version[=PHP-VERSION] php version (auto|v7[0-4]|v8[0123]) of the target (default: auto) --php-path[=PHP-PATH] path to the php binary (only needed in tracing chrooted ZTS target) --libpthread-path[=LIBPTHREAD-PATH] path to the libpthread.so (only needed in tracing chrooted ZTS target) -t, --template[=TEMPLATE] template name (phpspy|phpspy_with_opcode|json_lines) (default: phpspy) -o, --output=OUTPUT path to write output from this tool (default: stdout) -h, --help Display help for the given command . When no command is given display help for the list command -q, --quiet Do not output any message -V, --version Display this application version --ansi|--no-ansi Force (or disable --no-ansi) ANSI output -n, --no-interaction Do not ask any interactive question - v |vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug |
2、守护模式启动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | www@ShaoBoWan:~ /build/reli-prof $ /usr/local/php-8 .2.14 /bin/php . /reli inspector:daemon --help Description: concurrently get call traces from processes whose command -lines match a given regex Usage: inspector:daemon [options] Options: -P, --target-regex=TARGET-REGEX regex to find target processes which have matching command -line (required) -T, --threads[=THREADS] number of workers (default: 8) -d, --depth[=DEPTH] max depth -s, -- sleep -ns[=SLEEP-NS] nanoseconds between traces (default: 1000 * 1000 * 10) -r, --max-retries[=MAX-RETRIES] max retries on contiguous errors of read (default: 10) -S, --stop-process[=STOP-PROCESS] stop the target process while reading its trace (default: off) --php-regex[=PHP-REGEX] regex to find the php binary loaded in the target process --libpthread-regex[=LIBPTHREAD-REGEX] regex to find the libpthread.so loaded in the target process --php-version[=PHP-VERSION] php version (auto|v7[0-4]|v8[0123]) of the target (default: auto) --php-path[=PHP-PATH] path to the php binary (only needed in tracing chrooted ZTS target) --libpthread-path[=LIBPTHREAD-PATH] path to the libpthread.so (only needed in tracing chrooted ZTS target) -t, --template[=TEMPLATE] template name (phpspy|phpspy_with_opcode|json_lines) (default: phpspy) -o, --output=OUTPUT path to write output from this tool (default: stdout) -h, --help Display help for the given command . When no command is given display help for the list command -q, --quiet Do not output any message -V, --version Display this application version --ansi|--no-ansi Force (or disable --no-ansi) ANSI output -n, --no-interaction Do not ask any interactive question - v |vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug |
案例
这里跟踪一个webman框架进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | www@ShaoBoWan:~ /build/webman01 $ php start.php status Workerman[start.php] status ----------------------------------------------GLOBAL STATUS---------------------------------------------------- Workerman version:4.0.37 PHP version:8.0.7 start time :2024-01-01 00:19:33 run 0 days 0 hours load average: 0.07, 0.12, 0.98 event-loop:WorkermanEventsEvent 2 workers 3 processes worker_name exit_status exit_count webman 0 0 monitor 0 0 ----------------------------------------------PROCESS STATUS--------------------------------------------------- pid memory listening worker_name connections send_fail timers total_request qps status 2184553 0.65M http: //0 .0.0.0:8787 webman 0 0 0 0 0 [idle] 2184554 0.65M http: //0 .0.0.0:8787 webman 0 0 0 0 0 [idle] 2184555 0.57M none monitor 0 0 1 0 0 [idle] ----------------------------------------------PROCESS STATUS--------------------------------------------------- Summary 0M - - 0 0 1 0 0 [Summary] |
1、跟踪脚本
1 2 3 4 5 6 7 8 9 | /usr/local/php-8 .2.14 /bin/php . /reli i:trace -- php -r "fgets(STDIN);" 0 fgets :-1 1 <main> Command line code:1 0 fgets :-1 1 <main> Command line code:1 ...< /main >< /main > |
2、附加到正在运行的进程
1 2 3 4 5 6 7 8 9 10 11 12 13 | sudo /usr/local/php-8 .2.14 /bin/php . /reli i:trace -p 2184553 [ sudo ] password for www: 0 EventBase::loop :-1 1 WorkermanEventsEvent::loop /home/www/build/webman01/vendor/workerman/workerman/Events/Event .php:193 2 WorkermanWorker::run /home/www/build/webman01/vendor/workerman/workerman/Worker .php:2435 3 WorkermanWorker::forkOneWorkerForLinux /home/www/build/webman01/vendor/workerman/workerman/Worker .php:1555 4 WorkermanWorker::forkWorkersForLinux /home/www/build/webman01/vendor/workerman/workerman/Worker .php:1397 5 WorkermanWorker::forkWorkers /home/www/build/webman01/vendor/workerman/workerman/Worker .php:1371 6 WorkermanWorker::runAll /home/www/build/webman01/vendor/workerman/workerman/Worker .php:549 7 <main> /home/www/build/webman01/start .php:112 ...< /main > |
3、获取进程内存地址
1 2 3 | sudo /usr/local/php-8 .2.14 /bin/php . /reli i:eg -p 2184553 0x55e17fe1b1a0 |
4、跟踪当前进程正在执行的操作码
如果用户想分析一个真正的CPU受限的应用程序,那么他或她不仅想知道哪一行是慢的,还想知道操作码是什么。在这种情况下,使用 –template=phpspy_with_opcode 与 inspector:trace 或 inspector:daemon 。
1 | sudo /usr/local/php-8.2.14/bin/php ./reli i:trace --template=phpspy_with_opcode -p 2184553 |
输出如下所示
1 2 3 4 5 6 7 8 9 10 | 0 EventBase::loop :-1 1 WorkermanEventsEvent::loop /home/www/build/webman01/vendor/workerman/workerman/Events/Event .php:193:ZEND_DO_FCALL 2 WorkermanWorker::run /home/www/build/webman01/vendor/workerman/workerman/Worker .php:2435:ZEND_DO_FCALL 3 WorkermanWorker::forkOneWorkerForLinux /home/www/build/webman01/vendor/workerman/workerman/Worker .php:1555:ZEND_DO_FCALL 4 WorkermanWorker::forkWorkersForLinux /home/www/build/webman01/vendor/workerman/workerman/Worker .php:1397:ZEND_DO_FCALL 5 WorkermanWorker::forkWorkers /home/www/build/webman01/vendor/workerman/workerman/Worker .php:1371:ZEND_DO_FCALL 6 WorkermanWorker::runAll /home/www/build/webman01/vendor/workerman/workerman/Worker .php:549:ZEND_DO_FCALL 7 <main> /home/www/build/webman01/start .php:112:ZEND_DO_FCALL ...< /main > |
当前执行的操作码成为调用堆栈的第一帧。因此,像火焰图这样的可视化跟踪可以显示操作码的使用情况。
出于信息目的,执行操作码也被添加到调用帧的每一端。除了第一个帧,函数调用的操作码(如ZEND_DO_FCALL)应该出现在那里。如果在目标流程中启用了JIT,则此信息可能会稍微不准确。
火焰图
火焰图是由 Brendan Gregg 发明的一种可视化方法,用于展示某一种系统资源或性能指标,是如何定量分布在目标软件里所有的代码路径上的。
- 系统资源指标可以是 CPU 时间、off-CPU 时间、内存使用、硬盘使用、延时等任何其他你能想到的资源。
- 代码路径 可以定义为目标软件代码中的调用栈轨迹。调用栈轨迹通常是由一组函数调用帧组成的,通常出现在 GDB 命令 bt 的输出中,以及 Python 或 Java 程序的异常错误信息当中。比如下面是一个 PHP 调用栈轨迹的样例:
1 2 3 4 5 6 7 8 9 | C:ngx_http_lua_ngx_timer_at at cache.lua:43 cache.lua:record_timing router.lua:338 router.lua:route v2_routing.lua:1214 v2_routing.lua:route access_by_lua.lua:130 |
1、Psalm 静态检查
Psalm 是一个用于PHP的开源静态分析工具,可以帮助识别代码中明显的和难以识别的bug。Psalm既适用于大型遗留代码库,也适用于小型现代代码库,可以帮助防止绝大多数类型相关的runtime 错误,并且能够利用其他语言中流行的安全编码模式。Psalm还可以自动修复发现的许多错误以改进代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | cd /home/www/build/reli-prof sudo /usr/local/php-8 .2.14 /bin/php . /reli i:trace -o traces -- /usr/local/php-8 .2.14 /bin/php . /vendor/bin/psalm .phar --no-cache Install the opcache extension to make use of JIT on PHP 8.0+ for a 20%+ performance boost! Target PHP version: 8.1 (inferred from composer.json) Enabled extensions: ffi. Scanning files... Analyzing files... ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 60 / 421 (14%) ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 120 / 421 (28%) ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 180 / 421 (42%) ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 240 / 421 (57%) ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 300 / 421 (71%) ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 360 / 421 (85%) ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 420 / 421 (99%) ░ ------------------------------ No errors found! ------------------------------ Checks took 7.42 seconds and used 134.539MB of memory Psalm was able to infer types for 99.9825% of the codebase |
以上会输出一个traces文件,大概内容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 0 array_merge :-1 1 PsalmInternalAnalyzerStatementsExpressionSimpleTypeInferer::handleArrayItem phar: ///home/www/build/reli-prof/vendor/psalm/phar/psalm .phar /src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer .php:398 2 PsalmInternalAnalyzerStatementsExpressionSimpleTypeInferer::inferArrayType phar: ///home/www/build/reli-prof/vendor/psalm/phar/psalm .phar /src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer .php:292 3 PsalmInternalAnalyzerStatementsExpressionSimpleTypeInferer::infer phar: ///home/www/build/reli-prof/vendor/psalm/phar/psalm .phar /src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer .php:210 4 PsalmInternalPhpVisitorReflectorClassLikeNodeScanner::visitClassConstDeclaration phar: ///home/www/build/reli-prof/vendor/psalm/phar/psalm .phar /src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner .php:760 5 PsalmInternalPhpVisitorReflectorClassLikeNodeScanner::start phar: ///home/www/build/reli-prof/vendor/psalm/phar/psalm .phar /src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner .php:451 6 PsalmInternalPhpVisitorReflectorVisitor::enterNode phar: ///home/www/build/reli-prof/vendor/psalm/phar/psalm .phar /src/Psalm/Internal/PhpVisitor/ReflectorVisitor .php:116 7 _HumbugBox427cd2d3980bPhpParserNodeTraverser::traverseArray phar: ///home/www/build/reli-prof/vendor/psalm/phar/psalm .phar /vendor/nikic/php-parser/lib/PhpParser/NodeTraverser .php:176 8 _HumbugBox427cd2d3980bPhpParserNodeTraverser::traverseNode phar: ///home/www/build/reli-prof/vendor/psalm/phar/psalm .phar /vendor/nikic/php-parser/lib/PhpParser/NodeTraverser .php:105 9 _HumbugBox427cd2d3980bPhpParserNodeTraverser::traverseArray phar: ///home/www/build/reli-prof/vendor/psalm/phar/psalm .phar /vendor/nikic/php-parser/lib/PhpParser/NodeTraverser .php:196 10 _HumbugBox427cd2d3980bPhpParserNodeTraverser::traverse phar: ///home/www/build/reli-prof/vendor/psalm/phar/psalm .phar /vendor/nikic/php-parser/lib/PhpParser/NodeTraverser .php:85 11 PsalmInternalScannerFileScanner::scan phar: ///home/www/build/reli-prof/vendor/psalm/phar/psalm .phar /src/Psalm/Internal/Scanner/FileScanner .php:51 12 PsalmInternalCodebaseScanner::scanFile phar: ///home/www/build/reli-prof/vendor/psalm/phar/psalm .phar /src/Psalm/Internal/Codebase/Scanner .php:398 13 PsalmInternalCodebaseScanner::scanAPath phar: ///home/www/build/reli-prof/vendor/psalm/phar/psalm .phar /src/Psalm/Internal/Codebase/Scanner .php:547 14 PsalmInternalCodebaseScanner::scanFilePaths phar: ///home/www/build/reli-prof/vendor/psalm/phar/psalm .phar /src/Psalm/Internal/Codebase/Scanner .php:310 15 PsalmInternalCodebaseScanner::scanFiles phar: ///home/www/build/reli-prof/vendor/psalm/phar/psalm .phar /src/Psalm/Internal/Codebase/Scanner .php:220 16 PsalmConfig::visitComposerAutoloadFiles phar: ///home/www/build/reli-prof/vendor/psalm/phar/psalm .phar /src/Psalm/Config .php:1840 17 PsalmInternalAnalyzerProjectAnalyzer::visitAutoloadFiles phar: ///home/www/build/reli-prof/vendor/psalm/phar/psalm .phar /src/Psalm/Internal/Analyzer/ProjectAnalyzer .php:249 18 PsalmInternalAnalyzerProjectAnalyzer::check phar: ///home/www/build/reli-prof/vendor/psalm/phar/psalm .phar /src/Psalm/Internal/Analyzer/ProjectAnalyzer .php:335 19 PsalmInternalCliPsalm::run phar: ///home/www/build/reli-prof/vendor/psalm/phar/psalm .phar /src/Psalm/Internal/Cli/Psalm .php:272 20 <main> phar: ///home/www/build/reli-prof/vendor/psalm/phar/psalm .phar /psalm :7 21 <main> /home/www/build/reli-prof/vendor/psalm/phar/psalm .phar:14 22 <main> /home/www/build/reli-prof/vendor/bin/psalm .phar:119< /main >< /main >< /main > |
3、生成火焰图
1 | sudo /usr/local/php-8.2.14/bin/php ./reli c:flamegraph < traces >flame.svg |
- 1.
使用浏览器打开预览flame.svg 图片
下来生成一个webman的
1 2 3 4 5 6 7 8 9 10 | Workerman[start.php] status ----------------------------------------------GLOBAL STATUS---------------------------------------------------- Workerman version:4.1.9 PHP version:7.4.16 start time :2023-12-22 22:56:56 run 9 days 3 hours load average: 0.04, 0.03, 0.09 event-loop:WorkermanEventsSelect ----------------------------------------------PROCESS STATUS--------------------------------------------------- pid memory listening worker_name connections send_fail timers total_request qps status 15456 16.42M http: //0 .0.0.0:7788 webman 0 0 3 939 0 [idle] ----------------------------------------------PROCESS STATUS--------------------------------------------------- Summary 131M - - 0 0 28 9191 0 [Summary] |
pid = 15456
1 2 3 | sudo /usr/local/php-8 .2.14 /bin/php . /reli i:trace -p 15456 -o traces-15456 -- /usr/local/php-8 .2.14 /bin/php . /vendor/bin/psalm .phar --no-cache sudo /usr/local/php-8 .2.14 /bin/php . /reli c:flamegraph flame-15456.svg |
一个简单Workerman火焰图到手啦!
其他
google-chrome安装(非必要)
下载
1 2 3 4 5 6 7 8 9 10 11 | $ wget https: //dl .google.com /linux/direct/google-chrome-stable_current_amd64 .deb --2023-12-31 10:43:53-- https: //dl .google.com /linux/direct/google-chrome-stable_current_amd64 .deb Resolving dl.google.com (dl.google.com)... 120.253.253.225 Connecting to dl.google.com (dl.google.com)|120.253.253.225|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 104953176 (100M) [application /x-debian-package ] Saving to: ‘google-chrome-stable_current_amd64.deb' google-chrome-stable_current_amd64.deb 100%[==============================================================================================================================>] 100.09M 11.9MB /s in 7.4s 2023-12-31 10:44:00 (13.5 MB /s ) - ‘google-chrome-stable_current_amd64.deb' saved [104953176 /104953176 ] |
安装
1 | sudo apt install . /google-chrome-stable_current_amd64 .deb |
FFI
PHP编译安装启用FFI扩展(编译增加配置–with-ffi)时出现错误No package ‘libffi’ found,检查后发现是因为缺少libffi库文件,利用以下命令安装,解决问题:
1 | sudo apt install libffi-dev |
proc_open 无权限问题
1 2 | PHP Warning: proc_open(): Exec failed: Permission denied in /home/www/build/reli-prof/src/Command/Converter/FlameGraphCommand .php on line 44 PHP Warning: proc_open(): Exec failed: Permission denied in /home/www/build/reli-prof/src/Command/Converter/FlameGraphCommand .php on line 55 |
赋予reli-prof目录执行权限
1 | sudo chmod 0775 -R /home/www/build/reli-prof |
到此这篇关于PHP使用Reli分析性能,生成PHP性能火焰图的文章就介绍到这了,更多相关PHP使用Reli分析性能内容请搜索IT俱乐部以前的文章或继续浏览下面的相关文章希望大家以后多多支持IT俱乐部!