使用perf_event_open监视Docker容器

时间:2018-10-19 12:48:30

标签: performance docker linux-kernel perf linux-containers

我用C语言编写了一个程序来检索性能事件,例如Docker容器的cpu周期。 我的意思是,在主机级别的用户空间程序(主机级别的监视,不在docker内部)。我将docker容器的pid用作 perf_event_open() 的pid条目,但是,我始终将 0 作为返回值。我已经针对其他非docker pid(例如firefox)测试了该程序,并且效果很好。

我将 PERF_FLAG_PID_CGROUP 设置为标志,没有任何改变! 这是代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/perf_event.h>
#include <asm/unistd.h>

    static long perf_event_open(struct perf_event_attr *hw_event, pid_t pid, int cpu, int group_fd, unsigned long flags)
   {
       int ret;

       ret = syscall(__NR_perf_event_open, hw_event, pid, cpu,
                      group_fd, flags);
       return ret;
   }

   int
   main(int argc, char **argv)
   {
       struct perf_event_attr pe;
       long long count;
       int fd;

       fd = open("/sys/fs/cgroup/perf_event/docker/f42c13cd9dd700544fe670e30d0b3216bdceaf01ddc370405618fdecfd10b26d", O_RDONLY);
       if (fd == -1)
          return 0;

       memset(&pe, 0, sizeof(struct perf_event_attr));
       pe.type = PERF_TYPE_HARDWARE;
       pe.size = sizeof(struct perf_event_attr);
       pe.config = PERF_COUNT_HW_CPU_CYCLES;
       pe.disabled = 1;
       pe.exclude_kernel = 0;
       pe.exclude_hv = 0;

       fd = perf_event_open(&pe, fd, -1, -1, PERF_FLAG_PID_CGROUP);
       if (fd == -1) {
          fprintf(stderr, "Error opening leader %llx\n", pe.config);
          exit(EXIT_FAILURE);
       }

       ioctl(fd, PERF_EVENT_IOC_RESET, 0);
       ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);

       usleep(100);

       ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
       read(fd, &count, sizeof(long long));

       printf("Used %lld instructions\n", count);

       close(fd);
   }

根据perf_event_open()的手册页,我还提供了在groupfs中的docker容器目录中打开的fd。不起作用!

您能帮我解决问题吗? 谢谢

更新: 我已经检查了其他事件,例如PERF_COUNT_HW_CACHE_REFERENCES, 我看到 0 作为返回值!

操作系统:Ubuntu 16.04

内核:4.15.0-041500-通用

体系结构:X86_64

1 个答案:

答案 0 :(得分:1)

您尚未指定要使用的Linux内核版本。我将根据最新的Linux内核版本来回答。

您传递给perf_event_open系统调用的参数看起来正确,除了一个。

在您的情况下,您将 cpu = -1 作为参数传递给perf_event_open

尽管这通常可以在常规的perf event过滤(即每个CPU或每个线程)中工作,但传递cpu = -1在基于perf的基于cgroup的过滤中不起作用。在cgroup模式下,pid参数用于将打开的fd传递到cgroupfs中的cgroup目录(您似乎已正确传递了该目录)。 cpu参数指定要监视来自该cgroup的线程的cpu。而当cpu=-1时,则意味着perf event会在任何CPU上测量指定的进程/线程(与该CPU是否属于您要测量的cgroup无关)。

This是最新Linux代码中的描述方式。

if ((flags & PERF_FLAG_PID_CGROUP) && (pid == -1 || cpu == -1)) return -EINVAL;

您可以清楚地看到,如果使用PID=-1 or CPU=-1,则syscall方法将返回错误。


工作示例

perf_event_open文档中可以很清楚地看到-

cgroup monitoring is available only for system-wide events and may therefore require extra permissions.

由于在这种情况下我们正在执行cgroup监视,这在我们监视容器的perf-events时已经被理解,因此我们将必须监视整个系统的事件。这意味着将对所有系统中可用的CPU进行监视。

工作代码

我的系统中有4个内核,因此我使用CPU-0、1、2、3

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/perf_event.h>
#include <asm/unistd.h>
#include <errno.h>

    static long perf_event_open(struct perf_event_attr *hw_event, pid_t pid, int cpu, int group_fd, unsigned long flags)
   {
       int ret;

       ret = syscall(__NR_perf_event_open, hw_event, pid, cpu,
                      group_fd, flags);
       return ret;
   }

   int
   main(int argc, char **argv)
   {
       struct perf_event_attr pe;
       long long count, count1, count2, count3;
       int fd, fd1, fd2, fd3, fd4;

       fd1 = open("/sys/fs/cgroup/perf_event/docker/001706b1a71617b0ce9d340f706d901e00ee398091dd62aded2a1863fc8c274a", O_RDONLY);
       if (fd1 == -1)
          return 0;

       memset(&pe, 0, sizeof(struct perf_event_attr));
       pe.type = PERF_TYPE_HARDWARE;
       pe.size = sizeof(struct perf_event_attr);
       pe.config = PERF_COUNT_HW_INSTRUCTIONS;
       pe.disabled = 1;
       pe.exclude_kernel = 0;
       pe.exclude_hv = 0;

       fd = perf_event_open(&pe, fd1, 0, -1, PERF_FLAG_PID_CGROUP|PERF_FLAG_FD_CLOEXEC);
       if (fd == -1) {
          fprintf(stderr, "Error opening leader: %s\n", strerror(errno));
          exit(EXIT_FAILURE);
       }
       fd2 = perf_event_open(&pe, fd1, 1, -1, PERF_FLAG_PID_CGROUP|PERF_FLAG_FD_CLOEXEC);
       if (fd2 == -1) {
          fprintf(stderr, "Error: %s\n", strerror(errno));
          exit(EXIT_FAILURE);
       }
       fd3 = perf_event_open(&pe, fd1, 2, -1, PERF_FLAG_PID_CGROUP|PERF_FLAG_FD_CLOEXEC);
       if (fd3 == -1) {
          fprintf(stderr, "Error: %s\n", strerror(errno));
          exit(EXIT_FAILURE);
       } 
       fd4 = perf_event_open(&pe, fd1, 3, -1, PERF_FLAG_PID_CGROUP|PERF_FLAG_FD_CLOEXEC);
       if (fd4 == -1) {
          fprintf(stderr, "Error: %s\n", strerror(errno));
          exit(EXIT_FAILURE);
       }

       ioctl(fd, PERF_EVENT_IOC_RESET, 0);
       ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);

       ioctl(fd2, PERF_EVENT_IOC_RESET, 0);
       ioctl(fd2, PERF_EVENT_IOC_ENABLE, 0);

       ioctl(fd3, PERF_EVENT_IOC_RESET, 0);
       ioctl(fd3, PERF_EVENT_IOC_ENABLE, 0);

       ioctl(fd4, PERF_EVENT_IOC_RESET, 0);
       ioctl(fd4, PERF_EVENT_IOC_ENABLE, 0);

       sleep(10);   // using sleep(10) to actually observe instructions

       ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
       ioctl(fd2, PERF_EVENT_IOC_DISABLE, 0);
       ioctl(fd3, PERF_EVENT_IOC_DISABLE, 0);
       ioctl(fd4, PERF_EVENT_IOC_DISABLE, 0);

       read(fd, &count, sizeof(long long));
       read(fd2, &count1, sizeof(long long));
       read(fd3, &count2, sizeof(long long));
       read(fd4, &count3, sizeof(long long));

       printf("Used %lld instructions\n", count+count1+count2+count3);

       close(fd);
       close(fd2);
       close(fd3);
       close(fd4);
  }

`

输出:Used 55174 instructions