【问题标题】:perf_event_open - how to monitoring multiple eventsperf_event_open - 如何监控多个事件
【发布时间】:2017-06-24 14:50:34
【问题描述】:

有谁知道如何设置perf_event_attr struct 可以触发PMU 通过perf_event_open() 监控多个(类型)事件?

perf record -e cycles,faults ls一样,它有两种不同的事件类型(PERF_TYPE_HARDWARE和PERF_TYPE_SOFTWARE),但在perf_event_open's manpage的例子中,perf_event_attr.type只能分配一个值。

任何建议将不胜感激,谢谢!

20170208 更新 感谢@gudok 为我指明方向,但结果似乎有些异常。 演示程序如下(用于测量整个系统的 CPU 周期和缓存未命中):

#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <string.h>
#include <sys/ioctl.h>
#include <linux/perf_event.h>
#include <linux/hw_breakpoint.h>
#include <asm/unistd.h>
#include <errno.h>
#include <stdint.h>
#include <inttypes.h>
#include <time.h>

struct read_format {
  uint64_t nr;
  struct {
    uint64_t value;
    uint64_t id;
  } values[];
};

int main(int argc, char* argv[]) {
  struct perf_event_attr pea;
  int fd1, fd2;
  uint64_t id1, id2;
  uint64_t val1, val2;
  char buf[4096];
  struct read_format* rf = (struct read_format*) buf;
  int i,j;
  struct timespec time, time2;

  time.tv_sec = 1;
  time.tv_nsec = 0;

  memset(&pea, 0, sizeof(struct perf_event_attr));
  pea.type = PERF_TYPE_HARDWARE;
  pea.size = sizeof(struct perf_event_attr);
  pea.config = PERF_COUNT_HW_CPU_CYCLES;
  pea.disabled = 1;
  pea.exclude_kernel = 1;
  pea.exclude_hv = 1;
  pea.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID;
  fd1 = syscall(__NR_perf_event_open, &pea, 0, -1, -1, 0);
  ioctl(fd1, PERF_EVENT_IOC_ID, &id1);

  memset(&pea, 0, sizeof(struct perf_event_attr));
  pea.type = PERF_TYPE_HARDWARE;
  pea.size = sizeof(struct perf_event_attr);
  pea.config = PERF_COUNT_HW_CACHE_MISSES;
  pea.disabled = 1;
  pea.exclude_kernel = 1;
  pea.exclude_hv = 1;
  pea.precise_ip = 2;  // want to using PEBS 
  pea.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID;
  fd2 = syscall(__NR_perf_event_open, &pea, 0, -1, fd1 /*!!!*/, 0);
  ioctl(fd2, PERF_EVENT_IOC_ID, &id2);

  ioctl(fd1, PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP);
  ioctl(fd1, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP);
  while (1) {
    nanosleep(&time, &time2);

    //ioctl(fd1, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP);

    read(fd1, buf, sizeof(buf));
    for (i = 0; i < rf->nr; i++) {
      if (rf->values[i].id == id1) {
        val1 = rf->values[i].value;
      } else if (rf->values[i].id == id2) {
        val2 = rf->values[i].value;
      }
    }

    printf("cpu cycles: %"PRIu64"\n", val1);
    printf("cache misses: %"PRIu64"\n", val2);

  }

  return 0;
}

输出是:

cpu cycles: 120   // Just have about 120 CPU cycles in a second
cache misses: 0   // and doesn't have any cache miss?
cpu cycles: 233
cache misses: 0
cpu cycles: 352
cache misses: 0
cpu cycles: 455
cache misses: 0
cpu cycles: 562
cache misses: 0
cpu cycles: 673
cache misses: 0
cpu cycles: 794
cache misses: 0
cpu cycles: 907
cache misses: 0
cpu cycles: 1011
cache misses: 0
cpu cycles: 1129
cache misses: 3
cpu cycles: 1269
cache misses: 4
cpu cycles: 1423

【问题讨论】:

    标签: linux intel perf


    【解决方案1】:

    这有点棘手。

    我们像往常一样创建第一个计数器。此外,我们通过PERF_FORMAT_GROUPPERF_FORMAT_ID 能够同时使用多个计数器。这个柜台将是我们的组长。

    struct perf_event_attr pea;
    int fd1, fd2;
    uint64_t id1, id2; 
    
    memset(&pea, 0, sizeof(struct perf_event_attr));
    pea.type = PERF_TYPE_HARDWARE;
    pea.size = sizeof(struct perf_event_attr);
    pea.config = PERF_COUNT_HW_CPU_CYCLES;
    pea.disabled = 1;
    pea.exclude_kernel = 1;
    pea.exclude_hv = 1;
    pea.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID;
    fd1 = syscall(__NR_perf_event_open, &pea, 0, -1, -1, 0);
    

    接下来,我们检索第一个计数器的标识符:

    ioctl(fd1, PERF_EVENT_IOC_ID, &id1);
    

    第二个(以及所有其他计数器)以相同的方式创建,只有一个例外:我们将 fd1 值作为组长参数传递:

    memset(&pea, 0, sizeof(struct perf_event_attr));
    pea.type = PERF_TYPE_SOFTWARE;
    pea.size = sizeof(struct perf_event_attr);
    pea.config = PERF_COUNT_SW_PAGE_FAULTS;
    pea.disabled = 1;
    pea.exclude_kernel = 1;
    pea.exclude_hv = 1;
    pea.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID;
    fd2 = syscall(__NR_perf_event_open, &pea, 0, -1, fd1, 0); // <-- here
    ioctl(fd2, PERF_EVENT_IOC_ID, &id2);
    

    接下来我们需要声明一个数据结构来一次读取多个计数器。您必须根据传递给perf_event_open 的标志来声明不同的字段集。手册页提到了所有可能的字段。在我们的例子中,我们传递了PERF_FORMAT_ID 标志,它添加了id 字段。这将使我们能够区分不同的计数器。

    struct read_format {
        uint64_t nr;
        struct {
            uint64_t value;
            uint64_t id;
        } values[/*2*/];
    };
    

    现在我们调用标准分析 ioctls:

    ioctl(fd1, PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP);
    ioctl(fd1, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP);
    do_something();
    ioctl(fd1, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP);
    

    最后,我们从组长文件描述符中读取计数器。两个计数器都以我们声明的单个 read_format 结构返回:

    char buf[4096];
    struct read_format* rf = (struct read_format*) buf;
    uint64_t val1, val2;
    
    read(fd1, buf, sizeof(buf));
    for (i = 0; i < rf->nr; i++) {
      if (rf->values[i].id == id1) {
        val1 = rf->values[i].value;
      } else if (rf->values[i].id == id2) {
        val2 = rf->values[i].value;
      }
    }
    printf("cpu cycles: %"PRIu64"\n", val1);
    printf("page faults: %"PRIu64"\n", val2);
    

    以下是完整的节目清单:

    #define _GNU_SOURCE
    #include <stdlib.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <sys/syscall.h>
    #include <string.h>
    #include <sys/ioctl.h>
    #include <linux/perf_event.h>
    #include <linux/hw_breakpoint.h>
    #include <asm/unistd.h>
    #include <errno.h>
    #include <stdint.h>
    #include <inttypes.h>
    
    struct read_format {
      uint64_t nr;
      struct {
        uint64_t value;
        uint64_t id;
      } values[];
    };
    
    void do_something() {
      int i;
      char* ptr;
    
      ptr = malloc(100*1024*1024);
      for (i = 0; i < 100*1024*1024; i++) {
        ptr[i] = (char) (i & 0xff); // pagefault
      }
      free(ptr);
    }
    
    int main(int argc, char* argv[]) {
      struct perf_event_attr pea;
      int fd1, fd2;
      uint64_t id1, id2;
      uint64_t val1, val2;
      char buf[4096];
      struct read_format* rf = (struct read_format*) buf;
      int i;
    
      memset(&pea, 0, sizeof(struct perf_event_attr));
      pea.type = PERF_TYPE_HARDWARE;
      pea.size = sizeof(struct perf_event_attr);
      pea.config = PERF_COUNT_HW_CPU_CYCLES;
      pea.disabled = 1;
      pea.exclude_kernel = 1;
      pea.exclude_hv = 1;
      pea.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID;
      fd1 = syscall(__NR_perf_event_open, &pea, 0, -1, -1, 0);
      ioctl(fd1, PERF_EVENT_IOC_ID, &id1);
    
      memset(&pea, 0, sizeof(struct perf_event_attr));
      pea.type = PERF_TYPE_SOFTWARE;
      pea.size = sizeof(struct perf_event_attr);
      pea.config = PERF_COUNT_SW_PAGE_FAULTS;
      pea.disabled = 1;
      pea.exclude_kernel = 1;
      pea.exclude_hv = 1;
      pea.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID;
      fd2 = syscall(__NR_perf_event_open, &pea, 0, -1, fd1 /*!!!*/, 0);
      ioctl(fd2, PERF_EVENT_IOC_ID, &id2);
    
    
      ioctl(fd1, PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP);
      ioctl(fd1, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP);
      do_something();
      ioctl(fd1, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP);
    
    
      read(fd1, buf, sizeof(buf));
      for (i = 0; i < rf->nr; i++) {
        if (rf->values[i].id == id1) {
          val1 = rf->values[i].value;
        } else if (rf->values[i].id == id2) {
          val2 = rf->values[i].value;
        }
      }
    
      printf("cpu cycles: %"PRIu64"\n", val1);
      printf("page faults: %"PRIu64"\n", val2);
    
      return 0;
    }
    

    【讨论】:

    • 感谢您的帮助:)它可以工作,但结果似乎有点不寻常。我想计算每秒的 CPU 周期,它显示大约只有 120 个周期。我使用的方法不正确吗? (在我的问题中更新了演示程序)再次感谢。
    • @KevinChi,睡眠呼叫期间不会计算 CPU 周期。睡眠期间的 CPU 时间不属于当前进程。在do_something 内部,您需要创建一个 1-2 bln 的虚拟循环。迭代和计数 1) cpu 周期 2) 执行此循环所花费的时间 (clock_gettime(CLOCK_THREAD_CPUTIME_ID))。将这两个值相除将获得每秒的 CPU 周期数。或者,您可以设置一秒计时器并运行无限循环,直到它触发。
    猜你喜欢
    • 2019-03-24
    • 1970-01-01
    • 1970-01-01
    • 2022-11-23
    • 1970-01-01
    • 2022-07-30
    • 1970-01-01
    • 2017-12-07
    • 1970-01-01
    相关资源
    最近更新 更多