我的目标是编写一些代码,以一定间隔记录所有CPU的当前调用堆栈。本质上,我想做与perf record
相同的操作,但自己使用perf_event_open
。
根据手册页,似乎我需要使用PERF_SAMPLE_CALLCHAIN
样本类型并使用mmap
读取结果。就是说,联机帮助页非常简洁,某些示例代码现在将走很长一段路。
有人可以指出我正确的方向吗?
最佳答案
了解这一点的最好方法是阅读Linux内核源代码,并了解如何自己模拟perf record -g
。
如您所正确识别的那样,perf events
的记录将从系统调用perf_event_open
开始。这就是我们可以开始的地方,
definition of perf_event_open
如果观察系统调用的参数,您将看到第一个参数是结构perf_event_attr * 类型。这是接受系统调用属性的参数。这是您需要修改以记录调用链的内容。示例代码可能是这样的(请记住,您可以按照自己的方式调整其他参数和struct 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事件处理方式的简便方法-
PMU-TOOLS by Andi Kleen
如果您开始阅读系统调用的源代码,则会看到正在调用perf_event_alloc函数。除其他功能外,该功能将设置缓冲区,以便使用
perf record
获得调用链。函数get_callchain_buffers负责设置调用链缓冲区。
perf_event_open
通过采样/计数机制工作,如果与您要分析的事件相对应的性能监视计数器溢出,则内核将收集所有与事件有关的信息并将其存储到环形缓冲区中。可以通过mmap(2)
准备和访问此环形缓冲区。编辑#1:
下图显示了描述在执行
perf record
时使用mmap的流程图。映射环形缓冲区的过程将从您调用
perf record
的第一个函数开始-后者是__cmd_record,这将先调用record__open,然后再调用record__mmap,再调用record__mmap_evlist,然后再调用perf_evlist__mmap_ex,然后再调用perf_evlist__mmap_per_cpu,然后是perf_evlist__mmap_per_evsel,最后结束就ojit_a而言,就每个事件的mmap而言,这是最繁重的工作。编辑#2:
是的,你是对的。当您将采样周期设置为1000时,这意味着该事件每发生1000次(默认情况下为周期),内核会将该事件的采样记录到此缓冲区中。这意味着
perf
计数器将设置为1000,以便它在0处溢出,并且您会中断并最终记录样本。关于c - 用 `perf record -g`模拟 `perf_event_open`,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/49160450/