Reli:PHP性能分析利器,生成PHP性能火焰圖
Reli 是一個(gè)用PHP編寫的采樣分析器(或VM狀態(tài)檢查器)。它可以從進(jìn)程外部讀取有關(guān)運(yùn)行PHP腳本的信息。它是一個(gè)獨(dú)立的CLI工具,因此目標(biāo)程序不需要任何修改。
能做什么?
- 檢測(cè)和可視化PHP腳本中的瓶頸。它不僅提供函數(shù)級(jí)的分析,而且還提供行級(jí)或操作碼級(jí)的解析
- 即使調(diào)用了很多快速函數(shù),也不會(huì)產(chǎn)生累積開銷的分析,因?yàn)檫@是一個(gè)采樣分析器
- 調(diào)查bug或性能故障的原因。即使PHP腳本處于無法解釋的無響應(yīng)狀態(tài),您也可以使用它來找出它在內(nèi)部做什么。
- 查找內(nèi)存瓶頸或內(nèi)存泄漏
如何工作?
- 解析解釋器的ELF二進(jìn)制文件
- 從 /proc/<pid>/maps讀取內(nèi)存映射
- 通過FFI使用ptrace(2)和process_vm_readv(2)來讀取外部進(jìn)程的內(nèi)存
- 分析PHP虛擬機(jī)(又名Zend Engine)中的內(nèi)部數(shù)據(jù)結(jié)構(gòu)
需求
- PHP 8.1+(NTS/ZTS)
- 64bit Linux x86_64
- 必須啟用FFI擴(kuò)展(編譯增加配置--with-ffi)
- 必須啟用PCNTL擴(kuò)展
安裝
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、獲取跟蹤配置
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] [--] [<cmd> [<args>...]]
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、守護(hù)模式啟動(dòng)
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
案例
這里跟蹤一個(gè)webman框架進(jìn)程
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:\Workerman\Events\Event
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、跟蹤腳本
/usr/local/php-8.2.14/bin/php ./reli i:trace -- php -r "fgets(STDIN);"
0 fgets <internal>:-1
1 <main> Command line code:1
0 fgets <internal>:-1
1 <main> Command line code:1
...
2、附加到正在運(yùn)行的進(jìn)程
sudo /usr/local/php-8.2.14/bin/php ./reli i:trace -p 2184553
[sudo] password for www:
0 EventBase::loop <internal>:-1
1 Workerman\Events\Event::loop /home/www/build/webman01/vendor/workerman/workerman/Events/Event.php:193
2 Workerman\Worker::run /home/www/build/webman01/vendor/workerman/workerman/Worker.php:2435
3 Workerman\Worker::forkOneWorkerForLinux /home/www/build/webman01/vendor/workerman/workerman/Worker.php:1555
4 Workerman\Worker::forkWorkersForLinux /home/www/build/webman01/vendor/workerman/workerman/Worker.php:1397
5 Workerman\Worker::forkWorkers /home/www/build/webman01/vendor/workerman/workerman/Worker.php:1371
6 Workerman\Worker::runAll /home/www/build/webman01/vendor/workerman/workerman/Worker.php:549
7 <main> /home/www/build/webman01/start.php:112
...
3、獲取進(jìn)程內(nèi)存地址
sudo /usr/local/php-8.2.14/bin/php ./reli i:eg -p 2184553
0x55e17fe1b1a0
4、跟蹤當(dāng)前進(jìn)程正在執(zhí)行的操作碼
如果用戶想分析一個(gè)真正的CPU受限的應(yīng)用程序,那么他或她不僅想知道哪一行是慢的,還想知道操作碼是什么。在這種情況下,使用 --template=phpspy_with_opcode 與 inspector:trace 或 inspector:daemon 。
sudo /usr/local/php-8.2.14/bin/php ./reli i:trace --template=phpspy_with_opcode -p 2184553
輸出如下所示:
0 EventBase::loop <internal>:-1
1 Workerman\Events\Event::loop /home/www/build/webman01/vendor/workerman/workerman/Events/Event.php:193:ZEND_DO_FCALL
2 Workerman\Worker::run /home/www/build/webman01/vendor/workerman/workerman/Worker.php:2435:ZEND_DO_FCALL
3 Workerman\Worker::forkOneWorkerForLinux /home/www/build/webman01/vendor/workerman/workerman/Worker.php:1555:ZEND_DO_FCALL
4 Workerman\Worker::forkWorkersForLinux /home/www/build/webman01/vendor/workerman/workerman/Worker.php:1397:ZEND_DO_FCALL
5 Workerman\Worker::forkWorkers /home/www/build/webman01/vendor/workerman/workerman/Worker.php:1371:ZEND_DO_FCALL
6 Workerman\Worker::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
...
當(dāng)前執(zhí)行的操作碼成為調(diào)用堆棧的第一幀。因此,像火焰圖這樣的可視化跟蹤可以顯示操作碼的使用情況。
出于信息目的,執(zhí)行操作碼也被添加到調(diào)用幀的每一端。除了第一個(gè)幀,函數(shù)調(diào)用的操作碼(如ZEND_DO_FCALL)應(yīng)該出現(xiàn)在那里。如果在目標(biāo)流程中啟用了JIT,則此信息可能會(huì)稍微不準(zhǔn)確。
火焰圖
火焰圖是由 Brendan Gregg 發(fā)明的一種可視化方法,用于展示某一種系統(tǒng)資源或性能指標(biāo),是如何定量分布在目標(biāo)軟件里所有的代碼路徑上的。
- 系統(tǒng)資源指標(biāo)可以是 CPU 時(shí)間、off-CPU 時(shí)間、內(nèi)存使用、硬盤使用、延時(shí)等任何其他你能想到的資源。
- 代碼路徑 可以定義為目標(biāo)軟件代碼中的調(diào)用棧軌跡。調(diào)用棧軌跡通常是由一組函數(shù)調(diào)用幀組成的,通常出現(xiàn)在 GDB 命令 bt 的輸出中,以及 Python 或 Java 程序的異常錯(cuò)誤信息當(dāng)中。比如下面是一個(gè) PHP 調(diào)用棧軌跡的樣例:
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 靜態(tài)檢查
Psalm 是一個(gè)用于PHP的開源靜態(tài)分析工具,可以幫助識(shí)別代碼中明顯的和難以識(shí)別的bug。Psalm既適用于大型遺留代碼庫,也適用于小型現(xiàn)代代碼庫,可以幫助防止絕大多數(shù)類型相關(guān)的runtime 錯(cuò)誤,并且能夠利用其他語言中流行的安全編碼模式。Psalm還可以自動(dòng)修復(fù)發(fā)現(xiàn)的許多錯(cuò)誤以改進(jìn)代碼。
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
以上會(huì)輸出一個(gè)traces文件,大概內(nèi)容如下
0 array_merge <internal>:-1
1 Psalm\Internal\Analyzer\Statements\Expression\SimpleTypeInferer::handleArrayItem phar:///home/www/build/reli-prof/vendor/psalm/phar/psalm.phar/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php:398
2 Psalm\Internal\Analyzer\Statements\Expression\SimpleTypeInferer::inferArrayType phar:///home/www/build/reli-prof/vendor/psalm/phar/psalm.phar/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php:292
3 Psalm\Internal\Analyzer\Statements\Expression\SimpleTypeInferer::infer phar:///home/www/build/reli-prof/vendor/psalm/phar/psalm.phar/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php:210
4 Psalm\Internal\PhpVisitor\Reflector\ClassLikeNodeScanner::visitClassConstDeclaration phar:///home/www/build/reli-prof/vendor/psalm/phar/psalm.phar/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php:760
5 Psalm\Internal\PhpVisitor\Reflector\ClassLikeNodeScanner::start phar:///home/www/build/reli-prof/vendor/psalm/phar/psalm.phar/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php:451
6 Psalm\Internal\PhpVisitor\ReflectorVisitor::enterNode phar:///home/www/build/reli-prof/vendor/psalm/phar/psalm.phar/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php:116
7 _HumbugBox427cd2d3980b\PhpParser\NodeTraverser::traverseArray phar:///home/www/build/reli-prof/vendor/psalm/phar/psalm.phar/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php:176
8 _HumbugBox427cd2d3980b\PhpParser\NodeTraverser::traverseNode phar:///home/www/build/reli-prof/vendor/psalm/phar/psalm.phar/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php:105
9 _HumbugBox427cd2d3980b\PhpParser\NodeTraverser::traverseArray phar:///home/www/build/reli-prof/vendor/psalm/phar/psalm.phar/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php:196
10 _HumbugBox427cd2d3980b\PhpParser\NodeTraverser::traverse phar:///home/www/build/reli-prof/vendor/psalm/phar/psalm.phar/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php:85
11 Psalm\Internal\Scanner\FileScanner::scan phar:///home/www/build/reli-prof/vendor/psalm/phar/psalm.phar/src/Psalm/Internal/Scanner/FileScanner.php:51
12 Psalm\Internal\Codebase\Scanner::scanFile phar:///home/www/build/reli-prof/vendor/psalm/phar/psalm.phar/src/Psalm/Internal/Codebase/Scanner.php:398
13 Psalm\Internal\Codebase\Scanner::scanAPath phar:///home/www/build/reli-prof/vendor/psalm/phar/psalm.phar/src/Psalm/Internal/Codebase/Scanner.php:547
14 Psalm\Internal\Codebase\Scanner::scanFilePaths phar:///home/www/build/reli-prof/vendor/psalm/phar/psalm.phar/src/Psalm/Internal/Codebase/Scanner.php:310
15 Psalm\Internal\Codebase\Scanner::scanFiles phar:///home/www/build/reli-prof/vendor/psalm/phar/psalm.phar/src/Psalm/Internal/Codebase/Scanner.php:220
16 Psalm\Config::visitComposerAutoloadFiles phar:///home/www/build/reli-prof/vendor/psalm/phar/psalm.phar/src/Psalm/Config.php:1840
17 Psalm\Internal\Analyzer\ProjectAnalyzer::visitAutoloadFiles phar:///home/www/build/reli-prof/vendor/psalm/phar/psalm.phar/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php:249
18 Psalm\Internal\Analyzer\ProjectAnalyzer::check phar:///home/www/build/reli-prof/vendor/psalm/phar/psalm.phar/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php:335
19 Psalm\Internal\Cli\Psalm::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
2、生成火焰圖
sudo /usr/local/php-8.2.14/bin/php ./reli c:flamegraph <traces >flame.svg
使用瀏覽器打開預(yù)覽flame.svg 圖片
圖片
下來生成一個(gè)webman的
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:\Workerman\Events\Select
----------------------------------------------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
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 <traces-15456 >flame-15456.svg
一個(gè)簡單Workerman火焰圖到手啦!
圖片
其他
google-chrome安裝(非必要)
下載
$ 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]
安裝
sudo apt install ./google-chrome-stable_current_amd64.deb
FFI
PHP編譯安裝啟用FFI擴(kuò)展(編譯增加配置--with-ffi)時(shí)出現(xiàn)錯(cuò)誤No package 'libffi' found,檢查后發(fā)現(xiàn)是因?yàn)槿鄙賚ibffi庫文件,利用以下命令安裝,解決問題:
sudo apt install libffi-dev
proc_open 無權(quán)限問題
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目錄執(zhí)行權(quán)限
sudo chmod 0775 -R /home/www/build/reli-prof