对于程序员来说,整个计算机系统由四个重要的模块组成,分别是:CPU,网络,磁盘,内存。在我们的程序或者系统出现问题时,我们应该分别有一定先后顺序的对这四块进行排查。而在Linux系统下,有很多高效的工具,可以帮助我们分析定位问题.以下分门别类来介绍一下,主要参考了下列文章:
1.CPU
top,strace,perf,vmstat
1.1 top
top命令可以实时动态地查看系统的整体运行情况,是一个综合了多方信息监测系统性能和运行信息的实用工具。
Top常用的可选参数和其对应的含义如下:
(1)-c:显示完整的命令;
(2)-d:屏幕刷新间隔时间;
(3)-i<时间>:设置间隔时间;
(4)-u<用户名>:指定用户名;
(5)-p<进程号>:指定进程;
(6)-n<次数>:循环显示的次数。
top执行起来的效果如下:
top 默认显示的是所有 CPU 的平均值,按下数字 1,就可以切换到每个 CPU 的使用率
前五行是系统整体的统计信息。第一行是任务队列信息,第二行和第三行为进程和CPU的信息,最后两行为内存信息。下面对一些比较重要的参数进行说明。
Load average:0.73,0.99,1.11。load average表示系统在过去1分钟5分钟15分钟的任务队列的平均长度。这个值越大就表示系统CPU越繁忙。
Cpu(s):
- 5.0%us(user 用户态 CPU 时间百分比)
- 3.9%sy(system 内核态 CPU 时间百分比) 占比过大,可能就需要考虑系统调用的使用频率
- 0.0%ni(nice 用户进程空间内改变过优先级的用户占用的cpu百分比)
- 90.9%id(idle 空闲cpu的百分比)
- 0.2%wa(等待输入输出cpu的百分比)
- 0.0 hi (irq 代表处理硬中断的 CPU 时间。)
- 0.2 si (softirq 代表处理软中断的 CPU 时间。)
- 0.0 st (steal 代表当系统运行在虚拟机中的时候,被其他虚拟机占用的 CPU 时间)
Mem:1850972 buff/cache(用作内核缓存的内存量)。
Swap:磁盘交换区容量。
CPU 使用率计算公式:CPU 使用率 = 1 - 空闲时间 / 总 CPU 时间
1.2 strace
它是测试服务器性能的重要工具。它限踪程序运行过程中执行的系统调用和接收到信号,并将系统调用名、参数、返回值及信号名输出到标准输出或者措定的文件。
strace的常用的选项以及选项对应的含义如下:
- -c 统计每一系统调用的所执行的时间,次数和出错的次数等
- -f 跟踪由fork调用所产生的子进程
- -t 在输出中的每一行前加上时间信息
- -tt 在输出中的每一行前加上时间信息(微妙级)
- -T 显示每一调用所耗的时间
- -e trace=set 只跟踪指定的系统调用。例如:-e trace=open,close,read,write表示只跟踪这四个系统调用。默认的为
set=all
- -e trace=file 只跟踪有关文件操作的系统调用
- -e trace=process 只跟踪有关进程控制的系统调用
- -e trace=network 跟踪与网络有关的所有系统调用
- -e strace=signal 跟踪所有与系统信号有关的 系统调用
- -e trace=ipc 跟踪所有与进程通讯有关的系统调用
- -o filename 将strace的输出写入文件filename -p pid 跟踪指定的进程pid
例如执行 strace cat /dev/null,会得到如下输出:
# Shengxi-Liu @ Shengxi-Liu-PC in ~ [14:53:59]
$ strace cat /dev/null
execve("/usr/bin/cat", ["cat", "/dev/null"], 0x7ffd4d875fc8 /* 71 vars */) = 0
brk(NULL) = 0x5592d55d0000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=142642, ...}) = 0
mmap(NULL, 142642, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fb51823d000
close(3) = 0
openat(AT_FDCWD, "/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\21\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2066504, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb518265000
mmap(NULL, 3889792, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb517c85000
mprotect(0x7fb517e32000, 2093056, PROT_NONE) = 0
mmap(0x7fb518031000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1ac000) = 0x7fb518031000
mmap(0x7fb518037000, 14976, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fb518037000
close(3) = 0
arch_prctl(ARCH_SET_FS, 0x7fb518266500) = 0
mprotect(0x7fb518031000, 16384, PROT_READ) = 0
mprotect(0x5592d54b0000, 4096, PROT_READ) = 0
mprotect(0x7fb518261000, 4096, PROT_READ) = 0
munmap(0x7fb51823d000, 142642) = 0
brk(NULL) = 0x5592d55d0000
brk(0x5592d55f1000) = 0x5592d55f1000
brk(NULL) = 0x5592d55f1000
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=113049440, ...}) = 0
mmap(NULL, 113049440, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fb5110b5000
close(3) = 0
fstat(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 1), ...}) = 0
openat(AT_FDCWD, "/dev/null", O_RDONLY) = 3
fstat(3, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 3), ...}) = 0
fadvise64(3, 0, 0, POSIX_FADV_SEQUENTIAL) = 0
mmap(NULL, 139264, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb51823f000
read(3, "", 131072) = 0
munmap(0x7fb51823f000, 139264) = 0
close(3) = 0
close(1) = 0
close(2) = 0
exit_group(0) = ?
+++ exited with 0 +++
每一行都是一条系统调用,等号左边是系统调用的函数名及其参数,右边是该调用的返回值。如果你知道你要找的是什么,你可以让strace
只跟踪一些类型的系统调用或者某一些函数.
对于过长的部分,strace
会用...
省略
出错会有提示
信号实验:
答:一个终端sleep 100000 ,另一个终端ps aux | grep sleep; strace -p PID
,结果如下:
strace -p 4632
strace: Process 4632 attached
restart_syscall(<... resuming interrupted nanosleep ...>) = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
--- SIGINT {si_signo=SIGINT, si_code=SI_KERNEL} ---
+++ killed by SIGINT +++
统计webserver某一段时间系统调用的统计情况:
这里很清楚的告诉你调用了那些系统函数,调用次数多少,消耗了多少时间等等这些信息,可见futex函数(即线程的切换操作)开销还是比较大的,值得研究
1.3 perf
perf 是 Linux 2.6.31 以后内置的性能分析工具,它以性能事件采样为基础,不仅可以分析系统的各种事件和内核性能,还可以用来分析指定应用程序的性能问题。perf工具的常用命令包括top,record,report等。
1.3.1 perf top
perf top
命令用来显示程序运行的整体状况。该命令主要用来观察整个系统当前的状态,比如可以通过查看该命令的输出来查看当前系统最耗时的内核函数或某个用户进程。Perf top的运行效果如下:
第一行包含三个数据,分别是采样数(Samples)、事件类型(event)和事件总数量(Event count)。比如这个例子中,peft 总共采集了 747 个 CPU 时钟事件,而总事件数为 174906250。采样数如果过少,那下面的排序和百分比就没什么实际参考价值了。
- Overhead,是该符号的性能事件在所有采样中的比例,用百分比来表示。
-Shared,是该函数或指令所在的动态共享对象(Dynamic Shared Object),如内核、 进程名、动态链接库名、内核模块名等。 - Object,是动态共享对象的类型。比如 [.] 表示用户空间的可执行程序、或者动态链接库,而 [k] 则表示内核空间。
- Symbol 是符号名,也就是函数名。当函数名未知时,用十六进制的地址来表示。
1.3.2 perf record
perf record
命令则用来记录指定事件在程序运行过程中的信息,而Perf report命令则用来报告基于前面record命令记录的事件信息生成的程序运行状况报告。我们通常用命令perf record -g -p pid
将进程在命令运行期间的各项指令运行所占CPU的比例存在perf.data
里面(-g表示记录函数之间的调用关系)。再用perf report --call-graph --stdio
将刚刚的统计结果展示出来。
1.3.3 对进程进行跟踪分析其调用:
perf top -g -p <pid>
***网络
netstat/ss
netstat命令用来打印Linux中网络系统的状态信息,可让你得知整个Linux系统的网络情况。
netstat的常用的选项如下:
- -a (all)显示所有选项
- -t (tcp)仅显示tcp相关选项
- -u (udp)仅显示udp相关选项
- -l (listen)仅列出有在Listen(监听)的服务状态
- -p (program)显示socket所属的进程的PID和名字
- -r (route)显示路由信息,路由表
- -i 显示网卡接口的数据流量
- -c 每隔一个固定时间,执行该netstat命令。
- -o 显示socket定时器的信息
常用命令:
查看端口状态:lsof -i:port
或者netstat -anp | grep port
知道PID
查看监听的是那个端口:
查看所有tcp监听端口:netstat -lt
lsof (列出打开的文件描述符)
lsof -p PID
显示进程打开的所有的文件 fd
lsof abc.txt
:查看所有打开了文件abc.txt的进程。
lsof filename
查看指定文件被哪个进程打开了
wireshark/tcpdump
- tcpdump -i enp1s0 host ip:抓取所有经过 enp1s0,目的或源地址是ip的网络数据。
- tcpdump -i eth1 dst host ***.***.***.***:抓取所有经过 eth1,目的地址是***.***.***.***的网络数据。
- tcpdump -i eth1 src host ***.***.***.***:抓取所有经过 eth1,源地址是***.***.***.***的网络数据。
- tcpdump -i eth1 port 36000:抓取所有经过 eth1,目的端口或源端口是36000的网络数据。
- tcpdump -i eth1 src port 36000:抓取所有经过 eth1,源端口是36000的网络数据。
- tcpdump -i eth1 dst port 36000:抓取所有经过 eth1,目的端口是36000的网络数据。
- tcpdump -i eth1 'src host ***.***.***.*** && src port 36000':
抓取所有经过 eth1,目的地址是10.136.12.1且目的端口是36000的网络数据。
实例:使用tcpdump
查看三次握手与四次挥手(本地回环)
服务器运行 ./server 127.0.0.1 10000
客户端:./client 127.0.0.1 10000
tcpdump -i lo -S port 10000
-i
指定本地回环或者某一个网卡-S
:用绝对而非相对数值列出TCP关联数;port
端口
08:11:13.675784 IP localhost.53616 > localhost.ndmp: Flags [S], seq 3914839384, win 43690, options [mss 65495,sackOK,TS val 1389282550 ecr 0,nop,wscale 7], length 0
08:11:13.675807 IP localhost.ndmp > localhost.53616: Flags [S.], seq 4097343029, ack 3914839385, win 43690, options [mss 65495,sackOK,TS val 1389282550 ecr 1389282550,nop,wscale 7], length 0
08:11:13.675829 IP localhost.53616 > localhost.ndmp: Flags [.], ack 4097343030, win 342, options [nop,nop,TS val 1389282550 ecr 1389282550], length 0
客户端:IP localhost.53616
服务器:localhost.ndmp
- 第一行:客户端发送一个
seq,3914839384
给服务端,对应下面三次握手示意图中的SYN J - 第二行:服务端确认第一行的请求:
seq,3914839384
, ack的值为seq+1,即(ack 3914839385),同时发送一个seq 4097343029
。对应下图三次握手中的(SYN K, ACK J+1) - 第三行:客户端确认服务端的请求序号(第二行中的
seq 4097343029
),对应下图tcp三路握手中的 (ACK K+1)
数据通信过程:
08:23:08.997338 IP localhost.ndmp > localhost.53652: Flags [P.], seq 2692191233:2692191259, ack 3905291528, win 342, options [nop,nop,TS val 1389997871 ecr 1389977871], length 26
08:23:08.997368 IP localhost.53652 > localhost.ndmp: Flags [.], ack 2692191259, win 342, options [nop,nop,TS val 1389997871 ecr 1389997871], length 0
四次挥手:(客户端主动断开连接)
08:39:50.125265 IP localhost.53690 > localhost.ndmp: Flags [F.], seq 610077165, ack 777760781, win 342, options [nop,nop,TS val 1390998999 ecr 1390998999], length 0
08:39:50.125457 IP localhost.ndmp > localhost.53690: Flags [.], ack 610077166, win 342, options [nop,nop,TS val 1390999000 ecr 1390998999], length 0
08:40:10.125372 IP localhost.ndmp > localhost.53690: Flags [F.], seq 777760781, ack 610077166, win 342, options [nop,nop,TS val 1391018999 ecr 1390998999], length 0
08:40:10.125402 IP localhost.53690 > localhost.ndmp: Flags [.], ack 777760782, win 342, options [nop,nop,TS val 1391018999 ecr 1391018999], length 0
对应下图去看就OK了!!!!
磁盘
4.1 iotop
iotop命令是一个用来监视磁盘I/O使用状况的top类工具。iotop具有与top相似的UI,其中包括PID、用户、I/O、进程等相关信息。Linux下的IO统计工具如iostat,nmon等大多数是只能统计到per设备的读写情况,如果你想知道每个进程是如何使用IO的就比较麻烦,使用iotop命令可以很方便的查看。
iotop 命令选项:
- -o:只显示有io操作的进程
- -n NUM:显示NUM次,主要用于非交互式模式。
- -d SEC:间隔SEC秒显示一次。
- -p PID:监控的进程pid。
- -u USER:监控的进程用户。
一般就是直接:sudo iotop -p PID
即可
内存
1.1 vmstat
vmstat是一个很全面的性能分析工具,可以观察到系统的进程状态、内存使用、虚拟内存使用、磁盘的 IO、中断、上下问切换、CPU使用等。
常用参数有:
- -f:显示启动后执行fork的次数;
- -s:以表格方式显示事件计数器和内存状态;
- -d:报告磁盘状态;
- -p:显示指定的硬盘分区统计信息;
- -S:输出信息的单位。k,K,m,M
vmstat 5 5 //每隔5秒输出一次,共5次
# Shengxi-Liu @ Shengxi-Liu-PC in ~/WebServer/src on git:master x [16:18:41]
$ vmstat 5 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 185932 302484 1266296 0 0 48 28 62 298 8 4 86 2 0
1 0 0 190284 302548 1268152 0 0 0 131 4296 5553 7 4 88 2 0
1 0 0 186976 302572 1266684 0 0 0 44 2754 3051 5 2 92 1 0
0 0 0 186740 302596 1267208 0 0 55 127 3890 4557 7 3 89 1 0
1 0 0 185524 302604 1267048 0 0 0 16 2718 3088 6 2 92 0 0
*** 注意第一行是自启动以来的平均结果
- Procs(进程):
- r: r: 运行队列中进程数量,这个值也可以判断是否需要增加CPU。(长期大于1)
- b: 等待IO的进程数量
- *** Memory(内存):
- swpd: swpd: 使用虚拟内存大小,如果swpd的值不为0,但是SI,SO的值长期为0,这种情况不会影响系统性能。(实际上就是swap分区被使用的大小,可以通过
free
来验证,两者相同) - free: 空闲物理内存大小。
- buff: 用作缓冲的内存大小
- cache: 用作缓存的内存大小,如果cache的值大的时候,说明cache处的文件数多,如果频繁访问到的文件都能被cache处,那么磁盘的读IO bi会非常小。
- swpd: swpd: 使用虚拟内存大小,如果swpd的值不为0,但是SI,SO的值长期为0,这种情况不会影响系统性能。(实际上就是swap分区被使用的大小,可以通过
- *** Swap:
- si: 每秒从交换区写到内存的大小,由磁盘调入内存。
- so: 每秒写入交换区的内存大小,由内存调入磁盘。
注意:内存够用的时候,这2个值都是0,如果这2个值长期大于0时,系统性能会受到影响,磁盘IO和CPU资源都会被消耗。有些朋友看到空闲内存(free)很少的或接近于0时,就认为内存不够用了,不能光看这一点,还要结合si和so,如果free很少,但是si和so也很少(大多时候是0),那么不用担心,系统性能这时不会受到影响的。
- IO:(现在的Linux版本块的大小为1024bytes)
- bi: 每秒读取的块数
- bo: 每秒写入的块数
注意:随机磁盘读写的时候,这2个值越大(如超出1024k),能看到CPU在IO等待的值也会越大。
- system:
- in: 每秒中断数,包括时钟中断
- cs: 每秒上下文切换数
- CPU(以百分比表示)
-
us: 用户进程执行时间(user time)
us的值比较高时,说明用户进程消耗的CPU时间多,但是如果长期超50%的使用,那么我们就该考虑优化程序算法或者进行加速。 -
sy: 系统进程执行时间(system time)
sy的值高时,说明系统内核消耗的CPU资源多,这并不是良性表现,我们应该检查原因。 -
id: 空闲时间(包括IO等待时间)
-
wa: 等待IO时间
wa的值高时,说明IO等待比较严重,这可能由于磁盘大量作随机访问造成,也有可能磁盘出现瓶颈(块操作)。
-
1.2 valgrind
valgrind --tool=memcheck --leak-check=full --show-reachable=yes --trace-children=yes ./a.out
其中–leak-check=full 指的是完全检查内存泄漏,
–show-reachable=yes是显示内存泄漏的地点,
–trace-children=yes是跟入子进程。
-q –quiet 安静地运行,只打印错误信息
附录:
gdb 调试多进程&&多线程
调试进程基本命令
默认设置下,在调试多进程程序时gdb只会调试主进程。gdb7以上的版本(gdb --version)支持多进程调试,只需要设置好follow-fork-mode(fork追踪模式)
以及detach-on-fork(另外的fork函数上停不停,off 为停)
即可。
这两个参数的设置命令分别是:set follow-fork-mode [parent|child],set detach-on-fork [on|off]
。两者结合起来构成了GDB的调试模式:
follow-fork-mode detach-on-fork 说明
parent on GDB默认的调试模式:只调试主进程
child on 只调试子进程
parent off 同时调试两个进程,gdb跟主进程,子进程block在fork位置
child off 同时调试两个进程,gdb跟子进程,主进程block在fork位置
- info inferiors 显示正在调试的进程
- inferior 2 进入进程2
- LWP: 表示轻量级进程
调试线程基本命令
-
info threads
显示当前可调试的所有线程,每个线程会有一个GDB为其分配的ID,后面操作线程的时候会用到这个ID。 前面有*的是当前调试的线程。 -
thread ID
切换当前调试的线程为指定ID的线程。 -
break file.c:100 thread all
在file.c文件第100行处为所有经过这里的线程设置断点。 -
thread apply ID1 ID2 command
让一个或者多个线程执行GDB命令command。 -
thread apply all command
让所有被调试线程执行GDB命令command。
重点:在使用step或者continue命令调试当前被调试线程的时候,其他线程也是同时执行的,怎么只让被调试程序执行呢?通过这个命令就可以实现这个需求
-
set scheduler-locking off|on|step
- off 不锁定任何线程,也就是所有线程都执行,这是默认值。
- on 只有当前被调试程序会执行。
- step 在单步的时候,除了next过一个函数的情况(熟悉情况的人可能知道,这其实是一个设置断点然后continue的行为)以外,只有当前线程会执行。
大杂炖:info b(打印断点信息),bt(查看堆栈),b test.cpp:18(哪一个文件多少行),c(执行到断点停下来),until(运行至当前语句块结束),finish (运行至函数结束并跳出,并打印函数的返回值)
调试代码:https://github.com/liushengxi13689209566/OS/blob/master/test_gdb.cpp
TCP状态速查
- LISTEN:侦听状态,等待远程机器的连接请求。
- SYN_SEND:在TCP三次握手期间,主动连接端发送了SYN包后,进入SYN_SEND状态,等待对方的ACK包。
- SYN_RECV:在TCP三次握手期间,主动接收端收到SYN包后,进入SYN_RECV状态。
- ESTABLISHED:完成TCP三次握手后,主动连接端进入ESTABLISHED状态。此时,TCP连接已经建立,可以进行通信。
- FIN_WAIT_1:在TCP四次挥手时,主动关闭端发送FIN包后,进入FIN_WAIT_1状态。
- FIN_WAIT_2:在TCP四次挥手时,主动关闭端收到ACK包后,进入FIN_WAIT_2状态。
- TIME_WAIT:在TCP四次挥手时,主动关闭端发送了ACK包之后,进入TIME_WAIT状态,等待最多2MSL时间,让被动关闭端收到ACK包。
- CLOSING:在TCP四次挥手期间,主动关闭端发送了FIN包后,没有收到对应的ACK包,却收到了对方的FIN包,此时进入CLOSING状态。
- CLOSE_WAIT:在TCP四次挥手期间,被动关闭端收到FIN包后,进入CLOSE_WAIT状态。
- )LAST_ACK:在TCP四次挥手时,被动关闭端发送FIN包后,进入LAST_ACK状态,等待对方的ACK包。