英语

PSI - 压力停顿信息

日期:

2018年4月

作者:

Johannes Weiner <hannes@cmpxchg.org>

当CPU、内存或IO设备发生争用时,工作负载会遇到延迟峰值、吞吐量损失,并面临OOM被杀的风险。

如果没有对这种争用的准确衡量,用户将被迫要么安全起见而低估其硬件资源,要么掷骰子并经常遭受过度提交所造成的破坏。

psi功能识别并量化了由此类资源短缺造成的破坏,以及它对复杂工作负载甚至整个系统的时间影响。

拥有对资源稀缺造成的生产力损失的准确衡量有助于用户调整工作负载以适应硬件 - 或根据工作负载需求配置硬件。

由于psi实时聚合这些信息,因此可以使用负载削减、将作业迁移到其他系统或数据中心,或有策略地暂停或终止低优先级或可重新启动的批处理作业等技术来动态管理系统。

这允许最大限度地利用硬件,而不会牺牲工作负载的健康或冒着OOM被杀等重大中断的风险。

压力接口

每个资源的压力信息通过/proc/pressure/中的相应文件导出——cpu、内存和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 以来已有报告,因此为了向后兼容,它被设置为零。

比率(以百分比表示)被跟踪为最近趋势,超过十秒、六十秒和三百秒的窗口,这提供了对短期事件以及中期和长期趋势的洞察力。 绝对停顿总时间(以微秒为单位)也被跟踪和导出,以允许检测在时间平均值中不一定会留下痕迹的延迟峰值,或平均自定义时间范围内的趋势。

监控压力阈值

用户可以注册触发器并使用poll(),以便在资源压力超过特定阈值时被唤醒。

触发器描述了特定时间窗口内的最大累积停顿时间,例如在任何500ms窗口内总共100ms的停顿时间来生成唤醒事件。

要注册触发器,用户必须打开/proc/pressure/下代表要监视的资源的psi接口文件,并写入所需的阈值和时间窗口。 应使用打开的文件描述符来等待使用select()、poll()或epoll()的触发器事件。 使用以下格式

<some|full> <stall amount in us> <time window in us>

例如,将“some 150000 1000000”写入/proc/pressure/memory将为在1秒时间窗口内测量的部分内存停顿添加150ms的阈值。 将“full 50000 1000000”写入/proc/pressure/io将为在1秒时间窗口内测量的完全io停顿添加50ms的阈值。

可以为多个psi指标设置触发器,并且可以为同一psi指标指定多个触发器。 但是,对于每个触发器,都需要一个单独的文件描述符才能将其与其他触发器分开轮询,因此,即使打开同一个psi接口文件,对于每个触发器都应进行单独的open() syscall。 对已存在psi触发器的文件描述符的写入操作将失败,并显示EBUSY。

监视器仅在系统进入受监视的psi指标的停顿状态时激活,并在退出停顿状态后停用。 当系统处于停顿状态时,以每个跟踪窗口10次的比率监视psi信号增长。

内核接受500ms到10s的窗口大小,因此最小监控更新间隔为50ms,最大为1s。 设置最小限制是为了防止过于频繁的轮询。 选择最大限制是一个足够高的数字,在此之后最有可能不需要监视器,而可以使用psi平均值代替。

非特权用户也可以创建监视器,唯一的限制是窗口大小必须是2s的倍数,以防止过度使用资源。

激活后,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监视器。