Post

【笔记】AMD IBS 使用笔记

实验需求使用 perf_event_open 调用 AMD IBS 实现对于线程访存的监控,因此开始了解一下 IBS。

AMD IBS(Instruction-Based Sampling,基于指令采样)是 AMD 处理器中一项硬件级性能分析技术,用于监控和记录处理器执行的指令流,为开发者和系统管理员提供细粒度的性能洞察。

IBS 文档:IBS (没什么有用的内容)

IBS 采样结构体内容详见:博客

调用方式

perf_event_open() + raw IBS

Linux 集成的 perf 子系统支持 IBS 事件,可以使用 perf_event_open() syscall 直接采集 ibs_op 类型事件,然后 mmap 一个 ring buffer 直接读取采样样本。

注意 mmap 的 buffer 大小必须是 $ 1 + 2^n $ 个 page,否则会 failed。以及记得检查当前内核是否支持 ibs。

首先需要初始化 perf_event_attr 的设置,对于 attr.configattr.sample_type 的设置还是存疑,没有找到详细描述的文档。

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
#include <linux/perf_event.h>
#include <asm/unistd.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

long page_size = sysconf(_SC_PAGESIZE);
int mmap_pages = 9;  // ring buffer 大小必须是 1+2^n 页
size_t mmap_size = page_size * mmap_pages;

int fd;
struct perf_event_attr attr;

void init_ibs_perf() {
    memset(&attr, 0, sizeof(struct perf_event_attr));
    attr.type = PERF_TYPE_RAW;  // 对于 IBS,用 RAW 类型
    attr.size = sizeof(struct perf_event_attr);
    attr.config =  (1ULL << 19) |  // Enable IBS Op sampling
                  (1ULL << 1) |   // Record Linear Address
                  (1ULL << 5) |   // Record Latency
                  (1ULL << 7);    // Record Cache Hit Info
    attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_RAW | PERF_SAMPLE_ADDR;
    attr.sample_period = 1000000;
    attr.exclude_kernel = 0;  // 监控内核空间
    attr.exclude_user = 0;    // 监控用户空间
    attr.disabled = 0;
    attr.read_format = 0;
}


然后发出 syscall 并 mmap 一块 ring buffer 以读取采样到的 raw data,同时还要启动计数器,否则无法读到 sample。

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
    fd = syscall(__NR_perf_event_open, &attr, 0, -1, -1, 0);
    if (fd < 0) {
        perror("perf_event_open failed");
        return -1;
    }

    // 先mmap,然后再启用计数器
    size_t mmap_size = sysconf(_SC_PAGESIZE) * (16 + 1);  // 更大的缓冲区
    void* buf = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (buf == MAP_FAILED) {
        perror("mmap failed");
        close(fd);
        return -1;
    }

    struct perf_event_mmap_page* meta_page = (struct perf_event_mmap_page*)buf;
    char* data_buffer = (char*)buf + meta_page->data_offset;
    size_t data_size = mmap_size - meta_page->data_offset;
intf("  data_tail: %lu\n", meta_page->data_tail);

    // 启用计数器
    if (ioctl(fd, PERF_EVENT_IOC_ENABLE, 0) < 0) {
        perror("ioctl enable failed");
        munmap(buf, mmap_size);
        close(fd);
        return -1;
    }

然后执行一些访存操作,使用内存屏障确保访存正确,然后从 ring buffer 中读取 sample 的值并进行 decode:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
    // 运行会产生内存访问的函数
    memory_intensive_function();

    printf("Function completed, checking for samples...\n");

    // 读取内存屏障,确保看到最新的data_head
    __sync_synchronize();
    unsigned long data_head = meta_page->data_head;
    unsigned long data_tail = meta_page->data_tail;

    printf("After execution:\n");
    printf("  data_head: %lu\n", data_head);
    printf("  data_tail: %lu\n", data_tail);
    printf("  Samples available: %lu\n", data_head - data_tail);

    if (data_head > data_tail) {
        // 有样本可读
        size_t bytes_available = data_head - data_tail;
        char* read_ptr = (char*)data_buffer + (data_tail % data_size);

        size_t bytes_processed = 0;
        while (bytes_processed < bytes_available) {
            struct perf_event_header* header = (struct perf_event_header*)read_ptr;

            printf("Event type: %u, size: %u\n", header->type, header->size);

            if (header->type == PERF_RECORD_SAMPLE) {
                void* sample_data = read_ptr + sizeof(*header);
                parse_sample(header, sample_data);
            }

            bytes_processed += header->size;
            read_ptr += header->size;

            // 处理环形缓冲区回绕
            if ((size_t)(read_ptr - (char*)data_buffer) >= data_size) {
                read_ptr = (char*)data_buffer;
            }
        }

        // 更新data_tail
        meta_page->data_tail = data_head;
    } else {
        printf("No samples collected. Try:\n");
        printf("1. Decreasing sample_period (currently %llu)\n", attr.sample_period);
        printf("2. Increasing memory access intensity\n");
        printf("3. Checking if IBS is supported on your CPU\n");
    }

MSR 寄存器直接访问

可以直接访问 IBS 的寄存器来启用采样读取数据。

名称编号说明
IBS_OP_CTL0xC0011033控制器寄存器
IBS_OP_DATA30xC001103B包含 DcMissLat 等信息
IBS_OP_RIP0xC0011035指令地址
IBS_OP_DATA0xC0011036执行状态

可以通过 rdmsr_on_cpu() + 定时中断轮询方式采集,但这种方法依赖内核权限。github 可以找到使用这种方式的参考

AMD IBS Toolkit

AMD 编写了针对应用场景的 IBS Toolkit,可以直接使用,原理是直接读取 MSR 寄存器,需要内核权限。

https://github.com/jlgreathouse/AMD_IBS_Toolkit

driver

AMD IBS Toolkit 提供了一个 driver 内核模块,用于监测 msr 寄存器并读取 ibs 数据到内核缓冲区,用户程序可以通过 ioctl() 与其通信,tools 中的 monitor 使用此种方式。

他提供了自动化脚本一键安装,允许用户态读取 ibs,但是应该需要搭配 tools/ibs_decoder 使用。

include

提供了标准接口,封装了 ibs 的标准数据结构以及一些常量

lib

一个独立的库,提供了一系列接口以供程序使用 ibs 监控,可以编译为 so 库使用,同样使用 ioctl 与内核模块通信,依赖 driver。

tools

提供了一系列 ibs 的使用 demo,包括使用 driver 的 monitor 和 decoder,使用 lib 的 daemon。

问题总结

不清楚 perf_event_attr 的各个参数与调用 ibs 之间的关系,导致目前读到的 sample 采样到的信息很少,且不清楚 ibs raw data 的详细格式,难以 decode。

amd ibs 的手册中没有如何使用 ibs 的内容,也没有找到关于 perf_event_open 如何配合 ibs 使用的资料,但是找到了 2012 年将 ibs 引入 linux kernel 的 patch

目前计划深入研究 AMD IBS Toolkit,并改造为一个库以供调用,或者使用读 MSR 的方式自己写一个。

考虑修改一下他的 lib,,目前看来绕不开他的 driver,看实际需求是沿用 driver 还是简化自己写。

This post is licensed under CC BY 4.0 by the author.

© sazikk. Some rights reserved.

Total views. 您是第个访问者

Using the Chirpy theme for Jekyll.