strace 命令是一个集诊断、调试、统计与一体的工具,我们可以使用 strace 对应用的系统调用和信号传递的跟踪结果来对应用进行分析,以达到解决问题或者是了解应用工作过程的目的。
strace 的最简单的用法就是执行一个指定的命令,在指定的命令结束之后它也就退出了。在命令执行的过程中,strace 会记录和解析命令进程的所有系统调用以及这个进程所接收到的所有的信号值。
通常我们调试 PHP 可以通过 PHP 函数 echo、var_dump、print_r 就能解决大多数问题。亦或通过 XDebug 单点调试调试 PHP。
那么,我们为何需要使用 strace 来调试 PHP 呢?
- 调试性能问题。查看系统调用的频率,找出耗时的程序段(磁盘 IO、网络 IO)。
- 查看程序读取的配置文件。以此定位配置文件加载是否错误。
- 查看某个 PHP 脚本长时间运行“假死”的情况。
1 磁盘 IO
PHP 性能问题通常出现在磁盘 IO、网络 IO。而磁盘 IO 通常是文件加载过多过大,以及日志写入磁盘有关。通过 strace 命令我们就知道这些对系统磁盘 IO 操作的位置耗时。得知是否出现性能问题需要优化。网络 IO 通常指连接数据库、调用第三方接口、其他服务连接等。如果一个网络连接太久就会造成我们的 PHP-FPM 进程不能得到有效利用,从而不能快速释放而加重服务器负载。
现有 fileIo.php
如下类似代码:
$handle = fopen(__FILE__, 'r');
$content = '';
while ($str = fgets($handle)) {
$content .= $str;
}
fclose($handle);
我们通过 strace
执行它。
$ strace -T php fileIo.php
会输出如下信息(部分):
execve("/usr/local/php71/bin/php", ["php", "cli.php", "index/fileIo"], [/* 23 vars */]) = 0 <0.000818>
brk(NULL) = 0x2d69000 <0.000041>
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f64f76bd000 <0.000046>
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) <0.000092>
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 <0.000090>
fstat(3, {st_mode=S_IFREG|0644, st_size=43150, ...}) = 0 <0.000041>
mmap(NULL, 43150, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f64f76b2000 <0.000091>
close(3) = 0 <0.000040>
open("/lib64/libcrypt.so.1", O_RDONLY|O_CLOEXEC) = 3 <0.000240>
根据对输出的日志,我们可以大致分为如下几部分:
1.1命令启动
execve("/usr/local/php71/bin/php", ["php", "fileIo.php"], [/* 23 vars */]) = 0
1.2 加载系统类库
日志当中类似以下信息都在加载 PHP 启动时要加载的系统类库。
open("/lib64/libcrypt.so.1", O_RDONLY|O_CLOEXEC) = 3 <0.000240>
open("/lib64/libz.so.1", O_RDONLY|O_CLOEXEC) = 3 <0.000063>
open("/usr/local/lib/libmcrypt.so.4", O_RDONLY|O_CLOEXEC) = 3 <0.000060>
1.3 PHP 命令查找
因为,我们在用 PHP 命令执行脚本的时候,并没有用绝对路径。所以,系统在执行的时候,会尝试去寻找 php 命令所在的位置。如下日志所示:
lstat("/usr/local/erlang/bin/php", 0x7fff6ef8e590) = -1 ENOENT (No such file or directory)
lstat("/usr/local/nginx/sbin/php", 0x7fff6ef8e590) = -1 ENOENT (No such file or directory)
lstat("/usr/local/php71/bin/php", {st_mode=S_IFREG|0755, st_size=40414496, ...}) = 0
所以,我们想优化脚本执行的速度,那么在运行脚本的时候可以使用绝对路径。这样系统就不需要去这些目录中遍列寻找。
1.4 PHP 配置文件加载
在日志中有如下信息:
open("/usr/local/php71/bin/php-cli.ini", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/local/php71/etc/php-cli.ini", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/local/php71/bin/php.ini", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/local/php71/etc/php.ini", O_RDONLY) = 3
可以看到,我们的 PHP 会优先去 PHP 安装目录下的 bin 目录加载 php-cli.ini 配置。不存在,接着去 etc 目录下加载 php-cli.ini 配置。不存在,接着又去 bin 下读取 php.ini 文件。不存在,又去 etc 目录下找 php.ini 配置。
通过这些加载的顺序以及文件路径,我们可以清晰知道配置的加载顺序,以及加载的配置是否有错。从而验证我们的 PHP 是否正确配置。
1.5 加载 PHP 扩展
当配置文件加载成功之后,PHP 会解析配置文件。然后,加载配置文件当中注册的扩展。如下日志所示:
open("/usr/local/php71/lib/php/extensions/no-debug-non-zts-20160303/xdebug.so", O_RDONLY|O_CLOEXEC) = 3
open("/usr/local/php71/lib/php/extensions/no-debug-non-zts-20160303/redis.so", O_RDONLY|O_CLOEXEC) = 3
open("/usr/local/php71/lib/php/extensions/no-debug-non-zts-20160303/yaf.so", O_RDONLY|O_CLOEXEC) = 3
注意这里调用系统底层的 C 函数 open。-1 代表文件或目录不存在,0 代表标准转入,1 代表标准输出,2 代表标准出错,那么 3 就代表打开文件成功之后返回的句柄 ID。
所以,我们经常会建议 PHP 里面加载的扩展不要太多。够用就行。这样在系统加载的时候 IO 压力就会小。当然,现在采用有了 PHP-FPM 之后,完全不用担心这个加载的开销了。
1.6 TRACE 部分
write(4, "TRACE START [2018-07-20 07:45:47"..., 34) = 34 <0.000046>
write(4, " 0.0026 349704 -> {main"..., 55) = 55 <0.000022>
write(4, " 0.0029 349704 -> fop"..., 77) = 77 <0.000044>
open("/root/fileIo.php", O_RDONLY) = 5 <0.000070>
fstat(5, {st_mode=S_IFREG|0644, st_size=126, ...}) = 0 <0.000011>
lseek(5, 0, SEEK_CUR) = 0 <0.000012>
write(3, "fl=(1) php:internal\nfn=(1) php::"..., 38) = 38 <0.000019>
write(3, "2 473 608\n\n", 11) = 11 <0.000012>
write(4, " 0.0038 350312 -> fge"..., 84) = 84 <0.000017>
read(5, "<?php\n$handle = fopen(__FILE__, "..., 8192) = 126 <0.000017>
write(3, "fl=(1)\nfn=(2) php::fgets\n", 25) = 25 <0.000020>
write(3, "4 137 8224\n\n", 12) = 12 <0.000040>
write(4, " 0.0042 358568 -> fge"..., 84) = 84 <0.000018>
write(3, "fl=(1)\nfn=(2)\n", 14) = 14 <0.000015>
write(3, "4 5 64\n\n", 8) = 8 <0.000011>
write(4, " 0.0044 358632 -> fge"..., 84) = 84 <0.000013>
write(3, "fl=(1)\nfn=(2)\n", 14) = 14 <0.000013>
write(3, "4 1 40\n\n", 8) = 8 <0.000019>
write(4, " 0.0046 358624 -> fge"..., 84) = 84 <0.000016>
write(3, "fl=(1)\nfn=(2)\n", 14) = 14 <0.000013>
write(3, "4 1 64\n\n", 8) = 8 <0.000010>
write(4, " 0.0048 358680 -> fge"..., 84) = 84 <0.000016>
write(3, "fl=(1)\nfn=(2)\n", 14) = 14 <0.000011>
write(3, "4 2 48\n\n", 8) = 8 <0.000018>
write(4, " 0.0050 358712 -> fge"..., 84) = 84 <0.000063>
write(3, "fl=(1)\nfn=(2)\n", 14) = 14 <0.000015>
write(3, "4 2 32\n\n", 8) = 8 <0.000011>
write(4, " 0.0052 358696 -> fge"..., 84) = 84 <0.000014>
write(3, "fl=(1)\nfn=(2)\n", 14) = 14 <0.000013>
write(3, "4 0 48\n\n", 8) = 8 <0.000010>
write(4, " 0.0054 358712 -> fge"..., 84) = 84 <0.000011>
read(5, "", 8192) = 0 <0.000024>
write(3, "fl=(1)\nfn=(2)\n", 14) = 14 <0.000066>
write(3, "4 124 0\n\n", 9) = 9 <0.000015>
write(4, " 0.0058 358664 -> fcl"..., 85) = 85 <0.000012>
close(5) = 0 <0.000014>
write(3, "fl=(1)\nfn=(3) php::fclose\n", 26) = 26 <0.000059>
write(3, "7 64 -8664\n\n", 12) = 12 <0.000014>
write(3, "fl=(2) /root/fileIo.php\nfn=(4) {"..., 58) = 58 <0.000011>
write(3, "1 2352 -168\ncfl=(1)\ncfn=(1)\ncall"..., 375) = 375 <0.000012>
close(2) = 0 <0.000023>
close(1) = 0 <0.000008>
close(0) = 0 <0.000009>
write(4, " 0.0063 184\nTRACE END "..., 57) = 57 <0.000016>
从上面的日志,我们可以发现如下字眼:
...TRACE START...
...TRACE END...
这确实也是我们 PHP 脚本真正逻辑执行的部分。
通过这部分信息,我们可以看到,我们确实通过系统底层的 open 方法打开了我们的脚本文件,并且调用了 read 方法读取脚本中的内容。
注意:
通过 open 函数打开 fileIo.php 脚本时返回的句柄 ID 的值是 5。所以,对该文件的读写操作在下面全部是与 5 相关。所以, write(3, ...) 与 write(4, ..) 这样的代码仅仅与系统底层相关。
2 网络 IO
在第 1 小节,我们通过快速查看了文件 IO 的相关 PHP 执行的调试。本小节,我们主要讲网络 IO 的调试。
2.1 TRACE 部分日志
write(4, "TRACE START [2018-07-20 08:31:43"..., 34) = 34 <0.000034>
write(4, " 0.0055 350160 -> {main"..., 54) = 54 <0.000057>
write(4, " 0.0057 350208 -> Red"..., 68) = 68 <0.000030>
stat("/etc/sysconfig/64bit_strstr_via_64bit_strstr_sse2_unaligned", 0x7ffc8db09af0) = -1 ENOENT (No such file or directory) <0.000029>
write(3, "fl=(1) php:internal\nfn=(1) php::"..., 51) = 51 <0.000032>
write(3, "2 5 0\n\n", 7) = 7 <0.000032>
write(4, " 0.0064 350208 -> Red"..., 79) = 79 <0.000029>
socket(AF_INET6, SOCK_DGRAM, IPPROTO_IP) = 5 <0.000035>
close(5) = 0 <0.000033>
socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 5 <0.000030>
fcntl(5, F_GETFL) = 0x2 (flags O_RDWR) <0.000024>
fcntl(5, F_SETFL, O_RDWR|O_NONBLOCK) = 0 <0.000032>
connect(5, {sa_family=AF_INET, sin_port=htons(6379), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRESS (Operation now in progress) <0.000128>
poll([{fd=5, events=POLLIN|POLLOUT|POLLERR|POLLHUP}], 1, 60000) = 1 ([{fd=5, revents=POLLOUT}]) <0.000047>
getsockopt(5, SOL_SOCKET, SO_ERROR, [0], [4]) = 0 <0.000043>
fcntl(5, F_SETFL, O_RDWR) = 0 <0.000043>
setsockopt(5, SOL_TCP, TCP_NODELAY, [1], 4) = 0 <0.000044>
write(3, "fl=(1)\nfn=(2) php::Redis->connec"..., 34) = 34 <0.000075>
write(3, "3 1884 656\n\n", 12) = 12 <0.000045>
write(4, " 0.0089 350864 -> Red"..., 67) = 67 <0.000048>
poll([{fd=5, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 0 (Timeout) <0.000043>
sendto(5, "*2\r\n$6\r\nSELECT\r\n$1\r\n1\r\n", 23, MSG_DONTWAIT, NULL, 0) = 23 <0.000084>
poll([{fd=5, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 1 ([{fd=5, revents=POLLIN}]) <0.000042>
recvfrom(5, "+", 1, MSG_PEEK, NULL, NULL) = 1 <0.000043>
poll([{fd=5, events=POLLIN|POLLERR|POLLHUP}], 1, 60000) = 1 ([{fd=5, revents=POLLIN}]) <0.000058>
recvfrom(5, "+OK\r\n", 8192, MSG_DONTWAIT, NULL, NULL) = 5 <0.000054>
write(3, "fl=(1)\nfn=(3) php::Redis->select"..., 33) = 33 <0.000052>
write(3, "4 1643 8192\n\n", 13) = 13 <0.000042>
write(4, " 0.0111 359056 -> Red"..., 83) = 83 <0.000049>
poll([{fd=5, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 0 (Timeout) <0.000044>
sendto(5, "*3\r\n$5\r\nLPUSH\r\n$8\r\nlist_key\r\n$12"..., 48, MSG_DONTWAIT, NULL, 0) = 48 <0.000058>
poll([{fd=5, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 1 ([{fd=5, revents=POLLIN}]) <0.000044>
recvfrom(5, ":", 1, MSG_PEEK, NULL, NULL) = 1 <0.000043>
poll([{fd=5, events=POLLIN|POLLERR|POLLHUP}], 1, 60000) = 1 ([{fd=5, revents=POLLIN}]) <0.000043>
recvfrom(5, ":1\r\n", 8192, MSG_DONTWAIT, NULL, NULL) = 4 <0.000053>
write(3, "fl=(1)\nfn=(4) php::Redis->lPush\n", 32) = 32 <0.000088>
write(3, "5 1183 0\n\n", 10) = 10 <0.000044>
write(4, " 0.0129 359056 -> Red"..., 70) = 70 <0.000052>
poll([{fd=5, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 0 (Timeout) <0.000045>
sendto(5, "*2\r\n$4\r\nLPOP\r\n$8\r\nlist_key\r\n", 28, MSG_DONTWAIT, NULL, 0) = 28 <0.000078>
poll([{fd=5, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 1 ([{fd=5, revents=POLLIN}]) <0.000046>
recvfrom(5, "$", 1, MSG_PEEK, NULL, NULL) = 1 <0.000045>
poll([{fd=5, events=POLLIN|POLLERR|POLLHUP}], 1, 60000) = 1 ([{fd=5, revents=POLLIN}]) <0.000049>
recvfrom(5, "$12\r\nhello world~\r\n", 8192, MSG_DONTWAIT, NULL, NULL) = 19 <0.000039>
write(3, "fl=(1)\nfn=(5) php::Redis->lPop\n", 31) = 31 <0.000082>
write(3, "6 1334 40\n\n", 11) = 11 <0.000043>
write(1, "hello world~", 12) = 12 <0.000046>
write(3, "fl=(2) /root/netIo.php\nfn=(6) {m"..., 58) = 58 <0.000091>
write(3, "1 3368 40\ncfl=(1)\ncfn=(1)\ncalls="..., 199) = 199 <0.000045>
write(4, " 0.0156 357168 -> Redis"..., 58) = 58 <0.000046>
close(5) = 0 <0.000059>
close(2) = 0 <0.000040>
close(1) = 0 <0.000040>
close(0) = 0 <0.000047>
write(4, " 0.0165 160\nTRACE END "..., 57) = 57 <0.000062>
我们发现,其中有一行日志如下:
connect(5, {sa_family=AF_INET, sin_port=htons(6379), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRESS (Operation now in progress) <0.000128>
这一行就是我们在连接 Redis 服务器。用时 0.000128 秒。
如果我们在运行的脚本当中怀疑连接 Redis 比较慢。那么就可以通过这样的方式确诊问题是否真正出在这里。
2.2 向队列发送数据
sendto(5, "*2\r\n$4\r\nLPOP\r\n$8\r\nlist_key\r\n", 28, MSG_DONTWAIT, NULL, 0) = 28 <0.000078>
Redis lPush 命令是通过系统底层 sendto 方法封装实现。
2.3 从接收队列数据
recvfrom(5, "$12\r\nhello world~\r\n", 8192, MSG_DONTWAIT, NULL, NULL) = 19 <0.000039>
2.4 输出数据
write(1, "hello world~", 12) = 12 <0.000046>
我们向命令行输出的时候就是调用了系统底层的 write 方法。第一个参数 1 代表标准输出的意思。在 C 语言里面,0 代表标准输入,1 代表标准输出。所谓标准输出指的就是命令行输出。
3 Strace 命令解读
3.1 普通使用
$ strace php xxx.php
通常使用直接在前面加上 strace 即可。就会打印出脚本执行中所有的 TRACE 信息。
3.2 显示运行时间
$ strace -T php xxx.php
有时我们除了要查看 TRACE 信息。可能还想知道每次系统调用所花费的系统时间。此时可以加 -T
参数显示花费的时间。
花费的时间单位要秒。能精确表示时间到纳纱。1 秒等于 1 百万纳秒。
3.3 TRACE 信息保存到文件
$ strace -T -o strace.log php xxx.php
一个复杂的脚本往往会输出大量的 TRACE 日志。如果直接输出到屏幕会给排查问题带来极大的不方便。这里我们可以把这些日志保存到指定的文件当中。
- o
后面空格之后紧张文件名即可。
3.4 统计执行的时间
我们可能想快速知晓我们的 PHP 脚本运行花费的总时间,在哪些位置花费时间的比例,以及哪些地方系统底层调用次数。通过这些数据可以大致推断出当前脚本可能出现的问题。
如下命令:
$ strace -c php xxx.php
如输出如下数据:
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
29.08 0.001196 7 184 1 mmap
20.54 0.000845 7 115 mprotect
14.64 0.000602 7 83 10 open
6.95 0.000286 4 79 fstat
6.15 0.000253 4 69 read
5.98 0.000246 6 43 munmap
5.42 0.000223 3 80 close
2.02 0.000083 28 3 sendto
1.82 0.000075 75 1 1 connect
1.56 0.000064 3 24 write
1.43 0.000059 5 11 poll
0.92 0.000038 1 43 brk
0.61 0.000025 0 80 rt_sigaction
0.56 0.000023 23 1 ftruncate
0.39 0.000016 2 7 3 access
0.19 0.000008 3 3 recvfrom
0.17 0.000007 1 9 2 lstat
0.15 0.000006 2 3 fcntl
0.12 0.000005 1 6 3 lseek
0.12 0.000005 2 3 socket
0.12 0.000005 5 1 execve
0.12 0.000005 3 2 getdents
0.10 0.000004 0 9 7 stat
0.10 0.000004 1 4 getcwd
0.10 0.000004 1 5 futex
0.07 0.000003 2 2 madvise
0.07 0.000003 3 1 setsockopt
0.07 0.000003 3 1 getsockopt
0.07 0.000003 2 2 umask
0.05 0.000002 1 2 rt_sigprocmask
0.05 0.000002 1 2 2 ioctl
0.05 0.000002 1 2 uname
0.05 0.000002 1 3 getrlimit
0.05 0.000002 1 2 getuid
0.05 0.000002 1 2 1 openat
0.02 0.000001 1 1 geteuid
0.02 0.000001 1 1 arch_prctl
0.00 0.000000 0 3 flock
0.00 0.000000 0 1 getgid
0.00 0.000000 0 1 getegid
0.00 0.000000 0 2 2 statfs
0.00 0.000000 0 1 set_tid_address
0.00 0.000000 0 1 set_robust_list
0.00 0.000000 0 1 dup3
------ ----------- ----------- --------- --------- ----------------
100.00 0.004113 899 32 total
通过上述输出的信息。我们知道了如下信息:
1)脚本运行花费 0.004113 秒。
2)耗时最高的三个底层系统调用为:mmap、mprotect、open。
3)每个系统调用的次数/错误次数。
4)脚本运行过程中调用了哪些系统底层函数。
脚本优化可以根据这些时间指标进行对应的优化。
比如,表格当中 errors 错误过多,有可能说明我们的环境尝试了过多的文件加载。导致系统遍列文件时做了过多错误的尝试。这就是为什么我们在写程序的时候推荐使用绝对路径的原因所在。
其次,如果一个脚本在执行过程中调用了太多次的系统 open 方法,说明我们的脚本有可能存在不合理的磁盘或网络 IO。通常是磁盘 IO。当出现磁盘 IO 问题的时候,就要引起足够的重视。
总之,任何异常的时间开销,我们都要引起足够的重视。
3.5 TRACE 已有进程
当一个进程已经启动了,我们想 TRACE 它。也是非常简单的。
$ strace -p 进程ID
嗯。就是这么简单。剩下的跟我们之前上面讲过的差不多。一样一样的。
3.6 更多可选参数说明
以上 5 小点是我们通常用得最多的参数。当然,strace 的功能远不仅如此。我们可以通过更多的可选参数发挥它的功能。
-c 统计每一系统调用的所执行的时间,次数和出错的次数等.
-d 输出 strace 关于标准错误的调试信息.
-f 跟踪由 fork 调用所产生的子进程.
-ff 如果提供 -o filename,则所有进程的跟踪结果输出到相应的filename.pid中,pid是各进程的进程号.
-F 尝试跟踪vfork调用.在-f时,vfork不被跟踪.
-h 输出简要的帮助信息.
-i 输出系统调用的入口指针.
-q 禁止输出关于脱离的消息.
-r 打印出相对时间关于,每一个系统调用.
-t 在输出中的每一行前加上时间信息.
-tt 在输出中的每一行前加上时间信息,微秒级.
-ttt 微秒级输出,以秒了表示时间.
-T 显示每一调用所耗的时间.
-v 输出所有的系统调用.一些调用关于环境变量,状态,输入输出等调用由于使用频繁,默认不输出.
-V 输出strace的版本信息.
-x 以十六进制形式输出非标准字符串
-xx 所有字符串以十六进制形式输出.
-a column 设置返回值的输出位置.默认 为40.
-e expr 指定一个表达式,用来控制如何跟踪.格式:[qualifier=][!]value1[,value2]...
qualifier只能是 trace,abbrev,verbose,raw,signal,read,write其中之一.value是用来限定的符号或数字.默认的 qualifier是 trace.感叹号是否定符号.例如:-eopen等价于 -e trace=open,表示只跟踪open调用.而-etrace!=open 表示跟踪除了open以外的其他调用.有两个特殊的符号 all 和 none. 注意有些shell使用!来执行历史记录里的命令,所以要使用\\.
-e trace=set 只跟踪指定的系统 调用.例如:-e trace=open,close,rean,write表示只跟踪这四个系统调用.默认的为set=all.
-e trace=file 只跟踪有关文件操作的系统调用.
-e trace=process 只跟踪有关进程控制的系统调用.
-e trace=network 跟踪与网络有关的所有系统调用.
-e strace=signal 跟踪所有与系统信号有关的 系统调用
-e trace=ipc 跟踪所有与进程通讯有关的系统调用
-e abbrev=set 设定strace输出的系统调用的结果集.-v 等与 abbrev=none.默认为abbrev=all.
-e raw=set 将指定的系统调用的参数以十六进制显示.
-e signal=set 指定跟踪的系统信号.默认为all.如 signal=!SIGIO(或者signal=!io),表示不跟踪SIGIO信号.
-e read=set 输出从指定文件中读出 的数据.例如: -e read=3,5
-e write=set 输出写入到指定文件中的数据.
-o filename 将strace的输出写入文件filename
-p pid 跟踪指定的进程pid.
-s strsize 指定输出的字符串的最大长度.默认为32.文件名一直全部输出.
-u username 以username的UID和GID执行被跟踪的命令
4 总结
总的来说,我们可以使用 strace 命令得知我们的 PHP 脚本运行时调用了底层哪些方法,以及运行的时间等信息。让我们可以判断脚本当中到底出了什么病况而后进行优化。
但是,strace 有一个不足:调试 PHP 的时候无法获知每个系统调用对应了 PHP 哪个脚本的哪个方法或哪一行。这样即使我们知道脚本存在时间上的异常耗时,也无法快速获知是哪里出现了问题。
要熟悉掌握 strace,还需要对底层系统方法有一定的了解。
网友评论