Linux性能优化从入门到实战:04 CPU篇:CPU使用率

时间秒杀一切 提交于 2019-11-28 01:24:53

  CPU使用率是单位时间内CPU使用情况的统计,以百分比方式展示。

$ top
top - 11:46:45 up 7 days, 11:52,  1 user,  load average: 0.00, 0.01, 0.00
Tasks: 198 total,   1 running, 197 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.2 us,  0.2 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  4044232 total,   420136 free,  1061244 used,  2562852 buff/cache
KiB Swap:  1046524 total,  1043128 free,     3396 used.  2619124 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
  921 root      20   0  531332 138408  58972 S   0.7  3.4  41:31.24 Xorg

  参数解析如下:

  • %user(us),代表用户态 CPU 时间。不包括下面的 nice 时间,但包括了 guest 时间。
  • %nice(ni),代表低优先级用户态 CPU 时间,也就是进程的 nice 值被调整为 1-19 之间时的 CPU 时间。nice 可取值范围是 -20 到 19,数值越大,优先级反而越低。
  • %system(sys),代表内核态 CPU 时间。
  • %idle(id),代表空闲时间。不包括等待 I/O 的时间(iowait)。
  • %iowait(wa),代表等待 I/O 的 CPU 时间,处于不可中断状态。
  • %irq(hi),代表处理硬中断的 CPU 时间。
  • %softirq(si),代表处理软中断的 CPU 时间。
  • %steal(st),代表当系统运行在虚拟机中的时候,被其他虚拟机占用的 CPU 时间。
  • %guest(guest),代表通过虚拟化运行其他操作系统的时间,也就是运行虚拟机的 CPU 时间。
  • %guest_nice(gnice),代表以低优先级运行虚拟机的时间。
  • %CPU,每个进程的实时 CPU 使用率,是用户态和内核态 CPU 使用率的总和。包括进程用户空间使用的 CPU、通过系统调用执行的的内核空间 CPU 、以及在就绪队列等待运行的 CPU。在虚拟化环境中,它还包括了运行虚拟机占用的 CPU。
      

    查看CPU使用的命令

      (1)top 默认使用 3 秒时间间隔,显示了系统总体的 CPU 和内存使用情况,以及各个进程的资源总体使用情况。vmstat、mpstat 提高更详细的分析多进程或者多线程应用。
      (2)ps 使用的却是进程的整个生命周期,显示了每个进程的资源使用情况。
      (3)pidstat 查看每个进程的详细CPU使用情况,包括用户态和内核态CPU。

  

CPU使用率是通过CPU时间计数计算而来

  而我们通常所说的 CPU 使用率,就是除了空闲时间外的其他时间占总 CPU 时间的百分比:
    在这里插入图片描述
  事实上,性能工具是取间隔时间作差,得到这段时间的平均CPU使用率:
    在这里插入图片描述
  同样Linux也给每个进程统计信息,/proc/[pid]/stat,通过上述方式计算使用率。
  为了维护 CPU 时间,Linux 通过事先定义的节拍率HZ,触发时间中断,并使用全局变量 Jiffies 记录了开机以来的节拍数。每发生一次时间中断,Jiffies 的值就加 1。节拍率 HZ 是内核的可配选项,可以设置为 100、250、、1000 等。不同的系统可能设置不同数值,你可以通过查询 /boot/config 内核选项来查看它的配置值。$ grep 'CONFIG_HZ=' /boot/conf-$(uname -r)
  节拍率 HZ 是内核选项,用户空间程序并不能直接访问,为了方便用户空间程序,内核还提供了一个用户空间节拍率 USE_HZ,它总是固定为 100,也就是 1/100 秒。
  Linux 通过 /proc 虚拟文件系统,向用户空间提供系统内部状态的信息,而 /proc/stat 提供的就是系统的 CPU 和任务统计信息,每一列则表示不同场景下 CPU 的累加节拍数,单位是 USER_HZ。
  

CPU使用率过高,如何进一步分析进程

  perf 以性能事件采样为基础,不仅可以分析系统的各种事件和内核性能,还可以用来分析指定应用程序的性能问题。
  (1)perf top,类似于 top,它能够实时显示占用 CPU.时钟最多的函数或者指令,中间不保存数据,因此可以用来查找热点函数。若定位到的函数是十六进制地址,说明没有找到待分析进程所依赖的库,解决方法是添加依赖库,或用perf record后在有依赖的环境中查看。

$ perf top
Samples: 175  of event 'cpu-clock', Event count (approx.): 43750000
Overhead  Shared Object            Symbol
   6.86%  perf                     [.] 0x00000000000a34ac
   5.71%  [kernel]                 [k] kallsyms_expand_symbol.constprop.1
   5.14%  [kernel]                 [k] format_decode

// 第一行 采样数(Samples)、事件类型(event)和事件总数量(Event count)
//        采集了 175 个 cpu-clock 事件,而总事件数则为 43750000
//        采样数过少,只有十几个时,下面的排序和百分比没有实际参考价值
// Overhead 是该符号的性能事件在所有采样中的比例
// Shared 是该函数或指令所在的动态共享对象(Dynamic Shared Object),如内核、进程名、动态链接库名、内核模块名等。
// Object 是动态共享对象的类型。比如 [.] 表示用户空间的可执行程序、或者动态链接库,而 [k] 则表示内核空间。
// Symbol 是符号名,也就是函数名。当函数名未知时,用十六进制的地址来表示。

  (2)perf record 和 perf report,perf record 提供了保存数据的功能,保存后的数据,需要用 perf report 解析展示,用于离线或者后续的分析。

$ sudo perf record -g --all-cpus
// ^C 停止记录,数据保存在 perf.data 中,-g 开启调用关系的采样,--all-cpus 采集所有CPU事件
$ sudo perf report -g

  

CPU使用率过高的总体分析步骤

  Step1:通过 top、pidstat 找到哪个进程CPU使用率过高;
  Step2:通过 perf 找到该进程中具体哪个函数使用过高。$ perf top -g -p <pid_id>
  Step3:通过 grep 查看该函数中的具体内容。$ grep -nr "<function_name>" <file_path>
  性能问题:
  (1)用户 CPU 和 Nice CPU 高,说明用户态进程占用了较多的 CPU,所以应该着重排查进程的性能问题。
  (2)系统 CPU 高,说明内核态占用了较多的 CPU,所以应该着重排查内核线程或者系统调用的性能问题。
  (3)I/O 等待 CPU 高,说明等待 I/O 的时间比较长,所以应该着重排查系统存储是不是出现了 I/O 问题。
  (4)软中断和硬中断高,说明软中断或硬中断的处理程序占用了较多的CPU,所以应该着重排查内核中的中断服务程序。
  

系统的 CPU 使用率很高,但为啥却找不到高 CPU 的应用?

  系统的 CPU 使用率,不仅包括进程用户态和内核态的运行,还包括中断处理、等待 I/O 以及内核线程等。所以,当你发现系统的 CPU 使用率很高的时候,不一定能找到相对应的高 CPU 使用率的进程。
  如下系统总CPU使用率80.8%,而单个进程的CPU使用率都较小。需要通过 top 、pidstat 等交叉确认系统和各进程的CPU使用率。
  并且仔细观察进程列表中的状态S,查看R、S等状态的进程是否正常。

$ top
...
%Cpu(s): 80.8 us, 15.1 sy,  0.0 ni,  2.8 id,  0.0 wa,  0.0 hi,  1.3 si,  0.0 st
...

PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
6882 root      20   0    8456   5052   3884 S   2.7  0.1   0:04.78 docker-containe
6947 systemd+  20   0   33104   3716   2340 S   2.7  0.0   0:04.92 nginx
7494 daemon    20   0  336696  15012   7332 S   2.0  0.2   0:03.55 php-fpm

发生以上情况的原因
(1)进程在不停地崩溃重启,比如因为段错误、配置错误等等,这时,进程在退出后可能又被监控系统自动重启了,而启动过程的资源初始化,很可能会占用相当多的 CPU。
(2)这些进程都是短时进程,也就是在其他应用内部通过 exec 调用的外面命令。这些命令一般都只运行很短的时间就会结束,你很难用 top 这种间隔时间比较长的工具发现。

发送上述问题的解决方法是找到父进程,从父进程入手,排查问题:
方法一
(1)通过 top、pidstat 等找到可疑进程;
(2)通过 pstree 用树状形式显示该进程与其他进程的关系;
(3)通过 grep 找到具体调用代码
方法二
(1)通过 perf record -g // 记录性能事件,等待大约 几秒后按 Ctrl+C 退出
(2)通过 perf report
方法三
  execsnoop 就是一个专为短时进程设计的工具,一般用于分析 Linux 内核的运行时行为。它通过 ftrace 实时监控进程的 exec() 行为,并输出短时进程的基本信息,包括进程 PID、父进程 PID、命令行参数以及执行的结果。
  https://github.com/brendangregg/perf-tools/blob/master/execsnoop
  

系统中出现大量不可中断进程和僵尸进程怎么办?

进程状态

  • R 是 Running 或 Runnable 的缩写,表示进程在 CPU 的就绪队列中,正在运行或者正在等待运行。
  • D 是 Disk Sleep 的缩写,也就是不可中断状态睡眠(Uninterruptible Sleep),一般表示进程正在跟硬件交互,并且交互过程不允许被其他进程或中断打断。
  • Z 是 Zombie 的缩写,表示僵尸进程,也就是进程实际上已经结束了,但是父进程还没有回收它的资源(比如进程的描述符、PID 等)。
  • S 是 Interruptible Sleep 的缩写,也就是可中断睡眠状态,表示进程因为等待某个事件而被系统挂起。当进程等待的事件发生时,它会被唤醒并进入 R 状态。
  • I 是 Idle 的缩写,也就是空闲状态,用在不可中断睡眠的内核线程上。硬件交互导致的不可中断进程用 D 表示,但对某些内核线程来说,它们有可能实际上并没有任何负载,用 Idle 正是为了区分这种情况。要注意,D 状态的进程会导致平均负载升高, I 状态的进程却不会。
  • T 或者 t,是 Stopped 或 Traced 的缩写,表示进程处于暂停或者跟踪状态。如(1)向一个进程发送 SIGSTOP 信号,它就会因响应这个信号变成暂停状态(Stopped);再向它发送 SIGCONT 信号,进程又会恢复运行(如果进程是终端里直接启动的,则需要你用 fg 命令,恢复到前台运行)。(2)调试器 gdb 调试一个进程时,在使用断点中断进程后,进程就会变成跟踪状态,这其实也是一种特殊的暂停状态,只不过你可以用调试器来跟踪并按需要控制进程的运行。
  • X 是 Dead 的缩写,表示进程已经消亡,所以你不会在 top 或者 ps 命令中看到它。

  不可中断状态,是为了保证进程数据与硬件状态一致,并且正常情况下,不可中断状态在很短时间内就会结束。所以,短时的不可中断状态进程,我们一般可以忽略。但如果系统或硬件发生了故障,进程可能会在不可中断状态保持很久,甚至导致系统中出现大量不可中断进程。这时系统可能出现了 I/O 等性能问题。

  僵尸进程,这是多进程应用很容易碰到的问题。正常情况下,当一个进程创建了子进程后,它应该通过系统调用 wait() 或者 waitpid() 等待子进程结束,回收子进程的资源;而子进程在结束时,会向它的父进程发送 SIGCHLD 信号,所以,父进程还可以注册 SIGCHLD 信号的处理函数,异步回收资源。如果父进程没这么做,或是子进程执行太快,父进程还没来得及处理子进程状态,子进程就已经提前退出,那这时的子进程就会变成僵尸进程。
  通常,僵尸进程持续的时间都比较短,在父进程回收它的资源后就会消亡;或者在父进程退出后,由 init 进程回收后也会消亡。但是一旦父进程没有处理子进程的终止,还一直保持运行状态,那么子进程就会一直处于僵尸状态。大量的僵尸进程会用尽 PID 进程号,导致新进程不能创建。

$ ps aux | grep /app
root      4009  0.0  0.0   4376  1008 pts/0    Ss+  05:51   0:00 /app
root      4287  0.6  0.4  37280 33660 pts/0    D+   05:54   0:00 /app
root      4288  0.6  0.4  37280 33668 pts/0    D+   05:54   0:00 /app

  s 表示这个进程是一个会话的领导进程,而 + 表示前台进程组。
  进程组表示一组相互关联的进程,比如每个子进程都是父进程所在组的成员。在后台运行的命令,构成后台进程组;在前台运行的命令,构成前台进程组。
  会话是指共享同一个控制终端的一个或多个进程组。

$ top
top - 05:56:23 up 17 days, 16:45,  2 users,  load average: 2.00, 1.68, 1.39
Tasks: 247 total,   1 running,  79 sleeping,   0 stopped, 115 zombie
%Cpu0  :  0.0 us,  0.7 sy,  0.0 ni, 38.9 id, 60.5 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  :  0.0 us,  0.7 sy,  0.0 ni,  4.7 id, 94.6 wa,  0.0 hi,  0.0 si,  0.0 st
...

PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
4340 root      20   0   44676   4048   3432 R   0.3  0.0   0:00.05 top
4345 root      20   0   37280  33624    860 D   0.3  0.0   0:00.01 app
4344 root      20   0   37280  33624    860 D   0.3  0.4   0:00.01 app
1 root      20   0  160072   9416   6752 S   0.0  0.1   0:38.59 systemd

// 第一行的平均负载( Load Average),平均负载正在升高,
//     而 1 分钟内的平均负载已经达到系统的 CPU 个数,说明系统很可能已经有了性能瓶颈。
// 第二行,有 1 个正在运行的进程,但僵尸进程比较多,而且还在不停增加,说明有子进程在退出时没被清理。
// 第三、四行,用户 CPU 和系统 CPU 都不高,但 iowait 分别是 60.5% 和 94.6%,好像有点儿不正常。
// 最后,CPU 使用率都不高;但有两个进程处于 D 状态,它们可能在等待 I/O。

  第一点,iowait 太高了,导致系统的平均负载升高,甚至达到了系统 CPU 的个数。
  第二点,僵尸进程在不断增多,说明有程序没能正确清理子进程的资源。

  iowait 分析
  从系统整体了解 I/O 情况 -> 定位到具体进程 -> 定位到具体函数
  (1)dstat 是一个新的性能工具,它吸收了 vmstat、iostat、ifstat 等几种工具的优点,可以同时观察系统的 CPU、磁盘 I/O、网络以及内存使用情况,本例需关注系统整体和 I/O 的问题。

$ dstat 1 10  // 输出间隔 1 秒,总共输出 10 组数据
You did not select any stats, using -cdngy by default.
----total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai hiq siq| read  writ| recv  send|  in   out | int   csw 
  0   0  99   0   0   0|2224B  118k|   0     0 |   0     5B| 157   614 
  0   1  99   0   0   0|   0     0 |1914B    0 |   0     0 | 318   820 
  1   0  99   0   0   0|   0   196k| 208B    0 |   0     0 | 271   789 
  1   1  98   0   0   0|   0     0 |   0     0 |   0     0 | 292   772 

  (2)通过 top 找到 D 状态的进程。
  (3)通过 pidstat 找到具体进程的 I/O 状态,-d 参数输出 I/O 使用情况,-p 参数指定具体进程号:pidstat -d -p 4344 1 3
  (4)若上述 pidstat 找不到具体 I/O 原因,可以去掉 -p 参数,查看所有进程。
  (5)通过 strace -p / perf record (top) -g 动态追踪到具体进程中的某函数。

  僵尸进程分析
  既然僵尸进程是因为父进程没有回收子进程的资源而出现的,那么,也就是找出父进程的问题。
  (1)通过 pstree -aps 找出当前进程的父进程。// -a 表示输出命令行选项 p 表示 PID s 表示指定进程的父进程
  (2)查看父进程的代码,看看子进程结束的处理是否正确,比如有没有调用 wait() 或 waitpid() ,或是,有没有注册 SIGCHLD 信号的处理函数。
  
  
  
  

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!