使用funcgraph-retval和bpftrace/kprobe快速定位并解决cpu控制器无法使能的问题

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 使用funcgraph-retval和bpftrace/kprobe快速定位并解决cpu控制器无法使能的问题

版本

Linux 6.5

背景

在学习cgroupv2的时候,想给子cgroup开启cpu控制器结果失败了:

# 查看可以开启哪些控制器
root@ubuntu-vm:/sys/fs/cgroup# cat cgroup.controllers
cpuset cpu io memory hugetlb pids rdma misc
# 上面看到,是支持cpu控制器的,通过下面命令查看目前子cgroup开启了哪些控制器
root@ubuntu-vm:/sys/fs/cgroup# cat cgroup.subtree_control
memory pids
# 通过下面的命令给子cgroup开启cpu控制器
root@ubuntu-vm:/sys/fs/cgroup# echo +cpu > cgroup.subtree_control
-bash: echo: write error: Invalid argument

在给子cgroup开启cpu控制器时提示参数无效,即-EINVAL,错误码是-22.

定位

之前给linux内核的function graph增加了显示函数返回值的功能,正好可以派上用场。

  • 使用下面的命令配置ftrace
echo 0 > /sys/kernel/debug/tracing/tracing_on
echo 14080 > /sys/kernel/debug/tracing/buffer_size_kb
echo ksys_write > /sys/kernel/debug/tracing/set_graph_function
echo $$ > /sys/kernel/debug/tracing/set_ftrace_pid
echo 1 > /sys/kernel/debug/tracing/options/funcgraph-retval
echo 1 > /sys/kernel/debug/tracing/options/funcgraph-retval-trim
echo function_graph > /sys/kernel/debug/tracing/current_tracer

目前社区版本还不支持funcgraph-retval-trim,这个是为了对返回值进行裁剪

然后使用下面的方法抓取log:

> /sys/kernel/debug/tracing/trace;echo 1 > /sys/kernel/debug/tracing/tracing_on; echo +cpu > cgroup.subtree_control;echo 0 > /sys/kernel/debug/tracing/tracing_on

收集到trace日志后,从上往下搜索-22错误码,看到下面的内容:

4)               |                cgroup_migrate_execute() {
4)               |                  cpu_cgroup_can_attach() {
4)               |                    cgroup_taskset_first() {
4)   0.190 us    |                      cgroup_taskset_next(); /* = 0xffff8881003b0000 */
4)   0.551 us    |                    } /* cgroup_taskset_first = 0xffff8881003b0000 */
4)   0.170 us    |                    sched_rt_can_attach(); /* = 0x1 */
4)   0.180 us    |                    cgroup_taskset_next(); /* = 0xffff888100994e00 */
4)   0.171 us    |                    sched_rt_can_attach(); /* = 0x1 */
4)   0.180 us    |                    cgroup_taskset_next(); /* = 0xffff88810bed4e00 */
4)   0.170 us    |                    sched_rt_can_attach(); /* = 0x1 */
4)   0.191 us    |                    cgroup_taskset_next(); /* = 0xffff8881083d1a00 */
4)   0.170 us    |                    sched_rt_can_attach(); /* = 0x1 */
4)   0.170 us    |                    cgroup_taskset_next(); /* = 0xffff888108e20000 */
4)   0.181 us    |                    sched_rt_can_attach(); /* = 0x0 */
4)   4.248 us    |                  } /* cpu_cgroup_can_attach = -22 */

可以看到,cpu_cgroup_can_attach先返回了-22错误码,具体分析源码:

#ifdef CONFIG_RT_GROUP_SCHED
static int cpu_cgroup_can_attach(struct cgroup_taskset *tset)
{
  struct task_struct *task;
  struct cgroup_subsys_state *css;
  cgroup_taskset_for_each(task, css, tset) {
    if (!sched_rt_can_attach(css_tg(css), task))
      return -EINVAL;
  }
  return 0;
}
#endif

结合日志和源码,是由于sched_rt_can_attach返回了0,才会返回-EINVAL。

继续查看sched_rt_can_attach:

int sched_rt_can_attach(struct task_group *tg, struct task_struct *tsk)
{
  /* Don't accept realtime tasks when there is no way for them to run */
  if (rt_task(tsk) && tg->rt_bandwidth.rt_runtime == 0)
    return 0;
  return 1;
}

返回0的条件:进程是实时进程,但是目的task group没有给实时任务设置时间份额。

内核文档中有下面的描述:

WARNING: cgroup2 doesn't yet support control of realtime processes and the cpu controller can only be enabled when all RT processes are in the root cgroup. Be aware that system management software may already have placed RT processes into nonroot cgroups during the system boot process, and these processes may need to be moved to the root cgroup before the cpu controller can be enabled.

上面的意思是说,在开启CPU控制器之前,需要首先将实时任务移动到根cgroup下。

那这里是哪个实时进程导致的呢?sched_rt_can_attach函数的第二个参数就是task_struct地址,可以借助bpftrace查看这个对应的哪个进程:

# cat trace.bt
#!/usr/bin/env bpftrace
kprobe:sched_rt_can_attach
{
printf("task: %lx, comm: %s\n", arg1, ((struct task_struct *)arg1)->comm);
}

运行上面的脚本,然后再次执行开启CPU控制器的操作,可以看到下面的日志:

root@ubuntu-vm:~# ./trace.bt
Attaching 1 probe...
task: ffff8881003b0000, comm: systemd
task: ffff888107e38000, comm: agetty
task: ffff888107f3ce00, comm: agetty
task: ffff888107e39a00, comm: systemd-journal
task: ffff88810862b400, comm: multipathd

可以看到,最后一个进程是multipathd,这个进程是否为实时进程呢?

# ps -eo pid,tid,class,rtprio,ni,pri,psr,pcpu,stat,wchan:14,comm | grep -E 'PID|multipathd'
    PID     TID CLS RTPRIO  NI PRI PSR %CPU STAT WCHAN          COMMAND
    153     153 RR      99   - 139   6  0.0 SLsl futex_wait_que multipathd

可以看到确实是实时进程。

下面手动将这个进程加到根cgroup下:

root@ubuntu-vm:/sys/fs/cgroup# cat /proc/153/cgroup
0::/system.slice/multipathd.service
root@ubuntu-vm:/sys/fs/cgroup# echo 153 > cgroup.procs
root@ubuntu-vm:/sys/fs/cgroup# cat /proc/153/cgroup
0::/

然后再次开启CPU控制器:

root@ubuntu-vm:/sys/fs/cgroup# echo +cpu > cgroup.subtree_control
root@ubuntu-vm:/sys/fs/cgroup# cat cgroup.subtree_control
cpu memory pids

到这里,这个问题就解决了。

如果bpftrace不能用的话,可以使用kprobe_event,下面是comm在task_struct中的偏移:

(gdb) p &((struct task_struct *)0)->comm
$1 = (char (*)[16]) 0x840

或者:

crash> *task_struct.comm -ox
struct task_struct {
   [0x840] char comm[16];
}

用下面的命令添加kprobe_event,同时对ftrace进一步配置:

echo 'p sched_rt_can_attach $arg* +0x840($arg2):string' > dynamic_events
echo kprobe_ftrace_handler > /sys/kernel/debug/tracing/set_graph_notrace
echo 1 > events/kprobes/p_sched_rt_can_attach_0/enable

上面$arg*的用法是新版本的内核才有的,借助BTF来获取函数的入参,比之前方便多了,可以用来输出函数的全部入参

这个方法跟funcgraph-retval结合起来,既实现了输出内核函数的入参,同时也输出了内核函数的返回值

再次按照之前的方法复现一次,可以抓到下面的log:

2)               |                cgroup_migrate_execute() {
2)               |                  cpu_cgroup_can_attach() {
2)               |                    cgroup_taskset_first() {
2)   0.190 us    |                      cgroup_taskset_next(); /* = 0xffff8881003b0000 */
2)   0.581 us    |                    } /* cgroup_taskset_first = 0xffff8881003b0000 */
2)               |                    sched_rt_can_attach() {
2)               |                      /* p_sched_rt_can_attach_0: (sched_rt_can_attach+0x4/0x30) tg=0xffff88810a1b1c00 tsk=0xffff8881003b0000 arg3="systemd" */
2)   4.529 us    |                    } /* sched_rt_can_attach = 0x1 */
2)   0.291 us    |                    cgroup_taskset_next(); /* = 0xffff888107e38000 */
2)               |                    sched_rt_can_attach() {
2)               |                      /* p_sched_rt_can_attach_0: (sched_rt_can_attach+0x4/0x30) tg=0xffff88810a1b1880 tsk=0xffff888107e38000 arg3="agetty" */
2)   1.603 us    |                    } /* sched_rt_can_attach = 0x1 */
2)   0.251 us    |                    cgroup_taskset_next(); /* = 0xffff888107f3ce00 */
2)               |                    sched_rt_can_attach() {
2)               |                      /* p_sched_rt_can_attach_0: (sched_rt_can_attach+0x4/0x30) tg=0xffff88810a1b1880 tsk=0xffff888107f3ce00 arg3="agetty" */
2)   1.413 us    |                    } /* sched_rt_can_attach = 0x1 */
2)   0.241 us    |                    cgroup_taskset_next(); /* = 0xffff888107e39a00 */
2)               |                    sched_rt_can_attach() {
2)               |                      /* p_sched_rt_can_attach_0: (sched_rt_can_attach+0x4/0x30) tg=0xffff88810a1b1880 tsk=0xffff888107e39a00 arg3="systemd-journal" */
2)   2.324 us    |                    } /* sched_rt_can_attach = 0x1 */
2)   0.250 us    |                    cgroup_taskset_next(); /* = 0xffff88810862b400 */
2)               |                    sched_rt_can_attach() {
2)               |                      /* p_sched_rt_can_attach_0: (sched_rt_can_attach+0x4/0x30) tg=0xffff88810a1b1880 tsk=0xffff88810862b400 arg3="multipathd" */
2)   2.014 us    |                    } /* sched_rt_can_attach = 0x0 */
2) + 15.820 us   |                  } /* cpu_cgroup_can_attach = -22 */

kprobe_event的好处是,可以跟function_graph的日志一块结合起来看,也比较方便。上面的日志显示调用sched_rt_can_attach时,当进程是multipathd时,返回了0,进而导致cpu_cgroup_can_attach返回了-22.

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
7月前
CPU过高问题定位
CPU过高问题定位
66 0
|
7月前
|
存储 缓存 并行计算
CPU组成元素:运算器+控制器(一)
CPU组成元素:运算器+控制器
1487 0
|
7月前
|
设计模式 监控 安全
如何定位当生产环境CPU飙升的时候的问题
在当今的信息化时代,计算机系统在各行各业都发挥着重要的作用。然而,当生产环境中的CPU飙升时,系统性能会受到影响,甚至导致整个系统瘫痪。这不仅会对企业造成经济损失,还会对用户体验造成严重影响。因此,如何定位并解决生产环境中CPU飙升的问题,已成为众多企业和开发人员亟待解决的问题之一。本文旨在探讨如何定位生产环境中CPU飙升的问题,并提供相应的解决方案。通过了解CPU飙升的原因、定位方法以及解决方案,企业和开发人员可以更好地应对生产环境中出现的CPU飙升问题,提高系统性能和用户体验。
147 1
|
7月前
|
存储 数据处理 vr&ar
②CPU - 运算器、控制器 【软考-软件设计师考点】
②CPU - 运算器、控制器 【软考-软件设计师考点】
248 0
|
2月前
线程CPU异常定位分析
【10月更文挑战第3天】 开发过程中会出现一些CPU异常升高的问题,想要定位到具体的位置就需要一系列的分析,记录一些分析手段。
78 0
|
2月前
|
Java
Java面试题之cpu占用率100%,进行定位和解决
这篇文章介绍了如何定位和解决Java服务中CPU占用率过高的问题,包括使用top命令找到高CPU占用的进程和线程,以及使用jstack工具获取堆栈信息来确定问题代码位置的步骤。
140 0
Java面试题之cpu占用率100%,进行定位和解决
|
4月前
|
小程序 JavaScript Java
【Java】服务CPU占用率100%,教你用jstack排查定位
本文详细讲解如何使用jstack排查定位CPU高占用问题。首先介绍jstack的基本概念:它是诊断Java应用程序线程问题的工具,能生成线程堆栈快照,帮助找出程序中的瓶颈。接着,文章通过具体步骤演示如何使用`top`命令找到高CPU占用的Java进程及线程,再结合`jstack`命令获取堆栈信息并进行分析,最终定位问题代码。
404 1
【Java】服务CPU占用率100%,教你用jstack排查定位
|
7月前
|
存储 缓存 调度
CPU组成元素:运算器+控制器(二)
CPU组成元素:运算器+控制器
145 0
|
5月前
|
Java
Jstack 查看线程状态及定位占用 cpu 较高的 java 线程
Jstack 查看线程状态及定位占用 cpu 较高的 java 线程
690 2
|
5月前
|
监控 Java 中间件
FGC频繁导致CPU 飙升定位及JVM配置优化总结
FGC频繁导致CPU 飙升定位及JVM配置优化总结
193 0