前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >IT项目研发过程中的利器——用Top分析CPU利用率

IT项目研发过程中的利器——用Top分析CPU利用率

作者头像
方亮
发布2024-03-19 08:28:12
1520
发布2024-03-19 08:28:12
举报
文章被收录于专栏:方亮方亮

top是linux程序员经常使用的分析机器运行状态的工具。但是并不是所有人都能清楚如何使用该工具对程序占用CPU资源的情况进行分析,比如图中us、sy、ni、id、wa和si等各是什么意思?高低都能说明什么问题?本文将抛砖引玉,讲解下该工具的使用。

被测试工具和环境

为了做好这些实验,我fork了https://github.com/resurrecting-open-source-projects/stress,并在此基础上做了一些功能新增,以支持更多的测试。被测试的工具地址是https://github.com/f304646673/stress

编译的方法是

代码语言:javascript
复制
cd stress
sh build.sh

同时,测试环境我选用了hyper-V中安装ubuntu22 TLS,4核4G。由于后面会测试到物理内存和虚拟内存,于是我强制要求最大内存量是4G。否则hyper-V会在物理内存不够时,一直找系统要更多的物理内存,从而影响测试进度。

确定CPU利用率

在top工具的%CPU(s)行,我们首先需要关注的是id的值。

id全称是idle,即空闲状态。

上图表示CPU资源的99.9%处于idle(空闲)状态。那么CPU的利用率就是100%-99.9%=0.1%。

这个CPU利用率是很低的,一般我们需要将CPU利用率至少保持40%以上。比如一些环境要求,在机器实例数保持不变的情况下,可以多承载1倍的流量,则CPU利用率就不能高于50%,但是又不能太低,于是控制40%是合理的;而一些环境有负载均衡和动态扩缩容做的比较好,不需要一台机器扛住额外的压力,于是可以把CPU利用率控制在80%甚至更高。具体的做法可以有:

  • 使用更低配置的实例。
  • 将实例通过容器化切割成更小的资源单元。
  • 混合部署CPU利用率高的程序。

一般我们更多遇到的是id比较低,即CPU利用率很高的情况。后面我们主要讲解us,sy、ni、wa和si比较高的情况。

us

us表示CPU time spent in user space。这主要是一些我们代码中不涉及系统其他资源,只是单纯计算的逻辑对CPU的占用。比如我们计算一个数的平方根,或者做一些其他计算。

我们可以使用下面指令测试什么代码会导致us上涨。

代码语言:javascript
复制
stress -c 8 -t 30

上面指令是fork出8个子进程,然后在内部不停计算一个随机数的平方根。这会导致CPU在用户层消耗了全部资源,进而idle变成了0。

代码语言:javascript
复制
int
hogcpu (void)
{
    const int max = 1000000;
    while (1) {
        for (int i = 0; i < max; i++)
            sqrt (rand ());
        global_count++;
    }
    return 0;
}

如果us比较,则需要关注代码中是否存在一些死循环。但是也有可能单纯是因为计算负责导致的。这就需要结合业务来看。如果希望知道问题出在哪儿,可以通过《动态执行流程分析和性能瓶颈分析的利器——gperftools的Cpu Profiler》或者《动态执行流程分析和性能瓶颈分析的利器——valgrind的callgrind》进行深入分析。

sy

sy表示CPU time spent in kernel space。它主要是我们代码中涉及的一些被保护的资源的调用,而导致CPU消耗在内核层的资源量。比较常见的是内存分配和信号处理。

信号处理

比如下面指令会导致信号处理被频繁触发

代码语言:javascript
复制
stress --cpu-sys 8 -t 60
代码语言:javascript
复制
int hogcpu_sys(void)
{
    const int max = 1000000;
    while (1) 
    {
        for (int i = 0; i < max; i++) 
            raise (SIGINT);
        global_count++;
    }
    return 0;
}

网上有些人说,信号(signal)处理会体现在si(software interrupt)的提升,但是经过试验,我发现主要提升在sy上。

内存分配和释放

代码语言:javascript
复制
stress -m 8 -t 60

上面的指令会导致stress启动8个子进程,在子进程中频繁分配和释放内存。

代码语言:javascript
复制
int
hogvm (long long bytes, long long stride, long long hang, int keep)
{
    long long i;
    char *ptr = 0;
    char c;
    int do_malloc = 1;

    while (1)
    {
        if (do_malloc)
        {
            dbg (stdout, "allocating %lli bytes ...\n", bytes);
            if (!(ptr = (char *) malloc (bytes * sizeof (char))))
            {
                err (stderr, "hogvm malloc failed: %s\n", strerror (errno));
                return 1;
            }
            if (keep)
                do_malloc = 0;
        }

        dbg (stdout, "touching bytes in strides of %lli bytes ...\n", stride);
        for (i = 0; i < bytes; i += stride)
            ptr[i] = 'Z';           /* Ensure that COW happens.  */

        if (hang == 0)
        {
            dbg (stdout, "sleeping forever with allocated memory\n");
            while (1)
                sleep (1024);
        }
        else if (hang > 0)
        {
            dbg (stdout, "sleeping for %llis with allocated memory\n", hang);
            sleep (hang);
        }

        for (i = 0; i < bytes; i += stride)
        {
            c = ptr[i];
            if (c != 'Z')
            {
                err (stderr, "memory corruption at: %p\n", ptr + i);
                return 1;
            }
        }

        if (do_malloc)
        {
            free (ptr);
            dbg (stdout, "freed %lli bytes\n", bytes);
        }
    }

    return 0;
}

这段代码比较有意思是每隔stride字节,写入一个‘Z’。这是为了触发写时复制(COW)机制,同时可以保证GCC在编译时,不会将这种既申请又释放的逻辑优化成什么都不做。

一般情况下,sy比较高是不太正常的,因为它说明CPU陷入到内核层太多了。我们需要检查代码,或使用《动态执行流程分析和性能瓶颈分析的利器——gperftools的Cpu Profiler》或者《动态执行流程分析和性能瓶颈分析的利器——valgrind的callgrind》中介绍的工具进行分析。

ni

ni表示CPU time spent on low priority processes,即CPU耗费在低优先级程序上的资源量。

一般情况下,这个值接近0。

我们可以使用下面指令让该值上涨。

代码语言:javascript
复制
stress --cpu-nice 19 -c 8 -t 60

上面指令会导致stress创建出8个子进程,每个子进程最终会将自己的优先级调到19(最低)。

代码语言:javascript
复制
            case 0:            /* child */
                worker_init();
                if (do_cpu_nice) {
                    int ret = nice (do_cpu_nice);
                    if (ret == -1)
                    {
                        err (stderr, "nice failed: %s\n", strerror (errno));
                        return 1;
                    }
                }
                alarm (timeout);
                usleep (backoff);
                if (do_dryrun)
                    exit (0);
                exit (hogcpu ());

ni值很高说明系统中没有优先级高的程序在运行,于是CPU都被优先级低的程序给抢了。

试验

我们开两个终端,分别执行

代码语言:javascript
复制
stress --cpu-nice 19 -c 4 -t 60
代码语言:javascript
复制
stress -c 4 -t 60

前者启动了4个优先级最低的stress进程做sqrt计算;后者启动了4个正常优先级的stress也做sqrt计算。

可以看到CPU会被高优先级的stress抢走(19最低,-20最高,一般是0),于是ni的值也不高,但是us的值很高。

然后再看计算量,可以看到高优先级的stress一共完成了22469次计算,而低优先级的只完成了1190次计算。这也符合CPU的占用率分布情况。

代码语言:javascript
复制
fangliang@fangliang:~/stress$ stress --cpu-nice 19 -c 4 -t 60
stress: info: [84362] dispatching hogs: 4 cpu, 0 io, 0 vm, 0 hdd, 0 cpu_sys 
stress: info: [84366] stress: info: [84364] stress: info: [84365] stress: info: [84362] pid: 84366, count: 291
stress: info: [84362] pid: 84364, count: 290
stress: info: [84362] pid: 84365, count: 305
stress: info: [84363] stress: info: [84362] pid: 84363, count: 304
stress: info: [84362] successful run completed in 60s.total count is 1190
代码语言:javascript
复制
fangliang@fangliang:~/stress$ stress -c 4 -t 60
stress: info: [84329] dispatching hogs: 4 cpu, 0 io, 0 vm, 0 hdd, 0 cpu_sys 
stress: info: [84331] stress: info: [84330] stress: info: [84333] stress: info: [84332] stress: info: [84329] pid: 84330, count: 5641
stress: info: [84329] pid: 84331, count: 5598
stress: info: [84329] pid: 84332, count: 5624
stress: info: [84329] pid: 84333, count: 5606
stress: info: [84329] successful run completed in 60s.total count is 22469

wa

wa表示CPU time spent in wait (on disk),即CPU花在等待IO完成上的比例。

我们CPU处理速度和磁盘写入速度是不一致的,所以我们执行写入操作实际是CPU将内容从用户态交给内核态,内核态通知磁盘写入,然后等待其完成,于是就有wa的存在。

如果wa很高说明该系统上程序写入文件占比很高,没有充分利用CPU资源——因为等待占的时间太多了。

我们可以通过下面指令来测试

代码语言:javascript
复制
stress -d 8 -t 60

可以看到CPU主要处于wa状态,而此时磁盘已经满负荷运行了。

代码语言:javascript
复制
iostat -xdm 1

如果wa比较高,我们则需要优化文件IO操作来提升CPU利用率。因为处于该等待状态的CPU实际是可以被利用的。后面有机会我会开篇博文讲下这块如何优化。

si

si表示CPU time spent servicing/handling software interrupts,即处理软中断的时间。一种比较常见的软中断是物理内存不够时,系统申请虚拟内存。

我们可以通过下面指令进行测试

代码语言:javascript
复制
stress -m 2 --vm-bytes 1073741824 --vm-beginning 3221225472 -t 600

上面的指令–vm-beginning 3221225472会在主程序启动时,分配3G内存并占用不释放,然后-m 2 --vm-bytes 1073741824会启动2个子进程频繁申请释放1G内存。这样就会导致频繁的虚拟内存申请和释放,进而触发软中断,导致si的值上升。

如果si的值比较高,需要考虑诸如物理内存不够导致虚拟内存频繁申请和释放等问题。

hi和st

  • hi: hardware irq (or) % CPU time spent servicing/handling hardware interrupts
  • st: steal time - - % CPU time in involuntary wait by virtual cpu while hypervisor is servicing another processor (or) % CPU time stolen from a virtual machine

目前没有模拟出来这两种场景,现实中遇到的也比较少。

平均负载

下图中三个值是系统计算的CPU 1分钟、5分钟和15分钟的平均负载。

这三个值横向比较,只能说明CPU负载的一个趋势。比如上图:1分钟平均负载(0.07)< 5分钟平均负载(0.76)< 15分钟平均负载(1.36),说明系统负载在降低,即系统变得空闲些。反之则说明系统越来越忙碌。

单个看每个值,则需要结合系统的CPU数量。

代码语言:javascript
复制
cat /proc/cpuinfo | grep "processor" | wc -l

比如我的测试系统是4核心,则上面指令返回4。

要通过load average/cpu count来判断一段时间内CPU的状态。

我们先做些试验。

满载运行

此时我们用下面指令把4个核心压满,且运行20分钟。

代码语言:javascript
复制
stress -c 4 -t 1200

可以发现在4核满负载运行接近20分钟时,5分钟和15分钟load average的值都不到4。1分钟的load average大概是在程序运行6分钟时才稳定在4。通过观察可以发现,这些数值在后期变化会越来越慢。

超负载运行

下面指令我们启动16个进程压4个核。

代码语言:javascript
复制
stress -c 16 -t 1200

可以看到load average很快超过了启动4个程序时的值。1分钟、5分钟和15分钟的单个核心负载是14.81/4=3.7025,7.7/4=1.925,4.62/4=1.155。这些值大于1,说明CPU超负载了。

总结

load average横向对比

1、5、15分钟值基本相等,说明系统稳定运行;

哪个值和其他值对比出现偏离,说明产生了变化。

  • 1分钟偏离,说明系统在变忙碌(值大),或者变空闲(值小)。
  • 5分钟偏离,说明中期系统出现抖动。
  • 15分钟偏离,说明系统在变空闲(值大),或者变忙碌(值小)。

一般只要出现非平稳状态,就要定位原因,否则就是隐患。

load average和CPU核心数

load average/cpu count如果大于1,说明CPU超负载运行,即大家都要等待CPU空闲时才能被调度;反之不满载。根据经验,这个值控制在0.8附近比较合适。

参考资料

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-02-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 被测试工具和环境
  • 确定CPU利用率
  • us
  • sy
    • 信号处理
      • 内存分配和释放
      • ni
        • 试验
        • wa
        • si
        • hi和st
        • 平均负载
          • 满载运行
            • 超负载运行
              • 总结
                • load average横向对比
                • load average和CPU核心数
            • 参考资料
            相关产品与服务
            负载均衡
            负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档