sj-i / php-profiler
Requires
- php: ^8.0
- ext-ffi: *
- ext-filter: *
- ext-json: *
- ext-pcntl: *
- amphp/amp: 2.6.1
- amphp/parallel: 1.4.1
- hassankhan/config: 3.0.0
- monolog/monolog: 2.3.5
- myclabs/php-enum: 1.8.3
- php-di/php-di: 6.3.5
- sj-i/php-cast: 1.0.0
- symfony/console: 6.0.3
- webmozart/assert: 1.10.0
Requires (Dev)
- ext-posix: *
- jetbrains/phpstorm-stubs: 2021.3
- mockery/mockery: 1.5.0
- php-coveralls/php-coveralls: 2.5.2
- phpunit/phpunit: 9.5.13
- psalm/plugin-mockery: 0.9.1
- squizlabs/php_codesniffer: 3.6.2
- vimeo/psalm: 4.20.0
- 0.6.x-dev
- 0.5.x-dev
- 0.5.0
- 0.4.2
- 0.4.1
- 0.4.0
- 0.3.8
- 0.3.7
- v0.3.6
- v0.3.5
- v0.3.4
- v0.3.3
- v0.3.2
- v0.3.1
- v0.3.0
- v0.2.1
- v0.2.0
- v0.1.0
- v0.0.9
- v0.0.8
- v0.0.7
- v0.0.6
- v0.0.5
- v0.0.4
- v0.0.3
- v0.0.2
- v0.0.1
- dev-fix_docs
- dev-rename-to-reli
- dev-renovate/hassankhan-config-3.x
- dev-renovate/actions-cache-3.x
- dev-renovate/phpunit-phpunit-9.x
- dev-renovate/monolog-monolog-2.x
- dev-renovate/vimeo-psalm-4.x
- dev-renovate/php-8.x
- dev-dwarf
- dev-tracing-in-c
- dev-phpcon2020
- dev-experiment-opcode-tracer
This package is auto-updated.
Last update: 2022-03-28 04:58:51 UTC
README
Reli是一个用PHP编写的采样分析器(或虚拟机状态检查器),可以从进程外部读取运行中的PHP脚本信息。它是一个独立的CLI工具,因此目标程序无需任何修改。此工具之前的名称是sj-i/php-profiler(我们目前正在更改名称)。
我能用它做什么?
- 检测和可视化PHP脚本中的瓶颈
- 它不仅提供函数级别的分析,还提供行级别或指令级别解析
- 即使在调用大量快速函数时,也能进行无累积开销的分析(这是一个采样分析器,见下面的链接,tideways,xhprof,以及xdebug的分析器,许多分析器都有这个开销)
- 调查错误或性能失败的原因
- 即使PHP脚本处于无法解释的无响应状态,您也可以使用它来找出它内部正在做什么。
它如何工作
它通过以下技术实现
- 解析解释器的ELF二进制文件
- 从/proc/
/maps读取内存映射 - 使用ptrace(2)和process_vm_readv(2)通过FFI读取外部进程的内存
- 分析PHP虚拟机(又称Zend引擎)中的内部数据结构
如果您有额外的CPU资源,此软件的开销将是可以忽略不计的。
与phpspy的区别,何时使用reli
Reli深受adsr/phpspy的启发。
这两个之间主要的区别是reli几乎完全用PHP编写,而phpspy是用C编写的。在分析时,有时您可能希望自定义获取信息和方式。如果对于PHP开发者的可定制性很重要,您可以使用此软件,虽然这会牺牲一些性能(尽管我们希望代价不是很大)。
此外,reli可以从ZTS解释器中找到虚拟机状态。例如,在守护进程模式下,通过ext-parallel启动的线程的跟踪将自动检索。目前这只能用phpspy完成。Reli还提供只获取目标EG地址的功能,因此如果您想使用phpspy进行实际分析,即使目标为ZTS也可以。
reli的其他功能,phpspy目前还没有包括
- 输出更准确的行号
- 使用PHP模板自定义输出格式
- 获取PHP-VM的运行指令
- 自动从剥离的PHP二进制文件中检索目标PHP版本
- 以speedscope格式输出跟踪
没有特别的原因说明为什么这些功能不能在 phpspy 端实现,所以未来可能在 phpspy 上实现这些功能。
另一方面,有一些功能 phpspy 可以做,但 reli 目前还不能做。
- 重定向子进程的输出
- 强制设置 EG 的地址
- 从 sapi_globals 中检索数据
- callgrind 支持
- 读取变量
- 运行更快,开销更低。
- 等等。
未来,许多可以用 phpspy 实现的功能将使用 reli 实现。
需求
支持的 PHP 版本
执行
- PHP 8.0+ (NTS / ZTS)
- 64位 Linux x86_64
- 必须启用 FFI 扩展。
- 如果目标进程是 ZTS,则必须启用 PCNTL 扩展。
目标
- PHP 7.0+ (NTS / ZTS)
- 64位 Linux x86_64
在针对 ZTS 时,目标进程必须加载 libpthread.so,并且您还必须有解释器和 libpthread.so 的未剥离二进制文件,以便从 TLS 中查找 EG。
安装
从 Composer 安装
composer create-project reliforp/reli-prof
cd reli
./reli
从 Git 安装
git clone git@github.com:reliforp/reli-prof.git
cd reli
composer install
./reli
使用
获取调用跟踪
./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[01]) 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
守护进程模式
./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[01]) 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
类似 top 的模式
./reli inspector:top --help Description: show an aggregated view of traces in real time in a form similar to the UNIX top command. Usage: inspector:top [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[01]) 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) -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
获取 EG 的地址
./reli inspector:eg --help Description: get EG address from an outer process or thread Usage: inspector:eg_address [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 --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[01]) 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) -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
示例
跟踪脚本
$ ./reli i:trace -- php -r "fgets(STDIN);" 0 fgets <internal>:-1 1 <main> <internal>:-1 0 fgets <internal>:-1 1 <main> <internal>:-1 0 fgets <internal>:-1 1 <main> <internal>:-1 <press q to exit> ...
附加到正在运行的进程
$ sudo php ./reli i:trace -p 2182685 0 time_nanosleep <internal>:-1 1 PhpProfiler\Lib\Loop\LoopMiddleware\NanoSleepMiddleware::invoke /home/sji/work/reli/src/Lib/Loop/LoopMiddleware/NanoSleepMiddleware.php:33 2 PhpProfiler\Lib\Loop\LoopMiddleware\KeyboardCancelMiddleware::invoke /home/sji/work/reli/src/Lib/Loop/LoopMiddleware/KeyboardCancelMiddleware.php:39 3 PhpProfiler\Lib\Loop\LoopMiddleware\RetryOnExceptionMiddleware::invoke /home/sji/work/reli/src/Lib/Loop/LoopMiddleware/RetryOnExceptionMiddleware.php:37 4 PhpProfiler\Lib\Loop\Loop::invoke /home/sji/work/reli/src/Lib/Loop/Loop.php:26 5 PhpProfiler\Command\Inspector\GetTraceCommand::execute /home/sji/work/reli/src/Command/Inspector/GetTraceCommand.php:133 6 Symfony\Component\Console\Command\Command::run /home/sji/work/reli/vendor/symfony/console/Command/Command.php:291 7 Symfony\Component\Console\Application::doRunCommand /home/sji/work/reli/vendor/symfony/console/Application.php:979 8 Symfony\Component\Console\Application::doRun /home/sji/work/reli/vendor/symfony/console/Application.php:299 9 Symfony\Component\Console\Application::run /home/sji/work/reli/vendor/symfony/console/Application.php:171 10 <main> /home/sji/work/reli/reli:45 0 time_nanosleep <internal>:-1 1 PhpProfiler\Lib\Loop\LoopMiddleware\NanoSleepMiddleware::invoke /home/sji/work/reli/src/Lib/Loop/LoopMiddleware/NanoSleepMiddleware.php:33 2 PhpProfiler\Lib\Loop\LoopMiddleware\KeyboardCancelMiddleware::invoke /home/sji/work/reli/src/Lib/Loop/LoopMiddleware/KeyboardCancelMiddleware.php:39 3 PhpProfiler\Lib\Loop\LoopMiddleware\RetryOnExceptionMiddleware::invoke /home/sji/work/reli/src/Lib/Loop/LoopMiddleware/RetryOnExceptionMiddleware.php:37 4 PhpProfiler\Lib\Loop\Loop::invoke /home/sji/work/reli/src/Lib/Loop/Loop.php:26 5 PhpProfiler\Command\Inspector\GetTraceCommand::execute /home/sji/work/reli/src/Command/Inspector/GetTraceCommand.php:133 6 Symfony\Component\Console\Command\Command::run /home/sji/work/reli/vendor/symfony/console/Command/Command.php:291 7 Symfony\Component\Console\Application::doRunCommand /home/sji/work/reli/vendor/symfony/console/Application.php:979 8 Symfony\Component\Console\Application::doRun /home/sji/work/reli/vendor/symfony/console/Application.php:299 9 Symfony\Component\Console\Application::run /home/sji/work/reli/vendor/symfony/console/Application.php:171 10 <main> /home/sji/work/reli/reli:45 <press q to exit> ...
执行进程必须具有 CAP_SYS_PTRACE 能力。(通常以 root 用户运行就足够了。)
守护进程模式
$ sudo php ./reli i:daemon -P "^/usr/sbin/httpd"
执行进程必须具有 CAP_SYS_PTRACE 能力。(通常以 root 用户运行就足够了。)
获取 EG 的地址
$ sudo php ./reli i:eg -p 2183131 0x555ae7825d80
执行进程必须具有 CAP_SYS_PTRACE 能力。(通常以 root 用户运行就足够了。)
在跟踪中显示当前执行的指令
如果用户想要分析一个真正占用 CPU 的应用程序,那么他或她不仅想知道哪些行是慢的,还想知道哪些指令是慢的。在这种情况下,使用 --template=phpspy_with_opcode
与 inspector:trace
或 inspector:daemon
。
$ sudo php ./reli i:trace --template=phpspy_with_opcode -p <pid of the target process or thread>
输出将类似于以下内容。
0 <VM>::ZEND_ASSIGN <VM>:-1
1 Mandelbrot::iterate /home/sji/work/test/mandelbrot.php:33:ZEND_ASSIGN
2 Mandelbrot::__construct /home/sji/work/test/mandelbrot.php:12:ZEND_DO_FCALL
3 <main> /home/sji/work/test/mandelbrot.php:45:ZEND_DO_FCALL
0 <VM>::ZEND_ASSIGN <VM>:-1
1 Mandelbrot::iterate /home/sji/work/test/mandelbrot.php:30:ZEND_ASSIGN
2 Mandelbrot::__construct /home/sji/work/test/mandelbrot.php:12:ZEND_DO_FCALL
3 <main> /home/sji/work/test/mandelbrot.php:45:ZEND_DO_FCALL
当前执行的指令成为调用栈的第一帧。因此,像火焰图这样的跟踪可视化可以显示指令的使用情况。
出于信息目的,执行指令也添加到每个调用帧的末尾。除了第一帧之外,像 ZEND_DO_FCALL 这样的函数调用指令应该出现在那里。
如果目标进程启用了 JIT,则这些信息可能略有不准确。
在 Docker 容器和主机进程上使用
$ docker pull sjidev/reli $ docker run -it --security-opt="apparmor=unconfined" --cap-add=SYS_PTRACE --pid=host sjidev/reli i:trace -p <pid of the target process or thread>
从跟踪信息生成 flamegraphs
$ ./reli i:trace -o traces -- php ./vendor/bin/psalm --no-cache $ ./reli c:flamegraph <traces >flame.svg $ google-chrome flame.svg
下方的 flamegraph 可视化了执行 psalm 命令的跟踪信息。
从 phpspy 兼容的跟踪信息生成 speedscope 格式
$ sudo php ./reli i:trace -p <pid of the target process or thread> >traces $ ./reli c:speedscope <traces >profile.speedscope.json $ speedscope profile.speedscope.json
参见 #101。
故障排除
我收到了错误消息 "php module not found" 并且无法获取跟踪信息!
如果你的 PHP 可执行文件使用的是非标准二进制名称,并且不以 /php
结尾,请使用 --php-regex
选项来指定包含 PHP 解释器的可执行文件(或共享对象)的名称。
我认为跟踪信息不准确。
-S
选项将提供更好的结果。使用此选项将在每次采样时暂时停止目标进程的执行,但获得的跟踪信息将更加准确。如果您在分析像基准测试程序这样的 CPU 重量型程序时没有停止 VM 的运行,您可能会误判瓶颈,因为您将错过更多非常快速转换且未被良好检测到的 VM 状态。
在 Ubuntu 21.10 或更高版本上从 ZTS 目标检索跟踪信息不工作。
尝试指定 --libpthread-regex="libc.so"
作为选项。
我无法在 Amazon Linux 2 上获取跟踪信息。
首先,尝试 cat /proc/<pid>/maps
来检查目标 PHP 进程的内存映射。如果第一个模块没有指示 PHP 二进制的位置,看起来像是一个匿名区域,请尝试指定 --php-regex="^$"
作为选项。
目标
通过这个项目,我们希望实现以下 5 个目标。
- 能够密切观察运行中的 PHP 脚本内部发生的事情。
- 成为 PHP 程序员创建自定义 PHP 分析器的框架。
- 对 PHP 在 Web 之外的使用进行实验,因为 PHP 的最新改进(如 JIT 和 FFI)已经打开了大门。
- 为 PHP 程序员提供另一个了解 PHP 内部实现的入口点。
- 创建一个让我觉得编写起来有趣的程序。
授权协议
- MIT(主要是)
- tools/flamegraph/flamegraph.pl 是从 https://github.com/brendangregg/FlameGraph 复制的,并受 CDDL 1.0 授权。参见 tools/flamegraph/docs/cddl1.txt 和脚本的头部。
- 一些定义内部结构的 C 头文件是从 php-src 提取的。它们受 Zend 引擎授权协议的许可。参见 src/Lib/PhpInternals/Headers。因此,这里有由 Zend 引擎授权协议要求的话。
This product includes the Zend Engine, freely available at
http://www.zend.com
“Reli”这个名字意味着什么?
"Reli" 这个词没有任何意义,尽管你可以自由地将这个工具想象成可靠的、宗教的、令人愉悦的,或者任何你喜欢的 "reli-" 形式的词。
最初,这个工具的名字只是 "php-profiler"。由于许可问题(#175),这个简单的名字不得不更改。
因此,我们对原始名字进行了一次随机字符串操作。将 'php-profiler' 进行 strrev
操作后得到 'reliforp-php'
,它可以读作 "reli for p(php)"。因此,这个工具的名字现在是 "Reli for PH*"。你也可以简单地称之为 "Reli"。
参见
- adsr/phpspy
- Reli 受 phpspy 的启发很大。