纯干货,PSI 原理解析与应用

2024-03-31 07:18
文章标签 应用 原理 解析 干货 psi

本文主要是介绍纯干货,PSI 原理解析与应用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

https://blog.csdn.net/feelabclihu/article/details/105534140

 

一、什么是 PSI

Pressure Stall Information 提供了一种评估系统资源压力的方法。系统有三个基础资源:CPU、Memory 和 IO,无论这些资源配置如何增加,似乎永远无法满足软件的需求。一旦产生资源竞争,就有可能带来延迟增大,使用户体验到卡顿。

如果没有一种相对准确的方法检测系统的资源压力程度,有两种后果。一种是资源使用者过度克制,没有充分使用系统资源;另一种是经常产生资源竞争,过度使用资源导致等待延迟过大。准确的检测方法可以帮忙资源使用者确定合适的工作量,同时也可以帮助系统制定高效的资源调度策略,最大化利用系统资源,最大化改善用户体验。

Facebook 在 2018 年开源了一套解决重要计算集群管理问题的 Linux 内核组件和相关工具,PSI 是其中重要的资源度量工具,它提供了一种实时检测系统资源竞争程度的方法,以竞争等待时间的方式呈现,简单而准确地供用户以及资源调度者进行决策。

二、为什么出现 PSI

在此之前,Linux 也有一些资源压力的评估方法,最具代表性的是 load average 和 vmpressure。

1、Load Average

系统平均负载是指在特定时间间隔内运行队列中(在 CPU 上运行或者等待运行)的平均进程数。Linux 进程中 running 和 uninterruptible 状态进程数量加起来的占比就是当前系统 load。其具体算法为:

for_each_possible_cpu(cpu)

nr_active += cpu_of(cpu)->nr_running + cpu_of(cpu)->nr_uninterruptible;

avenrun[n] = avenrun[0] * exp_n + nr_active * (1 - exp_n)

Linux 命令 uptime、top 等都可以获得 load average 的输出,例如:

$ uptime

16:08 up 13 days, 5:06, 1 user, load averages: 0.15 0.41 0.26

Load averages 的三个值分别代表最近 1/5/15 分钟的平均系统负载。在多核系统中,这些值有可能经常大于1,比如四核系统的 100% 负载为 4,八核系统的 100% 负载为 8。

Loadavg 有它固有的一些缺陷:

  • uninterruptible的进程,无法区分它是在等待 CPU 还是 IO。无法精确评估单个资源的竞争程度;

  • 最短的时间粒度是 1 分钟,以 5 秒间隔采样。很难精细化管理资源竞争毛刺和短期过度使用;

  • 结果以进程数量呈现,还要结合 cpu 数量运算,很难直观判断当前系统资源是否紧张,是否影响任务吞吐量。

2、Vmpressure

Vmpressure 的计算在每次系统尝试做do_try_to_free_pages 回收内存时进行。其计算方法非常简单:

(1 - reclaimed/scanned)*100,也就是说回收失败的内存页越多,内存压力越大。

同时 vmpressure 提供了通知机制,用户态或内核态程序都可以注册事件通知,应对不同等级的压力。

默认定义了三级压力:low/medium/critical。low 代表正常回收;medium 代表中等压力,可能存在页交换或回写,默认值是 65%;critical 代表内存压力很大,即将 OOM,建议应用即可采取行动,默认值是 90%。

vmpressure 也有一些缺陷:

  • 结果仅体现内存回收压力,不能反映系统在申请内存上的资源等待时间;

  • 计算周期比较粗;

  • 粗略的几个等级通知,无法精细化管理。

三、PSI 软件架构

1、蓝图 

PSI 相关的软件结构图如下所示:

对上,PSI 模块通过文件系统节点向用户空间开放两种形态的接口。一种是系统级别的接口,即输出整个系统级别的资源压力信息。另外一种是结合 control group,进行更精细化的分组。

对下,PSI 模块通过在内存管理模块以及调度器模块中插桩,我们可以跟踪每一个任务由于 memory、io 以及 CPU 资源而进入等待状态的信息。例如系统中处于 iowait 状态的 task 数目、由于等待 memory 资源而处于阻塞状态的任务数目。

基于 task 维度的信息,PSI 模块会将其汇聚成 PSI group 上的 per cpu 维度的时间信息。例如该cpu上部分任务由于等待 IO 操作而阻塞的时间长度(CPU 并没有浪费,还有其他任务在执行)。PSI group 还会设定一个固定的周期去计算该采样周期内核的当前 psi 值(基于该 group 的 per cpu 时间统计信息)。

为了避免 PSI 值的抖动,实际上上层应用通过系统调用获取某个 PSI group 的压力值的时候会上报近期一段时间值的滑动平均值。

2、PSI 用户接口定义

每类资源的压力信息都通过 proc 文件系统的独立文件来提供,路径为 /proc/pressure/ -- cpu, memory, and io.

其中 CPU 压力信息格式如下:

some avg10=2.98 avg60=2.81 avg300=1.41 total=268109926

memory 和 io 格式如下:

some avg10=0.30 avg60=0.12 avg300=0.02 total=4170757

full avg10=0.12 avg60=0.05 avg300=0.01 total=1856503

avg10、avg60、avg300 分别代表 10s、60s、300s 的时间周期内的阻塞时间百分比。total 是总累计时间,以毫秒为单位。

some 这一行,代表至少有一个任务在某个资源上阻塞的时间占比,full 这一行,代表所有的非idle任务同时被阻塞的时间占比,这期间 cpu 被完全浪费,会带来严重的性能问题。我们以 IO 的 some 和 full 来举例说明,假设在 60 秒的时间段内,系统有两个 task,在 60 秒的周期内的运行情况如下图所示:

红色阴影部分表示任务由于等待 IO 资源而进入阻塞状态。Task A 和 Task B 同时阻塞的部分为 full,占比 16.66%;至少有一个任务阻塞(仅 Task B 阻塞的部分也计算入内)的部分为 some,占比 50%。

some 和 full 都是在某一时间段内阻塞时间占比的总和,阻塞时间不一定连续,如下图所示:

IO 和 memory 都有 some 和 full 两个维度,那是因为的确有可能系统中的所有任务都阻塞在 IO 或者 memory 资源,同时 CPU 进入 idle 状态。

但是 CPU 资源不可能出现这个情况:不可能全部的 runnable 的任务都等待 CPU 资源,至少有一个 runnable 任务会被调度器选中占有 CPU 资源,因此 CPU 资源没有 full 维度的 PSI 信息呈现。

通过这些阻塞占比数据,我们可以看到短期以及中长期一段时间内各种资源的压力情况,可以较精确的确定时延抖动原因,并制定对应的负载管理策略。

四、源码解析

PSI 相关源代码比较简单,核心功能都在  kernel/sched/psi.c 文件中实现。

1、初始化

第一步,在 psi_proc_init 函数中完成 PSI 接口文件节点的创建。首先建立proc/pressure目录,然后 3 个 proc_create 函数创建了 io、memory 和 cpu 三个 proc 属性文件:

proc_mkdir("pressure", NULL);

proc_create("pressure/io", 0, NULL, &psi_io_fops);

proc_create("pressure/memory", 0, NULL, &psi_memory_fops);

proc_create("pressure/cpu", 0, NULL, &psi_cpu_fops);

第二步,在 psi_init 函数中初始化统计管理结构和更新任务的周期:

psi_period = jiffies_to_nsecs(PSI_FREQ);

group_init(&psi_system);

我们把相关的任务组成一个 group,然后针对这个任务组计算其 PSI 值。如果不支持 control group,那么实际上系统中只有一个 PSI group:

static DEFINE_PER_CPU(struct psi_group_cpu, system_group_pcpu);

static struct psi_group psi_system = {

.pcpu = &system_group_pcpu,

};

如果支持 cgroup(需要 mount cgroup2 文件系统),那么系统中会有多个 PSI group,形成层级结构。我们可以在挂载的 cgroup 文件系统下面获取 per-group 的 PSI 信息。

我们也可以从 proc 文件系统下面获取整个系统级别的 PSI 信息。Cgroup 中各个分组的 PSI 信息跟踪是类似的,后续我们的文章主要基于系统级别的 PSI 信息跟踪来描述代码逻辑流程。

struct psi_group 用来定义 PSI 统计管理数据,其中包括各 cpu 状态、周期性更新函数、更新时间戳、以及各 PSI 状态的时间记录。PSI 状态一共有六种:

enum psi_states {

PSI_IO_SOME,

PSI_IO_FULL,

PSI_MEM_SOME,

PSI_MEM_FULL,

PSI_CPU_SOME,

/* Only per-CPU, to weigh the CPU in the global average: */

PSI_NONIDLE,

NR_PSI_STATES,

};

前 5 种状态的定义在本文上一节已经介绍,PSI_NONIDLE 是指 CPU 非空闲状态,最终的时间占比是以 CPU 非空闲时间来计算的。

2、状态埋点

整个 PSI 技术的核心难点其实在于如何准确捕捉到任务状态的变化,并统计状态持续时间。我们首先看看 task 维度的埋点信息。

PSI 作者在 task_struct 结构中加入了一个成员:PSI_flags,用于标注任务所处状态,状态定义有以下几种:

#define TSK_IOWAIT(1 << NR_IOWAIT)

#define TSK_MEMSTALL(1 << NR_MEMSTALL)

#define TSK_RUNNING(1 << NR_RUNNING)

状态的标记主要通过函数 psi_task_change,这个函数在任务每次进出调度队列时,都会被调用,从而准确标注任务状态。

其中 psi_memstall_tick 并没有任务状态的转换,只是在每个调度 tick 及时更新各状态的积累时间。

3、周期性统计

周期性的更新任务 psi_update_work 函数非常简单,更新统计数据,然后设定下一次任务唤醒的时间。周期间隔为 PSI_FREQ,2s。

更新统计数据的函数 update_stats,主要有两步:

第一步 get_recent_times,对每个 cpu 更新各状态的时间并统计各状态系统总时间;

第二步 calc_avgs,更新每个状态的 10s、60s、300s 三个间隔的时间占比。

计算一个 PSI group 的 PS I值的过程示意图如下所示:

从底层看,一个 psi group 的 PSI 值是基于任务数目统计的,当一个任务状态发生变化的时候,首先需要遍历该任务所属的 PSI group(如果不支持 cgroup,那么系统只有一个全局的 PSI group),更新 PSI group 的 task counter。

一旦 task counter 发生了变化,那么我们需要进一步更新对应 CPU 上的时间统计信息。例如 iowait task count 从 0 变成 1,那么 SOME 维度的 io wait time 需要更新。具体的 per-CPU PSI 状态时间统计信息如下:

完成了上面 6 种状态的时间统计之后,在系统的每个 cpu 上就建立了 6 条 time line,而上层的 PSI group 会以固定周期来采样 time line 的数组。采样点之间相减就可以得到该周期内各种状态的时间长度值。通过下面的公式我们可以计算单个 CPU 上的 PSI 值:

%SOME = time(SOME) / period

%FULL = time(FULL) / period

在多 CPU 场景下,我们要综合考虑 CPU 个数和 non idle task 的个数,计算公式如下:

tNONIDLE = sum(tNONIDLE[i])

tSOME = sum(tSOME[i] * tNONIDLE[i]) / tNONIDLE

tFULL = sum(tFULL[i] * tNONIDLE[i]) / tNONIDLE

%SOME = tSOME / period

%FULL = tFULL / period

tNONIDLE[i]、tSOME[i] 和 tFULL[i] 已经在 per-CPU 状态统计中获取了,通过上面的公式即可以计算该 psi group 在当前周期内的 PSI 值。

在计算三种间隔的时间占比时,有人可能会有疑问,周期是 2s,如何做到每次都更新三种数据呢?这个问题其实在上面讲到的老技术 load average 计算时已经解决,采用公式:a1 = a0 * e + a * (1 - e);

于是得到:newload = load * exp + active * (FIXED_1 - exp)

其中 active 是当前更新周期的 load average,load 是上个周期得到的 load average,exp 的定义如下:

#define EXP_10s 1677 /* 1/exp(2s/10s) as fixed-point */

#define EXP_60s 1981 /* 1/exp(2s/60s) */

#define EXP_300s   2034 /* 1/exp(2s/300s) */

五、PSI 的应用

有了 PSI 对系统资源压力的准确评估,可以做很多有意义的功能来最大化系统资源的利用。比如 facebook 开发的 cgroup2 和 oomd。oomd 是一个用户态的 out of  memory 监控管理服务。

Android 早期在 kernel 新增了一个功能叫 lmk(low memory killer),在有了 PSI 之后,android 将默认的 LMK 替换成了用户态的 LMKD。其代码存放于 android/system/core/lmkd/。

其核心思想是给 /proc/pressure/memory 的 SOME 和 FULL 设定阈值,当延时超过阈值时,触发 lmkd daemon 进程选择进程杀死。同时,还可以结合 meminfo 的剩余内存大小来判断需要清理的程度和所选进程的优先级。

参考文献:

[1]Getting Started with PSI, https://facebookmicrosites.github.io/psi/docs/overview#pressure-metric-definitions

[2]psi: pressure stall information for CPU, memory, and IO v2, https://lwn.net/Articles/759658/

这篇关于纯干货,PSI 原理解析与应用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/863735

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

hdu1394(线段树点更新的应用)

题意:求一个序列经过一定的操作得到的序列的最小逆序数 这题会用到逆序数的一个性质,在0到n-1这些数字组成的乱序排列,将第一个数字A移到最后一位,得到的逆序数为res-a+(n-a-1) 知道上面的知识点后,可以用暴力来解 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#in

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

zoj3820(树的直径的应用)

题意:在一颗树上找两个点,使得所有点到选择与其更近的一个点的距离的最大值最小。 思路:如果是选择一个点的话,那么点就是直径的中点。现在考虑两个点的情况,先求树的直径,再把直径最中间的边去掉,再求剩下的两个子树中直径的中点。 代码如下: #include <stdio.h>#include <string.h>#include <algorithm>#include <map>#

hdu4407(容斥原理)

题意:给一串数字1,2,......n,两个操作:1、修改第k个数字,2、查询区间[l,r]中与n互质的数之和。 解题思路:咱一看,像线段树,但是如果用线段树做,那么每个区间一定要记录所有的素因子,这样会超内存。然后我就做不来了。后来看了题解,原来是用容斥原理来做的。还记得这道题目吗?求区间[1,r]中与p互质的数的个数,如果不会的话就先去做那题吧。现在这题是求区间[l,r]中与n互质的数的和

【区块链 + 人才服务】可信教育区块链治理系统 | FISCO BCOS应用案例

伴随着区块链技术的不断完善,其在教育信息化中的应用也在持续发展。利用区块链数据共识、不可篡改的特性, 将与教育相关的数据要素在区块链上进行存证确权,在确保数据可信的前提下,促进教育的公平、透明、开放,为教育教学质量提升赋能,实现教育数据的安全共享、高等教育体系的智慧治理。 可信教育区块链治理系统的顶层治理架构由教育部、高校、企业、学生等多方角色共同参与建设、维护,支撑教育资源共享、教学质量评估、