看了很多文章,很多大牛都提到要关注自己程序的性能问题。比如说,除了你的程序可以work之外,还需要考虑你的程序的cpu占用率,内存占用率,io占用率等。整理一下性能问题一些指标和定位方法。
cpu相关
评价指标cpu的性能分析常用的两个指标是cpu利用率 和 平均负载。
-
平均负载
在linux下面使用uptime命令可以查看到机器的平均负载。按照man uptime给出的定义,平均负载反应的是当前的机器的“活跃”进程数。$ man uptime DESCRIPTION uptime gives a one line display of the following information. The current time, how long the system has been running, how many users are currently logged on, and the system load averages for the past 1, 5, and 15 minutes. This is the same information contained in the header line displayed by w(1).
$ uptime 14:58:14 up 263 days, 18:18, 1 user, load average: 0.93, 0.97, 1.05
uptime的含义:
uptime返回的是1,5,15分钟的“活跃”进程数。活跃的进程的定义是:
处于R状态的进程 (处于运行状态的进程 或者是就绪状态的进程)
处于D状态的进程 (处于等待IO的不可被信号中断)如果uptime高的话,那么可能的情况是:
1)系统中有大量CPU密集型的进程,比如大量进程都在进行计算,此时的系统平均负载与CPU使用率的关联就很大。
2)系统中有大量IO密集型进程,这些进程中有很多时间都在等待IO完成,此时系统平均负载高但是CPU使用率并不见得就高。
3) 就绪队列中有大量处于就绪状态等待CPU资源的进程,这种情况下会导致系统平均负载升高,而且CPU使用率也变高。
cpu利用率
cpu利用率有两个维度,一个是机器某个cpu核心的利用率,另外一个是进程的cpu利用率。如果用top命令可以看到这两个维度分别的指标。下面这张图是某个cpu核心的利用率,图片中下面是各种进程的cpu利用率。下面先从机器某个核心的cpu利用率说起
-
1. 某个核心的cpu利用率
通过top命令,经常可以看到机器上每个cpu核心的负载,如下图所示。
翻译一下每列的含义:
us:CPU运行用户空间进程的时间比例
sy:CPU运行内核进程时间比例
ni:CPU 运行低优先级进程时间比例
id:CPU空闲状态时间比例
iowait: CPU等待I/O操作完成的时间比例
hi:CPU 处理硬件中断时间比例
si: CPU 处理软中断时间比例
steal:虚拟 CPU 被占用强制等待时间比例
cpu某个核心的利用率 = 1 - ((iowait时间 + idle时间)/ 总时间 。
因此,如果要计算某个时间点的cpu利用率,可以先取两个采样点读取/proc/stat文件,然后按照下面的公式计算:
CPU Usage
= 1 - (△idle+△iowait) / △total_time
= 1 - (idle2+iowait2 - idle1 - iowait1) / (total_time2 - total_time1)
- 2. 某个进程的cpu利用率
如何计算某个进程的cpu利用率,计算的基本公式如下 = 这个进程自己的占用的cpu时间 * 核心数 / cpu每个核心各种状态的总时间。写的详细一点,大约是:
某个进程的cpu利用率
= (进程用户态时间 + 内核态时间 + 用户态等待子进程的消耗
+ 内核态等待子进程的消耗)/ cpu总时间△t
对于某个进程,可以根据其pid找到在/proc/pid/stat 找打这个进程各种状态占用的cpu节拍数。
找到里面的关键几列,分别是:
utime :用户态时间
sttime :系统态时间
cutime : 子进程用户态时间
cstime : 子进程系统态时间
写个小脚本就可以统计这个事情:
# 打印该进程进程用户态时间 + 内核态时间 +
# 用户态等待子进程的消耗 + 内核态等待子进程的消耗 占用的时间之和
$ cat /proc/16820/stat | awk '{print "cpu_total_slice : " $14+$15+$16+$17}'
cpu_total_slice : 4405
了解了cpu利用率的原理,那就不难写出一个类似于ps命令或top这样的工具去统计某个进程的cpu利用率或者机器cpu的利用率。用shell写一个demo,验证一下:
# 统计某个进程的cpu占用率
print_process_cpu_precent()
{
cpu_total_time1=`cat /proc/stat | head -n1 |awk '{print $2+$3+$4+$5+$6+$7+$8}'`
process_time1=`cat /proc/${PID}/stat | head -n1 | awk '{print $14+$15+$16+$17}'`
sleep 1
cpu_total_time2=`cat /proc/stat | head -n1 |awk '{print $2+$3+$4+$5+$6+$7+$8}'`
process_time2=`cat /proc/${PID}/stat | head -n1 | awk '{print $14+$15+$16+$17}'`
cpu_time=$((cpu_total_time2 - cpu_total_time1))
process_time=$((process_time2 - process_time1))
cpu_percent=$((100 * process_time * cpu_core_num / cpu_time))
echo "process : $PID, cpu percent : $cpu_percent"
}
封装成一个脚本跑起来:
https://github.com/zhaozhengcoder/CoderNoteBook/blob/master/example_code/shell/cpu.sh
通过stress命令模拟cpu和io密集型的任务来对应这个脚本统计的结果和pidstat,mpstat的结果对比效果准不准。
#产生一个io密集型的进程供测试
stress -i 1
$ pidstat -p 18493 1 100
04:01:48 PM 18493 0.00 36.00 0.00 36.00 0 stress
04:01:49 PM 18493 0.00 42.00 0.00 42.00 0 stress
04:01:50 PM 18493 0.00 37.00 0.00 37.00 0 stress
04:01:51 PM 18493 0.00 40.00 0.00 40.00 0 stress
04:01:52 PM 18493 0.00 37.00 0.00 37.00 0 stress
04:01:53 PM 18493 0.00 41.00 0.00 41.00 0 stress
04:01:54 PM 18493 0.00 36.00 0.00 36.00 0 stress
04:01:55 PM 18493 0.00 43.00 0.00 43.00 0 stress
04:01:56 PM 18493 0.00 40.00 0.00 40.00 0 stress
04:01:57 PM 18493 0.00 53.00 0.00 53.00 0 stress
./test_cpu.sh 18493
process : 18493, cpu percent : 40
process : 18493, cpu percent : 40
process : 18493, cpu percent : 40
process : 18493, cpu percent : 44
process : 18493, cpu percent : 37
process : 18493, cpu percent : 42
process : 18493, cpu percent : 38
process : 18493, cpu percent : 40
process : 18493, cpu percent : 44
process : 18493, cpu percent : 54
// 总的来说还是比较接近,相差不大
mpstat -P ALL 1 100
04:05:04 PM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
04:05:05 PM all 6.16 0.00 5.90 3.46 0.00 0.00 0.00 0.00 84.48
04:05:05 PM 0 2.02 0.00 42.42 53.54 0.00 0.00 0.00 0.00 2.02
04:05:05 PM 1 14.00 0.00 3.00 0.00 0.00 0.00 0.00 0.00 83.00
04:05:05 PM 2 3.19 0.00 4.26 0.00 0.00 0.00 0.00 0.00 92.55
04:05:05 PM 3 18.18 0.00 3.03 0.00 0.00 0.00 0.00 0.00 78.79
04:05:05 PM 4 6.52 0.00 13.04 0.00 0.00 0.00 0.00 0.00 80.43
04:05:05 PM 5 5.05 0.00 1.01 1.01 0.00 0.00 0.00 0.00 92.93
04:05:05 PM 6 1.01 0.00 2.02 0.00 0.00 0.00 0.00 0.00 96.97
04:05:05 PM 7 3.16 0.00 1.05 0.00 0.00 0.00 0.00 0.00 95.79
04:05:05 PM 8 5.15 0.00 3.09 0.00 0.00 0.00 0.00 0.00 91.75
04:05:05 PM 9 3.03 0.00 2.02 0.00 0.00 0.00 0.00 0.00 94.95
04:05:05 PM 10 3.23 0.00 2.15 0.00 0.00 0.00 0.00 0.00 94.62
04:05:05 PM 11 26.73 0.00 5.94 0.00 0.00 0.00 0.00 0.00 67.33
04:05:05 PM 12 3.06 0.00 0.00 0.00 0.00 0.00 0.00 0.00 96.94
04:05:05 PM 13 0.99 0.00 2.97 0.99 0.00 0.00 0.00 0.00 95.05
04:05:05 PM 14 1.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 99.00
04:05:05 PM 15 4.30 0.00 4.30 0.00 0.00 0.00 0.00 0.00 91.40
$ ./test_cpu.sh 18493
[cpu 1] user percent : 3, syc percent 35
[cpu 2] user percent : 13, syc percent 3
[cpu 3] user percent : 3, syc percent 5
[cpu 4] user percent : 10, syc percent 4
[cpu 5] user percent : 6, syc percent 10
[cpu 6] user percent : 5, syc percent 0
[cpu 7] user percent : 0, syc percent 0
[cpu 8] user percent : 5, syc percent 0
[cpu 9] user percent : 4, syc percent 3
[cpu 10] user percent : 0, syc percent 0
[cpu 11] user percent : 0, syc percent 10
[cpu 12] user percent : 20, syc percent 5
[cpu 13] user percent : 5, syc percent 15
[cpu 14] user percent : 1, syc percent 3
[cpu 15] user percent : 5, syc percent 10
[cpu 16] user percent : 3, syc percent 4
- cpu利用率是否会把子线程的利用率计算进去?
关于cpu利用率,cpu的利用率是否会包含子线程占用cpu的比例么?比如对于如下的进程,ps看到的cpu利用率是多少呢? 测试结果如下,cpu的占用率是包含自己的子线程的。void threadfunc1() { int val = 1; for (int i = 0; i < max_print_times;) { val = (val + 1) * val; } } int main() { std::thread t1(threadfunc1); t1.join(); return 0; }
- 关于查看线程的一些命令
# 查看进程下面有几个线程 $ pstree -p 1 init(1)─┬─init(7)───zsh(8)───pstree(103) └─{init}(6) # 显示系统的线程 $ ps -efT $ top 命令之后,输入H # 查看一个进程下那个子线程的cpu比较高 $ top -H -p 5680 PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 5681 user+ 20 0 26116 1128 924 R 99.9 0.0 8:23.30 a.out 5680 user+ 20 0 26116 1128 924 S 0.0 0.0 0:00.00 a.out
在日常的开发中,我常用top命令看一下各种进程的状态。如果是想确定某个操作是否会影响cpu的性能。我一般会top -b > profile.log
, 然后触发某个操作,最后打开统计的log,观察这段时间内性能指标的各种变化。如果可以确定是某个进程的话,也可以使用pidstat -p xxxx 1
的方式观察。
内存相关
-
相关指标
可以用free,top命令看到关于内存的指标:$free -g total used free shared buff/cache available Mem: 15 1 9 1 4 12 Swap: 0 0 0 $top VIRT RES SHR %MEM VIRT: 进程申请的虚拟内存 RES : 进程当前占用的物理内存(不包括共享内存) SHR : 进程当前占用的共享内存 %MEM : 进程当前先用的物理内存的比例 (RES/物理内存)
关于VIRT,需要注意的是进程malloc内存之后,并不会立刻使VIRT升高,只用写内存的时候,才会是VIRT升高。换句话说,malloc的时候,不会立刻给进程分配内存,只有写入的时候,才会触发分配(cow机制)。
做一个对比的测试,下面这两段代码占用的内存(VIRT占用)完全不一样。区别只是一个申请了内存之后,什么都没有做,另外一个是申请之后,对内存进行了写入。const long mem_size = 10 * 1024* 1024; int times = 10; for (int i = 0; i <times; i++) { int * mem_arr = new int[mem_size]; for (int j = 0; j < mem_size; j++) { mem_arr[j] = j; } }
const long mem_size = 10 * 1024* 1024; int times = 10; for (int i = 0; i <times; i++) { int * mem_arr = new int[mem_size]; // for (int j = 0; j < mem_size; j++) // { // mem_arr[j] = j; // } }
-
常用的定位内存问题的命令:
查看当前机器内存占用的情况 free -g top 命名输入之后,输入M(按照内存占用高低排序)
观察机器内存的变化 $ vmstat 1 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 0 0 0 11327748 2178472 1960200 0 0 0 5 0 0 0 0 100 0 0 0 0 0 11327768 2178472 1960200 0 0 0 0 875 890 0 0 100 0 0 0 0 0 11327628 2178472 1960200 0 0 0 0 857 906 0 0 100 0 0 0 0 0 11327628 2178472 1960200 0 0 0 96 845 896 0 0 100 0 0
-
buff和cache是什么含义?
在日常的开发中,如果打开打开一个大文件再次打开会更快一些。linux在内存和磁盘之后,构建了buff和cache来作为缓存。以前一直有一个错误印象,cache是读缓存,buff是写缓存。准确的说,cache是读写文件系统的缓存,buff是写磁盘系统的缓存。# 清空缓存 $ echo 3 > /proc/sys/vm/drop_caches 写文件 $ dd if=/dev/urandom of=/tmp/file bs=1M count=50 读文件 $ dd if=/tmp/file of=/dev/null 写磁盘 $ dd if=/dev/urandom of=/dev/sdb1 bs=1M count=2048 读磁盘: $ dd if=/dev/sda1 of=/dev/null bs=1M count=1024 # 使用vmstat观察cache和buff的变化 vmstat -1
-
oom问题
比如某个进程申请的内存过大(或者是由于bug导致申请多过的内存),导致机器没有内存的时候,会出现oom。oom发生的时候,并不一定会kill正在申请内存的进程,而是会根据每个进程的score分数去kill掉一个。于是就会出现是服务a的bug,疯狂申请内存,最后导致oom去kill了服务b。这个问题不难理解,有的时候出现某个服务莫名其妙的挂了,逻辑上并没有什么错误,可以排查一下是否是oom导致。cat /var/message/log | grep "Out of memory"
举一个自己遇到的关于内存的问题:有一个物理机部署某个进程启动的时候,在init的逻辑在读取一个系统文件时候,返回了false,导致进程启动失败。排查过程:
- 查看日志,发现是init阶段某个系统调用失败,导致init失败 - 使用strace 启动 trace -o out.txt -e trace=all ./xxxx - 在日志中看到如下错误: open("/usr/bin/sd-agent/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory) - 看提示是内存分配失败,使用free -g查看可用内存,可用内存不足。 - 最终排查下来,这类问题的学名称为: fork/clone failures with ENOMEM - 解决办法除了让物理机保持足够的可用内存之外 还可以临时修改了linux vm分配内存的策略,vm overcommit 修改为1。 https://stackoverflow.com/questions/1367373/python-subprocess-popen-oserror-errno-12-cannot-allocate-memory
io相关
基本指标:
io利用率:指的是磁盘处理 I/O 的时间百分比
io吞吐量:每秒的 I/O 请求大小,比如xxMB每秒
IOPS:每秒的 I/O 请求数
常用命令:
iotop
pidstat -d 1
lsof -p 18940
-
如何定位高io的进程?
// 测试代码如下,死循环打印日志到文件 while(1) { LOG(INFO) << "this is infor message"; LOG(ERROR) << "this is error message"; }
-
iostat看到io的利用率升高:
iostat -d -x 1 %util 升高 wkB/s 有数据在写入磁盘 Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util vda 0.00 0.00 0.00 50.00 0.00 20480.00 819.20 0.88 17.60 0.00 17.60 2.64 13.20 vdb 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 scd0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
iotop命令
Total DISK READ : 0.00 B/s | Total DISK WRITE : 16.40 M/s Actual DISK READ: 0.00 B/s | Actual DISK WRITE: 15.69 M/s TID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND 26098 be/4 root 0.00 B/s 0.00 B/s 0.00 % 0.01 % [kworker/4:2] 11106 be/4 root 0.00 B/s 3.92 K/s 0.00 % 0.00 % sap1007 13627 be/4 user_zhao 0.00 B/s 16.39 M/s 0.00 % 0.00 % ./test 1 be/4 root 0.00 B/s 0.00 B/s 0.00 % 0.00 % systemd
-
如何定位到具体进程:
# 找到在io的进程 $ pidstat -d 1 11:02:54 UID PID kB_rd/s kB_wr/s kB_ccwr/s Command 11:02:55 1000 8721 0.00 18459.41 0.00 test
-
如果定位到具体的io的文件:
$ lsof -p 8721 test 8721 user_zhao 4w REG 252,1 369094695 1444369 /home//src/test_ERROR20210525-110157.8721 $strace -p 8721 write(4, "E0525 11:26:09.772125 11740 test"..., 68) = 68 write(5, "E0525 11:26:09.772125 11740 test"..., 68) = 68 write(3, ":48] this is error glog message\n"..., 4096) = 4096 write(2, "E0525 11:26:09.772125 11740 test"..., 68) = 68 gettid() = 11740 gettid() = 11740 $ pstack 8721 #0 0x00007fcb3ede0850 in __write_nocancel () from /lib64/libc.so.6 #1 0x00007fcb3ed6cfb3 in _IO_new_file_write () from /lib64/libc.so.6 #2 0x00007fcb3ed6d74f in __GI__IO_file_xsputn () from /lib64/libc.so.6 #3 0x00007fcb3ed6303d in fwrite () from /lib64/libc.so.6 #4 0x00007fcb3fb05b36 in ?? () #5 0x0000000001304800 in ?? () #6 0x00007fcb3fb10606 in ?? () #7 0x000000000000003f in ?? () #8 0x0000000060ac6edb in ?? () #9 0x0000000000000001 in ?? () #10 0x00000000013047a8 in ?? () #11 0x00007fff26baba10 in ?? () #12 0x9a65cc0f603d7500 in ?? () #13 0x0000000000000000 in ?? ()
网络相关
todo
从进程的角度分析
上面是站在系统的角度去分析cpu,内存,io等各种系统指标。另外一个中情况是我想看某个进程做什么,他的cpu,io,内存是什么样,它打开哪些文件,进行了哪些系统调用,哪些热点函数导致了cpu负载的上升等。
# 查看进程的各种性能指标
pidstat -p xxx
# 查看进程的系统调用
strace -p xxxx
# 查看进程当前的调用栈
pstack -p xxxx
# 查看进程的打开文件表
lsof -p xxxxx
# strace根据线程
strace -p PID后加上-f,多进程和多线程都可以跟踪。
-
计算机各种操作耗时
其他补充
-
热点函数定位
发现某个进程cpu过载后,要如何定位到具体是哪段逻辑导致的呢?可以使用perf工具在进一步定位问题。故意写一段大循环的逻辑,使用perf命令进行定位热点函数。对于下面的一段代码进行分析,完整的代码:
https://github.com/zhaozhengcoder/CoderNoteBook/blob/master/example_code/linux_perf_example/perf_demo.c 使用perf命令对进程进行采样,找到热点函数。perf 常用的命令:
sudo perf record -F 99 -a -g ./demo1 # -F 99 表示采样的频率 # -a 录取所有CPU的事件 # -g 使能函数调用图功能 -o 指定录取保存数据的文件名 -g 使能函数调用图功能 -C 录取指定CPU的事件 # 生成报告的预览 perf report
perf report的输出可以看到在执行过程中,每个函数的占比。可以看到在下图中,main,func1,func2函数占了很高的比例。
# 产生比较详细的报告
sudo perf report -n --stdio
如果是一个已经启动好的进程,也可以attach上去进行分析分析
sudo perf record -F 99 -p 4989 -g
# -p
指明进程的pid
- 画火焰图
git clone --depth 1 https://github.com/brendangregg/FlameGraph.git
# 到处out.perf 文件
sudo perf script > out.perf
# 折叠调用栈
FlameGraph/stackcollapse-perf.pl out.perf > out.folded
# 生成火焰图
$ FlameGraph/flamegraph.pl out.folded > out.svg
刚才是一个简单的例子,也可以用perf去观测一下nginx work的进程,生成火焰图。
# 压测一下
ab -n 10000 -c 10 http://127.0.0.1/
参考:
- perf 分析
http://senlinzhan.github.io/2018/03/18/perf/ - 一个不错的git rep
https://github.com/lidaohang/quick_location