DTrace的運(yùn)行需要用戶(hù)使用DTrace D腳本來(lái)激活并監(jiān)控PHP探針,否則它不會(huì)運(yùn)行,也就不會(huì)對(duì)正常的應(yīng)用執(zhí)行帶來(lái)任何性能損耗。即使PHP探針被激活,它的消耗也非常低,因此可以放心在生產(chǎn)系統(tǒng)中使用,不會(huì)對(duì)系統(tǒng)性能造成明顯影響。
一、概述
許多操作系統(tǒng)(如Solaris、macOS、Oracle Linux和BSD)都提供了DTrace跟蹤調(diào)試框架,它可以永遠(yuǎn)可用,并且額外消耗極低的資源。使用DTrace,可以跟蹤操作系統(tǒng)行為和用戶(hù)程序的執(zhí)行情況,并顯示參數(shù)值,也可以用來(lái)分析性能問(wèn)題。用戶(hù)可以使用DTrace D腳本語(yǔ)言創(chuàng)建腳本文件來(lái)監(jiān)控探針,進(jìn)而高效分析數(shù)據(jù)指針,這使得DTrace成為開(kāi)發(fā)人員和系統(tǒng)管理員的有力工具。
在運(yùn)行時(shí)可以激活 PHP “用戶(hù)級(jí)靜態(tài)定義跟蹤”(USDT)探針。 舉例說(shuō)明,如果 D 腳本正在監(jiān)控 PHP function-entry 探針, 那么,每當(dāng) PHP 腳本函數(shù)被調(diào)用的時(shí)候,這個(gè)探針將被觸發(fā),同時(shí) D 腳本中所關(guān)聯(lián)的動(dòng)作代碼將被執(zhí)行。 在腳本的動(dòng)作代碼中,可以打印出諸如函數(shù)所在源文件等信息的探針參數(shù), 也可以記錄諸如函數(shù)執(zhí)行次數(shù)這樣的聚合數(shù)據(jù)。
二、使用PHP和DTrace
在支持 DTrace 動(dòng)態(tài)跟蹤的平臺(tái)上,可以配置 PHP 打開(kāi) DTrace 靜態(tài)探針。
1、PHP DTrace 靜態(tài)探針配置
在 Oracle Linux 系統(tǒng)上啟動(dòng) UEK3 內(nèi)核,并進(jìn)行如下操作:
# modprobe fasttrap # chmod 666 /dev/dtrace/helper
除了 chmod 命令,也可以使用 ACL 包規(guī)則來(lái)限制特定用戶(hù)對(duì)于設(shè)備的訪(fǎng)問(wèn)權(quán)限。
使用 –enable-dtrace 配置參數(shù)構(gòu)建 PHP:
# ./configure --enable-dtrace ... # make # make install
這樣就啟用了 PHP 核心的靜態(tài)探針。對(duì)于提供了自有探針的 PHP 擴(kuò)展需要分別構(gòu)建。
2、PHP 核心的 DTrace 靜態(tài)探針
PHP 中包括以下可用的靜態(tài)探針:
3、PHP 中可用的靜態(tài)探針
要列出 PHP 中可用的靜態(tài)探針,開(kāi)啟一個(gè) PHP 進(jìn)程,然后運(yùn)行:
# dtrace -l
輸出信息類(lèi)似如下所示:
?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 和當(dāng)前進(jìn)程 id 的組成。如果運(yùn)行的是 Apache web 服務(wù)器,那么模塊名稱(chēng)可能是 libphp5.so, 并且可能會(huì)出現(xiàn)多塊信息,每個(gè)運(yùn)行中的 Apache 進(jìn)程對(duì)應(yīng)一個(gè)輸出塊。
Function Name 一列表示 Provider 對(duì)應(yīng)的 PHP 內(nèi)部 C 實(shí)現(xiàn)函數(shù)名稱(chēng)。
如果沒(méi)有運(yùn)行任何 PHP 進(jìn)程,那么就不會(huì)顯示任何 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 選項(xiàng)。 此選項(xiàng)保證即使在沒(méi)有任何 PHP 進(jìn)程運(yùn)行的時(shí)候腳本也能夠正確執(zhí)行。 如果省略了此選項(xiàng),當(dāng)沒(méi)有任何探針可監(jiān)控的時(shí)候,腳本會(huì)立即終止執(zhí)行。
在運(yùn)行此腳本的過(guò)程中,它將監(jiān)控全部 PHP 核心靜態(tài)探針。 運(yùn)行 D 腳本:
# ./all_probes.d
再運(yùn)行一個(gè) PHP 腳本或者 PHP 應(yīng)用,用來(lái)進(jìn)行監(jiān)控的 D 腳本會(huì)輸出每個(gè)探針被觸發(fā)時(shí)所攜帶的參數(shù)。
監(jiān)控完成之后,使用 CTRL+C 來(lái)終止 D 腳本的執(zhí)行。
在多 CPU 的主機(jī)上,探針的顯示順序可能不是連續(xù)的,這取決于哪顆 CPU 執(zhí)行探針以及多個(gè) CPU 之間的線(xiàn)程遷移情況。 可以通過(guò)顯示探針時(shí)間戳來(lái)減少混淆,例如:
php*:::function-entry { printf("%lld: PHP function-entry ", walltimestamp); [ . . .] }
三、PHP DTrace靜態(tài)探針
在某些 Linux 發(fā)行版上,可以使用 SystemTap 跟蹤工具來(lái)跟蹤 PHP 的靜態(tài) 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 靜態(tài)探針
# 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 腳本的執(zhí)行過(guò)程中,上述腳本會(huì)跟蹤所有的 PHP 核心靜態(tài)探針:
# stap -c 'sapi/cli/php test.php' all_probes.stp