Эмулировать `perf record -g` с `perf_event_open`

Моя цель — написать код для записи текущего стека вызовов для всех процессоров с некоторым интервалом. По сути, я хотел бы сделать то же самое, что и perf record, но сам используя perf_event_open.

Судя по справочной странице, мне нужно использовать образец типа PERF_SAMPLE_CALLCHAIN и прочитать результаты с помощью mmap. Тем не менее, справочная страница невероятно лаконична, и прямо сейчас не помешал бы пример кода.

Может ли кто-нибудь указать мне в правильном направлении?


person Michael McLoughlin    schedule 07.03.2018    source источник


Ответы (1)


Лучший способ узнать об этом — прочитать исходный код ядра Linux и посмотреть, как вы сами можете эмулировать perf record -g.

Как вы правильно определили, запись perf events начнется с системного вызова perf_event_open. Итак, с чего мы можем начать,

определение 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);
     }

Примечание. Хороший и простой способ понять, как обрабатывать все эти перфорационные события, можно увидеть ниже —

ИНСТРУМЕНТЫ PMU от Энди Клин

Если вы начнете читать исходный код системного вызова, вы увидите, что функция perf_event_alloc вызывается. Эта функция, среди прочего, настроит буфер для получения цепочек вызовов с помощью perf record.

Функция get_callchain_buffers отвечает за настройку буферы цепочки вызовов.

perf_event_open работает через механизм выборки/подсчета, при котором, если счетчик мониторинга производительности, соответствующий событию, которое вы профилируете, переполняется, вся информация, относящаяся к событию, будет собрана и сохранена ядром в кольцевом буфере. Этот кольцевой буфер можно подготовить и получить к нему доступ через mmap(2).

Редактировать №1:

Блок-схема, описывающая использование mmap при выполнении perf record, показана на изображении ниже.

введите здесь описание изображения

Процесс mmaping кольцевых буферов начнется с первой функции при вызове perf record, то есть __cmd_record, это вызывает record__open, который затем вызывает record__mmap, за которым следует вызов record__mmap_evlist, который затем вызывает perf_evlist__mmap_ex, за которым следует perf_evlist__mma p_per_cpu и, наконец, в perf_evlist__mmap_per_evsel, который выполняет большую часть тяжелой работы по созданию mmap для каждого события.

Редактировать № 2:

Да вы правы. Когда вы устанавливаете период выборки, скажем, 1000, это означает, что для каждого 1000-го возникновения события (которое по умолчанию составляет циклы) ядро ​​будет записывать выборку этого события в этот буфер. . Это означает, что счетчики perf будут установлены на 1000, так что они переполнятся при 0, и вы получите прерывание и, в конечном итоге, запись сэмплов.

person Arnabjyoti Kalita    schedule 08.03.2018
comment
Это хороший ответ, однако одна интересная часть — чтение из mpage — отсутствует. Не могли бы вы расширить ответ на это? - person Zulan; 08.03.2018
comment
Очень ценю, что вы нашли время, чтобы ответить. Несколько вещей 1. Меня также интересует код для доступа к кольцевому буферу mmaped. 2. Я также ожидал, что pe.sample_freq или pe.sample_period будут иметь какое-то значение? 3. Можете ли вы подтвердить интерпретацию pe.sample_period: в сочетании с PERF_COUNT_HW_INSTRUCTIONS означает ли это, что мы отбираем все pe.sample_period устаревшие инструкции. - person Michael McLoughlin; 08.03.2018