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。
广告时间
喜欢花花的朋友,欢迎来我家自己开的小店—— 毛豆花园 。
每天关照植物,观察花朵、树叶与季节的消长与循环,让其成为生活里的一种意义。
参考
- Getting Started with PSI · PSI
- Linux Pressure Stall Information (PSI) by Example - Unixism
- http://www. kernel.org/doc/html/lat est/accounting/psi.html
原创文章,转载请注明出处。
- Cgroup 内存使用的监测手段
- Linux 的资源控制监测 - PSI [下]
- drgn - Linux 调试的另一只翅膀 [下]
- Linux - 如何确定内存泄露没有发生
- NUMA 的平衡和调度
- Linux - 如何查看函数的参数 [二]
- Linux - 再议内存回收之 swappiness
- Linux - 如何查看函数的参数 [一]
- Linux - 内核 benchmark 测试之 MMTests
- 多核系统的负载均衡 [二]
- sysctl 参数防篡改 - 基于 ebpf 的实现 [二]
- sysctl 参数防篡改 - 基于传统方法的实现
- 关于 eBPF 的一些粗浅理解
- Linux 的调度延迟 - 原理与观测
- 模型与抽象 - 从 fanotify 的控制说开来
- Linux中的RCU机制[二] - GP的处理
- 由 Timer Wheel 带来的思考
- Linux 调度 - 切换类型的划分
- Linux 中的等待队列机制
- Linux 中执行调度的时机