Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,112 @@
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/accounting/delay-accounting.rst
:Translator: Yang Yang <yang.yang29@zte.com.cn>
========
延时计数
========
任务在等待某些内核资源可用时,会造成延时。例如一个可运行的任务可能会等待
一个空闲CPU来运行。
基于每任务的延时计数功能度量由以下情况造成的任务延时:
a) 等待一个CPU(任务为可运行)
b) 完成由该任务发起的块I/O同步请求
c) 页面交换
d) 内存回收
e) 抖动
f) 直接规整
g) 写保护复制
并将这些统计信息通过taskstats接口提供给用户空间。
这些延时信息为适当的调整任务CPU优先级、io优先级、rss限制提供反馈。重要任务
长期延时,表示可能需要提高其相关优先级。
通过使用taskstats接口,本功能还可提供一个线程组(对应传统Unix进程)所有任务
(或线程)的总延时统计信息。此类汇总往往是需要的,由内核来完成更加高效。
用户空间的实体,特别是资源管理程序,可将延时统计信息汇总到任意组中。为实现
这一点,任务的延时统计信息在其生命周期内和退出时皆可获取,从而确保可进行
连续、完整的监控。
接口
----
延时计数使用taskstats接口,该接口由本目录另一个单独的文档详细描述。Taskstats
向用户态返回一个通用数据结构,对应每pid或每tgid的统计信息。延时计数功能填写
该数据结构的特定字段。见
include/uapi/linux/taskstats.h
其描述了延时计数相关字段。系统通常以计数器形式返回 CPU、同步块 I/O、交换、内存
回收、页缓存抖动、直接规整、写保护复制等的累积延时。
取任务某计数器两个连续读数的差值,将得到任务在该时间间隔内等待对应资源的总延时。
当任务退出时,内核会将包含每任务的统计信息发送给用户空间,而无需额外的命令。
若其为线程组最后一个退出的任务,内核还会发送每tgid的统计信息。更多详细信息见
taskstats接口的描述。
tools/accounting目录中的用户空间程序getdelays.c提供了一些简单的命令,用以显示
延时统计信息。其也是使用taskstats接口的示例。
用法
----
使用以下配置编译内核::
CONFIG_TASK_DELAY_ACCT=y
CONFIG_TASKSTATS=y
延时计数在启动时默认关闭。
若需开启,在启动参数中增加::
delayacct
本文后续的说明基于延时计数已开启。也可在系统运行时,使用sysctl的
kernel.task_delayacct进行开关。注意,只有在启用延时计数后启动的
任务才会有相关信息。
系统启动后,使用类似getdelays.c的工具获取任务或线程组(tgid)的延时信息。
getdelays命令的一般格式::
getdelays [-dilv] [-t tgid] [-p pid]
获取pid为10的任务从系统启动后的延时信息::
# ./getdelays -d -p 10
(输出信息和下例相似)
获取所有tgid为5的任务从系统启动后的总延时信息::
# ./getdelays -d -t 5
print delayacct stats ON
TGID 5
CPU count real total virtual total delay total delay average
8 7000000 6872122 3382277 0.423ms
IO count delay total delay average
0 0 0.000ms
SWAP count delay total delay average
0 0 0.000ms
RECLAIM count delay total delay average
0 0 0.000ms
THRASHING count delay total delay average
0 0 0.000ms
COMPACT count delay total delay average
0 0 0.000ms
WPCOPY count delay total delay average
0 0 0ms
获取pid为1的IO计数,它只和-p一起使用::
# ./getdelays -i -p 1
printing IO accounting
linuxrc: read=65536, write=0, cancelled_write=0
上面的命令与-v一起使用,可以获取更多调试信息。
@@ -0,0 +1,25 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/accounting/index.rst
:Translator: Yang Yang <yang.yang29@zte.com.cn>
.. _cn_accounting_index.rst:
====
计数
====
.. toctree::
:maxdepth: 1
delay-accounting
psi
taskstats
Todolist:
cgroupstats
taskstats-struct
@@ -0,0 +1,155 @@
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/accounting/psi.rst
:Translator: Yang Yang <yang.yang29@zte.com.cn>
.. _cn_psi.rst:
=================
PSI——压力阻塞信息
=================
:日期: April, 2018
:作者: Johannes Weiner <hannes@cmpxchg.org>
当CPU、memory或IO设备处于竞争状态,业务负载会遭受时延毛刺、吞吐量降低,
及面临OOM的风险。
如果没有一种准确的方法度量系统竞争程度,则有两种后果:一种是用户过于节制,
未充分利用系统资源;另一种是过度使用,经常性面临业务中断的风险。
psi特性能够识别和量化资源竞争导致的业务中断,及其对复杂负载乃至整个系统在
时间上的影响。
准确度量因资源不足造成的生产力损失,有助于用户基于硬件调整业务负载,或基
于业务负载配置硬件。
psi能够实时的提供相关信息,因此系统可基于psi实现动态的负载管理。如实施
卸载、迁移、策略性的停止或杀死低优先级或可重启的批处理任务。
psi帮助用户实现硬件资源利用率的最大化。同时无需牺牲业务负载健康度,也无需
面临OOM等造成业务中断的风险。
压力接口
========
压力信息可通过/proc/pressure/ --cpu、memory、io文件分别获取。
CPU相关信息格式如下:
some avg10=0.00 avg60=0.00 avg300=0.00 total=0
内存和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行代表所有非idle任务同时阻塞于特定资源的时间占比。在这种状态下CPU资源
完全被浪费,相对于正常运行,业务负载由于耗费更多时间等待而受到严重影响。
由于此情况严重影响系统性能,因此清楚的识别本情况并与some行所代表的情况区分开,
将有助于分析及提升系统性能。这就是full独立于some行的原因。
avg代表阻塞时间占比(百分比),为最近10秒、60秒、300秒内的均值。这样我们
既可观察到短期事件的影响,也可看到中等及长时间内的趋势。total代表总阻塞
时间(单位微秒),可用于观察时延毛刺,这种毛刺可能在均值中无法体现。
监控压力门限
============
用户可注册触发器,通过poll()监控资源压力是否超过门限。
触发器定义:指定时间窗口期内累积阻塞时间的最大值。比如可定义500ms内积累
100ms阻塞,即触发一次唤醒事件。
触发器注册方法:用户打开代表特定资源的psi接口文件,写入门限、时间窗口的值。
所打开的文件描述符用于等待事件,可使用select()、poll()、epoll()。
写入信息的格式如下:
<some|full> <stall amount in us> <time window in us>
示例:向/proc/pressure/memory写入"some 150000 1000000"将新增触发器,将在
1秒内至少一个任务阻塞于内存的总时间超过150ms时触发。向/proc/pressure/io写入
"full 50000 1000000"将新增触发器,将在1秒内所有任务都阻塞于io的总时间超过50ms时触发。
触发器可针对多个psi度量值设置,同一个psi度量值可设置多个触发器。每个触发器需要
单独的文件描述符用于轮询,以区分于其他触发器。所以即使对于同一个psi接口文件,
每个触发器也需要单独的调用open()。
监控器在被监控资源进入阻塞状态时启动,在系统退出阻塞状态后停用。系统进入阻塞
状态后,监控psi增长的频率为每监控窗口刷新10次。
内核接受的窗口为500ms~10s,所以监控间隔为50ms~1s。设置窗口下限目的是为了
防止过于频繁的轮询。设置窗口上限的目的是因为窗口过长则无意义,此时查看
psi接口提供的均值即可。
监控器在激活后,至少在跟踪窗口期间将保持活动状态。以避免随着系统进入和退出
阻塞状态,监控器过于频繁的进入和退出活动状态。
用户态通知在监控窗口内会受到速率限制。当对应的文件描述符关闭,触发器会自动注销。
用户态监控器使用示例
====================
::
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <string.h>
#include <unistd.h>
/* 监控内存部分阻塞,监控时间窗口为1秒、阻塞门限为150毫秒。*/
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_CGROUP=y及挂载了cgroup2文件系统的系统,能够获取cgroups内任务的psi。
此场景下cgroupfs挂载点的子目录包含cpu.pressure、memory.pressure、io.pressure文件,
内容格式与/proc/pressure/下的文件相同。
可设置基于cgroup的psi监控器,方法与系统级psi监控器相同。
@@ -0,0 +1,145 @@
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/accounting/taskstats.rst
:Translator: Yang Yang <yang.yang29@zte.com.cn>
================
每任务的统计接口
================
Taskstats是一个基于netlink的接口,用于从内核向用户空间发送每任务及每进程的
统计信息。
Taskstats设计目的:
- 在任务生命周期内和退出时高效的提供统计信息
- 统一不同计数子系统的接口
- 支持未来计数系统的扩展
术语
----
“pid”、“tid”、“任务”互换使用,用于描述由struct task_struct定义的标准
Linux任务。“每pid的统计数据”等价于“每任务的统计数据”。
“tgid”、“进程”、“线程组”互换使用,用于描述共享mm_struct的任务集,
也就是传统的Unix进程。尽管使用了tgid这个词,即使一个任务是线程组组长,
对它的处理也没有什么不同。只要一个进程还有任何归属它的任务,它就被认为
活着。
用法
----
为了在任务生命周期内获得统计信息,用户空间需打开一个单播的netlink套接字
(NETLINK_GENERIC族)然后发送指定pid或tgid的命令。响应消息中包含单个
任务的统计信息(若指定了pid)或进程所有任务汇总的统计信息(若指定了tgid)。
为了在任务退出时获取统计信息,用户空间的监听者发送一个指定cpu掩码的注册命令。
cpu掩码内的cpu上有任务退出时,每pid的统计信息将发送给注册成功的监听者。使用
cpu掩码可以限制一个监听者收到的数据,并有助于对netlink接口进行流量控制,后文
将进行更详细的解释。
如果正在退出的任务是线程组中最后一个退出的线程,额外一条包含每tgid统计信息的
记录也将发送给用户空间。后者包含线程组中所有线程(包括过去和现在)的每pid统计
信息总和。
getdelays.c是一个简单的示例,用以演示如何使用taskstats接口获取延迟统计信息。
用户可注册cpu掩码、发送命令和处理响应、监听每tid/tgid退出数据、将收到的数据
写入文件、通过增大接收缓冲区进行基本的流量控制。
接口
----
内核用户接口封装在include/linux/taskstats.h。
为避免本文档随着接口的演进而过期,本文仅给出当前版本的概要。当本文与taskstats.h
不一致时,以taskstats.h为准。
struct taskstats是每pid和每tgid数据共用的计数结构体。它是版本化的,可在内核新增
计数子系统时进行扩展。taskstats.h中定义了各字段及语义。
用户、内核空间的数据交换是属于NETLINK_GENERIC族的netlink消息,使用netlink属性
接口。消息格式如下::
+----------+- - -+-------------+-------------------+
| nlmsghdr | Pad | genlmsghdr | taskstats payload |
+----------+- - -+-------------+-------------------+
Taskstats载荷有三种类型:
1. 命令:由用户发送给内核。获取指定pid/tgid数据的命令包含一个类型为
TASKSTATS_CMD_ATTR_PID/TGID的属性,该属性包含u32的pid或tgid载荷。
pid/tgid指示用户空间要统计的任务/进程。
注册/注销获取指定cpu集上退出数据的命令包含一个类型为
TASKSTATS_CMD_ATTR_REGISTER/DEREGISTER_CPUMASK的属性,该属性包含cpu掩码载荷。
cpu掩码是以ascii码表示,用逗号分隔的cpu范围。例如若需监听1,2,3,5,7,8号cpu的
退出数据,cpu掩码表示为"1-3,5,7-8"。若用户空间在关闭监听套接字前忘了注销监听
的cpu集,随着时间的推移,内核会清理此监听集。但是,出于提效的目的,建议明确
执行注销。
2. 命令的应答:内核发出应答用户空间的命令。载荷有三类属性:
a) TASKSTATS_TYPE_AGGR_PID/TGID: 本属性不包含载荷,用以指示其后为被统计对象
的pig/tgid。
b) TASKSTATS_TYPE_PID/TGID:本属性的载荷为pig/tgid,其统计信息将被返回。
c) TASKSTATS_TYPE_STATS:本属性的载荷为一个struct taskstats实例。每pid和
每tgid统计信息共用该结构体。
3. 内核会在任务退出时发送新消息。其载荷包含一系列以下类型的属性:
a) TASKSTATS_TYPE_AGGR_PID:指示其后两个属性为pid+stats。
b) TASKSTATS_TYPE_PID:包含退出任务的pid。
c) TASKSTATS_TYPE_STATS:包含退出任务的每pid统计信息
d) TASKSTATS_TYPE_AGGR_TGID:指示其后两个属性为tgid+stats。
e) TASKSTATS_TYPE_TGID:包含任务所属进程的tgid
f) TASKSTATS_TYPE_STATS:包含退出任务所属进程的每tgid统计信息
每tgid的统计
------------
除了每任务的统计信息,taskstats还提供每进程的统计信息,因为资源管理通常以进程
粒度完成,并且仅在用户空间聚合任务统计信息效率低下且可能不准确(缺乏原子性)。
然而,除了每任务统计信息,在内核中维护每进程统计信息存在额外的时间和空间开销。
为解决此问题,taskstats代码将退出任务的统计信息累积到进程范围的数据结构中。
当进程最后一个任务退出时,累积的进程级数据也会发送到用户空间(与每任务数据一起)。
当用户查询每tgid数据时,内核将指定线程组中所有活动线程的统计信息相加,并添加到
该线程组的累积总数(含之前退出的线程)。
扩展taskstats
-------------
有两种方法可在未来修改内核扩展taskstats接口,以导出更多的每任务/进程统计信息:
1. 在现有struct taskstats末尾增加字段。该结构体中的版本号确保了向后兼容性。
用户空间将仅使用与其版本对应的结构体字段。
2. 定义单独的统计结构体并使用netlink属性接口返回对应的数据。由于用户空间独立
处理每个netlink属性,所以总是可以忽略其不理解类型的属性(因为使用了旧版本接口)。
在1.和2.之间进行选择,属于权衡灵活性和开销的问题。若仅需增加少数字段,那么1.是
首选方法,因为内核和用户空间无需承担处理新netlink属性的开销。但若新字段过多的
扩展现有结构体,导致不同的用户空间计数程序不必要的接收大型结构体,而对结构体
字段并不感兴趣,那么2.是值得的。
Taskstats的流量控制
-------------------
当退出任务数速率变大,监听者可能跟不上内核发送每tid/tgid退出数据的速率,而导致
数据丢失。taskstats结构体变大、cpu数量上升,都会导致这种可能性增加。
为避免统计信息丢失,用户空间应执行以下操作中至少一项:
- 增大监听者用于接收退出数据的netlink套接字接收缓存区。
- 创建更多的监听者,减少每个监听者监听的cpu数量。极端情况下可为每个cpu创建
一个监听者。用户还可考虑将监听者的cpu亲和性设置为监听cpu的子集,特别是当他们
仅监听一个cpu。
尽管采取了这些措施,若用户空间仍收到指示接收缓存区溢出的ENOBUFS错误消息,
则应采取其他措施处理数据丢失。