PSI - 压力停滞信息¶
- 日期:
2018 年 4 月
- 作者:
Johannes Weiner <hannes@cmpxchg.org>
当 CPU、内存或 IO 设备发生争用时,工作负载会遇到延迟峰值、吞吐量损失,并面临被 OOM 杀死的风险。
如果没有对这种争用的准确测量,用户要么被迫采取保守策略并低估其硬件资源的使用,要么就只能碰运气,频繁地遭受过度提交所造成的破坏。
psi 功能识别并量化了此类资源短缺造成的破坏,以及对复杂工作负载甚至整个系统的时间影响。
准确衡量资源稀缺造成的生产力损失有助于用户根据硬件调整工作负载的大小,或根据工作负载需求配置硬件。
由于 psi 实时聚合此信息,因此可以使用负载削减、将作业迁移到其他系统或数据中心,或者有策略地暂停或杀死低优先级或可重启的批处理作业等技术来动态管理系统。
这允许在不牺牲工作负载健康或冒着诸如 OOM 杀死等重大破坏的风险的情况下,最大限度地利用硬件。
压力接口¶
每个资源的压力信息通过 /proc/pressure/ 中各自的文件导出 - cpu、memory 和 io。
格式如下
some avg10=0.00 avg60=0.00 avg300=0.00 total=0
full avg10=0.00 avg60=0.00 avg300=0.00 total=0
“some” 行指示至少某些任务在给定资源上停滞的时间份额。
“full” 行指示所有非空闲任务同时在给定资源上停滞的时间份额。在这种状态下,实际的 CPU 周期将被浪费,并且在这种状态下花费大量时间的工作负载被认为是颠簸的。这对性能有严重的影响,因此将这种情况与某些任务停滞但 CPU 仍在执行有效工作的状态区分开来很有用。因此,在这种停滞状态子集中花费的时间会被单独跟踪,并在“full”平均值中导出。
CPU full 在系统级别未定义,但自 5.13 以来已报告,因此为了向后兼容,将其设置为零。
这些比率(以 % 表示)被跟踪为最近在 10 秒、60 秒和 300 秒窗口上的趋势,这提供了对短期事件以及中期和长期趋势的洞察。总的绝对停滞时间(以 us 为单位)也会被跟踪和导出,以便检测在时间平均值中不一定会产生影响的延迟峰值,或者在自定义时间范围内平均趋势。
监控压力阈值¶
用户可以注册触发器并使用 poll() 在资源压力超过特定阈值时被唤醒。
触发器描述了特定时间窗口内的最大累积停滞时间,例如在任何 500 毫秒窗口内,总停滞时间达到 100 毫秒以生成唤醒事件。
要注册触发器,用户必须打开 /proc/pressure/ 下表示要监控的资源的 psi 接口文件,并写入所需的阈值和时间窗口。打开的文件描述符应用于使用 select()、poll() 或 epoll() 等待触发事件。使用以下格式
<some|full> <stall amount in us> <time window in us>
例如,将“some 150000 1000000”写入 /proc/pressure/memory 将为在 1 秒时间窗口内测量的部分内存停滞添加 150 毫秒的阈值。将“full 50000 1000000”写入 /proc/pressure/io 将为在 1 秒时间窗口内测量的完全 io 停滞添加 50 毫秒的阈值。
可以为多个 psi 指标设置触发器,并且可以为同一个 psi 指标指定多个触发器。但是,对于每个触发器,都需要一个单独的文件描述符才能将其与其他触发器分开轮询,因此即使打开相同的 psi 接口文件,也应对每个触发器执行单独的 open() 系统调用。对具有已存在的 psi 触发器的文件描述符的写入操作将失败,并返回 EBUSY。
监视器仅在系统进入受监控的 psi 指标的停滞状态时激活,并在退出停滞状态时停用。当系统处于停滞状态时,以每个跟踪窗口 10 次的速率监控 psi 信号增长。
内核接受的窗口大小范围为 500 毫秒到 10 秒,因此最小监控更新间隔为 50 毫秒,最大为 1 秒。设置最小限制是为了防止过于频繁的轮询。选择最大限制是因为它是一个足够高的数字,超过该数字后,很可能不需要监视器,并且可以使用 psi 平均值。
非特权用户也可以创建监视器,唯一的限制是窗口大小必须是 2 秒的倍数,以防止过度使用资源。
激活后,psi 监视器至少保持活动一个跟踪窗口的持续时间,以避免在系统进出停滞状态时重复激活/停用。
发送到用户空间的通知被速率限制为每个跟踪窗口一个。
当用于定义触发器的文件描述符关闭时,触发器将取消注册。
用户空间监视器使用示例¶
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <string.h>
#include <unistd.h>
/*
* Monitor memory partial stall with 1s tracking window size
* and 150ms threshold.
*/
int main() {
const char trig[] = "some 150000 1000000";
struct pollfd fds;
int n;
fds.fd = open("/proc/pressure/memory", O_RDWR | O_NONBLOCK);
if (fds.fd < 0) {
printf("/proc/pressure/memory open error: %s\n",
strerror(errno));
return 1;
}
fds.events = POLLPRI;
if (write(fds.fd, trig, strlen(trig) + 1) < 0) {
printf("/proc/pressure/memory write error: %s\n",
strerror(errno));
return 1;
}
printf("waiting for events...\n");
while (1) {
n = poll(&fds, 1, -1);
if (n < 0) {
printf("poll error: %s\n", strerror(errno));
return 1;
}
if (fds.revents & POLLERR) {
printf("got POLLERR, event source is gone\n");
return 0;
}
if (fds.revents & POLLPRI) {
printf("event triggered!\n");
} else {
printf("unknown event received: 0x%x\n", fds.revents);
return 1;
}
}
return 0;
}
Cgroup2 接口¶
在具有 CONFIG_CGROUPS=y 内核和已挂载 cgroup2 文件系统的系统中,还会跟踪分组到 cgroup 中的任务的压力停滞信息。cgroupfs 挂载点中的每个子目录都包含 cpu.pressure、memory.pressure 和 io.pressure 文件;格式与 /proc/pressure/ 文件相同。
可以指定每个 cgroup 的 psi 监视器,并以与系统范围的监视器相同的方式使用。