cpulimit设计理念及其思考

发布时间:2023年12月20日

背景

前几天,同事咨询了我一个问题:IO占用能和cpu使用率那样,有方法来控制吗?这个问题的背景是因为客户提了两个需求,如下:

说实话,针对这两点需求,我的第一反应是有一点思路,但是并没有具体的方案。比如可以通过sleep的方式减少对CPU、IO的占用。但是如何去设计呢?令我比较有兴趣。

经讨论,进程CPU使用率可以通过开源工具cpulimit进行控制,但是进程的IO占用目前并没有好的工具可以实现。好奇心爆棚的我,比较想了解cpulimit的实现原理,以及在这之上是否能够对进程IO占用有借鉴意义。

开源工具cpulimit体积很小,就2千行代码左右,两个小时基本就能看完。了解其大致原理后,感觉对实现控制进程IO占用的功能也有一定的参考作用。通过本篇,希望能够和大家分享一下我的思路。若由任何不妥的地方或问题,欢迎评论区讨论。

cpulimit 使用及设计分析

开源工具cpulimit可通过git下载,本地编译,验证。

下载:

git clone https://github.com/opsengine/cpulimit.git

编译:

yihua@ubuntu:~/cpulimit$ make
cd src && make all
make[1]: Entering directory '/home/yihua/cpulimit/src'
cc -c list.c -Wall -g -D_GNU_SOURCE
cc -c process_iterator.c -Wall -g -D_GNU_SOURCE
cc -c process_group.c -Wall -g -D_GNU_SOURCE
cc -o cpulimit cpulimit.c list.o process_iterator.o process_group.o -Wall -g -D_GNU_SOURCE
cpulimit.c:46:18: warning: extra tokens at end of #ifdef directive
 #ifdef __APPLE__ || __FREEBSD__
                  
make[1]: Leaving directory '/home/yihua/cpulimit/src'
cd tests && make all
make[1]: Entering directory '/home/yihua/cpulimit/tests'
cc -o busy busy.c -lpthread -Wall -g
cc -I../src -o process_iterator_test process_iterator_test.c ../src/list.o ../src/process_iterator.o ../src/process_group.o -lpthread -Wall -g
process_iterator_test.c:31:18: warning: extra tokens at end of #ifdef directive
 #ifdef __APPLE__ || __FREEBSD__

make[1]: Leaving directory '/home/yihua/cpulimit/tests'
yihua@ubuntu:~/cpulimit$

验证:

  1. 通过stress工具模拟CPU密集型场景。stress -c 4,4表示创建4个子进程,因为我的环境是4核,故创建4个进程。
  2. 通过mpstat查看系统的cpu使用率。mpstat -P ALL 1,显示更新频率为1s。
yihua@ubuntu:~/cpulimit$ mpstat -P ALL 1
Linux 4.15.0-213-generic (ubuntu)       12/20/2023      _x86_64_        (4 CPU)

12:57:49 AM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
12:57:50 AM  all   99.25    0.00    0.75    0.00    0.00    0.00    0.00    0.00    0.00    0.00
12:57:50 AM    0  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
12:57:50 AM    1   97.00    0.00    3.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
12:57:50 AM    2  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
12:57:50 AM    3  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00

12:57:50 AM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
12:57:51 AM  all   99.50    0.00    0.50    0.00    0.00    0.00    0.00    0.00    0.00    0.00
12:57:51 AM    0  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
12:57:51 AM    1   98.00    0.00    2.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
12:57:51 AM    2  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
12:57:51 AM    3  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00

12:57:51 AM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
12:57:52 AM  all   99.00    0.00    1.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
12:57:52 AM    0   99.00    0.00    1.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
12:57:52 AM    1   97.98    0.00    2.02    0.00    0.00    0.00    0.00    0.00    0.00    0.00
12:57:52 AM    2  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
12:57:52 AM    3   99.00    0.00    1.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00

由上可知,CPU占用率约100%。

  1. 通过cpulimit控制stress进程的cpu使用率。./src/cpulimit -p pid -l 50 -i
  2. 再观察系统的CPU使用率,如下:
yihua@ubuntu:~$ mpstat -P ALL 1
Linux 4.15.0-213-generic (ubuntu)       12/20/2023      _x86_64_        (4 CPU)

01:10:45 AM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
01:10:46 AM  all   13.40    0.00    1.99    0.00    0.00    0.00    0.00    0.00    0.00   84.62
01:10:46 AM    0   11.11    0.00    2.02    0.00    0.00    0.00    0.00    0.00    0.00   86.87
01:10:46 AM    1   15.84    0.00    3.96    0.00    0.00    0.00    0.00    0.00    0.00   80.20
01:10:46 AM    2   12.87    0.00    0.99    0.00    0.00    0.00    0.00    0.00    0.00   86.14
01:10:46 AM    3   13.13    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   86.87

01:10:46 AM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
01:10:47 AM  all   12.00    0.00    1.50    0.00    0.00    0.25    0.00    0.00    0.00   86.25
01:10:47 AM    0   10.00    0.00    2.00    0.00    0.00    0.00    0.00    0.00    0.00   88.00
01:10:47 AM    1   15.15    0.00    3.03    0.00    0.00    0.00    0.00    0.00    0.00   81.82
01:10:47 AM    2   12.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   88.00
01:10:47 AM    3   12.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   88.00

01:10:47 AM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
01:10:48 AM  all   12.78    0.00    0.75    0.00    0.00    0.00    0.00    0.00    0.00   86.47
01:10:48 AM    0   15.00    0.00    3.00    0.00    0.00    0.00    0.00    0.00    0.00   82.00
01:10:48 AM    1   12.87    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   87.13
01:10:48 AM    2   12.12    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   87.88
01:10:48 AM    3   11.88    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   88.12

由上可知,CPU0 + CPU1 + CPU2 + CPU3约等于50。

cpulimt设计流程分析

cpulimit真的很小,建议小伙伴们自己走读一遍,加强理解。cpulimit -p pid -l 50 -i其大致流程如下:

  1. 设定一个应用时间片TIME_SLOT=100ms。计算得到 T w o r k T_{work} Twork?时间和 T s l e e p T_{sleep} Tsleep?时间。即 T w o r k T_{work} Twork?=TIME_SLOT*limit,$T_{sleep}=TIME_SLOT*(1-limit)
  2. 通过pid,在proc文件系统中,找到进程及其子进程的信息目录。
  3. 通过获取/proc/pid/stat文件中的utimestime的时长,utime+stime得到该进程已消耗CPU的节拍数。
  4. 将进程及其子进程的节拍数相加,得到该进程消耗的总的节拍数。即为实际运行时长 T r e l w o r k T_{rel_work} Trelw?ork?
    1. T r e l w o r k T_{rel_work} Trelw?ork? > T w o r k T_{work} Twork?,说明超过了限定值。需要释放CPU使用权。cpulimit则会想所有的进程发送信号SIGSTOP,并自身nanosleep TIME_SLOT - T r e l w o r k T_{rel_work} Trelw?ork?时长。
    2. T r e l w o r k T_{rel_work} Trelw?ork? < T w o r k T_{work} Twork?,说明为达到限定值,可以继续占用CPU使用权。cpulimit则会想所有的进程发送信号SIGCONT,并自身nanosleep T w o r k T_{work} Twork? - T r e l w o r k T_{rel_work} Trelw?ork? 时长。
  5. 重复1~3步骤。

伪代码大致流程如下:

while(true)
{
    //1. 更新进程组的资源消耗
    update_process_group(&pgroup);
    ...
    for (node = pgroup.proclist->first; node != NULL; node = node->next) {
        struct process *proc = (struct process*)(node->data);
        if (proc->cpu_usage < 0) {
            continue;
        }
        if (pcpu < 0) pcpu = 0;
        pcpu += proc->cpu_usage;
    }
    ...
    //2. 计算sleeptime、worktime
    workingrate = MIN(workingrate / pcpu * limit, 1);
    tsleep.tv_nsec = TIME_SLOT * 1000 - twork.tv_nsec;

    //3. 唤醒进程组
    while (node != NULL)
    {
        struct list_node *next_node = node->next;
        struct process *proc = (struct process*)(node->data);
        kill(proc->pid,SIGCONT);
        node = next_node;
    }
    //3.1 让进程组运行twork时长
    gettimeofday(&startwork, NULL);
    nanosleep(&twork, NULL);
    gettimeofday(&endwork, NULL);


    //3.2 让进程组sleep twork时长
    if (tsleep.tv_nsec>0) {
    node = pgroup.proclist->first;
    while (node != NULL)
    {
        struct list_node *next_node = node->next;
        struct process *proc = (struct process*)(node->data);
        kill(proc->pid,SIGSTOP);
        node = next_node;
    }
    nanosleep(&tsleep,NULL);
}

思考

进程IO占用是否能够采用上述的方式:通过监控进程发送SIGSTOPSIGCONT控制目标进程的运行和休眠呢?

其实略加思考,就知道是不可行的。我们可以思考一下这个问题:客户提出IO占用可控的目的是什么?

答:无非就是防止该进程一直占用IO资源,其它业务进程获取不到资源,影响正常业务执行。如果采用cpulimit的方案,那么就会出现一种情况:若目标进程正在持有该IO资源,即使操作系统通过SIGSTOP信号,将目标进程挂起,那么其它进程也无法获取该IO资源,并不能达到客户的预期

脑洞大开(仅供参考)

通过cpulimit的设计思路,以及我们程序的应用场景,于是乎我想到了一种解决方案,似乎也能达到IO占用控制的效果。如有任何不对的地方,还请不吝指教。

我们的程序IO主要就是read/write 分区。我可以封装自己的read/write接口。其逻辑大致如下:

  1. 设定一个应用时间片TIME_SLOT=100ms。计算得到 T w o r k T_{work} Twork?时间和 T s l e e p T_{sleep} Tsleep?时间。即 T w o r k T_{work} Twork?=TIME_SLOT*limit T s l e e p T_{sleep} Tsleep?=TIME_SLOT*(1-limit)
  2. 通过proc文件系统,获取进程当前已经消耗的cpu时间 T i o 1 T_{io1} Tio1?。并记录当前时间系统时间 T 总 1 T_{总1} T1?
  3. 执行read/write系统调用。
  4. 通过proc文件系统,获取进程当前已经消耗的cpu时间 T i o 2 T_{io2} Tio2?。并记录当前时间系统时间 T 总 2 T_{总2} T2?
    1. T i o 2 T_{io2} Tio2? - T i o 1 T_{io1} Tio1? < T w o r k T_{work} Twork? T 总 2 T_{总2} T2?- T 总 1 T_{总1} T1? < T w o r k T_{work} Twork? ,说明还未达到限定值,可以继续执行IO。
    2. T i o 2 T_{io2} Tio2? - T i o 1 T_{io1} Tio1? > T w o r k T_{work} Twork?,说明单位时间内,已达到占用比,执行下一步骤休眠。
    3. T 总 2 T_{总2} T2?- T 总 1 T_{总1} T1? > T w o r k T_{work} Twork?,说明单位时间内,IO被其它进程抢占,未达到限定值。回到步骤2。
  5. nanosleep T w o r k T_{work} Twork? - ( T 总 2 T_{总2} T2?- T 总 1 T_{总1} T1?) 。

以上便是我暂时的设想,当然真正实现时,需要考虑一些细节。比如步骤3和步骤4.1,若不加处理,大量的时间会消耗在proc文件系统读取上。

总结

以上便是的初步设想,后续有时间会进行代码验证,有兴趣的朋友可以关注哈。
若我的内容对您有所帮助,还请关注我的公众号。不定期分享干活,剖析案例,也可以一起讨论分享。
我的宗旨:
踩完您工作中的所有坑并分享给您,让你的工作无bug,人生尽是坦途。
在这里插入图片描述

文章来源:https://blog.csdn.net/xieyihua1994/article/details/135118740
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。