用 `perf_event_open` 模拟 `perf record -g`
Emulate `perf record -g` with `perf_event_open`
我的目标是编写一些代码以在某个时间间隔记录所有 CPU 的当前调用堆栈。本质上我想做与 perf record
相同的事情,但我自己使用 perf_event_open
。
根据联机帮助页,我似乎需要使用 PERF_SAMPLE_CALLCHAIN
示例类型并使用 mmap
读取结果。也就是说,联机帮助页非常简洁,一些示例代码现在会有很长的路要走。
有人能指出我正确的方向吗?
了解这一点的最佳方法是阅读 Linux 内核源代码并了解如何自己模拟 perf record -g
。
正如您正确识别的那样,perf events
的记录将从系统调用 perf_event_open
开始。这就是我们可以开始的地方,
如果你观察系统调用的参数,你会发现第一个参数是一个struct perf_event_attr *类型。这是接受系统调用属性的参数。这是您需要修改以记录调用链的内容。示例代码可能是这样的(请记住,您可以按照自己的方式调整结构 perf_event_attr 的其他参数和成员):
int buf_size_shift = 8;
static unsigned perf_mmap_size(int buf_size_shift)
{
return ((1U << buf_size_shift) + 1) * sysconf(_SC_PAGESIZE);
}
int main(int argc, char **argv)
{
struct perf_event_attr pe;
long long count;
int fd;
memset(&pe, 0, sizeof(struct perf_event_attr));
pe.type = PERF_TYPE_HARDWARE;
pe.sample_type = PERF_SAMPLE_CALLCHAIN; /* this is what allows you to obtain callchains */
pe.size = sizeof(struct perf_event_attr);
pe.config = PERF_COUNT_HW_INSTRUCTIONS;
pe.disabled = 1;
pe.exclude_kernel = 1;
pe.sample_period = 1000;
pe.exclude_hv = 1;
fd = perf_event_open(&pe, 0, -1, -1, 0);
if (fd == -1) {
fprintf(stderr, "Error opening leader %llx\n", pe.config);
exit(EXIT_FAILURE);
}
/* associate a buffer with the file */
struct perf_event_mmap_page *mpage;
mpage = mmap(NULL, perf_mmap_size(buf_size_shift),
PROT_READ|PROT_WRITE, MAP_SHARED,
fd, 0);
if (mpage == (struct perf_event_mmap_page *)-1L) {
close(fd);
return -1;
}
ioctl(fd, PERF_EVENT_IOC_RESET, 0);
ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);
printf("Measuring instruction count for this printf\n");
ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
read(fd, &count, sizeof(long long));
printf("Used %lld instructions\n", count);
close(fd);
}
注意:下面是理解所有这些 perf 事件处理的一种简单易行的方法 -
如果您开始阅读系统调用的源代码,您会看到正在调用一个函数 perf_event_alloc。此函数将使用 perf record
.
设置缓冲区以获取调用链。
函数get_callchain_buffers负责设置调用链缓冲区。
perf_event_open
通过 sampling/counting 机制工作,如果与您正在分析的事件对应的性能监控计数器溢出,那么所有与事件相关的信息将被收集并存储到 ring-buffer 由内核。这个 ring-buffer 可以通过 mmap(2)
.
准备和访问
编辑 #1:
下图显示了描述在执行 perf record
时使用 mmap 的流程图。
mmap 环形缓冲区的过程将从调用 perf record
时的第一个函数开始 - 即 __cmd_record, this calls record__open, which then calls record__mmap, followed by a call to record__mmap_evlist, which then calls perf_evlist__mmap_ex, this is followed by perf_evlist__mmap_per_cpu and finally ending up in perf_evlist__mmap_per_evsel 就执行 mmap 而言,它完成了大部分 heavy-lifting对于每个事件而言。
编辑#2:
是的,你是对的。当您将采样周期设置为 1000 时,这意味着事件每发生 1000 次(默认情况下为 cycles),内核将记录该事件的样本进入这个缓冲区。这意味着 perf
计数器将设置为 1000,因此它会在 0 时溢出,您将获得中断并最终记录样本。
我的目标是编写一些代码以在某个时间间隔记录所有 CPU 的当前调用堆栈。本质上我想做与 perf record
相同的事情,但我自己使用 perf_event_open
。
根据联机帮助页,我似乎需要使用 PERF_SAMPLE_CALLCHAIN
示例类型并使用 mmap
读取结果。也就是说,联机帮助页非常简洁,一些示例代码现在会有很长的路要走。
有人能指出我正确的方向吗?
了解这一点的最佳方法是阅读 Linux 内核源代码并了解如何自己模拟 perf record -g
。
正如您正确识别的那样,perf events
的记录将从系统调用 perf_event_open
开始。这就是我们可以开始的地方,
如果你观察系统调用的参数,你会发现第一个参数是一个struct perf_event_attr *类型。这是接受系统调用属性的参数。这是您需要修改以记录调用链的内容。示例代码可能是这样的(请记住,您可以按照自己的方式调整结构 perf_event_attr 的其他参数和成员):
int buf_size_shift = 8;
static unsigned perf_mmap_size(int buf_size_shift)
{
return ((1U << buf_size_shift) + 1) * sysconf(_SC_PAGESIZE);
}
int main(int argc, char **argv)
{
struct perf_event_attr pe;
long long count;
int fd;
memset(&pe, 0, sizeof(struct perf_event_attr));
pe.type = PERF_TYPE_HARDWARE;
pe.sample_type = PERF_SAMPLE_CALLCHAIN; /* this is what allows you to obtain callchains */
pe.size = sizeof(struct perf_event_attr);
pe.config = PERF_COUNT_HW_INSTRUCTIONS;
pe.disabled = 1;
pe.exclude_kernel = 1;
pe.sample_period = 1000;
pe.exclude_hv = 1;
fd = perf_event_open(&pe, 0, -1, -1, 0);
if (fd == -1) {
fprintf(stderr, "Error opening leader %llx\n", pe.config);
exit(EXIT_FAILURE);
}
/* associate a buffer with the file */
struct perf_event_mmap_page *mpage;
mpage = mmap(NULL, perf_mmap_size(buf_size_shift),
PROT_READ|PROT_WRITE, MAP_SHARED,
fd, 0);
if (mpage == (struct perf_event_mmap_page *)-1L) {
close(fd);
return -1;
}
ioctl(fd, PERF_EVENT_IOC_RESET, 0);
ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);
printf("Measuring instruction count for this printf\n");
ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
read(fd, &count, sizeof(long long));
printf("Used %lld instructions\n", count);
close(fd);
}
注意:下面是理解所有这些 perf 事件处理的一种简单易行的方法 -
如果您开始阅读系统调用的源代码,您会看到正在调用一个函数 perf_event_alloc。此函数将使用 perf record
.
函数get_callchain_buffers负责设置调用链缓冲区。
perf_event_open
通过 sampling/counting 机制工作,如果与您正在分析的事件对应的性能监控计数器溢出,那么所有与事件相关的信息将被收集并存储到 ring-buffer 由内核。这个 ring-buffer 可以通过 mmap(2)
.
编辑 #1:
下图显示了描述在执行 perf record
时使用 mmap 的流程图。
mmap 环形缓冲区的过程将从调用 perf record
时的第一个函数开始 - 即 __cmd_record, this calls record__open, which then calls record__mmap, followed by a call to record__mmap_evlist, which then calls perf_evlist__mmap_ex, this is followed by perf_evlist__mmap_per_cpu and finally ending up in perf_evlist__mmap_per_evsel 就执行 mmap 而言,它完成了大部分 heavy-lifting对于每个事件而言。
编辑#2:
是的,你是对的。当您将采样周期设置为 1000 时,这意味着事件每发生 1000 次(默认情况下为 cycles),内核将记录该事件的样本进入这个缓冲区。这意味着 perf
计数器将设置为 1000,因此它会在 0 时溢出,您将获得中断并最终记录样本。