作者:Christopher Jones
2013 年 11 月发布(改编自 Christopher Jones 的 PHP and Oracle 博客)
|
DTrace 是一个受欢迎的、“总是可用”的跟踪实用程序,用于识别开发和生产系统中的性能和行为问题。适用于 Oracle Linux 的标准“UEK3”内核包括了 DTrace 支持。通过跟踪 PHP 核心和 PHP OCI8 扩展中的用户探测器,我们可以有效地识别 PHP 脚本问题。DTrace 让您可以跟踪用户应用程序与操作系统之间的交互。
在 Oracle Linux 上,DTrace 实用程序需要一个 Oracle Unbreakable Linux Network (ULN) 订阅。
首先,通过 Oracle E-Delivery 安装 Oracle Linux 6.4。
# yum install kernel-uek
# yum install dtrace-utils
需要使用 --enable-dtrace
参数构建 PHP。您可以安装一些启用了 DTrace 的预先构建的评估版 RPM,请参见“使用 Oracle Linux“playground”预构建软件包的 DTrace PHP”。
也可以按照如下所述重新构建 PHP:
$ tar -xJf php-5.4.20.tar.bz2 $ cd php-5.4.20
$ ./configure \ --prefix=$HOME/p54 \ --enable-dtrace \ --disable-all --disable-cgi \ --with-pear --enable-xml --enable-libxml --with-zlib
这将构建一个启用了 DTrace 的最小的命令行 PHP。所有不需要的扩展均被禁用。您可根据需要包含其他扩展。
--prefix
选项表示安装到一个本地目录,方便您查看安装的文件。用完快照之后,清理该目录很容易。
pear
、xml
和 zlib
选项允许使用 pecl
命令。
$ make $ make install
php.ini-development
复制到 $HOME/p54/lib/php.ini
,编辑该文件来设置时区,例如:date.timezone = America/Los_Angeles
评估版 PHP RPM 包括可以连接到 Oracle Database 的 OCI8 扩展;请参见前面提到的博文。
不过,如果您自己编译 PHP,需要手动将 PHP OCI8 作为一个“共享的”扩展添加:
root
用户身份安装 Oracle Instant Client:# yum install oracle-instantclient12.1-basic oracle-instantclient12.1-devel
PATH
:$ export PATH=$HOME/p54/bin:$PATH
http://pecl.php.net
,请设置一个 PEAR 代理:$ pear config-set http_proxy http://myproxy.example.com:80/
PHP_DTRACE
,然后安装 PHP OCI8:$ PHP_DTRACE=yes pecl install oci8-2.0.2
本文后面将要用到的 DTrace 探测器定义基于 PHP OCI8 2.0.2,因此需要安装明确的版本。如果您要安装更高的版本,请查看探测器及其参数,了解最新版本中的小改进。
当提示 ORACLE_HOME
目录时,无需输入文本,直接按 Return。安装将自动检测 Oracle Instant Client RPM。配置将继续,输出将包含类似下面的内容:
[ . . . ] checking for Oracle Database OCI8 support... yes, shared checking PHP version... 5.4.20, ok checking OCI8 DTrace support... yes [ . . . ] configure: WARNING: OCI8 extension: ORACLE_HOME is not set, looking for default Oracle Instant Client instead checking Oracle Instant Client directory... /usr/lib/oracle/12.1/client64/lib checking Oracle Instant Client SDK header directory... /usr/include/oracle/12.1/client64 checking Oracle Instant Client library version compatibility... 12.1 [ . . . ]
php.ini
,添加 PHP OCI8:extension=oci8.so
$ php --ri oci8 oci8 OCI8 Support => enabled OCI8 DTrace Support => enabled OCI8 Version => 2.0.2-dev Revision => $Id: b30fb4bef45d9f5ce8a56b736f1546ea0cff08ef $ Oracle Run-time Client Library Version => 12.1.0.1.0 Oracle Compile-time Instant Client Version => 12.1 Directive => Local Value => Master Value oci8.max_persistent => -1 => -1 oci8.persistent_timeout => -1 => -1 oci8.ping_interval => 60 => 60 oci8.privileged_connect => Off => Off oci8.statement_cache_size => 20 => 20 oci8.default_prefetch => 100 => 100 oci8.old_oci_close_semantics => Off => Off oci8.connection_class => no value => no value oci8.events => Off => Off Statistics => Active Persistent Connections => 0 Active Connections => 0
为了支持 DTrace,需要从 PECL 安装 PHP OCI8 2.0,因为 PHP 5.4 和 PHP 5.5 包含的 PHP OCI8 1.4 没有 DTrace 探测器。将来,在 PHP 5.6 发布后,您就能够在构建 PHP 时配置支持 DTrace 的 PHP OCI8。
当然,您可以通过 Oracle Instant Client zip 文件安装 PHP OCI8,也可以仅使用现有的 ORACLE_HOME
安装。
您可以在不包含或未配置 DTrace 的 PHP 版本上安装启用 DTrace 的 PHP OCI8。这包括 PHP 早期版本。您将能够跟踪 PHP OCI8 探测器,但无法跟踪核心 PHP 探测器。类似地,您可以在启用 DTrace 的 PHP 上安装禁用 DTrace 的 PHP OCI8。
如果您使用 phpize
和 configure
(而不是 pecl
)从 PECL 安装 PHP OCI8 2.0 ,则仍需设置 PHP_DTRACE=yes
。这是因为 PECL 软件包有限的 configure
脚本将忽略 --enable-dtrace
选项。
PHP OCI8 2.0 配置脚本适合“真正的”DTrace 使用,但 Linux SystemTap 不会跟踪扩展。
注意,DTrace 优化后的二进制文件产生的输出可能与我们所见到的代码不尽相同。
root
用户身份启用 DTrace,允许普通用户记录跟踪信息:# modprobe fasttrap # chmod 666 /dev/dtrace/helper
您可以使用 ACL 软件包规则限制特定用户对设备的访问,而非使用 chmod
命令。
php
,不带任何选项。PHP 将启动,然后等待输入:$ php
root
用户身份列出可用的 DTrace 探测器。列出了 PHP 核心探测器和 PHP OCI8 探测器:# dtrace -l -m php -m oci8.so 4 php9559 php dtrace_compile_file compile-file-entry 5 php9559 php dtrace_compile_file compile-file-return 6 php9559 php zend_error error 7 php9559 php ZEND_CATCH_SPEC_CONST_CV_HANDLER exception-caught 8 php9559 php zend_throw_exception_internal exception-thrown 9 php9559 php dtrace_execute_ex execute-entry 10 php9559 php dtrace_execute_internal execute-entry 11 php9559 php dtrace_execute_ex execute-return 12 php9559 php dtrace_execute_internal execute-return 13 php9559 php dtrace_execute_ex function-entry 14 php9559 php dtrace_execute_ex function-return 15 php9559 php php_request_shutdown request-shutdown 16 php9559 php php_request_startup request-startup 17 php9559 oci8.so php_oci_dtrace_check_connection oci8-check-connection 18 php9559 oci8.so php_oci_do_connect oci8-connect-entry 19 php9559 oci8.so php_oci_persistent_helper oci8-connect-expiry 20 php9559 oci8.so php_oci_do_connect_ex oci8-connect-lookup 21 php9559 oci8.so php_oci_pconnection_list_np_dtor oci8-connect-p-dtor-close 22 php9559 oci8.so php_oci_pconnection_list_np_dtor oci8-connect-p-dtor-release 23 php9559 oci8.so php_oci_do_connect oci8-connect-return 24 php9559 oci8.so php_oci_do_connect_ex oci8-connect-type 25 php9559 oci8.so php_oci_error oci8-error 26 php9559 oci8.so php_oci_statement_execute oci8-execute-mode 27 php9559 oci8.so php_oci_create_spool oci8-sesspool-create 28 php9559 oci8.so php_oci_create_session oci8-sesspool-stats 29 php9559 oci8.so php_oci_create_session oci8-sesspool-type 30 php9559 oci8.so php_oci_statement_create oci8-sqltext
PHP 手册中详细记载了核心 PHP 探测器。PHP OCI8 探测器如下所述。
php
可执行文件。$ php ^C $
静态 PHP OCI8 2.0 探测器可分为“用户”探测器和“维护者”探测器。后者对于 PHP OCI8 维护者更有用,可在扩展开发期间验证功能。所有探测器均以参数形式返回数据。官方文档中详细介绍了 PHP OCI8 DTrace 探测器。
在 OCI8 2.0.2 中,用户探测器包括:
oci8-connect-entry
— 由 oci_connect()
、oci_pconnect()
和 oci_new_connect()
启动。在建立数据库连接之前触发。char *username
— 连接用户名。char *dbname
— 数据库连接字符串。char *charset
— 指定的字符集。long session_mode
— OCI_SYSDBA (0x2)
、OCI_SYSOPER (0x4)
和 OCI_CRED_EXT (1<<31)
的二进制“或”(或者过去我所使用平台上的 -2147483648
)。默认情况下,设置为 0
。int persistent
— 如果调用了 oci_pconnect()
,则设置为 1
;否则设置为 0
。int exclusive
— 如果调用了 oci_new_connect()
,则设置为 1
;否则设置为 0
。oci8-connect-return
— 在连接的末尾触发。void *connection
— 连接结构的地址。oci8-check-connection
— 如果 Oracle 错误可能已经导致连接失效,则启动。void *connection
— 连接结构的地址。int is_open
— 如果 errcode
或 server_status
指示连接无效且必须重新创建,则为 0
。long errcode
— Oracle 错误编号。unsigned long server_status
— 为 Oracle 库的一个指示器,指示连接是否无效。如果因为 errcode
指示连接无效导致 is_open
为 0
,则 server_status
为其默认值 1
。oci8-sqltext
— 执行 oci_parse()
时启动。void *connection
— 连接结构的地址。char *sql
— 所执行的 SQL 语句的文本。oci8-error
— 如果发生 Oracle 错误,则启动。int status
— Oracle 返回失败的 Oracle 库调用的状态,如 -1
代表 Oracle 的 OCI_ERROR
,1
代表 Oracle 的 OCI_SUCCESS_WITH_INFO
。请参见 Oracle 的 oci.h
,了解所有定义。long errcode
— Oracle 错误编号。oci8-execute-mode
— 指示 oci_execute()
调用的提交状态。void *connection
— 连接结构的地址。unsigned int mode
— 传递给 Oracle 库的模式,如 OCI_NO_AUTO_COMMIT (0x00)
、OCI_DESCRIBE_ONLY (0x10)
或 OCI_COMMIT_ON_SUCCESS (0x20)
。注:最新的 OCI8 2.0.6 中包含 oci8-connection-close
探测器,有几个探测器现在也具有 client_id
参数和指向语句结构的指针。
维护者探测器如下所示。参数描述请参考 PHP OCI8 源代码。
oci8-connect-p-dtor-close
void *connection
oci8-connect-p-dtor-release
void *connection
oci8-connect-lookup
void *connection
int is_stub
oci8-connect-expiry
void *connection
int is_stub
long idle_expiry
long timestamp
oci8-connect-type
int persistent
int exclusive
void *connection
long num_persistent
long num_connections
oci8-sesspool-create
void *session_pool
oci8-sesspool-stats
unsigned long free
unsigned long busy
unsigned long open
oci8-sesspool-type
int type
void *session_pool
PHP OCI8 2.0 中的探测器代替了 PHP OCI8 1.4 中使用的 oci_internal_debug()
跟踪。该函数已变成一条空指令,不执行任何操作。
执行以下步骤。
oci8.php
来查询数据库:<?php error_reporting(0); ini_set('display_errors', 'Off'); function do_query($c, $sql) { $s = oci_parse($c, $sql); if (!$s) return; $r = oci_execute($s); if (!$r) return; while (($row = oci_fetch_row($s)) != false) { foreach ($row as $item) { echo $item . " "; } echo "\n"; } } $c = oci_new_connect('hr', 'welcome', 'localhost/pdborcl'); do_query($c, "select city from locations where rownum < 5 order by 1"); do_query($c, "select something from does_not_exist"); ?>
user_oci8.d
来探测 oci8.php
的执行:#!/usr/sbin/dtrace -Zs # This script is for OCI8 2.0.2 php*:::oci8-connect-entry { printf("PHP connect-entry\n"); printf("\t username %s\n", arg0 ? copyinstr(arg0) : ""); printf("\t dbname %s\n", arg1 ? copyinstr(arg1) : ""); printf("\t charset %s\n", arg2 ? copyinstr(arg2) : ""); printf("\t session_mode %ld\n", (long)arg3); printf("\t persistent %d\n", (int)arg4); printf("\t exclusive %d\n", (int)arg5); } php*:::oci8-connect-return { printf("PHP oci8-connect-return\n"); printf("\t connection 0x%p\n", (void *)arg0); } php*:::oci8-connection-close { printf("PHP oci8-connect-close\n"); printf("\t connection 0x%p\n", (void *)arg0); } php*:::oci8-error { printf("PHP oci8-error\n"); printf("\t status %d\n", (int)arg0); printf("\t errcode %ld\n", (long)arg1); } php*:::oci8-check-connection { printf("PHP oci8-check-connection\n"); printf("\t connection 0x%p\n", (void *)arg0); printf("\t is_open %d\n", arg1); printf("\t errcode %ld\n", (long)arg2); printf("\t server_status %lu\n", (unsigned long)arg3); } php*:::oci8-sqltext { printf("PHP oci8-sqltext\n"); printf("\t connection 0x%p\n", (void *)arg0); printf("\t sql %s\n", arg0 ? copyinstr(arg1) : ""); } php*:::oci8-execute-mode { printf("PHP oci8-execute-mode\n"); printf("\t connection 0x%p\n", (void *)arg0); printf("\t mode 0x%x\n", arg1); }
使用 OCI8 2.0.6,一些参数的数值需要调整,才能满足现在一些新增参数的需要。
root
用户身份启动 D 脚本。该脚本将暂停,等待探测器触发:# chmod +x user_oci8.d # ./user_oci8.d
(稍后,体验完 PHP 之后,可以使用 Ctrl-C 关闭此终端。)
$ php oci8.php Beijing Bern Bombay Geneva
# ./user_oci8.d dtrace: script 'user_oci8.d' matched 0 probes CPU ID FUNCTION:NAME 1 18 php_oci_do_connect:oci8-connect-entry PHP connect-entry username hr dbname localhost/pdborcl charset session_mode 0 persistent 0 exclusive 0 0 23 php_oci_do_connect:oci8-connect-return PHP oci8-connect-return connection 0x7f64e112cff0 0 31 php_oci_statement_create:oci8-sqltext PHP oci8-sqltext connection 0x7f64e112cff0 sql select city from locations where rownum < 5 order by 1 0 27 php_oci_statement_execute:oci8-execute-mode PHP oci8-execute-mode connection 0x7f64e112cff0 mode 0x20 0 31 php_oci_statement_create:oci8-sqltext PHP oci8-sqltext connection 0x7f64e112cff0 sql select something from does_not_exist 0 27 php_oci_statement_execute:oci8-execute-mode PHP oci8-execute-mode connection 0x7f64e112cff0 mode 0x20 0 26 php_oci_error:oci8-error PHP oci8-error status -1 errcode 942 0 17 php_oci_dtrace_check_connection:oci8-check-connection PHP oci8-check-connection connection 0x7f64e112cff0 is_open 1 errcode 942 server_status 1 0 25 php_oci_connection_close:oci8-connection-close PHP oci8-connect-close connection 0x7f64e112cff0
(将 -q
添加到 user_oci8.d
的 /usr/sbin/dtrace
参数将隐藏 CPU 和 ID 详细信息。)
在多 CPU 计算机上,探测器可能不会顺序出现,具体取决于哪个 CPU 正在处理探测器。显示探测器时间戳将有助于消除混淆,例如:
php*:::oci8-connect-entry { printf("PHP connect-entry at %lld\n", walltimestamp); }
从 user_oci8.d
DTrace 输出中,您可以看到以下内容:
oci8-connect-entry
)。连接到 localhost/pdborcl
数据库的用户 hr
。因为 exclusive
和 persistent
均为 0
,所以是一个 oci_connect()
调用。未请求任何显式的字符集。请求的是默认的会话模式(oci_connect
的第五个可选参数)。oci8-sqltext
),并以 0x20
(即 OCI_COMMIT_ON_SUCCESS
)模式执行 (oci-execute-mode
)。oci8-error
) 一条 Oracle 错误 — ORA-942
(表或视图不存在
)。oci8-check-connection
)。is_open
的值为 1
,表示连接正常。通过这些信息,您可以跟踪有问题的语句执行和连接问题。
DTrace 是一个非常强大的实用程序,本文只是有关其用法的只言片语。通过上述示例,可将 PHP OCI8 跟踪与核心 PHP 跟踪相集成。您可以设置 PHP 函数调用的时间,计算调用的次数。您可以深入了解 PHP 进行的操作系统调用。Bryan Cantrill 在 DTrace 和 PHP(演示)中发布了一些核心 PHP 跟踪的示例。(注意,博客平台升级导致其博文中的一个反斜杠显示为两个反斜杠。而且,您不再需要单独的 PHP DTrace 扩展。)Brendan Gregg 的 DTrace Toolkit 包含许多有用的 DTrace 脚本。还有各种不同的博客。
记住,DTrace 的目的是始终启用其功能,适合开发和生产(此过程最需要 DTrace)。DTrace 的设计意味着探测器不受监视时零开销。
Christopher 任职于 Oracle 的 Linux 和虚拟化小组,主要关注上游工具。他是适合 Oracle Database 的 PHP OCI8 扩展的维护人员负责人,撰写了许多有关 PHP 和 Oracle 技术的技术文章。他与人合著了畅销书《The Underground PHP and Oracle Manual》,该书可从 OTN 上免费下载。
修订版 1.0,2013 年 11 月 18 日 |