内存访问监控

1. 概述

通过对进程内存访问数据采样,计算进程对remote内存访问产生的带宽流量来间接推断跨numa访问的严重程度,从而实现带宽感知调度决策、RTD间调度优化,并向带宽分配功能提供反馈,从而实现对内存带宽的控制

2. 硬件方案

2.1 cpu支持

  • Intel RDT
    • https://cloud-atlas.readthedocs.io/zh-cn/latest/kernel/cpu/intel/intel_rdt/intel_rdt_arch.html
    • https://www.intel.cn/content/www/cn/zh/content-details/851356/intel-resource-director-technology-intel-rdt-architecture-specification.html
    • https://eci.intel.com/docs/3.0/development/intel-pqos.html
  • AMD PQos(与RDT对等的)
  • 海光类似AMD,我在机器上看也是有相关cpu feature的
  • 鲲鹏 PMU,应该也是支持这个内存访问采样的,他们有提供对应的用户态库。但是对跨numa内存访问的功能似乎不能指定进程进行才采样
  • arm MPAM(Memmory Partitioning and Monitoring)

2.2 内核resctrl支持

2.2.1 概述

概述: resctrl问津啊系统通过sysfs暴露给用户态,能够在用户态使用这些接口文件。此功能需要开启CONFIG_X86_CPU_RESCTRL编译配置

cpuinfo flag:

特性 flag
RDT(Resource Director Technology) Allocation rdt_a
CAT(Cache Allocation Technology) cat_l3、cat_l2
CDP(Code and Data Prioritization) cdp_l3、cdp_l2
CQM(Cache QoS Monitoring) cqm_llc、cqm_occup_llc
MBM(Memory Bandwidth Monitoring)【采样内存访问所需的特性】 cqm_mbm_total、cqm_mbm_local
MBA(Memory Bandwidth Allocation) Mba
SMBA(Slow Memory Bandwidth Allocation)
BMEC(Bandwidth Monitoring Event Configuration)
ABMC(Assignable Bandwidth Monitoring Counters)

每个接口文件的详细介绍看linux doc: https://docs.kernel.org/filesystems/resctrl.html

arm MAPM较新,内核支持的较晚,相关链接:

  • 将x86/resctrl抽象为公共接口以便arm MPAM接入:https://lwn.net/Articles/1021264/
  • arm mpam引入:https://lwn.net/Articles/1042576/
  • arm MAPM介绍:https://developer.arm.com/documentation/108032/0100/A-closer-look-at-MPAM-software/Linux-MPAM-overview

2.2.2 使用

1. 使用此功能需要挂载该文件系统:

1
mount -t resctrl resctrl /sys/fs/resctrl

2. 在mon_group下创建监控组:

1
2
cd /sys/fs/resctrl/mon_groups
mkdir test-mon

3. 设置要监控的cpu和进程:

1
2
echo 0 > cpus_list
echo <PID> > tasks

4. 查看内存访问数据量:

1
2
cat mon_data/mon_L3_00/mbm_local_bytes # 本地
cat mon_data/mon_L3_00/mbm_total_bytes # 总共

如果cpu不属于这个L3 cache domain,读取这两个接口文件会返回Unavailable

想要知道cpu0跟哪些cpu共享L3 cache,可以执行cat /sys/devices/system/cpu/cpu0/cache/index3/shared_cpu_list,看其他cpu类似

5. 删除子group:

1
rmdir test-mon

2.3 用户态工具

2.3.1 pqos

2.3.1.1 编译安装

仓库代码: https://eci.intel.com/docs/3.0/development/intel-pqos.html

概述: 该工具可以不需要内核支持,直接使用CAT、CMT、MBM、CDP功能,直接访问msr寄存器。支持Intel RDT和AMD PQoS,我觉得海光cpu应该也是可以使用这个工具

1. 添加动态库链接目录:

1
2
echo "/usr/local/lib" >> /etc/ld.so.conf
ldconfig

2. 修改lib/pqos.h的enum pqos_vendor,添加PQOS_VENDOR_HYGON

1
2
3
4
5
6
enum pqos_vendor {
PQOS_VENDOR_UNKNOWN = 0, /**< UNKNOWN */
PQOS_VENDOR_INTEL = 1, /**< INTEL */
PQOS_VENDOR_AMD = 2, /**< AMD */
PQOS_VENDOR_HYGON = 3 /**< HYGON */
};

3. 修改lib/cpuinfo.c的detect_vendor(),添加PQOS_VENDOR_HYGON的检测:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static enum pqos_vendor
detect_vendor(void)
{
enum pqos_vendor cpu_vendor;
struct cpuid_out vendor;

lcpuid(0x0, 0x0, &vendor);
if (vendor.ebx == 0x756e6547 && vendor.edx == 0x49656e69 &&
vendor.ecx == 0x6c65746e) {
cpu_vendor = PQOS_VENDOR_INTEL;
} else if (vendor.ebx == 0x68747541 && vendor.edx == 0x69746E65 &&
vendor.ecx == 0x444D4163) {
cpu_vendor = PQOS_VENDOR_AMD;
} else if (vendor.ebx == 0x6f677948 && vendor.edx == 0x6e65476e &&
vendor.ecx == 0x656e6975) {
cpu_vendor = PQOS_VENDOR_HYGON;
} else {
cpu_vendor = PQOS_VENDOR_UNKNOWN;
}
return cpu_vendor;
}

4. 修改lib/cpuinfo.c的init_config(),添加对PQOS_VENDOR_HYGON的处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
static int
init_config(struct cpuinfo_config *config, enum pqos_vendor vendor)
{
memset(config, 0, sizeof(struct cpuinfo_config));

/**
* Make sure to initialize all the pointers to avoid
* NULL check while calling
*/
if (vendor == PQOS_VENDOR_INTEL) {
config->cpuid_cache_leaf = 4;
config->mba_max = PQOS_MBA_LINEAR_MAX;
config->mba_msr_reg = PQOS_MSR_MBA_MASK_START;
config->mba_default_val = 0;
} else if (vendor == PQOS_VENDOR_AMD) {
config->cpuid_cache_leaf = 0x8000001D;
config->mba_max = PQOS_MBA_MAX_AMD;
config->mba_msr_reg = PQOS_MSR_MBA_MASK_START_AMD;
config->mba_default_val = PQOS_MBA_MAX_AMD;
config->smba_msr_reg = PQOS_MSR_SMBA_MASK_START_AMD;
} else if (vendor == PQOS_VENDOR_HYGON) {
config->cpuid_cache_leaf = 0x8000001D;
config->mba_max = PQOS_MBA_MAX_AMD;
config->mba_msr_reg = PQOS_MSR_MBA_MASK_START_AMD;
config->mba_default_val = PQOS_MBA_MAX_AMD;
config->smba_msr_reg = PQOS_MSR_SMBA_MASK_START_AMD;
} else {
LOG_ERROR("init_config: init failed!");
return -EFAULT;
}

return 0;
}

5. 编译安装:

要求gcc 9及以上

1
2
make
make install

2.3.1.2 使用

MBM的使用方法: MBL表示本地内存带宽,MBR表示远程内存带宽

  1. 监控指定核心的内存带宽使用情况:

    1
    pqos -m 'mbl:cores;mbr:cores
  2. 示例:

    1
    pqos -m 'mbl:1,3-4;mbr:1,3-4'
  3. 输出示例如下:

    1
    2
    3
    4
    5
    TIME 2019-05-07 08:01:16  
    CORE IPC MISSES MBL\[MB/s\] MBR\[MB/s\]
    1 1.17 124254k 15161.8 0.1
    3 1.54 38010k 4595.0 0.1
    4 1.41 135584k 8210.6 0.2
  4. 监控每个进程/任务的内存带宽使用情况:

    1
    pqos -I -p 'mbl:pids;mbr:pids'
  5. 示例:

    1
    pqos -I -p 'mbl:65627;mbr:65627'

在海光cpu上的测试: 需要修改intel-cmt-cat源码使其支持海光cpu的vendor

  1. 执行测试程序:

    1
    taskset -c 4 memtester 1000M
  2. 监控指定核心:

    1
    2
    3
    pqos -I -p 'mbl:77614;mbr:77614'

    pqos -I -p 'mbl:69943;mbr:69943'
  3. 结果:

2.3.1.3 MBM监控结果说明

  • 通过写入IA32_QM_EVTSEL MSR设置RMID和Event ID,硬件就会查看特定数据,并通过IA32_QM_CTR MSR返回结果。

  • MBM监控的是L3 cache到主内存的内存请求带宽

  • MBM计数器记录的值是累积值,cpu代理和非cpu代理发出的每个内存请求都会被应将标记上相应的RMID tag,硬件会根据这些RMID收集资源监控要遥测数据

  • pqos工具输出的结果是每秒读msr的值,然后跟上一秒读的值作差值算出带宽(pqos工具如果使用的是resctrl接口,会通过创建子group,如果是核心模式,指定多少个核心就创建多少个子group分别监控指定核心;如果是进程模式,就只创建一个,然后会累积所有L3 cache domain统计的内存访问带宽)

  • 如果不想使用pqos工具,可以直接使用linux提供的resctrl接口。跟cgroup差不多,详见2.2 内核resctrl支持

2.3.2 鲲鹏libkperf库

2.3.3.1 PMU检查

应该是只要支持鲲鹏的PMU特性就可以使用该库,检查是否支持PMU:

1
perf stat -a sleep 10

输入如下说明支持:

如果如下,一些性能指标没有采集到说明不支持:

2.3.3.2 使用

仓库代码: https://gitee.com/openeuler/libkperf.git

采集每个numa的跨nuam访问HHA的操作比例示例代码: 似乎是不能以进程为粒度的,而是以整个numa节点为粒度的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// c++代码示例
#include <iostream>
#include "symbol.h"
#include "pmu.h"

PmuDeviceAttr devAttr[2];
// 采集跨numa访问HHA的操作比例
devAttr[0].metric = PMU_HHA_CROSS_NUMA;
// 采集跨socket访问HHA的操作比例
devAttr[1].metric = PMU_HHA_CROSS_SOCKET;
// 初始化采集任务
int pd = PmuDeviceOpen(devAttr, 2);
// 开始采集
PmuEnable(pd);
sleep(1);
PmuDisable(pd);
PmuData *oriData = nullptr;
int oriLen = PmuRead(pd, &oriData);
PmuDeviceData *devData = nullptr;
auto len = PmuGetDevMetric(oriData, oriLen, devAttr, 2, &devData);
// devData的长度等于设备numa的个数
for (int i = 0; i < len / 2; ++i) {
std::cout << "HHA cross-numa operations ratio (Numa: " << devData[i].numaId << "): " << devData[i].count<< "\n";
}
for (int i = len / 2; i < len; ++i) {
std::cout << "HHA cross-socket operations ratio (Numa: " << devData[i].numaId << "): " << devData[i].count<< "\n";
}
DevDataFree(devData);
PmuDataFree(oriData);
PmuClose(pd);

详见:https://gitee.com/openeuler/libkperf/blob/master/docs/Details_Usage.md#%E9%87%87%E9%9B%86%E8%B7%A8numa%E8%B7%A8socket%E8%AE%BF%E9%97%AEhha%E6%AF%94%E4%BE%8B

标题是《采集跨numa/跨socket访问HHA比例》

2.3.3 perf mem

需要硬件支持:

  • intel TPEBS
  • AMD IBS Op
  • Arm64 SPE

3. 软件方案

目前没有调研到纯软件来实现内存访问监控比较好的方法。似乎内核只能通过page fault来感知到内存访问,所以监控page fault是一个方法

  1. 使用内核提供的tracepoint事件追踪page fault
  2. 编写bpf程序,根据虚拟地址address转换为pfn,从pfn中获取struct page,然后获取page所属的nuam id,维护计数器统计该访问是本地访问还是远程访问(统计结果通过ebpf map传到用户态)
  3. 将page更新到epbf map中,然后用户态程序定义一个时间窗口,定时将这些page通过mprotect() syscall修改页面保护为PROT_NONE,则后面进程再次访问该page会触发page fault

内存访问监控
http://example.com/2025/12/04/内存访问监控/
作者
凌云行者
发布于
2025年12月4日
许可协议