CPU性能篇:CPU使用率以及统计原理;查看CPU使用率;CPU 使用率过高的根因排查思路(锁定高消耗CPU的代码);perf工具;案例

倪朋飞 《Linux 性能优化实战》 学习笔记

复制代码
CPU使用率以及统计原理
==========================================================================================================
Linux 作为一个多任务操作系统,将每个 CPU 的时间划分为很短的时间片,再通过调度器轮流分配给各个任务使用,因此造成多任务同时运行的错觉。
为了维护 CPU 时间,Linux 通过事先定义的节拍率(内核中表示为 HZ),触发时间中断,并使用全局变量 Jiffies 记录了开机以来的节拍数。每发生一次时间中断,Jiffies 的值就加 1。
节拍率 HZ 是内核的可配选项,可以设置为 100、250、1000 等。不同的系统可能设置不同数值,你可以通过查询 /boot/config 内核选项来查看它的配置值。
    grep 'CONFIG_HZ=' /boot/config-$(uname -r)

Linux 通过 /proc 虚拟文件系统,向用户空间提供了系统内部状态的信息,而 /proc/stat 提供的就是系统的 CPU 和任务统计信息。
    $ cat /proc/stat | grep ^cpu
    cpu  280580 7407 286084 172900810 83602 0 583 0 0 0
    cpu0 144745 4181 176701 86423902 52076 0 301 0 0 0
    cpu1 135834 3226 109383 86476907 31525 0 282 0 0 0
    //man proc    然后"/proc/stat"查找说明
    1.user(通常缩写为 us),代表用户态 CPU 时间。注意,它不包括下面的 nice 时间,但包括了 guest 时间。
    2.nice(通常缩写为 ni),代表低优先级用户态 CPU 时间,也就是进程的 nice 值被调整为 1-19 之间时的 CPU 时间。这里注意,nice 可取值范围是 -20 到 19,数值越大,优先级反而越低。
    3.system(通常缩写为 sys),代表内核态 CPU 时间。
    4.idle(通常缩写为 id),代表空闲时间。注意,它不包括等待 I/O 的时间(iowait)。
    5.iowait(通常缩写为 wa),代表等待 I/O 的 CPU 时间。
    6.irq(通常缩写为 hi),代表处理硬中断的 CPU 时间。
    7.softirq(通常缩写为 si),代表处理软中断的 CPU 时间。
    8.steal(通常缩写为 st),代表当系统运行在虚拟机中的时候,被其他虚拟机占用的 CPU 时间。
    9.guest(通常缩写为 guest),代表通过虚拟化运行其他操作系统的时间,也就是运行虚拟机的 CPU 时间。
    10.guest_nice(通常缩写为 gnice),代表以低优先级运行虚拟机的时间。

CPU使用率统计原理
CPU 使用率=除了空闲时间外的其他时间占总 CPU 时间的百分比
注意:/proc/stat显示的是累计时间,直接查看意义不是很大
系统CPU使用率= 1 - (空闲时间new - 空闲时间old)/(总cpu时间new = 总cpu时间old)
类似的,进程的cpu数据保存在/proc/[pid]/stat中
CPU使用率以及统计原理
复制代码

 

复制代码
查看 CPU 使用率
==========================================================================================================
top 显示了系统总体的 CPU 和内存使用情况,以及各个进程的资源使用情况。
ps 则只显示了每个进程的资源使用情况。
    top 和 ps 这两个工具报告的 CPU 使用率,默认的结果很可能不一样:
        ###top 默认使用 3 秒时间间隔
        ###ps 使用的却是进程的整个生命周期。

top - 10:33:27 up 11:17,  4 users,  load average: 0.00, 0.01, 0.05
Tasks: 185 total,   1 running, 184 sleeping,   0 stopped,   0 zombie
%Cpu0  :  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st     #参数说明详见本节总结《CPU使用率以及统计原理》
%Cpu1  :  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st     #默认显示所有cpu的总数据,'按键1'切换为每个 CPU 的使用率
KiB Mem : 10054452 total,  8574028 free,   777312 used,   703112 buff/cache
KiB Swap:  2088956 total,  2088956 free,        0 used.  8986804 avail Mem

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
     1 root      20   0  128404   7076   4220 S   0.0  0.1   0:02.50 systemd        #%CPU 列,表示进程的 CPU 使用率。***它是用户态和内核态 CPU 使用率的总和***
     2 root      20   0       0      0      0 S   0.0  0.0   0:00.04 kthreadd       #包括进程用户空间使用的 CPU、通过系统调用执行的内核空间 CPU 、以及在就绪队列等待运行的 CPU。
     4 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 kworker/0:0H   #在虚拟化环境中,它还包括了运行虚拟机占用的 CPU。
     6 root      20   0       0      0      0 S   0.0  0.0   0:02.02 ksoftirqd/0
     7 root      rt   0       0      0      0 S   0.0  0.0   0:00.20 migration/0    
     8 root      20   0       0      0      0 S   0.0  0.0   0:00.00 rcu_bh
     9 root      20   0       0      0      0 S   0.0  0.0   0:02.26 rcu_sched
    10 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 lru-add-drain
    11 root      rt   0       0      0      0 S   0.0  0.0   0:00.16 watchdog/0

--------------------------------------------------------------------------------------------
[root@yefeng ~]# pidstat 1 5
Linux 3.10.0-1160.el7.x86_64 (yefeng)   05/29/2022      _x86_64_        (2 CPU)

10:38:45 AM   UID       PID    %usr %system  %guest    %CPU   CPU  Command
10:38:46 AM     0       418    0.00    1.00    0.00    1.00     0  xfsaild/dm-0
10:38:46 AM     0     32270    0.00    1.00    0.00    1.00     1  pidstat
...
Average:      UID       PID    %usr %system  %guest    %CPU   CPU  Command              #pidstat版本过低的关系,没有显示"%wait"
Average:        0       418    0.00    0.20    0.00    0.20     -  xfsaild/dm-0
Average:        0     30732    0.00    0.20    0.00    0.20     -  sshd
Average:        0     32270    0.20    0.80    0.00    1.00     -  pidstat

$ pidstat 1 5
15:56:02      UID       PID    %usr %system  %guest   %wait    %CPU   CPU  Command
15:56:03        0     15006    0.00    0.99    0.00    0.00    0.99     1  dockerd
...
Average:      UID       PID    %usr %system  %guest   %wait    %CPU   CPU  Command
Average:        0     15006    0.00    0.99    0.00    0.00    0.99     -  dockerd
    用户态 CPU 使用率 (%usr);
    内核态 CPU 使用率(%system);
    运行虚拟机 CPU 使用率(%guest);
    等待 CPU 使用率(%wait);
    以及总的 CPU 使用率(%CPU)。
查看 CPU 使用率
复制代码

 

复制代码
CPU 使用率过高的根因排查思路(锁定高消耗CPU的代码);perf工具
==========================================================================================================
CPU 使用率过高的排查思路:
    通过 top、ps、pidstat 等工具,你能够轻松找到 CPU 使用率较高(比如 100% )的进程。
    占用 CPU 的到底是代码里的哪个函数?
        1.GDB(The GNU Project Debugger), 这个功能强大的程序调试利器;但是GDB 并不适合在性能分析的早期应用,因为 GDB 调试程序的过程会中断程序运行,这在线上环境往往是不允许的。
        2.推荐 perf。perf 是 Linux 2.6.31 以后内置的性能分析工具。它以性能事件采样为基础,不仅可以分析系统的各种事件和内核性能,还可以用来分析指定应用程序的性能问题。

-----------------------------------------------------------------------------------------
perf top,类似于 top,它能够实时显示占用 CPU 时钟最多的函数或者指令,因此可以用来查找热点函数
perf record 和 perf report。 
    perf top 虽然实时展示了系统的性能信息,但它的缺点是并不保存数据,也就无法用于离线或者后续的分析。
    而 perf record 则提供了保存数据的功能,保存后的数据,需要你用 perf report 解析展示。

$ perf top
Samples: 833  of event 'cpu-clock', Event count (approx.): 97742399 #第一行包含三个数据,分别是采样数(Samples)、事件类型(event)和事件总数量(Event count)。
Overhead  Shared Object       Symbol                                #比如这个例子中,perf 总共采集了 833 个 CPU 时钟事件,而总事件数则为 97742399
                                                                    #采样数需要我们特别注意。如果采样数过少(比如只有十几个),那下面的排序和百分比就没什么实际参考价值了。
   7.28%  perf                [.] 0x00000000001f78a4
   4.72%  [kernel]            [k] vsnprintf
   4.32%  [kernel]            [k] module_get_kallsym
   3.65%  [kernel]            [k] _raw_spin_unlock_irqrestore
...
    第一列 Overhead ,是该符号的性能事件在所有采样中的比例,用百分比来表示。
    第二列 Shared ,是该函数或指令所在的动态共享对象(Dynamic Shared Object),如内核、进程名、动态链接库名、内核模块名等。
    第三列 Object ,是动态共享对象的类型。比如 [.] 表示用户空间的可执行程序、或者动态链接库,而 [k] 则表示内核空间。
    最后一列 Symbol 是符号名,也就是函数名。#####当函数名未知时,用十六进制的地址来表示。#####

在实际使用中,我们还经常为 perf top 和 perf record 加上 -g 参数,开启调用关系的采样,方便我们根据调用链来分析性能问题。
CPU 使用率过高的根因排查思路(锁定高消耗CPU的代码);perf工具
复制代码

 

复制代码
CPU 使用率小结:
=======================================================================================
CPU 使用率是最直观和最常用的系统性能指标,更是我们在排查性能问题时,通常会关注的第一个指标。
所以我们更要熟悉它的含义,尤其要弄清楚用户(%user)、Nice(%nice)、系统(%system) 、等待 I/O(%iowait) 、中断(%irq)以及软中断(%softirq)这几种不同 CPU 的使用率。比如说:
    1.用户 CPU 和 Nice CPU 高,说明用户态进程占用了较多的 CPU,所以应该着重排查进程的性能问题。
    2.系统 CPU 高,说明内核态占用了较多的 CPU,所以应该着重排查内核线程或者系统调用的性能问题。
    3.I/O 等待 CPU 高,说明等待 I/O 的时间比较长,所以应该着重排查系统存储是不是出现了 I/O 问题。
    4.软中断和硬中断高,说明软中断或硬中断的处理程序占用了较多的 CPU,所以应该着重排查内核中的中断服务程序。
碰到 CPU 使用率升高的问题,你可以借助 top、pidstat 等工具,确认引发 CPU 性能问题的来源;再使用 perf 等工具,排查出引起性能问题的具体函数。
CPU 使用率小结
复制代码

 

复制代码
"CPU使用率高,但top命令无法找到CPU高使用率进程"的分析;execsnoop工具
============================================================================================================================
通过top观察CPU使用率很高,但是看下面的进程的CPU使用率好像很正常,通过pidstat命令查看cpu也很正常。但通过top查看task数量不正常,处于R状态的进程是可疑点。

首先要想到可能是短时间的应用导致的问题,如下面的两个:
(1)应用里直接调用了其他二进制程序,这些程序通常运行时间比较短,通过top等工具发现不了
(2)应用本身在不停地崩溃重启,而启动过程的资源初始化,很可能会占用很多CPU资源



碰到常规问题无法解释的 CPU 使用率情况时,首先要想到有可能是短时应用导致的问题,比如有可能是下面这两种情况。
    第一,应用里直接调用了其他二进制程序,这些程序通常运行时间比较短,通过 top 等工具也不容易发现。
    第二,应用本身在不停地崩溃重启,而启动过程的资源初始化,很可能会占用相当多的 CPU。
    对于这类进程,我们可以用 pstree 或者 execsnoop 找到它们的父进程,再从父进程所在的应用入手,排查问题的根源。


==========================================================================================================
也可以通过这种方式发现stress问题
用for循环输出所有running状态的进程,然后进行排序,或许就能发现问题进程了。。。
for i in `seq 1 100`;do ps aux |awk '$8 ~/R/{print $0}' >> 1234.txt;sleep 1;done
for i in `seq 1 100`;do ps -eo ppid,pid,tid,class,rtprio,ni,pri,psr,pcpu,stat,comm |awk '$10 ~/R/{print $0}' >> 1234.txt;sleep 1;done
for i in `seq 1 100`;do ps -eo ppid,pid,tid,class,rtprio,ni,pri,psr,pcpu,stat,comm |awk '$10 ~/R/{print $0}' ;sleep 1;done
cat 1234.txt |awk '{print $11}' |sort |uniq -c
pstree | grep stress

execsnoop 就是一个专为短时进程设计的工具。它通过 ftrace 实时监控进程的 exec() 行为,并输出短时进程的基本信息,包括进程 PID、父进程 PID、命令行参数以及执行的结果。
用 execsnoop 监控上述案例,就可以直接得到 stress 进程的父进程 PID 以及它的命令行参数,并可以发现大量的 stress 进程在不停启动:
[root@yefeng ~]# ./execsnoop
Tracing exec()s. Ctrl-C to end.
Instrumenting sys_execve
   PID   PPID ARGS
120003 119982 gawk -v o=1 -v opt_name=0 -v name= -v opt_duration=0 [...]
120004 120002 cat -v trace_pipe
120001      0            <...>-120001 [001] d... 22601.215825: execsnoop_sys_execve: (SyS_execve+0x0/0x30)
120005  74993            <...>-120005 [000] d... 22601.216984: execsnoop_sys_execve: (SyS_execve+0x0/0x30)
120006  74998            <...>-120006 [000] d... 22601.217760: execsnoop_sys_execve: (SyS_execve+0x0/0x30)
120007 120001 /usr/local/bin/stress -t 1 -d 1
120008 120005 /usr/local/bin/stress -t 1 -d 1
120009 120006 /usr/local/bin/stress -t 1 -d 1
120000      0            <...>-120000 [000] d... 22601.221735: execsnoop_sys_execve: (SyS_execve+0x0/0x30)
120013 120000 /usr/local/bin/stress -t 1 -d 1
120014  74986            <...>-120014 [000] d... 22601.234865: execsnoop_sys_execve: (SyS_execve+0x0/0x30)
120016 120014 /usr/local/bin/stress -t 1 -d 1
120019  74993            <...>-120019 [000] d... 22601.238491: execsnoop_sys_execve: (SyS_execve+0x0/0x30)
120017  75007            <...>-120017 [001] d... 22601.241090: execsnoop_sys_execve: (SyS_execve+0x0/0x30)
120021 120017 /usr/local/bin/stress -t 1 -d 1
120020 120019 /usr/local/bin/stress -t 1 -d 1
120024  74998            <...>-120024 [001] d... 22601.253951: execsnoop_sys_execve: (SyS_execve+0x0/0x30)
120027  74986            <...>-120027 [001] d... 22601.257305: execsnoop_sys_execve: (SyS_execve+0x0/0x30)
120028 120027 /usr/local/bin/stress -t 1 -d 1
120026  74978            <...>-120026 [001] d... 22601.258705: execsnoop_sys_execve: (SyS_execve+0x0/0x30)
120030 120026 /usr/local/bin/stress -t 1 -d 1
120025 120024 /usr/local/bin/stress -t 1 -d 1
120033  74986            <...>-120033 [000] d... 22601.275611: execsnoop_sys_execve: (SyS_execve+0x0/0x30)
120035 120033 /usr/local/bin/stress -t 1 -d 1
120034  75007            <...>-120034 [001] d... 22601.278328: execsnoop_sys_execve: (SyS_execve+0x0/0x30)
120037 120034 /usr/local/bin/stress -t 1 -d 1
120039  74993            <...>-120039 [000] d... 22601.281742: execsnoop_sys_execve: (SyS_execve+0x0/0x30)
120040 120039 /usr/local/bin/stress -t 1 -d 1
120041  74986            <...>-120041 [001] d... 22601.290180: execsnoop_sys_execve: (SyS_execve+0x0/0x30)
120044  74998            <...>-120044 [000] d... 22601.292012: execsnoop_sys_execve: (SyS_execve+0x0/0x30)
120045 120044 /usr/local/bin/stress -t 1 -d 1
120046 120041 /usr/local/bin/stress -t 1 -d 1
120043  74978            <...>-120043 [001] d... 22601.293538: execsnoop_sys_execve: (SyS_execve+0x0/0x30)
120048 120043 /usr/local/bin/stress -t 1 -d 1
120051  75007            <...>-120051 [000] d... 22601.296635: execsnoop_sys_execve: (SyS_execve+0x0/0x30)
120052 120051 /usr/local/bin/stress -t 1 -d 1
120054  74993            <...>-120054 [001] d... 22601.314035: execsnoop_sys_execve: (SyS_execve+0x0/0x30)
120053  74998            <...>-120053 [001] d... 22601.315188: execsnoop_sys_execve: (SyS_execve+0x0/0x30)
120056 120054 /usr/local/bin/stress -t 1 -d 1
120057  74986            <...>-120057 [001] d... 22601.316863: execsnoop_sys_execve: (SyS_execve+0x0/0x30)
120058 120057 /usr/local/bin/stress -t 1 -d 1
120059 120053 /usr/local/bin/stress -t 1 -d 1
120062  74978            <...>-120062 [001] d... 22601.327741: execsnoop_sys_execve: (SyS_execve+0x0/0x30)
120064 120062 /usr/local/bin/stress -t 1 -d 1
120066  75007            <...>-120066 [001] d... 22601.330930: execsnoop_sys_execve: (SyS_execve+0x0/0x30)
120067 120066 /usr/local/bin/stress -t 1 -d 1
120071  74986            <...>-120071 [000] d... 22601.347464: execsnoop_sys_execve: (SyS_execve+0x0/0x30)
"CPU使用率高,但top命令无法找到CPU高使用率进程"的分析;execsnoop工具
复制代码

 

复制代码
案例1
==========================================================================================================
两台虚拟机,1台客户端,1台服务端
服务端运行容器:
    docker run --name nginx -p 10000:80 -itd feisky/nginx
    docker run --name phpfpm -itd --network container:nginx feisky/php-fpm

客户端开启测试:
curl http://192.168.1.150:10000/                #确认服务端工作正常
ab -c 10 -n 100 http://192.168.1.150:10000/     #发起100个请求,测试实验效果
ab -c 10 -n 10000 http://192.168.1.150:10000/   #发起10000个请求,这样才有足够的排查时间。。。





排查过程:
top
perf top -g -p 2942     #-g开启调用关系分析,-p指定php-fpm的进程号2942


perf record -g -p 2942      #在当前目录下生成perf.data文件
docker cp perf.data phpfpm:/tmp     #将perf.data文件复制到容器中
docker exec -i -t phpfpm bash       #进入容器    
cd /tmp/
apt-get update && apt-get install -y linux-perf linux-tools procps
perf_4.9 report                     #注意:最后运行的工具名字是容器内部安装的版本 perf_4.9,而不是 perf 命令,这是因为 perf 会去跟内核的版本进行匹配,但镜像里面安装的perf版本有可能跟虚拟机的内核版本不一致。






-------------------------------------------------------------------------------
排查过程:
    cpu使用率高的根因:程序存在问题,某一段代码大量使用了sqrt() 函数进行计算,使得用户态CPU跑满了。。。

    [root@yefeng ~]# ab -c 10 -n 100 http://192.168.1.150:10000/
    This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
    Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
    Licensed to The Apache Software Foundation, http://www.apache.org/

    Benchmarking 192.168.1.150 (be patient).....done


    Server Software:        nginx/1.15.4
    Server Hostname:        192.168.1.150
    Server Port:            10000

    Document Path:          /
    Document Length:        9 bytes

    Concurrency Level:      10
    Time taken for tests:   4.147 seconds
    Complete requests:      100
    Failed requests:        0
    Write errors:           0
    Total transferred:      17200 bytes
    HTML transferred:       900 bytes
    Requests per second:    24.11 [#/sec] (mean)            ####Nginx 承受的每秒平均请求数只有 24.11
    Time per request:       414.711 [ms] (mean)
    Time per request:       41.471 [ms] (mean, across all concurrent requests)
    Transfer rate:          4.05 [Kbytes/sec] received

    Connection Times (ms)
                  min  mean[+/-sd] median   max
    Connect:        0    0   0.1      0       1
    Processing:    80  394  82.9    396     557
    Waiting:       80  394  82.9    396     557
    Total:         80  394  82.9    396     557

    Percentage of the requests served within a certain time (ms)
      50%    396
      66%    440
      75%    450
      80%    456
      90%    474
      95%    528
      98%    557
      99%    557
     100%    557 (longest request)
-----------------------------------------------------------
服务端开始排查

top - 10:56:34 up 38 min,  1 user,  load average: 0.91, 0.21, 0.09      #进入top界面后,按下"按键1"
Tasks: 185 total,   6 running, 179 sleeping,   0 stopped,   0 zombie
%Cpu0  : 99.0 us,  0.3 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.7 si,  0.0 st     #2个cpu的用户态 CPU 时间几乎占满了
%Cpu1  : 99.7 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.3 si,  0.0 st
KiB Mem : 10054452 total,  8742068 free,   801068 used,   511316 buff/cache
KiB Swap:  2088956 total,  2088956 free,        0 used.  8965728 avail Mem

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
  2942 bin       20   0  336684   9368   1688 R  45.8  0.1   0:05.23 php-fpm        #几个 php-fpm 进程的 CPU 使用率加起来接近 200%
  2946 bin       20   0  336684   9360   1680 R  42.5  0.1   0:04.18 php-fpm        #确认是用户空间的 php-fpm 进程,导致 CPU 使用率骤升。
  2943 bin       20   0  336684   9364   1684 R  38.9  0.1   0:05.11 php-fpm
  2944 bin       20   0  336684   9360   1680 R  35.5  0.1   0:03.96 php-fpm
  2945 bin       20   0  336684   9360   1680 R  34.6  0.1   0:04.30 php-fpm
  2855 101       20   0   33092   2156    776 S   1.0  0.0   0:00.16 nginx
   504 root      20   0   39308   4932   4420 S   0.7  0.0   0:00.36 systemd-journal
  2259 root      20   0  736872  31120  14688 S   0.7  0.3   0:01.92 dockerd-current
    14 root      20   0       0      0      0 S   0.3  0.0   0:00.05 ksoftirqd/1
   761 root      20   0  295380   5224   3960 S   0.3  0.1   0:01.64 vmtoolsd
  2811 root      20   0  339844   4996   1652 S   0.3  0.0   0:00.04 docker-containe
  2925 root      20   0       0      0      0 S   0.3  0.0   0:00.12 kworker/0:1
  2947 root      20   0  164300   2412   1616 R   0.3  0.0   0:00.04 top
     1 root      20   0  128580   7212   4244 S   0.0  0.1   0:02.14 systemd
     2 root      20   0       0      0      0 S   0.0  0.0   0:00.00 kthreadd
     4 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 kworker/0:0H
     6 root      20   0       0      0      0 S   0.0  0.0   0:00.03 ksoftirqd/0


perf top -g -p 2942     #-g开启调用关系分析,-p指定php-fpm的进程号2942
    Samples: 1K of event 'cpu-clock', 4000 Hz, Event count (approx.): 332000000 lost: 0/0 drop: 0/0
      Children      Self  Shared Object       Symbol
    +   99.92%     0.00%  [unknown]           [.] 0x6cb6258d4c544155             
    +   99.92%     0.00%  libc-2.24.so        [.] 0x00007fd16ef262e1             
    -   99.40%     0.00%  php-fpm             [.] 0x000055642eacc642             #按方向键切换到 php-fpm,再按下回车键展开 php-fpm 的调用关系,你会发现,调用关系最终到了 sqrt 和 add_function。
         0x55642eacc642                                                          #实际查看结果为16进制数字(因为当函数名未知时,用十六进制的地址来表示)
         0x55642e87d6fc                                                          #原因为perf无法找到待分析进程所依赖的库
         0x55642e92cf94                                                          #这个问题其实是在分析Docker容器应用时经常会碰到的一个问题,因为容器应用所依赖的库都在镜像里面。
       - 0x55642e9c4323                                                          
          + 93.00% 0x55642e9c396e                                                
            1.73% 0x55642e9c397a                                                 
            1.13% 0x55642e9c3971                                                 
            0.75% 0x55642e9c3966                                                 
    +   99.40%     0.00%  php-fpm             [.] 0x000055642e87d6fc             
    +   99.40%     0.00%  php-fpm             [.] 0x000055642e92cf94             
    +   99.40%     0.00%  php-fpm             [.] 0x000055642e9c4323             
    +   93.00%     0.00%  php-fpm             [.] 0x000055642e9c396e             
    +   20.11%     0.00%  php-fpm             [.] 0x000055642e9c5a7c             
    +   16.11%     0.00%  php-fpm             [.] 0x000055642ea8eea3             
    +    5.35%     5.35%  libm-2.24.so        [.] 0x000000000002127e             
    +    5.35%     0.00%  libm-2.24.so        [.] 0x00007fd172fce27e         

两个解决思路:
(1)在容器外面构建相同路径的依赖库。这种方法不推荐,一是因为找出这些依赖比较麻烦,更重要的是构建这些路径会污染虚拟机的环境。
(2)在容器外面把分析纪录保存下来,到容器里面再去查看结果,这样库和符号的路径就都是对的了。

    perf record -g -p 2942      #在当前目录下生成perf.data文件
    docker cp perf.data phpfpm:/tmp     #将perf.data文件复制到容器中
    docker exec -i -t phpfpm bash       #进入容器    
    cd /tmp/
    apt-get update && apt-get install -y linux-perf linux-tools procps
    perf_4.9 report                     #注意:最后运行的工具名字是容器内部安装的版本 perf_4.9,而不是 perf 命令,这是因为 perf 会去跟内核的版本进行匹配,但镜像里面安装的perf版本有可能跟虚拟机的内核版本不一致。

    Samples: 31K of event 'cpu-clock', Event count (approx.): 7809250000
      Children      Self  Command  Shared Object            Symbol                  
    -   99.96%     0.00%  php-fpm  libc-2.24.so             [.] __libc_start_main   
       - __libc_start_main                                                          
          - 99.55% 0x9cb642                                                         
             - 99.54% php_execute_script                                            
                - 99.50% zend_execute_scripts                                       
                   - 99.46% zend_execute                                            
                      - 96.62% execute_ex                                           
                         - 18.71% 0x8c4a7c                                          
                              5.30% sqrt                                            #调用关系最终到了 sqrt 和 add_function。看来,我们需要从这两个函数入手了。
                              0.67% 0x681d1a                                        
                              0.59% 0x681d21                                        
                         - 15.56% 0x98dea3                                          
                            - 3.23% 0x98dd97                                        
                                 add_function                                       #调用关系最终到了 sqrt 和 add_function。看来,我们需要从这两个函数入手了。
                              1.39% 0x98de23                                        
                              0.66% add_function                                    
                              0.52% 0x98dd9e                                        
                           3.73% 0x94ede0                                           
                           2.24% 0x8c4a6c                                           
                           2.05% 0x9513cc                                           
                           1.95% 0x8c4ae5                                           


$ docker cp phpfpm:/app .           # 从容器phpfpm中将PHP源码拷贝出来

$ grep sqrt -r app/                 #使用grep查找函数调用,找到了sqrt调用      
    app/index.php:  $x += sqrt($x);
$ grep add_function -r app/         #没找到add_function调用,这其实是PHP内置函数
       
[root@yefeng ~]# cat app/index.php      #查看调用sqrt函数的源文件
<?php
// test only.
$x = 0.0001;
for ($i = 0; $i <= 1000000; $i++) {     #nginx并发低的原因,被这个循环占用了大量的cpu资源
  $x += sqrt($x);
}

echo "It works!"

-------------------------------------------------------------------------------------
服务端修复
# 停止原来的应用
$ docker rm -f nginx phpfpm
# 运行优化后的应用
$ docker run --name nginx -p 10000:80 -itd feisky/nginx:cpu-fix
$ docker run --name phpfpm -itd --network container:nginx feisky/php-fpm:cpu-f      

#下载新的镜像失败;进入phpfpm容器直接删除index.php文件内的for循环
[root@yefeng ~]# ab -c 10 -n 10000 http://192.168.1.150:10000/
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 192.168.1.150 (be patient)
Completed 1000 requests
……
Finished 10000 requests


Server Software:        nginx/1.15.4
Server Hostname:        192.168.1.150
Server Port:            10000

Document Path:          /
Document Length:        9 bytes

Concurrency Level:      10
Time taken for tests:   3.550 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      1720000 bytes
HTML transferred:       90000 bytes
Requests per second:    2817.01 [#/sec] (mean)              #Nginx 承受的每秒平均请求数从24提升为2817
Time per request:       3.550 [ms] (mean)
Time per request:       0.355 [ms] (mean, across all concurrent requests)
Transfer rate:          473.17 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.4      0      21
Processing:     1    3   1.9      3      27
Waiting:        1    3   1.8      3      27
Total:          1    4   1.9      3      28

Percentage of the requests served within a certain time (ms)
  50%      3
  66%      4
  75%      4
  80%      4
  90%      5
  95%      6
  98%      8
  99%     11
 100%     28 (longest request)
案例1
复制代码
复制代码
案例2
==========================================================================================================
两台虚拟机,1台客户端,1台服务端
服务端运行容器:
    docker run --name nginx -p 10000:80 -itd feisky/nginx:sp
    docker run --name phpfpm -itd --network container:nginx feisky/php-fpm:sp

客户端开启测试:
curl http://192.168.1.150:10000/                #确认服务端工作正常
ab -c 100 -n 1000 http://192.168.1.150:10000/    #并发100个请求测试Nginx性能,总共测试1000个请求
ab -c 5 -t 600 http://192.168.1.150:10000/      #将测试的并发请求数改成 5,同时把请求时长设置为 10 分钟

排查命令
top
pidstat 1
top -o S 
pidstat -p 14442 
ps aux | grep 14442
pstree | grep stress
grep stress -r app  #app是一个目录



-----------------------------------------------------------------------------
确认问题
[root@yefeng ~]# ab -c 100 -n 1000 http://192.168.1.150:10000/
……
Concurrency Level:      100
Time taken for tests:   5.772 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      172000 bytes
HTML transferred:       9000 bytes
Requests per second:    173.24 [#/sec] (mean)   ######Nginx 能承受的每秒平均请求数,只有 173多一点
Time per request:       577.242 [ms] (mean)
Time per request:       5.772 [ms] (mean, across all concurrent requests)
Transfer rate:          29.10 [Kbytes/sec] received


ab -c 5 -t 600 http://192.168.1.150:10000/      #将测试的并发请求数改成 5,同时把请求时长设置为 10 分钟
-----------------------------------------------------------------------------
排查分析过程
    top显示cpu使用率很高,但找不到相关进程的根因:应用里直接调用了其他二进制程序,这些程序通常运行时间比较短,通过top等工具发现不了
    
top - 15:44:55 up  5:27,  2 users,  load average: 2.14, 2.61, 3.36                  #top命令默认以CPU进行排序
Tasks: 196 total,   6 running, 190 sleeping,   0 stopped,   0 zombie
%Cpu0  : 66.9 us, 22.3 sy,  0.0 ni,  8.1 id,  0.0 wa,  0.0 hi,  2.7 si,  0.0 st     #可以发现2个CPU的使用率都比较高
%Cpu1  : 66.4 us, 23.8 sy,  0.0 ni,  4.4 id,  0.0 wa,  0.0 hi,  5.4 si,  0.0 st     #系统的整体 CPU 使用率是比较高的:用户 CPU 使用率(us)已经到了 66%,系统 CPU 为 23%,而空闲 CPU (id)则只有 4.4%。
KiB Mem : 10054452 total,  7695996 free,   881544 used,  1476912 buff/cache
KiB Swap:  2088956 total,  2088956 free,        0 used.  8584384 avail Mem

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
  5378 101       20   0   33092   2192    772 R   6.3  0.0   3:53.09 nginx          #但是CPU 使用率最高的进程也只不过才 6.3%,看起来并不高。
126541 bin       20   0  336684   9588   1912 S   3.3  0.1   0:00.69 php-fpm        #Nginx 和 php-fpm 是运行 Web 服务的,它们会占用一些 CPU 也不意外,并且 6% 的 CPU 使用率也不算高
126558 bin       20   0  336684   9576   1900 S   3.3  0.1   0:00.68 php-fpm
126567 bin       20   0  336684   9580   1904 S   3.3  0.1   0:00.68 php-fpm
126546 bin       20   0  336684   9588   1912 S   3.0  0.1   0:00.69 php-fpm
126553 bin       20   0  336684   9576   1900 S   3.0  0.1   0:00.68 php-fpm
   504 root      20   0  133696  74496  73984 S   2.0  0.7   2:23.96 systemd-journal
  2259 root      20   0  885304  44888  16440 S   1.7  0.4   1:18.16 dockerd-current    #后面的进程CPU 使用率更低,看起来不太像会导致用户 CPU 使用率达到 66%。
  5334 root      20   0  341252   5012   1652 S   1.3  0.0   0:46.48 docker-containe
………………………………
     6 root      20   0       0      0      0 S   0.0  0.0   0:03.22 ksoftirqd/0
     7 root      rt   0       0      0      0 S   0.0  0.0   0:00.93 migration/0
     8 root      20   0       0      0      0 S   0.0  0.0   0:00.00 rcu_bh
     9 root      20   0       0      0      0 R   0.0  0.0   0:09.69 rcu_sched
    10 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 lru-add-drain
    11 root      rt   0       0      0      0 S   0.0  0.0   0:00.06 watchdog/0


[root@yefeng ~]# pidstat 1      #间隔1秒输出一组数据(按Ctrl+C结束)
Linux 3.10.0-1160.el7.x86_64 (yefeng)   05/29/2022      _x86_64_        (2 CPU)

03:52:19 PM   UID       PID    %usr %system  %guest    %CPU   CPU  Command
03:52:20 PM     0       504    2.97    2.97    0.00    5.94     1  systemd-journal      #pidstat命令也失效了,CPU使用率加起来也不高
03:52:20 PM     0      1133    0.00    0.99    0.00    0.99     1  rsyslogd
03:52:20 PM     0      2259    0.00    0.99    0.00    0.99     0  dockerd-current
03:52:20 PM     0      2265    0.00    0.99    0.00    0.99     0  docker-containe
03:52:20 PM     0      5334    0.00    0.99    0.00    0.99     1  docker-containe
03:52:20 PM   101      5378    0.00    4.95    0.00    4.95     0  nginx
03:52:20 PM     1     15738    0.99    2.97    0.00    3.96     0  php-fpm
03:52:20 PM     1     15746    0.00    2.97    0.00    2.97     0  php-fpm
03:52:20 PM     1     15755    0.99    3.96    0.00    4.95     1  php-fpm
03:52:20 PM     1     15757    0.00    4.95    0.00    4.95     0  php-fpm
03:52:20 PM     1     15767    0.00    2.97    0.00    2.97     0  php-fpm
03:52:20 PM     0     17700    0.00    0.99    0.00    0.99     1  pidstat


top -o S            #使用指定列(指定列名)进行排查,进入后按R进行sort
top 命令使用教程    https://blog.csdn.net/qq_19922839/article/details/120011790
top - 15:56:23 up  5:38,  2 users,  load average: 5.81, 4.73, 4.11                  
Tasks: 199 total,   9 running, 189 sleeping,   0 stopped,   1 zombie                #这里存在遗漏信息:平均负载很高,Running 状态的进程有点多
%Cpu(s): 64.6 us, 23.5 sy,  0.0 ni,  7.8 id,  0.0 wa,  0.0 hi,  4.1 si,  0.0 st
KiB Mem : 10054452 total,  7599556 free,   901728 used,  1553168 buff/cache
KiB Swap:  2088956 total,  2088956 free,        0 used.  8542316 avail Mem

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
     9 root      20   0       0      0      0 R   0.3  0.0   0:10.89 rcu_sched
  5422 root      20   0  336300  35156  27756 R   0.0  0.3   0:11.15 php-fpm
 14436 bin       20   0    7272    428    344 R   0.0  0.0   0:00.00 stress         #感觉stress有些奇怪,所以进行进一步分析
 14442 bin       20   0    8172    600    104 R   0.0  0.0   0:00.00 stress         #pidstat命令、ps命令均无法输出该进程的相关信息(因为这个进程已经没了!)
 14443 bin       20   0    8180   1120    104 R   0.0  0.0   0:00.00 stress         #重新观察,发现stress的进程ID一直在变化
 14444 bin       20   0  336684   7676      0 R   0.0  0.1   0:00.00 php-fpm
 15738 bin       20   0  336684   9588   1912 R   3.3  0.1   0:08.30 php-fpm
 15755 bin       20   0  336684   9576   1900 R   4.0  0.1   0:08.10 php-fpm
 87967 root      20   0  164312   2444   1624 R   0.0  0.0   0:00.14 top
     1 root      20   0  128580   7212   4244 S   0.0  0.1   0:02.70 systemd
     2 root      20   0       0      0      0 S   0.0  0.0   0:00.02 kthreadd
     4 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 kworker/0:0H
     6 root      20   0       0      0      0 S   0.3  0.0   0:03.72 ksoftirqd/0
     7 root      rt   0       0      0      0 S   0.0  0.0   0:01.05 migration/0
     8 root      20   0       0      0      0 S   0.0  0.0   0:00.00 rcu_bh
    10 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 lru-add-drain
    11 root      rt   0       0      0      0 S   0.0  0.0   0:00.06 watchdog/0
    12 root      rt   0       0      0      0 S   0.0  0.0   0:00.05 watchdog/1


[root@yefeng ~]# pidstat -p 14442                   #查看指定进程,发现无输出(因为这个进程已经没了!)
Linux 3.10.0-1160.el7.x86_64 (yefeng)   05/29/2022      _x86_64_        (2 CPU)

03:59:10 PM   UID       PID    %usr %system  %guest    %CPU   CPU  Command

[root@yefeng ~]# ps aux | grep 14442                #ps命令查看该进程,也没有输出(因为这个进程已经没了!)


###进程的 PID 在变,无非也就两个原因
    第一个原因,进程在不停地崩溃重启,比如因为段错误、配置错误等等,这时,进程在退出后可能又被监控系统自动重启了。
    第二个原因,这些进程都是短时进程,也就是在其他应用内部通过 exec 调用的外面命令。这些命令一般都只运行很短的时间就会结束,你很难用 top 这种间隔时间比较长的工具发现(上面的案例,我们碰巧发现了)。

stress,我们前面提到过,它是一个常用的压力测试工具。它的 PID 在不断变化中,看起来像是被其他进程调用的短时进程。要想继续分析下去,还得找到它们的父进程。

[root@yefeng ~]# pstree | grep stress       #stress 是被 php-fpm 调用的子进程,并且进程数量不止一个
        |                 |                 |-docker-containe-+-php-fpm-+-4*[php-fpm---sh---stress---stress]

[root@yefeng ~]# docker ps                  #php-fpm是在phpfpm容器中被调用
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                   NAMES
7a2735a8986f        feisky/php-fpm:sp   "php-fpm -F --pid ..."   2 hours ago         Up 2 hours                                  phpfpm
944341a9c1a1        feisky/nginx:sp     "nginx -g 'daemon ..."   2 hours ago         Up 2 hours          0.0.0.0:10000->80/tcp   nginx


查看源码   
[root@yefeng ~]# docker cp phpfpm:/app .        # 拷贝源码到本地
[root@yefeng ~]# grep stress -r app             # grep 查找看看是不是有代码在调用stress命令
app/index.php:// fake I/O with stress (via write()/unlink()).
app/index.php:$result = exec("/usr/local/bin/stress -t 1 -d 1 2>&1", $output, $status);
[root@yefeng ~]# cat app/index.php              #查看源码
<?php
// fake I/O with stress (via write()/unlink()).     #从注释上看,stress 会通过 write() 和 unlink() 对 I/O 进程进行压测,看来,这应该就是系统 CPU 使用率升高的根源了
$result = exec("/usr/local/bin/stress -t 1 -d 1 2>&1", $output, $status);
if (isset($_GET["verbose"]) && $_GET["verbose"]==1 && $status != 0) {   #stress 模拟的是 I/O 压力,而之前在 top 的输出中看到的,却一直是用户 CPU 和系统 CPU 升高,并没见到 iowait 升高。
  echo "Server internal error: ";
  print_r($output);
} else {
  echo "It works!";
}
?>


[root@yefeng ~]# curl http://192.168.1.150:10000/?verbose=1
Server internal error: Array
(
    [0] => stress: info: [41611] dispatching hogs: 0 cpu, 0 io, 0 vm, 1 hdd
    [1] => stress: FAIL: [41612] (563) mkstemp failed: Permission denied            ###错误信息
    [2] => stress: FAIL: [41611] (394) <-- worker 41612 returned error 1
    [3] => stress: WARN: [41611] (396) now reaping child worker processes
    [4] => stress: FAIL: [41611] (400) kill error: No such process
    [5] => stress: FAIL: [41611] (451) failed run completed in 0s                   ###错误信息
)                                               #我们发现了一个 PHP 调用外部 stress 命令的 bug:没有权限创建临时文件。

[root@yefeng ~]# perf record -g
[root@yefeng ~]# perf report
案例2
复制代码

 

posted @   雲淡風輕333  阅读(814)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示