17. 系统监测工具
第17章 系统监测工具
1. tcpdump
tcpdump常见的选项总结如下:
1 |
|
除了使用选项外,tcpdump还支持用表达式来进一步过滤数据包。tcpdump表达式的操作数分为3种:类型(type)、方向(dir)和协议(proto)。下面依次介绍之。
类型,解释其后面紧跟着的参数的含义。
- tcpdump支持的类型包括host、net、port和portrange。它们分别指定主机名(或IP地址),用CIDR方法表示的网络地址,端口号以及端口范围。
- 比如,要抓取整个1.2.3.0/255.255.255.0网络上的数据包,可以使用如下命令:
1 |
|
方向,src指定数据包的发送端,dst指定数据包的目的端。比如要抓取进入端口13579的数据包,可以使用如下命令:
1
$tcpdump dst port 13579
协议,指定目标协议。比如要抓取所有ICMP数据包,可以使用如下命令:
1
$tcpdump icmp
当然,我们还可以使用逻辑操作符来组织上述操作数以创建更复杂的表达式。
- tcpdump支持的逻辑操作符和编程语言中的逻辑操作符完全相同,包括and(或者&&)、or(或者||)、not(或者!)。
- 比如要抓取主机ernest-laptop和所有非Kongming20的主机之间交换的IP数据包,可以使用如下命令:
1 |
|
如果表达式比较复杂,那么我们可以使用括号将它们分组。不过在使用括号时,我们要么使用反斜杠“”对它转义,要么用单引号“'”将其括住,以避免它被shell所解释。比如要抓取来自主机10.0.2.4,目标端口是3389或22的数据包,可以使用如下命令:
1
$tcpdump'src 10.0.2.4 and(dst port 3389 or 22)'
此外,tcpdump还允许直接使用数据包中的部分协议字段的内容来过滤数据包。比如,仅抓取TCP同步报文段,可使用如下命令:
1
$tcpdump'tcp[13]&2!=0'
1
$tcpdump'tcp[tcpflags]&tcp-syn!=0'.
最后,tcpdump的具体输出格式除了与选项有关外,还与协议有关。前文中我们讨论过IP、TCP、ICMP、DNS等协议的tcpdump输出格式。关于其他协议的tcpdump输出格式,请读者自己参考tcpdump的man手册,本书不再赘述。
2. lsof
lsof(list open file)是一个列出当前系统打开的文件描述符的工具。通过它我们可以了解感兴趣的进程打开了哪些文件描述符,或者我们感兴趣的文件描述符被哪些进程打开了。
lsof命令常用的选项包括:
-i,显示socket文件描述符。该选项的使用方法是: 1
$lsof-i[46][protocol][@hostname|ipaddr][:service|port]
1 |
|
如果-i选项后不指定任何参数,则lsof命令将显示所有socket文件描述符。
-u,显示指定用户启动的所有进程打开的所有文件描述符。
-c,显示指定的命令打开的所有文件描述符。比如要查看websrv程序打开了哪些文件描述符,可以使用如下命令:
1
$lsof-c websrv
-p,显示指定进程打开的所有文件描述符。
-t,仅显示打开了目标文件描述符的进程的PID。
我们还可以直接将文件名作为lsof命令的参数,以查看哪些进程打开了该文件。
下面介绍一个实例:查看websrv服务器打开了哪些文件描述符。具体操作如代码清单17-1所示。
代码清单17-1 用lsof命令查看websrv服务器打开的文件描述符
1 |
|
lsof命令的输出内容相当丰富,其中每行内容都包含如下字段:
COMMAND,执行程序所使用的终端命令(默认仅显示前9个字符)。
PID,文件描述符所属进程的PID。
USER,拥有该文件描述符的用户的用户名。
FD,文件描述符的描述。其中cwd表示进程的工作目录,rtd表示用户的根目录,txt表示进程运行的程序代码,mem表示直接映射到内存中的文件(本例中都是动态库)。有的FD是以“数字+访问权限”表示的,其中数字是文件描述符的具体数值,访问权限包括r(可读)、w(可写)和u(可读可写)。在本例中,0u、1u、2u分别表示标准输入、标准输出和标准错误输出;3u表示处于LISTEN状态的监听socket;4u表示epoll内核事件表对应的文件描述符。
TYPE,文件描述符的类型。其中DIR是目录,REG是普通文件,CHR是字符设备文件,IPv4是IPv4类型的socket文件描述符,0000是未知类型。更多文件描述符的类型请参考lsof命令的man手册,这里不再赘述。
DEVICE,文件所属设备。对于字符设备和块设备,其表示方法是“主设备号,次设备号”。由代码清单17-1可见,测试机器上的程序文件和动态库都存放在设备“8,3”中。其中,“8”表示这是一个SCSI硬盘;“3”表示这是该硬盘上的第3个分区,即sda3。websrv程序的标准输入、标准输出和标准错误输出对应的设备是“136,3”。其中,“136”表示这是一个伪终端;“3”表示它是第3个伪终端,即/dev/pts/3。关于设备编号的更多细节,请参考文档http://www.kernel.org/pub/linux/docs/lanana/device-list/devices-2.6.txt。对于FIFO类型的文件,比如管道和socket,该字段将显示一个内核引用目标文件的地址,或者是其i节点号。
SIZE/OFF,文件大小或者偏移值。如果该字段显示为“0t”或者“0x”,就表示这是一个偏移值,否则就表示这是一个文件大小。对字符设备或者FIFO类型的文件定义文件大小没有意义,所以该字段将显示一个偏移值。
NODE,文件的i节点号。对于socket,则显示为协议类型,比如“TCP”。
NAME,文件的名字。
如果我们使用telnet命令向websrv服务器发起一个连接,则再次执行代码清单17-1中的lsof命令时,其输出将多出如下一行:
1 |
|
该输出表示服务器打开了一个IPv4类型的socket,其值是5,且它处于ESTABLISHED状态。该socket对应的连接的本端socket地址是(127.0.0.1,13579),远端socket地址则是(127.0.0.1,48215)。
3. nc
nc(netcat)命令短小精干、功能强大,有着“瑞士军刀”的美誉。它主要被用来快速构建网络连接。我们可以让它以服务器方式运行,监听某个端口并接收客户连接,因此它可用来调试客户端程序。我们也可以使之以客户端方式运行,向服务器发起连接并收发数据,因此它可以用来调试服务器程序,此时它有点像telnet程序。
nc命令常用的选项包括:
1 |
|
-z,扫描目标机器上的某个或某些服务是否开启(端口扫描)。比如,要扫描机器ernest-laptop上端口号在20~50之间的服务,可以使用如下命令:
1 |
|
举例来说,我们可以使用如下方式来连接websrv服务器并向它发送数据:
1 |
|
这里我们使用了-C选项,这样每次我们按下回车键向服务器发送一行数据时,nc客户端程序都会给服务器额外发送一个<CR><LF>,而这正是websrv服务器期望的HTTP行结束符。发送完第三行数据之后,我们得到了服务器的响应,内容正是我们期望的:服务器没有找到被请求的资源文件a.html。可见,nc命令是一个很方便的快速测试工具,通过它我们能很快找出服务器的逻辑错误。
4. strace
strace是测试服务器性能的重要工具。它跟踪程序运行过程中执行的系统调用和接收到的信号,并将系统调用名、参数、返回值及信号名输出到标准输出或者指定的文件。
strace命令常用的选项包括:
-c,统计每个系统调用执行时间、执行次数和出错次数。
-f,跟踪由fork调用生成的子进程。
-t,在输出的每一行信息前加上时间信息。
-e,指定一个表达式,用来控制如何跟踪系统调用(或接收到的信号,下同)。其格式是: [qualifier=][!]value1[,value2]...。 qualifier可以是trace、abbrev、verbose、raw、signal、read和write中之一,默认是trace。value是用于进一步限制被跟踪的系统调用的符号或数值。它的两个特殊取值是all和none,分别表示跟踪所有由qualifier指定类型的系统调用和不跟踪任何该类型的系统调用。关于value的其他取值,我们简单地列举一些:
-e trace=set,只跟踪指定的系统调用。例如,-e trace=open,close,read,write表示只跟踪open、close、read和write这四种系统调用。
-e trace=file,只跟踪与文件操作相关的系统调用。
-e trace=process,只跟踪与进程控制相关的系统调用。
-e trace=network,只跟踪与网络相关的系统调用。
-e trace=signal,只跟踪与信号相关的系统调用。
-e trace=ipc,只跟踪与进程间通信相关的系统调用。
-e signal=set,只跟踪指定的信号。比如,-e signal=!SIGIO表示跟踪除SIGIO之外的所有信号。
-e read=set,输出从指定文件中读入的数据。例如,-e read=3,5表示输出所有从文件描述符3和5读入的数据。
-o,将strace的输出写入指定的文件。
strace命令的每一行输出都包含这些字段:系统调用名称、参数和返回值。比如下面的示例:
1 |
|
这行输出表示:程序“cat/dev/null”在运行过程中执行了open系统调用。open调用以只读的方式打开了大文件/dev/null,然后返回了一个值为3的文件描述符。需要注意的是,该示例命令将输出很多内容,这里我们省略了很多次要的信息,在后面的实例中,我们也仅显示主题相关的内容。
当系统调用发生错误时,strace命令将输出错误标识和描述,比如下面的示例:
1 |
|
strace命令对不同的参数类型将有不同的输出方式,比如:
对于C风格的字符串,strace将输出字符串的内容。默认的最大输出长度是32字节,过长的部分strace会使用“…”省略。比如,ls-l命令在运行过程中将读取/etc/passwd文件:
1 |
|
需要注意的是,文件名并不被strace当作C风格的字符串,其内容总是被完整地输出。
对于结构体,strace将用“{}”输出该结构体的每个字段,并用“,”将每个字段隔开。对于字段较多的结构体,strace将用“…”省略部分输出。比如:
1 |
|
上面的strace输出显示,lstat64系统调用的第1个参数是字符串输入参数“/dev/null”;第二个参数则是stat结构体类型的输出参数(指针),strace仅显示了该结构体参数的两个字段:st_mode和st_rdev。需要注意的是,当系统调用失败时,输出参数将显示为传入前的值。
对于位集合参数(比如信号集类型sigset_t),strace将用“[]”输出该集合中所有被置1的位,并用空格将每一项隔开。假设某个程序中有如下代码:
1 |
|
则针对该程序的strace命令将输出如下内容:
1 |
|
针对其他参数类型的输出方式,请读者参考strace的man手册,这里不再赘述。对于程序接收到的信号,strace将输出该信号的值及其描述。比如,我们在一个终端上运行“sleep 100”命令,然后在另一个终端上使用strace命令跟踪该进程,接着用“Ctrl+C”终止“sleep 100”进程以观察strace的输出。具体操作如下:
1 |
|
下面考虑一个使用strace命令的完整、具体的例子:查看websrv服务器在处理客户连接和数据时使用系统调用的情况。具体操作如下:
1 |
|
可见,服务器当前正在执行epoll_wait系统调用以等待客户请求。值得注意的是,epoll_wait的第一个参数(标识epoll内核事件表的文件描述符)的值是4,这和前面lsof命令的输出一致。接下来使用17.3节描述的方式对服务器发起一个连接并发送HTTP请求,此时strace命令的输出如代码清单17-2所示。
代码清单17-2 strace命令的输出
1 |
|
上面的输出分为五个部分,我们用空行将每个部分隔开。
第一部分从第一次epoll_wait系统调用开始。此次epoll_wait调用检测到了文件描述符3上的EPOLLIN事件。从代码清单17-1中lsof的输出来看,文件描述符3正是服务器的监听socket。因此,这个事件表示有新客户连接到来,于是websrv服务器对监听socket执行了accept调用,accept返回一个新的连接socket,其值为5。接着,服务器清除这个新socket上的错误,设置其SO_REUSEADDR属性,然后往epoll内核事件表中注册该socket上的EPOLLRDHUP和EPOLLONESHOT两个事件,最后设置新socket为非阻塞的。
第二部分从第二次epoll_wait系统调用开始。此次epoll_wait调用检测到了文件描述符5上的EPOLLIN事件,这表示客户端的第一行数据到达了,于是服务器执行了两次recv系统调用来接收数据。第一次recv调用读取到38字节的客户数据,即“GET http://localhost/a.html HTTP/1.1”。第二次recv调用则失败了,errno是EAGAIN,这表示目前没有更多的客户数据可读。此后,服务器调用了futex函数对互斥锁解锁,以唤醒等待互斥锁的线程。可见,POSIX线程库中的pthread_mutex_unlock函数在内部调用了futex函数。
第三、四部分的内容和第二部分类似,我们不再赘述。
第五部分中,epoll_wait调用检测到了文件描述符5上的EPOLLOUT事件,这表示工作线程正确地处理了客户请求,并准备好了待发送的数据,因此主线程开始执行writev系统调用往客户端写入HTTP应答。最后,服务器从epoll内核事件表中移除文件描述符5上的所有注册事件,并关闭该文件描述符。
由此可见,strace命令使我们能够清楚地查看每次系统调用发生的时机,以及相关参数的值,这比用gdb调试更方便。
5. netstat
netstat是一个功能很强大的网络信息统计工具。它可以打印本地网卡接口上的全部连接、路由表信息、网卡接口信息等。对本书而言,我们主要利用的是上述功能中的第一个,即显示TCP连接及其状态信息。毕竟,要获得路由表信息和网卡接口信息,我们可以使用输出内容更丰富的route和ifconfig命令。
netstat命令常用的选项包括:
-n,使用IP地址表示主机,而不是主机名;使用数字表示端口号,而不是服务名称。
-a,显示结果中也包含监听socket。
-t,仅显示TCP连接。
-r,显示路由信息。
-i,显示网卡接口的数据流量。
-c,每隔1 s输出一次。
-o,显示socket定时器(比如保活定时器)的信息。
-p,显示socket所属的进程的PID和名字。
下面我们运行websrv服务器,并执行telnet命令对它发起一个连接请求:
1 |
|
然后执行命令netstat-nat|grep 127.0.0.1:13579查看连接状态,结果如下:
1 |
|
由以上结果可见,netstat的每行输出都包含如下6个字段(默认情况):
Proto,协议名。
Recv-Q,socket内核接收缓冲区中尚未被应用程序读取的数据量。
Send-Q,未被对方确认的数据量。
Local Address,本端的IP地址和端口号。
Foreign Address,对方的IP地址和端口号。
State,socket的状态。对于无状态协议,比如UDP协议,这一字段将显示为空。而对面向连接的协议而言,netstat支持的State包括ESTABLISHED、SYN_SENT、SYN_RCVD、FIN_WAIT1、FIN_WAIT2、TIME_WAIT、CLOSE、CLOSE_WAIT、LAST_ACK、LISTEN、CLOSING、UNKNOWN。它们的含义和图3-8中的同名状态一致[1]。
上面的输出中,第1行表示本地socket地址127.0.0.1:13579处于LISTEN状态,并等待任何远端socket(用0.0.0.0:*表示)对它发起连接。第2行表示服务器和远端地址127.0.0.1:48220建立了一个连接。第3行只是从客户端的角度重复输出第2行信息表示的这个连接,因为我们是在同一台机器上运行服务器程序(websrv)和客户端程序(telnet)的。
在服务器程序开发过程中,我们一定要确保每个连接在任一时刻都处于我们期望的状态。因此我们应该习惯于使用netstat命令。
[1]SYN_RCVD和CLOSE分别对应图3-8中的SYN_RECV和CLOSED,UNKNOWN表示未知状态。
6. vmstat
vmstat是virtual memory statistics的缩写,它能实时输出系统的各类资源的使用情况,比如进程信息、内存使用、CPU使用率以及I/O使用情况。
vmstat命令常用的选项和参数包括:
-f,显示系统自启动以来执行的fork次数。
-s,显示内存相关的统计信息以及多种系统活动的数量(比如CPU上下文切换次数)。
-d,显示磁盘相关的统计信息。
-p,显示指定磁盘分区的统计信息。
-S,使用指定的单位来显示。参数k、K、m、M分别代表1000、1024、1 000 000和1 048 576字节。
-delay,采样间隔(单位是s),即每隔delay的时间输出一次统计信息。
-count,采样次数,即共输出count次统计信息。
默认情况下,vmstat输出的内容相当丰富。请看下面的示例:
1 |
|
注意,第1行输出是自系统启动以来的平均结果,而后面的输出则是采样间隔内的平均结果。vmstat的每条输出都包含6个字段,它们的含义分别是:
procs,进程信息。“r”表示等待运行的进程数目;“b”表示处于不可中断睡眠状态的进程数目。
memory,内存信息,各项的单位都是千字节(KB)。“swpd”表示虚拟内存的使用数量。“free”表示空闲内存的数量。“buff”表示作为“buffer cache”的内存数量。从磁盘读入的数据可能被保持在“buffer cache”中,以便下一次快速访问。“cache”表示作为“page cache”的内存数量。待写入磁盘的数据将首先被放到“page cache”中,然后由磁盘中断程序写入磁盘。
swap,交换分区(虚拟内存)的使用信息,各项的单位都是KB/s。“si”表示数据由磁盘交换至内存的速率;“so”表示数据由内存交换至磁盘的速率。如果这两个值经常发生变化,则说明内存不足。
io,块设备的使用信息,单位是block/s。“bi”表示从块设备读入块的速率;“bo”表示向块设备写入块的速率。
system,系统信息。“in”表示每秒发生的中断次数;“cs”表示每秒发生的上下文切换(进程切换)次数。
cpu,CPU使用信息。“us”表示系统所有进程运行在用户空间的时间占CPU总运行时间的比例;“sy”表示系统所有进程运行在内核空间的时间占CPU总运行时间的比例;“id”表示CPU处于空闲状态的时间占CPU总运行时间的比例;“wa”表示CPU等待I/O事件的时间占CPU总运行时间的比例。
不过,我们可以使用iostat命令获得磁盘使用情况的更多信息,也可以使用mpstat获得CPU使用情况的更多信息。vmstat命令主要用于查看系统内存的使用情况。
7. ifstat
ifstat是interface statistics的缩写,它是一个简单的网络流量监测工具。其常用的选项和参数包括:
-a,监测系统上的所有网卡接口。
-i,指定要监测的网卡接口。
-t,在每行输出信息前加上时间戳。
-b,以Kbit/s为单位显示数据,而不是默认的KB/s。
-delay,采样间隔(单位是s),即每隔delay的时间输出一次统计信息。
-count,采样次数,即共输出count次统计信息。
举例来说,我们在测试机器ernest-laptop上执行如下命令:
1 |
|
从输出来看,ernest-laptop拥有两个网卡接口:虚拟的回路接口lo以及以太网网卡接口eth0。ifstat的每条输出都以KB/s为单位显示各网卡接口上接收和发送数据的速率。因此,使用ifstat命令就可以大概估计各个时段服务器的总输入、输出流量。
8. mpstat
mpstat是multi-processor statistics的缩写,它能实时监测多处理器系统上每个CPU的使用情况。mpstat命令和iostat命令通常都集成在包sysstat中,安装sysstat即可获得这两个命令。mpstat命令的典型用法是(mpstat命令的选项不多,这里不再专门介绍):
1 |
|
选项P指定要监控的CPU号(0~CPU个数-1),其值“ALL”表示监听所有的CPU。interval参数是采样间隔(单位是s),即每隔interval的时间输出一次统计信息。count参数是采样次数,即共输出count次统计信息,但mpstat最后还会输出这count次采样结果的平均值。与vmstat命令一样,mpstat命令输出的第一次结果是自系统启动以来的平均结果,而后面(count-1)次输出结果则是采样间隔内的平均结果。
举例来说,我们在测试机器Kongming20上执行如下命令:
1 |
|
为了显示的方便,我们省略了每行输出前导的时间戳。每次采样的输出都包含3条信息,每条信息都包含如下几个字段:
CPU,指示该条信息是哪个CPU的数据。“0”表示是第1个CPU的数据,“1”表示是第2个CPU的数据,“all”则表示是这两个CPU数据的平均值。
%usr,除了nice值为负的进程,系统上其他进程运行在用户空间的时间占CPU总运行时间的比例。
%nice,nice值为负的进程运行在用户空间的时间占CPU总运行时间的比例。
%sys,系统上所有进程运行在内核空间的时间占CPU总运行时间的比例,但不包括硬件和软件中断消耗的CPU时间。
%iowait,CPU等待磁盘操作的时间占CPU总运行时间的比例。
%irq,CPU用于处理硬件中断的时间占CPU总运行时间的比例。
%soft,CPU用于处理软件中断的时间占CPU总运行时间的比例。
%steal,一个物理CPU可以包含一对虚拟CPU,这一对虚拟CPU由超级管理程序管理。当超级管理程序在处理某个虚拟CPU时,另外一个虚拟CPU则必须等待它处理完成才能运行。这部分等待时间就是所谓的steal时间。该字段表示steal时间占CPU总运行时间的比例。
%guest,运行虚拟CPU的时间占CPU总运行时间的比例。
%idle,系统空闲的时间占CPU总运行时间的比例。
在所有这些输出字段中,我们最关心的是%user、%sys和%idle。它们基本上反映了我们的代码中业务逻辑代码和系统调用所占的比例,以及系统还能承受多大的负载。很显然,在上面的输出中,执行系统调用占用的CPU时间比执行用户业务逻辑占用的CPU时间要多。这是因为我们在该机器上运行了16.4节介绍的压力测试工具,它在不停地执行recv/send系统调用来收发数据。