Использование perf_event_open для мониторинга контейнеров Docker

Я написал программу на C для получения событий производительности, таких как циклы процессора контейнеров Docker. Я имею в виду программу пользовательского пространства на уровне хоста (мониторинг на уровне хоста, а не внутри докера). Я даю pid контейнера докера как запись pid perf_event_open( ), однако у меня всегда 0 в качестве возвращаемого значения. Я протестировал программу для других 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(), я также открываю fd в каталоге контейнера докеров в groupfs. Не работает!

Не могли бы вы помочь мне решить проблему? Спасибо

Обновление: я проверил другие события, например PERF_COUNT_HW_CACHE_REFERENCES, и вижу 0 в качестве возвращаемого значения!

ОС: Ubuntu 16.04

Ядро: 4.15.0-041500-универсальный

Архитектура: X86_64


person M.B    schedule 19.10.2018    source источник
comment
Пример кода, пожалуйста?   -  person oblio    schedule 19.10.2018
comment
Добро пожаловать в Stackoverflow! Не могли бы вы добавить дополнительную информацию к вашему вопросу? Помня об этом, добавьте следующее: Вопросы должны включать желаемое поведение, конкретную проблему или ошибку и кратчайший код, необходимый для их воспроизведения в самом вопросе. Вопросы без четкого формулирования проблемы бесполезны для других читателей. См. раздел Как создать минимальный, полный и проверяемый пример.   -  person tgogos    schedule 19.10.2018


Ответы (1)


Вы не указали, какую версию ядра Linux вы используете. Я буду основывать свой ответ на последней версии ядра Linux.

Параметры, которые вы передали системному вызову perf_event_open, выглядят правильно, за исключением одного.

В вашем случае вы передаете cpu = -1 в качестве параметра perf_event_open.

Хотя это обычно работает при обычной фильтрации perf event (т. е. для каждого процессора или потока), передача cpu = -1 не будет работать при фильтрации на основе контрольной группы для perf. В режиме cgroup аргумент pid используется для передачи fd, открытого в каталог cgroup в cgroupfs (который вы, похоже, передали правильно). Аргумент cpu указывает процессор, на котором следует отслеживать потоки из этой контрольной группы. И когда cpu=-1 это означает, что perf event измеряет указанный процесс/поток на любом процессоре (независимо от того, принадлежит ли этот процессор к контрольной группе, в которой вы измеряете).

Вот это показано в последнем коде Linux.

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

Вы можете ясно видеть, что если PID=-1 or CPU=-1, метод системного вызова вернет ошибку.


Рабочий пример

Из документации perf_event_open совершенно ясно, что --

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

Поскольку в этом случае мы выполняем мониторинг cgroup, что уже понятно, поскольку мы отслеживаем perf-events контейнера, нам придется отслеживать событие в масштабах всей системы. Это означает, что мониторинг осуществляется для ВСЕХ доступных процессоров в системе.

Рабочий код

В моей системе 4 ядра, поэтому я использую процессоры - 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

person Arnabjyoti Kalita    schedule 21.10.2018
comment
Спасибо за Ваш ответ. Я согласен с вами по параметру процессора. Я установил --cpuset-cpus=0 для докера и передал cpu=0 в perf_event_open(). Однако у меня всегда есть 0 в качестве возвращаемого значения. Ничего не меняется! В чем проблема? - person M.B; 22.10.2018
comment
@MB вы можете проверить и некоторые другие аппаратные события? Что-то вроде cache_misses или количество инструкций — пожалуйста, сообщите, если вы видите 0 и в этих случаях. Также опишите архитектуру вашей системы, а также версию и вариант Linux, который вы используете. Пожалуйста, обновите это в вопросе. - person Arnabjyoti Kalita; 22.10.2018
comment
@A.Kaltia: Спасибо, я добавил к вопросу другую информацию. - person M.B; 22.10.2018
comment
@М.Б. вместо запуска usleep(100) между включением и отключением событий, можете ли вы запустить фиктивный цикл из 1-2 миллиардов итераций? Можете ли вы затем рассчитать cpu-cycles и другие аппаратные события? Процессорное время во время сна не принадлежит текущему процессу!! - person Arnabjyoti Kalita; 29.10.2018
comment
@ Арнабйоти Калита. спасибо, я делаю именно так, как вы сказали, вместо того, чтобы запускать usleep(100) между включением и отключением, я поставил цикл for, ничего не изменилось!. Вы тестировали эту программу на своем компьютере с докер-контейнером? - person M.B; 30.10.2018
comment
Привет @М.Б. Мне удалось получить ненулевые значения для некоторых perf events, таких как PERF_EVENT_HW_INSTRUCTIONS. Я скоро обновлю свой ответ. - person Arnabjyoti Kalita; 04.11.2018
comment
@arnabjyoti.kalita. какую ОС вы используете? Я обновил свое ядро ​​до последней версии (4.19.1) и запускаю ваш код как root, у меня 0! есть ли какая-то конкретная конфигурация в вашей системе? Спасибо. - person M.B; 05.11.2018
comment
Используете ли вы все ядра ЦП при вызове perf_event_open ? Я использую ядро ​​4.18. Помните, что при использовании мониторинга на основе cgroups можно отслеживать только общесистемные события. Если вы все еще застряли, запустите perf stat -e instructions --cgroup=docker/001706b1a71617b0ce9d340f706d901e00ee398091dd62aded2a1863fc8c274a -a sleep 10 и посмотрите, получите ли вы какое-либо ненулевое значение сейчас. - person Arnabjyoti Kalita; 05.11.2018
comment
Я хочу отслеживать инструкции и события кеша (промах кеша, ссылки на кеш), чтобы оценить производительность докера, работает ли он в этом случае? - person M.B; 06.11.2018
comment
Да, это сработает. perf stat внутренне использует системный вызов perf_event_open для мониторинга счетчиков производительности. Вы можете сделать strace perf stat -e instructions... , чтобы увидеть это. Но да, сначала прочитайте справочные страницы для perf stat. - person Arnabjyoti Kalita; 06.11.2018
comment
@ Arnabjyoti Kalita, большое спасибо за ваши ответы/комментарии. У меня всегда 0 в качестве возвращаемого значения perf_event_open. Я не знаю почему. Я запускаю именно ваш код. Это не работает! на моем компьютере. У меня процессор 4 ядра. - person M.B; 07.11.2018
comment
Последнее, что я могу сказать, это попробовать с большим значением sleep. Что-то вроде sleep(60). Вы можете -- strace perf stat -e instructions --cgroup=docker/<container-id> -a sleep 10 ? Вы видите, как PERF_EVENT_OPEN вызывается в выводе strace? - person Arnabjyoti Kalita; 08.11.2018
comment
@ Арнабйоти Калита, спасибо. когда я запускаю программу и отслеживаю ее, в информации об отладке я обнаружил следующее: access(/etc/ld.so.nohwcap, F_OK) = -1 ENOENT (Нет такого файла или каталога), что это? это единственное, что может пойти не так при выполнении программы. - person M.B; 26.11.2018
comment
@М.Б. здесь много ответов по этому поводу. Я считаю, что это не должно вызывать проблем. Если бы это было так, операция провалилась бы еще до ее завершения. Единственное, что я могу предложить, это просто убедиться, что все ядра процессора используются. Для 4 ядер вы должны увидеть 4 PERF_EVENT_OPEN системных вызова. Попробуйте увеличить значение sleep. Помните, что при мониторинге cgroup можно отслеживать только системные события. - person Arnabjyoti Kalita; 26.11.2018
comment
У меня ровно 4 ядра. Мониторю все 4 ядра. На самом деле я использую ваш код, но он у меня не работает! Я обновил ядро ​​до 4.19. Все в порядке. это чудесно! Я пытаюсь решить проблему, и я скажу вам, когда я нашел проблему. - person M.B; 27.11.2018