SPX - 一个简单的PHP分析器
[![构建状态][:badge-ci:]][:link-ci:] ![支持的PHP版本: 5.4 .. 8.x][:badge-php-versions:] ![支持的平台: GNU/Linux, macOS & FreeBSD][:badge-supported-platforms:] ![支持的架构: x86-64 或 ARM64][:badge-supported-arch:] [![许可证][:badge-license:]][:link-license:]
SPX,全称为_Simple Profiling eXtension_,是另一个PHP性能分析扩展。 它与其他类似扩展的区别在于:
- 完全免费且只在您的基础设施内使用(即不会将数据泄露给SaaS)。
- 使用非常简单:只需设置一个环境变量(命令行)或切换一个单选按钮(Web请求)即可对脚本进行性能分析。因此,您无需:
- 手动修改您的代码(甚至支持Ctrl-C中断长时间运行的命令行脚本)。
- 使用专门的浏览器扩展或命令行启动器。
- 多指标支持:目前支持22种(各种时间和内存指标、包含的文件、使用的对象、I/O等)。
- 能够在不丢失上下文的情况下收集数据。例如,Xhprof(以及可能的分支)按调用者/被调用者对聚合数据,这意味着丢失完整的调用栈,并且禁止基于时间线或火焰图的分析。
- 附带Web界面,允许:
- 为当前浏览器会话启用/配置性能分析
- 列出已分析脚本的报告
- 选择一份报告进行深入分析,具有以下交互式可视化功能:
- 时间线(可扩展到数百万个函数调用)
- 扁平配置文件
- 火焰图
要求
目前平台支持相当有限。如果您的平台不受支持,请随时提出问题。 当前要求是:
- x86-64 或 ARM64
- GNU/Linux、macOS 或 FreeBSD
- zlib开发包(例如,基于Debian的发行版上的zlib1g-dev)
- PHP 5.4 到 8.3
- 非ZTS(线程)版本的PHP(ZTS支持是理论上的)
安装
先决条件
- PHP开发包(与您已安装的PHP版本相对应)。
- zlib开发包:
- 对于基于Debian的发行版(包括Ubuntu、Kubuntu等),只需运行:
sudo apt-get install zlib1g-dev
。 - 对于基于Fedora的发行版(包括CentOS、AlmaLinux、Rocky Linux等),只需运行:
sudo dnf install zlib-devel
。
- 对于基于Debian的发行版(包括Ubuntu、Kubuntu等),只需运行:
安装扩展
git clone https://github.com/NoiseByNorthwest/php-spx.git
cd php-spx
git checkout release/latest
phpize
./configure
make
sudo make install
然后在您的php.ini中添加extension=spx.so
,或在包含目录中创建一个专用的spx.ini文件。
您可能还想覆盖默认SPX配置以便能够分析Web请求,例如使用这个配置来适用于本地开发环境。
Linux、PHP-FPM 和 I/O 统计
在GNU/Linux上,SPX使用procfs(即通过读取/proc
目录下的文件)来获取当前进程或线程的一些统计信息。当您选择以下指标中的至少一个时,就会在底层执行此操作:mor
、io
、ior
或iow
。
但是,在大多数PHP-FPM设置中,您会遇到权限问题,阻止SPX打开/proc/self
目录下的文件。
这是因为PHP-FPM主进程以root身份运行,而子进程以另一个非特权用户身份运行。
在这种情况下,必须在FPM池配置中添加process.dumpable = yes
行,以便子进程能够读取/proc/self
下的任何文件。
开发状态
这仍然是实验性的。API可能会改变,功能可能会增加或删除,或者开发可能会被冻结。
您仍然可以在非生产环境中安全地使用它。
欢迎贡献,但请注意这个项目的实验性质,并请遵循这里描述的贡献规则:CONTRIBUTING.md
基本用法
Web请求
假设开发环境使用这里描述的配置,并且您的应用程序可以通过http://localhost
访问。
只需用浏览器打开以下URL:http://localhost/?SPX_KEY=dev&SPX_UI_URI=/
即可访问Web界面的控制面板。
注意:http://localhost/
必须通过标准Web服务器功能(如目录索引或URL重写)由PHP脚本提供服务。但是,PHP脚本不会被执行,SPX将拦截并禁用其执行,以替代提供其内容。
如果您只看到空白页面,请确保在PHP配置文件中设置zlib.output_compression = 0
然后您将看到以下表单:
然后开启"已启用"。此时,通过一组专用cookie,为当前域和您当前的浏览器会话启用了性能分析。
也可以使用Curl触发性能分析,如下例所示:
curl --cookie "SPX_ENABLED=1; SPX_KEY=dev" http://localhost/
注意:您也可以通过spx.http_profiling_enabled
设置在INI配置级别启用性能分析,从而对所有HTTP请求生效。但是,请记住,在高流量环境中使用此设置可能会快速耗尽SPX数据目录所在存储设备的容量。
然后刷新您想要分析的Web请求,并刷新控制面板以查看控制面板表单下方列表中生成的报告。
然后点击列表中的报告,享受分析界面。
命令行脚本
即时扁平配置文件
只需在命令行前加上SPX_ENABLED=1
即可触发性能分析。您将在执行结束时在STDERR上看到打印的扁平配置文件,即使您通过按Ctrl-C中止它,如下例所示:
$ SPX_ENABLED=1 composer update
加载composer仓库信息
更新依赖(包括require-dev)
^C
*** SPX 报告 ***
全局统计:
调用的函数 : 27.5K
不同的函数 : 714
墙钟时间 : 7.39s
ZE内存 : 62.6MB
扁平配置文件:
墙钟时间 | ZE内存 |
包含 | *排除 | 包含 | 排除 | 调用次数 | 函数
----------+----------+----------+----------+----------+----------
101.6ms | 101.6ms | 41.8MB | 41.8MB | 12 | Composer\Json\JsonFile::parseJson
53.6ms | 53.6ms | 544B | 544B | 4 | Composer\Cache::sha256
6.91s | 41.5ms | 41.5MB | -7.5MB | 4 | Composer\Repository\ComposerRepository::fetchFile
6.85s | 32.3ms | 47.5MB | 5.4MB | 5 | 1@Composer\Repository\ComposerRepository::loadProviderListings
7.8ms | 7.8ms | 0B | 0B | 4 | Composer\Cache::write
1.1ms | 1.1ms | -72B | -72B | 1 | Composer\Console\Application::Composer\Console\{closure}
828.5us | 828.5us | 976B | 976B | 12 | Composer\Util\RemoteFilesystem::findHeaderValue
497.6us | 491.0us | 710.2KB | 710.2KB | 1 | Composer\Cache::read
2.4ms | 332.6us | 20.9KB | -378.8KB | 34 | 3@Symfony\Component\Finder\Iterator\FilterIterator::rewind
298.9us | 298.9us | 2.2KB | 2.2KB | 47 | Symfony\Component\Finder\Iterator\FileTypeFilterIterator::accept
注意:只需添加SPX_FP_LIVE=1
即可在脚本执行期间启用扁平配置文件的实时刷新。
为Web界面生成性能分析报告
您只需指定SPX_REPORT=full
即可生成可通过Web界面访问的报告:
SPX_ENABLED=1 SPX_REPORT=full ./bin/console cache:clear
处理长期运行/守护进程
如果您的CLI脚本是长期运行和/或守护进程(例如通过supervisord),分析其整个生命周期可能没有意义。这在等待处理任务的服务情况下尤其如此。
为了处理这种情况,SPX允许禁用性能分析的自动启动,并暴露2个用户层函数,spx_profiler_start(): void
和spx_profiler_stop(): ?string
,分别控制被分析跨度的开始和结束。
以下是如何修改您的脚本:
<?php
while ($task = get_next_ready_task()) {
spx_profiler_start();
try {
$task->process();
} finally {
spx_profiler_stop();
}
}
当然,这个脚本必须至少启用性能分析并禁用自动启动,如下面的命令所示:
SPX_ENABLED=1 SPX_REPORT=full SPX_AUTO_START=0 my_script.php
自动启动也可以通过spx.http_profiling_auto_start
INI参数或通过控制面板为Web请求禁用。
注意事项:
spx_profiler_start()
和spx_profiler_stop()
可以安全嵌套。- 当使用_full_报告类型进行性能分析时,
spx_profiler_stop()
返回报告键,这样您就可以将其存储在某处,例如与被分析跨度相关的其他信息中。使用报告键,您可以构建分析界面URL,该URL以此模式结尾/?SPX_UI_URI=/report.html&key=<report key>
。 - 在CLI上下文中,当禁用自动启动时,SPX不注册任何信号处理程序(即SIGINT/SIGTERM)。
为当前完整报告添加自定义元数据
当使用_full_报告作为输出进行性能分析时,为当前报告添加自定义元数据可能会很方便,这样您就可以轻松检索它或将其与其他类似报告区分开来。
这对于长期运行进程的用例尤其如此,否则无法将一个报告与同一进程的其他报告区分开来。
为此,SPX暴露了spx_profiler_full_report_set_custom_metadata_str(string $customMetadataStr): void
函数。
正如您可能注意到的,此函数接受一个字符串作为自定义元数据,以实现SPX端的灵活性和简单性。由您来将任何结构化数据编码为字符串,例如使用JSON格式。
元数据字符串限制为4KB,这对大多数用例来说足够大了。如果您传递一个超过此限制的字符串,它将被丢弃,并会发出一个通知日志。
此字符串将与当前报告的其他元数据一起存储,您可以在Web界面的报告列表中检索它。
只要 profiler 已经启动且尚未结束,spx_profiler_full_report_set_custom_metadata_str()
就可以在任何时候调用,这意味着:
- 当启用自动启动(默认模式)时,可以在脚本执行的任何时刻调用。
- 当禁用自动启动时,可以在调用
spx_profiler_start()
之后和调用spx_profiler_stop()
之前的任何时刻调用。
以下是一个示例:
<?php
while ($task = get_next_ready_task()) {
spx_profiler_start();
spx_profiler_full_report_set_custom_metadata_str(json_encode(
[
'taskId' => $task->getId(),
]
));
try {
$task->process();
} finally {
spx_profiler_stop();
}
}
高级用法
配置
名称 | 默认值 | 可更改 | 描述 |
---|---|---|---|
spx.data_dir | /tmp/spx | PHP_INI_SYSTEM | 存储性能分析报告的目录。例如,在多服务器架构的情况下,您可以将其更改为指向共享文件系统。 |
spx.http_enabled | 0 | PHP_INI_SYSTEM | 是否启用网络界面和HTTP请求分析。 |
spx.http_key | PHP_INI_SYSTEM | 用于身份验证的密钥(更多详情请参见安全问题)。您可以使用以下命令生成16字节的随机密钥作为十六进制字符串: openssl rand -hex 16 。 | |
spx.http_ip_var | REMOTE_ADDR | PHP_INI_SYSTEM | 用于身份验证的客户端IP地址所在的 $_SERVER 键(更多详情请参见安全问题)。当您的应用程序位于反向代理后面时,需要覆盖默认值。 |
spx.http_trusted_proxies | 127.0.0.1 | PHP_INI_SYSTEM | 可信代理列表,以逗号分隔的IP地址列表。当 spx.http_ip_var 的值为 REMOTE_ADDR 时,此设置将被忽略。 |
spx.http_ip_whitelist | PHP_INI_SYSTEM | 用于身份验证的IP地址白名单,以逗号分隔的IP地址列表,使用 * 允许所有IP地址。 | |
spx.http_ui_assets_dir | /usr/local/share/misc/php-spx/assets/web-ui | PHP_INI_SYSTEM | 安装网络界面文件的目录。在大多数情况下,您无需更改它。 |
spx.http_profiling_enabled | NULL | PHP_INI_SYSTEM | SPX_ENABLED 参数的INI级别对应项,仅适用于HTTP请求。更多详情请参见此处。 |
spx.http_profiling_auto_start | NULL | PHP_INI_SYSTEM | SPX_AUTO_START 参数的INI级别对应项,仅适用于HTTP请求。更多详情请参见此处。 |
spx.http_profiling_builtins | NULL | PHP_INI_SYSTEM | SPX_BUILTINS 参数的INI级别对应项,仅适用于HTTP请求。更多详情请参见此处。 |
spx.http_profiling_sampling_period | NULL | PHP_INI_SYSTEM | SPX_SAMPLING_PERIOD 参数的INI级别对应项,仅适用于HTTP请求。更多详情请参见此处。 |
spx.http_profiling_depth | NULL | PHP_INI_SYSTEM | SPX_DEPTH 参数的INI级别对应项,仅适用于HTTP请求。更多详情请参见此处。 |
spx.http_profiling_metrics | NULL | PHP_INI_SYSTEM | SPX_METRICS 参数的INI级别对应项,仅适用于HTTP请求。更多详情请参见此处。 |
私有环境
对于您的本地和私有开发环境,由于不需要身份验证,您可以使用以下配置:
spx.http_enabled=1
spx.http_key="dev"
spx.http_ip_whitelist="127.0.0.1"
然后通过 http(s)://<your application host>/?SPX_KEY=dev&SPX_UI_URI=/
访问网络界面。
可用指标
以下是可收集的指标列表。默认情况下,仅收集 Wall time 和 Zend Engine memory usage。
键 (命令行) | 名称 | 描述 |
---|---|---|
wt | Wall time | 绝对经过时间。 |
ct | CPU time | 在CPU上运行所花费的时间。 |
it | Idle time | 离开CPU的时间,即等待CPU、I/O完成、获取锁...或显式休眠。 |
zm | Zend Engine memory usage | 等同于 memory_get_usage(false) 。 |
zmac | Zend Engine allocation count | 执行的内存分配次数(即分配的块数)。 |
zmab | Zend Engine allocated bytes* | 分配的字节数。 |
zmfc | Zend Engine free count | 执行的内存释放次数(即释放的块数)。 |
zmfb | Zend Engine freed bytes* | 释放的字节数。 |
zgr | Zend Engine GC run count | GC(循环收集器)被触发的次数(手动或自动)。 |
zgb | Zend Engine GC root buffer length | 根缓冲区长度,请参阅此处的解释。这有助于跟踪垃圾收集器的压力。 |
zgc | Zend Engine GC collected cycle count | 通过所有GC运行收集的循环总数。 |
zif | Zend Engine included file count | 包含的文件数。 |
zil | Zend Engine included line count | 包含的行数。 |
zuc | Zend Engine user class count | 用户定义类的数量。 |
zuf | Zend Engine user function count | 用户定义函数的数量(包括用户定义类/实例方法)。 |
zuo | Zend Engine user opcode count | 包含的用户定义操作码数量(所有用户定义文件/函数/方法操作码的总和)。 |
zo | Zend Engine object count | 用户代码当前持有的对象数量。 |
ze | Zend Engine error count | 引发的PHP错误数量。 |
mor | Process's own RSS** | 进程内存中保留在RAM中的部分。不考虑共享(与其他进程)的内存块。此指标可用于突出显示PHP扩展或更深层次(例如第三方C库)中的内存泄漏。 |
io | I/O (reads + writes)** | 执行I/O时读取或写入的字节数。 |
ior | I/O (reads)** | 执行I/O时读取的字节数。 |
iow | I/O (writes)** | 执行I/O时写入的字节数。 |
*: 如果您使用自定义分配器或通过将 USE_ZEND_ALLOC
环境变量设置为 0
强制使用libc分配器,则不会收集分配和释放的字节数。
**: macOS和FreeBSD不支持RSS和I/O指标。在GNU/Linux上,如果您使用PHP-FPM,应该阅读此内容。
命令行脚本
可用报告类型
与仅支持 full 报告类型(可由网络界面利用)的网络请求分析不同,命令行脚本分析支持多种类型的报告。 以下是列表:
键 | 名称 | 描述 |
---|---|---|
fp | Flat profile | SPX提供的平面分析。这是默认报告类型,直接打印在STDERR上。 |
full | Full report | 这是网络界面的报告类型。报告将存储在SPX数据目录中,因此可在网络界面端进行分析。 |
trace | Trace file | 自定义格式(人类可读文本)的跟踪文件。 |
可用参数
名称 | 默认值 | 描述 |
---|---|---|
SPX_ENABLED | 0 | 是否启用SPX分析器(即触发分析)。禁用时不会对应用程序的性能产生影响。 |
SPX_AUTO_START | 1 | 是否启用SPX分析器的自动启动。禁用自动启动时,您必须在运行时通过 spx_profiler_start() 和 spx_profiler_stop() 函数自行启动和停止分析。更多详情请参见此处。 |
SPX_BUILTINS | 0 | 是否分析内部函数、脚本编译、GC运行和请求关闭。 |
SPX_DEPTH | 0 | 分析必须停止的堆栈深度(即聚合更深层调用的度量)。0(默认值)表示无限制。 |
SPX_SAMPLING_PERIOD | 0 | 是否根据指定的采样周期定期收集当前调用堆栈的数据(0 表示不采样)。结果通常会不太准确,但在某些情况下,它可能会更加准确,因为不会过度评估多次调用的小函数。如果您想准确找到时间瓶颈,建议尝试采样(使用不同的周期)。在分析长时间运行且CPU密集的脚本时,此选项将允许您控制报告大小,从而保持其足够小以便网络界面利用。更多详情请参见此处。 |
SPX_METRICS | wt,zm | 要收集的可用指标键的逗号分隔列表。所有报告类型都可以利用多指标分析。 |
SPX_REPORT | fp | 选择的报告键。 |
SPX_FP_FOCUS | wt | 平面分析排序的指标键。 |
SPX_FP_INC | 0 | 是否在平面分析中按包含值而不是排他值对函数进行排序。 |
SPX_FP_REL | 0 | 是否在平面分析中将指标值显示为相对值(即百分比)。 |
SPX_FP_LIMIT | 10 | 平面分析的大小(即显示的前N个函数)。 |
SPX_FP_LIVE | 0 | 是否启用平面分析实时刷新。由于它通过ANSI转义序列操作光标位置,因此使用STDOUT作为输出,替换脚本输出(STDOUT和STDERR)。 |
SPX_FP_COLOR | 1 | 是否启用平面分析的彩色模式。 |
SPX_TRACE_SAFE | 0 | 默认情况下,跟踪文件的写入方式会确保准确性,但在进程崩溃(例如段错误)的情况下,可能会丢失一些日志。如果您想确保持久性(例如,找到崩溃前的最后一个事件),只需将此参数设置为1即可。 |
SPX_TRACE_FILE | 自定义跟踪文件名。如果未指定,将在 /tmp 中生成并在脚本结束时显示在STDERR上。 |
设置参数
正如您可能已经在相应的基本用法示例中注意到的那样,为命令行脚本设置SPX参数只需设置一个具有相同名称的环境变量。
网络界面
支持的浏览器
由于网络界面使用了高级JavaScript功能,因此仅支持以下浏览器:
- 任何基于Chromium的浏览器的最新版本。
- Firefox的最新版本。
控制面板和报告列表
这是网络界面的主页,分为两部分:
- 用于设置当前浏览器会话的分析设置的控制面板。
- 可排序的分析报告列表表格。单击一行可以转到相应报告的分析屏幕。
分析屏幕
[点击此 默认情况下,可视化中的函数相关块根据其成本进行着色,颜色比例尺显示在屏幕右上角。
您也可以通过点击屏幕顶部指标选择器后面显示的颜色方案模式链接来定义自定义颜色方案。 然后会出现一个下拉窗口,允许您在"默认"和"类别"模式之间切换,并为"类别"模式定义(添加/编辑/删除)您的类别(颜色、名称、模式列表)(请参见下面的截图)。
时间线概览
这个可视化是所有被调用函数的时间线概览。 您可以通过简单地水平拖动来更改所选时间范围,用透明的绿色矩形表示。
除了挂钟时间外,当前指标也绘制在前景层上(当前值随时间变化)。
支持的控制:
- 水平左键拖动:移动所选时间范围
- 点击所选时间范围矩形进行调整:移动所选时间范围的一个边界
时间线焦点
这个可视化是一个交互式时间线,能够控制并保持对所选时间范围的焦点。
支持的控制:
- 左键拖动:时间范围移动(水平)或深度范围移动(垂直)
- 中键垂直拖动:时间范围放大/缩小
- 鼠标滚轮:时间范围放大/缩小
- 悬停在函数调用上以显示更多详细信息
- 双击函数调用:将当前时间范围设置为所选函数调用的时间范围
除了挂钟时间外,当前指标也绘制在前景层上(当前值随时间变化)。
平面配置文件
这个可视化是所选时间范围和所选指标的平面配置文件,以可排序的表格显示。
"Inc."和"Exc."子列分别对应:
- 函数的包含资源消耗,包括其调用函数的消耗
- 函数的排除资源消耗,不包括其调用函数的消耗
火焰图
这个由Brendan Gregg设计的可视化,允许快速找到所选时间范围和所选指标的热代码路径。 这个可视化不支持对应于可释放资源(内存、使用中的对象等)的指标。
函数高亮
您可以通过点击时间线或火焰图小部件中的一个跨度,或平面配置文件小部件中的名称来高亮显示一个函数。
安全问题
缺乏对这个问题的审查/反馈是SPX还不能被认为是生产就绪的主要原因。
SPX允许您分析Web请求以及命令行脚本,并通过其嵌入式Web UI列出和分析配置文件报告。 这就是为什么存在巨大的安全风险,因为攻击者可能:
- 访问Web UI并获取有关您应用程序的敏感信息。
- 在较小程度上,通过昂贵的分析设置对您的应用程序进行DoS攻击。
因此,除非对您的应用程序的访问已经在较低层(即在您的应用程序被命中之前,而不是由应用程序/PHP框架本身)受到限制,否则触发分析或访问Web UI的客户端必须进行身份验证。
SPX提供两因素身份验证,包括这两个必需的锁:
- IP地址白名单(精确字符串表示匹配)。
- 通过请求头、cookie或查询字符串参数提供的固定秘密随机密钥(由您自己生成)。
因此,客户端只有在其IP地址在白名单中且提供的密钥有效的情况下,才能通过Web请求分析您的应用程序。
关于准确性的说明
在跟踪模式(默认)下,当测量的函数执行时间:
- 接近或低于计时器精度
- 接近或低于SPX自身的每个函数开销
SPX在时间相关指标方面可能存在准确性问题。
第一个问题通过使用平台提供的最高分辨率计时器来缓解。在Linux、FreeBSD和最新的macOS版本上,计时器分辨率为1ns;在macOS 10.12/Sierra之前的版本上,计时器分辨率仅为1us。
第二个问题通过考虑SPX的时间(挂钟/CPU)开销并从测量的函数执行时间中减去来缓解。这是通过在开始分析脚本之前评估SPX恒定每函数开销来完成的。
然而,无论平台如何,如果您想最大化准确性以找到时间瓶颈,您还应该:
- 避免分析内部函数。
- 避免收集额外的指标。
- 尝试使用不同的采样周期进行采样模式。
- 尝试使用最大深度参数在给定深度停止分析。
存根
用于Intelephense的SPX函数存根
composer require --dev 8ctopus/php-spx-stubs
致谢
我从阅读以下内容中获得了很多灵感和提示:
许可证
SPX是根据GNU通用公共许可证(GPL-3)许可的开源软件。 有关更多信息,请参阅[LICENSE][:link-license:]文件。