DTrace的运行需要用户使用DTrace D脚本来激活并监控PHP探针,否则它不会运行,也就不会对正常的应用执行带来任何性能损耗。即使PHP探针被激活,它的消耗也非常低,因此可以放心在生产系统中使用,不会对系统性能造成明显影响。
一、概述
许多操作系统(如Solaris、macOS、Oracle Linux和BSD)都提供了DTrace跟踪调试框架,它可以永远可用,并且额外消耗极低的资源。使用DTrace,可以跟踪操作系统行为和用户程序的执行情况,并显示参数值,也可以用来分析性能问题。用户可以使用DTrace D脚本语言创建脚本文件来监控探针,进而高效分析数据指针,这使得DTrace成为开发人员和系统管理员的有力工具。
在运行时可以激活 PHP “用户级静态定义跟踪”(USDT)探针。 举例说明,如果 D 脚本正在监控 PHP function-entry 探针, 那么,每当 PHP 脚本函数被调用的时候,这个探针将被触发,同时 D 脚本中所关联的动作代码将被执行。 在脚本的动作代码中,可以打印出诸如函数所在源文件等信息的探针参数, 也可以记录诸如函数执行次数这样的聚合数据。
二、使用PHP和DTrace
在支持 DTrace 动态跟踪的平台上,可以配置 PHP 打开 DTrace 静态探针。
1、PHP DTrace 静态探针配置
在 Oracle Linux 系统上启动 UEK3 内核,并进行如下操作:
# modprobe fasttrap # chmod 666 /dev/dtrace/helper
除了 chmod 命令,也可以使用 ACL 包规则来限制特定用户对于设备的访问权限。
使用 –enable-dtrace 配置参数构建 PHP:
# ./configure --enable-dtrace ... # make # make install
这样就启用了 PHP 核心的静态探针。对于提供了自有探针的 PHP 扩展需要分别构建。
2、PHP 核心的 DTrace 静态探针
PHP 中包括以下可用的静态探针:
3、PHP 中可用的静态探针
要列出 PHP 中可用的静态探针,开启一个 PHP 进程,然后运行:
# dtrace -l
输出信息类似如下所示:
ID PROVIDER MODULE FUNCTION NAME [ . . . ] 4 php15271 php dtrace_compile_file compile-file-entry 5 php15271 php dtrace_compile_file compile-file-return 6 php15271 php zend_error error 7 php15271 php ZEND_CATCH_SPEC_CONST_CV_HANDLER exception-caught 8 php15271 php zend_throw_exception_internal exception-thrown 9 php15271 php dtrace_execute_ex execute-entry 10 php15271 php dtrace_execute_internal execute-entry 11 php15271 php dtrace_execute_ex execute-return 12 php15271 php dtrace_execute_internal execute-return 13 php15271 php dtrace_execute_ex function-entry 14 php15271 php dtrace_execute_ex function-return 15 php15271 php php_request_shutdown request-shutdown 16 php15271 php php_request_startup request-startup
Provider 一列由 php 和当前进程 id 的组成。如果运行的是 Apache web 服务器,那么模块名称可能是 libphp5.so, 并且可能会出现多块信息,每个运行中的 Apache 进程对应一个输出块。
Function Name 一列表示 Provider 对应的 PHP 内部 C 实现函数名称。
如果没有运行任何 PHP 进程,那么就不会显示任何 PHP 探针。
4、PHP DTrace 示例
下例展示了基本的 DTrace D 脚本。
#!/usr/sbin/dtrace -Zs #pragma D option quiet php*:::compile-file-entry { printf("PHP compile-file-entry\n"); printf(" compile_file %s\n", copyinstr(arg0)); printf(" compile_file_translated %s\n", copyinstr(arg1)); } php*:::compile-file-return { printf("PHP compile-file-return\n"); printf(" compile_file %s\n", copyinstr(arg0)); printf(" compile_file_translated %s\n", copyinstr(arg1)); } php*:::error { printf("PHP error\n"); printf(" errormsg %s\n", copyinstr(arg0)); printf(" request_file %s\n", copyinstr(arg1)); printf(" lineno %d\n", (int)arg2); } php*:::exception-caught { printf("PHP exception-caught\n"); printf(" classname %s\n", copyinstr(arg0)); } php*:::exception-thrown { printf("PHP exception-thrown\n"); printf(" classname %s\n", copyinstr(arg0)); } php*:::execute-entry { printf("PHP execute-entry\n"); printf(" request_file %s\n", copyinstr(arg0)); printf(" lineno %d\n", (int)arg1); } php*:::execute-return { printf("PHP execute-return\n"); printf(" request_file %s\n", copyinstr(arg0)); printf(" lineno %d\n", (int)arg1); } php*:::function-entry { printf("PHP function-entry\n"); printf(" function_name %s\n", copyinstr(arg0)); printf(" request_file %s\n", copyinstr(arg1)); printf(" lineno %d\n", (int)arg2); printf(" classname %s\n", copyinstr(arg3)); printf(" scope %s\n", copyinstr(arg4)); } php*:::function-return { printf("PHP function-return\n"); printf(" function_name %s\n", copyinstr(arg0)); printf(" request_file %s\n", copyinstr(arg1)); printf(" lineno %d\n", (int)arg2); printf(" classname %s\n", copyinstr(arg3)); printf(" scope %s\n", copyinstr(arg4)); } php*:::request-shutdown { printf("PHP request-shutdown\n"); printf(" file %s\n", copyinstr(arg0)); printf(" request_uri %s\n", copyinstr(arg1)); printf(" request_method %s\n", copyinstr(arg2)); } php*:::request-startup { printf("PHP request-startup\n"); printf(" file %s\n", copyinstr(arg0)); printf(" request_uri %s\n", copyinstr(arg1)); printf(" request_method %s\n", copyinstr(arg2)); }
此脚本在 dtrace 命令中使用了 -Z 选项。 此选项保证即使在没有任何 PHP 进程运行的时候脚本也能够正确执行。 如果省略了此选项,当没有任何探针可监控的时候,脚本会立即终止执行。
在运行此脚本的过程中,它将监控全部 PHP 核心静态探针。 运行 D 脚本:
# ./all_probes.d
再运行一个 PHP 脚本或者 PHP 应用,用来进行监控的 D 脚本会输出每个探针被触发时所携带的参数。
监控完成之后,使用 CTRL+C 来终止 D 脚本的执行。
在多 CPU 的主机上,探针的显示顺序可能不是连续的,这取决于哪颗 CPU 执行探针以及多个 CPU 之间的线程迁移情况。 可以通过显示探针时间戳来减少混淆,例如:
php*:::function-entry { printf("%lld: PHP function-entry ", walltimestamp); [ . . .] }
三、PHP DTrace静态探针
在某些 Linux 发行版上,可以使用 SystemTap 跟踪工具来跟踪 PHP 的静态 DTrace 探针。 此特性在 PHP 5.4.20 和 PHP 5.5 中具备。
1、安装支持 SystemTap 的 PHP
# yum install systemtap-sdt-devel
安装 PHP 并启用 DTrace 探针:
# ./configure --enable-dtrace ... # make
2、使用 SystemTap 列出 PHP 静态探针
# stap -l 'process.provider("php").mark("*")' -c 'sapi/cli/php -i'
输出如下:
process("sapi/cli/php").provider("php").mark("compile__file__entry") process("sapi/cli/php").provider("php").mark("compile__file__return") process("sapi/cli/php").provider("php").mark("error") process("sapi/cli/php").provider("php").mark("exception__caught") process("sapi/cli/php").provider("php").mark("exception__thrown") process("sapi/cli/php").provider("php").mark("execute__entry") process("sapi/cli/php").provider("php").mark("execute__return") process("sapi/cli/php").provider("php").mark("function__entry") process("sapi/cli/php").provider("php").mark("function__return") process("sapi/cli/php").provider("php").mark("request__shutdown") process("sapi/cli/php").provider("php").mark("request__startup")
3、SystemTap示例
probe process("sapi/cli/php").provider("php").mark("compile__file__entry") { printf("Probe compile__file__entry\n"); printf(" compile_file %s\n", user_string($arg1)); printf(" compile_file_translated %s\n", user_string($arg2)); } probe process("sapi/cli/php").provider("php").mark("compile__file__return") { printf("Probe compile__file__return\n"); printf(" compile_file %s\n", user_string($arg1)); printf(" compile_file_translated %s\n", user_string($arg2)); } probe process("sapi/cli/php").provider("php").mark("error") { printf("Probe error\n"); printf(" errormsg %s\n", user_string($arg1)); printf(" request_file %s\n", user_string($arg2)); printf(" lineno %d\n", $arg3); } probe process("sapi/cli/php").provider("php").mark("exception__caught") { printf("Probe exception__caught\n"); printf(" classname %s\n", user_string($arg1)); } probe process("sapi/cli/php").provider("php").mark("exception__thrown") { printf("Probe exception__thrown\n"); printf(" classname %s\n", user_string($arg1)); } probe process("sapi/cli/php").provider("php").mark("execute__entry") { printf("Probe execute__entry\n"); printf(" request_file %s\n", user_string($arg1)); printf(" lineno %d\n", $arg2); } probe process("sapi/cli/php").provider("php").mark("execute__return") { printf("Probe execute__return\n"); printf(" request_file %s\n", user_string($arg1)); printf(" lineno %d\n", $arg2); } probe process("sapi/cli/php").provider("php").mark("function__entry") { printf("Probe function__entry\n"); printf(" function_name %s\n", user_string($arg1)); printf(" request_file %s\n", user_string($arg2)); printf(" lineno %d\n", $arg3); printf(" classname %s\n", user_string($arg4)); printf(" scope %s\n", user_string($arg5)); } probe process("sapi/cli/php").provider("php").mark("function__return") { printf("Probe function__return: %s\n", user_string($arg1)); printf(" function_name %s\n", user_string($arg1)); printf(" request_file %s\n", user_string($arg2)); printf(" lineno %d\n", $arg3); printf(" classname %s\n", user_string($arg4)); printf(" scope %s\n", user_string($arg5)); } probe process("sapi/cli/php").provider("php").mark("request__shutdown") { printf("Probe request__shutdown\n"); printf(" file %s\n", user_string($arg1)); printf(" request_uri %s\n", user_string($arg2)); printf(" request_method %s\n", user_string($arg3)); } probe process("sapi/cli/php").provider("php").mark("request__startup") { printf("Probe request__startup\n"); printf(" file %s\n", user_string($arg1)); printf(" request_uri %s\n", user_string($arg2)); printf(" request_method %s\n", user_string($arg3)); }
在 PHP 脚本的执行过程中,上述脚本会跟踪所有的 PHP 核心静态探针:
# stap -c 'sapi/cli/php test.php' all_probes.stp