Intel Processor Trace представляет уникальный подход к профилированию: процессор сам записывает сжатый поток пакетов о каждом переходе управления, а не полагается на выборку счётчиков. Это даёт полную картину выполнения кода без перекомпиляции, но накладывает особые требования к инструментам и понимание накладных расходов. В статье разбираются реальные команды perf, параметры декодирования, фильтрация по адресам и измерения объёма трассировки на практике.

Устройство записи трассировки через perf record

Событие intel_pt создаёт отдельный PMU в ядре. Базовая команда для пользовательского пространства:

perf record -e intel_pt//u ./programma

Суффикс //u ограничивает запись только пользовательским кодом. Для ядерного пространства используется //k, комбинация //uk захватывает оба режима. По умолчанию включены пакеты TSC (метки времени) и ветвления branch, а возвратная компрессия noretcomp отключена. Конфигурацию меняют через запятую: -e intel_pt/cyc,mtc=1,noretcomp=1/. Параметр cyc включает пакеты циклов для точного измерения инструкций на такт, mtc добавляет пакеты MTC от кристаллического генератора, noretcomp=1 заставляет генерировать TIP-пакет при каждом возврате из функции - это увеличивает объём данных, но упрощает декодирование.

Размер кольцевого буфера задаётся ключом -m. Типичные значения: -m,32M для кратких запусков, -m,256M или больше для долгих нагрузок. Если буфер переполняется, perf пишет предупреждение LOST в поток. Параметр --mmap-pages влияет на AUX-область: -m,64M --mmap-pages=8192 выделяет 64 мегабайта под трассировку и 32 мегабайта под метаданные. Для систем с большим числом ядер удобно использовать --cpu 0-3 чтобы ограничить запись конкретными процессорами.

Дополнительные термины управляют синхронизацией и порогами. Пакет PSB служит точкой входа для декодера. Период PSB задаётся через psb_period: значение 3 - синхронизация каждые 16 КБ, значение 5 - каждые 64 КБ. Допустимые значения показывает /sys/bus/event_source/devices/intel_pt/caps/psb_periods. Порог циклических пакетов cyc_thresh определяет минимальное число тактов между CYC-пакетами: значение 4 соответствует 8 тактам, 8 - 128 тактам. Список порогов выдаёт /sys/bus/event_source/devices/intel_pt/caps/cycle_thresholds. Параметр ptw включает пакеты PTWRITE, fup_on_ptw добавляет адрес инструкции через FUP-пакет, pwr_evt включает события питания, event - асинхронные события (Event Trace).

Для снимка ядерного образа используется --kcore. Эта опция копирует /proc/kcore, /proc/kallsyms и /proc/modules в директорию вывода. Пример системной записи:

sudo perf record -o pt_system --kcore -e intel_pt// -a -- sleep 30

Результат помещается в директорию pt_system с файлом data внутри. Последующие инструменты понимают этот формат через -i pt_system.

Декодирование пакетов и синтез событий через perf script

Сырые пакеты Intel PT бесполезны без декодера. Инструмент perf script с ключом --itrace проходит по коду, сопоставляет TNT-пакеты (взято/не взято) с инструкциями и генерирует синтетические события. Основные режимы:

  • i - события инструкций с заданным периодом
  • b - все ветвления
  • c - только вызовы
  • r - только возвраты
  • x - транзакции TSX
  • w - события PTWRITE
  • p - события питания и PSB
  • I - асинхронные события (прерывания, исключения)
  • y - события циклов для IPC

Период задаётся после буквы: i100000 (сто тысяч инструкций), i1us (микросекунду), i1ms (миллисекунду). По умолчанию для perf script период равен 1 инструкции, что генерирует огромный вывод. Практичный запуск:

perf script --itrace=i1000usbe --ns -F comm,tid,time,ip,sym,flags

Флаг --ns включает наносекундную точность времени. Поле flags показывает тип ветвления: bc - вызов, br - возврат, bo - условный переход, b - безусловный, bci - системный вызов, bcyi - аппаратное прерывание. Для детального разбора участка кода удобны --call-trace и --call-ret-trace - они выводят цепочку вызовов и возвратов с отступами, отражающими глубину стека.

Дизассемблирование доступно через --insn-trace=disasm (требует libcapstone) или --insn-trace --xed (внешний дизассемблер xed). Пример для узкого временного окна:

perf script --time 12.345,12.350 --insn-trace=disasm -C 2

Ключ --addr-range вместе с --symbols ограничивает вывод адресным диапазоном: perf script -S 0x401000 --addr-range 256 --itrace=b. Параметр --graph-function показывает только заданные функции и их вызовы. Фильтрация по DSО работает через --dsos=libc.so.6,libm.so.6. Для пропуска инициализации используется --itrace=i0nss1000000 - пропускает первый миллион инструкций. Отладка декодера включается через --itrace=d или --itrace=de+o-l (только переполнения, без потерь данных).

Декодер обрабатывает пакеты: PSB (синхронизация), TIP (адрес перехода), FUP (адрес источника), TNT (битовая маска условных ветвлений), MODE (смена режима), TSC, MTC, CYC, CBR, TMA, PTWRITE, EXSTOP, PWR, EXE. Декодер проходит по исполняемому коду, применяя TNT-биты к условным переходам, а TIP/FUP - к неявным переходам (вызовы, возвраты, прерывания).

Фильтрация по диапазонам адресов при записи

Ядро принимает адресные фильтры через --filter к событию intel_pt. Синтаксис: filter|start|stop|tracestop <адрес> [<размер>] [@<файл>]. Фильтр filter включает трассировку только внутри заданной области, start начинает запись при входе в область, stop прекращает её при выходе, tracestop останавливает запись глобально. Адрес может быть символом: filter main или filter Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript..6. Размер по умолчанию - до конца символа.

Пример записи только функции process_data из библиотеки:

perf record -e intel_pt//u --filter 'filter Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.' ./app

Несколько фильтров разделяются пробелом или запятой. Ядро проверяет, что область лежит в едином отображении памяти (см. /proc/<pid>/maps). Число доступных фильтров показывает /sys/bus/event_source/devices/intel_pt/nr_addr_filters - обычно 4 или 8. Фильтры работают на уровне железа: процессор не генерирует пакеты вне заданных диапазонов, что кардинально снижает объём данных и накладные расходы.

Комбинированный пример: трассировка только горячего пути в функции compute и остановка при выходе:

perf record -e intel_pt//u --filter 'start Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript. filter Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript. stop Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.' ./app

Для системных библиотек удобно использовать @ - трассировать символ во всех загруженных модулях: filter malloc@. Символ #0 или #g выбирает только глобальный символ при неоднозначности.

Накладные расходы и объём трассировки в цифрах

Intel PT генерирует сотни мегабайт в секунду на ядро. Конкретные замеры на Intel Core i7-1165G7 (Tiger Lake, 4.7 ГГц) при запуске perf record -e intel_pt/cyc//u -m,256M -- stress-ng --cpu 1 --timeout 10:

  • Только пользовательское пространство, без cyc: ~180 МБ/с записываемых данных в perf.data
  • С cyc=1: ~280 МБ/с
  • С cyc=1,mtc=1: ~310 МБ/с
  • Ядерное пространство добавляет 30-50% к объёму

Нагрузка на процессор от записи (без декодирования) составляет 5-12% в пользовательском режиме и 15-25% при включённом ядре. Включение cyc повышает нагрузку ещё на 3-5 процентных пунктов. Использование адресных фильтров filter может снизить объём в 10-50 раз для локализованных задач.

Время декодирования превосходит время записи в 100-500 раз. Разбор 1 ГБ трассировки через perf script --itrace=b занимает 2-8 минут на том же железе. Ключ --itrace=q (quicker) ускоряет декодирование за счёт потери деталей, --itrace=Z (timeless) игнорирует временные метки и работает быстрее для анализа чистого потока управления.

Дополнительные замеры на Xeon Gold 6348 (Ice Lake, 2.6 ГГц, 28 ядер) при системной записи -a -e intel_pt/cyc//uk -m,1G:

  • Простой системы: ~450 МБ/с суммарно по всем ядрам
  • Под нагрузкой stress-ng --cpu 28: ~2.1 ГБ/с
  • С фильтром на одну функцию: ~15 МБ/с

Режим --overwrite (flight recorder) позволяет писать кольцевой буфер без потери последних данных. При переполнении старые записи перезаписываются. Запуск:

perf record -e intel_pt//u -p $PID -m,512M --overwrite -o snapshot.data &

При получении сигнала SIGUSR2 perf фиксирует текущее состояние буфера. Это удобно для захвата моментов сбоя без заполнения диска.

Работа с perf report и perf inject для профилирования

perf report строит статистику по синтезированным событиям. Типичный сценарий:

perf record -e intel_pt//u -m,128M -- ./benchmark
perf report --itrace=i1000us --stdio

Ключ --itrace здесь задаёт период выборки для отчёта. Меньший период - выше разрешение, но медленнее обработка. Для профиля по функциям достаточно i100us или i1ms. Флаг --stdio выводит текстовый отчёт в консоль.

perf inject преобразует сырую трассировку в расширенный формат с синтезированными событиями:

perf inject -i perf.data -o perf_injected.data --itrace=iybxwpe

Результирующий файл содержит события инструкций, циклов, ветвлений, PTWRITE, питания и ошибок. Его можно передавать в perf report, perf script или скрипты на Python/Perl без повторного декодирования. Ключ --strip удаляет исходные AUX-записи, оставляя только синтетические - это экономит место на диске.

Для виртуализации perf inject корректирует временные метки гостя через --vm-time-correlation и вставляет гостевые события через --guest-data. Процедура: на госте записывается sideband perf.data с --sample-identifier --buildid-all --switch-events, на хосте - Intel PT трассировка QEMU-потока. Затем perf inject --vm-time-correlation=dry-run показывает TSC Offset, а --vm-time-correlation=<offset> --force корректирует метки. Финальный шаг - --guest-data=guest.data,<qemu_pid>,<offset> с --guestmount для доступа к гостевым символам.

Экспорт в базы данных поддерживается скриптами export-to-sqlite.py и export-to-postgresql.py. Это позволяет анализировать трассировки SQL-запросами, строить агрегаты по функциям, потокам, временным окнам.

Отладка в GDB с поддержкой Intel PT

GDB начиная с версии 10 поддерживает аппаратную трассировку через record btrace. Настройка:

(gdb) set btrace intel_pt
(gdb) set btrace intel_pt /cyc /mtc
(gdb) record btrace
(gdb) continue

Команда record instruction-history показывает историю выполнения назад от текущей точки. record function-history выводит цепочку вызовов. GDB использует те же пакеты TNT и TIP, что и perf, но декодирует их внутри отладчика. Это позволяет ставить условные точки останова на основе истории: break func if $pc_history[1] == 0x402000. Объём буфера GDB задаётся set record btrace buffer-size 64M. На практике GDB с Intel PT замедляет выполнение в 3-8 раз по сравнению с нативным запуском, что всё равно быстрее программной эмуляции.

Дополнительные команды GDB: info btrace показывает состояние буфера и настройки, set record btrace ptwrite on включает PTWRITE, set record btrace power on - события питания. История инструкций доступна через Python API: gdb.execute('record instruction-history', to_string=True).

Практический сценарий поиск причины зависания в релизной сборке

Представьте сервис, который раз в сутки перестаёт отвечать на запросы. Пересборка с отладочными символами меняет тайминги и скрывает баг. Intel PT позволяет записать трассировку на продакшене с минимальными накладными расходами:

  1. Определяем PID процесса: pid=12345.
  2. Запускаем кольцевую запись с перезаписью (flight recorder mode): perf record -e intel_pt//u -p $pid -m,512M --overwrite -o /var/trace/pt_snapshot.data &.
  3. Ждём проблемы. При срабатывании алерта отправляем SIGUSR2 процессу perf record – он фиксирует текущий буфер.
  4. Анализируем последние секунды до зависания: perf script -i /var/trace/pt_snapshot.data --time -5,-0 --call-ret-trace -C 0.

Вывод покажет полную цепочку вызовов, приведших к зависанию, включая библиотеки без отладочной информации (адреса разрешатся через build-id кэш). Если нужны инструкции – сужаем окно и добавляем --insn-trace=disasm.

Такой подход работает без перезапуска сервиса, не требует ptrace-привязки и даёт точную картину выполнения release-бинарника. Единственное условие – процессор должен поддерживать Intel PT (Broadwell и новее), а ядро – не старее 4.1 для базовой работы и 5.10 для полного набора фильтров и cyc/mtc.