Linux 的资源控制监测 - PSI [下]

语言: CN / TW / HK

Linux 的资源控制监测 - PSI [上]

如何开启

PSI 的功能是一个可选项,在内核配置中,设置 CONFIG_PSI=y ,则 PSI 相关的部分会被编译进内核。但这只是表明系统具备了 PSI 的能力,由于默认配置通常为 CONFIG_PSI_DEFAULT_DISABLED=y ,所以实际并没有开启这项能力。

#ifdef CONFIG_PSI_DEFAULT_DISABLED
static bool psi_enable;
#else
static bool psi_enable = true;
#endif

static int __init setup_psi(char *str)
{
    return kstrtobool(str, ψ_enable) == 0;
}
__setup("psi=", setup_psi);

根据以上的代码不难看出(实现在 "kernel/sched/psi.c"),PSI 的 enable 被做成了一个内核启动参数,如果借助 grubby 的话,可以这样使能:

grubby --args="psi=1" --update-kernel=/boot/vmlinuz-$(uname -r)

笔者在 5.4 内核上尝试使用 PSI 时,发现无需传入 "psi=1",也能在 procfs 中看到 PSI 的相关接口:

# ls /proc/pressure/
cpu  io  memory

但是对这些接口无论读写,都提示 "Operation not supported"。百思不得其解,网络搜索也无果的情况下,最终通过阅读源码,才发现原因是 5.4 版本的内核在 "psi_proc_init" 函数中没有进行 "psi_enable" 的判断,就直接创建了 "/proc" 下的这些文件,导致可以看到而不能使用。

这里 是笔者对该问题的回答,由此也可见开源的好处,虽然多花费些时间,但终究能在源码里找到答案。此问题已在 5.6 版本的 这个 commit 里修复了。

如何计量

还是以内存资源为例,为了模拟资源紧缺的情况,需要不断地分配内存来触发 throttlen,但这可能导致系统 hung 住,所以决定基于 cgroup 来进行实验。启动一个 stress-ng 的压测容器,内存限制为 1G:

# docker run --memory 1G -it --rm alexeiled/stress-ng --brk 2 --stack 2 --bigheap 2

然后读取容器对应 cgroup 路径中的 "memory.pressure" 文件:

"some" 说明(至少)有一部分任务因为等待内存资源而 stall 了(在 cgroup v2 中就是内存使用超过了 memory.high),而 "full" 则代表所有 non-idle 的任务都同时 stall 了。total 是总的 stall 时间,单位以「微秒」计。

图片来源见文末第一个链接

而 10, 60, 300 分别表示在过去的 10 秒(短期),60 秒(中期),300 秒(长期)内的统计情况(跟 load average 选用 1 分钟,5 分钟,15 分钟作为时间窗口类似)。如上图所示,在 60 秒内,Task B 进入 stall 的时间是 30 秒,两个 task 都 stall 的时间为 10 秒,所以 "some" 的结果是 50%,而 "full" 是 16.66%【注-1】。

接下来思考两个问题。 一是设计上为什么要做 some 和 full 的区分

试想一下,如果所有任务都因为 memory 或者 I/O 止步不前了,这时 CPU 的 cycle 是不是被白白地浪费了,而如果只有部分任务等待,还可以通过睡眠,将 CPU 资源让渡给那些不会因 memory 或者 I/O 等待的任务,还有 running task,意味着系统还算是 productive 的。

所以如果出现了 full,将影响到整体任务执行的 throughput,应该特别的关注。那怎么关注?比如你觉得 1 秒内出现 50 ms 的 full stall 就很严重了,可以通过填入 目标阈值 来进行 poll 监听:

echo 'full 50000 1000000' > /proc/pressure/io

对应的代码实现大致是:

static const struct file_operations psi_cpu_fops = {
	...
	.poll = psi_fop_poll,
};
psi_fop_poll --> psi_trigger_poll

第二个问题 :这种 sliding tracking window 是如何对一段 滑动区间 内的数值进行测量和归总的?

首先,需要就进入和退出 stall 状态进行判断,对于内存和 CPU,相关的函数如下:

memory CPU
psi_memstall_enter
psi_memstall_exit
enqueue_task --> psi_enqueue
dequeue_task --> psi_dequeue
try_to_wake_up --> psi_ttwu_dequeue

然后,需要就过往一段时间的 "some/full" 信息进行存储,比如要在 E 点计算过去 60 秒的 "some" 值,从图上看是一目了然的,就是 A 到 B,以及 C 到 D 之间的时间,如果是在 F 点计算过去 60 秒呢?大家可以想想,如果由你来设计,你会怎样实现。

注-1:

此处举的是内存的例子,内存不足时所有任务都被迫等待是正常的("full" 的标准不包括正在进行内存回收的 reclaimer)。在 cgroup 的层面,因quota 耗尽导致 cgroup 中的所有任务都等待也是合理的,但在整个系统层面,不可能出现所有任务都等待 CPU 的情况(一个 CPU 都不可用),因此 system level 的 CPU 只应有 "some",而不应有 "full"。

但是自 5.13 内核之后,你会发现代表全局的 "/proc/pressure/cpu" 也有 "full" 这一行(来自 这个 commit ), 并且里面的 total 值还不为 0,着实让人困惑,后经和 commit 作者团队的交流,得知此问题已经被 fix了,之后 "full" 所在行的内容将为全 0。

广告时间

喜欢花花的朋友,欢迎来我家自己开的小店—— 毛豆花园

每天关照植物,观察花朵、树叶与季节的消长与循环,让其成为生活里的一种意义。

参考

原创文章,转载请注明出处。