Трассировщик функций ftrace
Ftrace – это внутренний трассировщик ядра, позволяющий разработчикам посмотреть, что происходит внутри ядра системы. Трассировщик можно использовать для отладки, анализа задержек или или производительности, связанных с операциями за пределами пользовательского пространства.
Хотя ftrace обычно считают трассировщиком функций, реально это целый набор средств трассировки, включающий отслеживание задержки для просмотра событий между отключением и включением прерываний, при вытеснении задачи или с момента ее пробуждения до фактического планирования.
Одним из наиболее распространенных применений ftrace является отслеживание событий. В ядре имеются сотни статических событий, которые могут быть включены через файловую систему tracefs для просмотра происходящего в отдельных частях ядра.
Дополнительную информацию о событиях можно найти в файле events.txt
документации ядра.
Детали реализации трассировщика описаны в файле ftrace-design.
Файловая система
Ftrace использует файловую систему tracefs для хранения управляющих файлов и данных.
При настройке tracefs в ядре (выбор любой из опций ftrace) создается каталог /sys/kernel/tracing. Для автоматического монтирования этого каталога можно добавить в файл /etc/fstab команду
tracefs /sys/kernel/tracing tracefs defaults 0 0
Можно монтировать файловую систему по мере надобности с помощью команды
mount -t tracefs nodev /sys/kernel/tracing
Для быстрого доступа к каталогу можно создать символьную ссылку
ln -s /sys/kernel/tracing /tracing
Примечание. До версии ядра 4.1 управление трассировкой происходило в рамках файловой системы debugfs, обычно называемой /sys/kernel/debug/tracing. Для совместимости с прежними версиями при монтировании файловой системы debugfs автоматически монтируется система tracefs как /sys/kernel/debug/tracing1.
Все файлы системы tracefs доступны и в файловой системе debugfs.
Все выбранные опции ftrace будут отражаться и в файловой системе tracefs. Далее в документе предполагается текущим каталог ftrace (cd /sys/kernel/tracing) и файлы будут именоваться относительно этого каталога без указания полного пути /sys/kernel/tracing.
Исходя из того, что настройка ftrace в ядре присутствует, после монтирования tracefs вы получите доступ к управлению и выходным файлам ftrace. Ниже описаны некоторые из этих файлов.
current_tracer
Указывает текущий настроенный трассировщик.
available_tracers
Содержит список трассировщиков, включенных в ядре. Эти трассировщики можно настраивать, помещая имя нужного трассировщика с помощью команды echo в файл current_tracer.
tracing_on
Устанавливает или показывает состояние записи в кольцевой буфер трассировки. Запись 0 в этот файл отключает запись, 1 включает. Отметим, что это влияет лишь на запись в кольцевой буфер, а связанные с трассировкой издержки могут сохраняться.
Функция ядра tracing_off() может применяться внутри ядра для отключения записи в кольцевой буфер, помещая 0 в указанный файл. Можно восстановить запись из пользовательского пространства, отправив в файл значение 1.
Отметим, что функция и триггер событий traceoff также будут помещать в файл значение 0 и останавливать запись. Для восстановления записи служит указанный выше способ записи значения 1 в файл.
trace
В этот файл записывается вывод трассировки в понятном человеку формате (см. ниже). Отметим, что трассировка приостанавливается при чтении этого файла (open).
trace_pipe
Вывод в этот файл такой же как в trace, но файл предназначен для потоковой трассировки. Чтение из файла блокируется на время записи новых данных. В отличие от trace, этот файл является «потребителем» – считывание из этого файла «потребляет» данные и они уже не будут видны при последующих обращениях. Файл trace является «статическим» и если трассировщик не добавляет в него данных, при каждом обращении будет считываться одна и та же информация. При считывании файла trace_pipe трассировка не отключается.
trace_options
Этот файл позволяет пользователю контролировать объем данных, отображаемых в одном из указанных выше выходных файлов. Имеются также опции для изменения режима работы трассировщика и событий (трассировка стека, временные метки и . п.).
options
В этом каталоге расположены файлы для каждой доступной опции трассировки (те же, что в trace_options). Опции могут быть включены и отключены записью 1 или 0 (соответственно) в файл с именем опции.
tracing_max_latency
Некоторые из трассировщиков записывают максимальные задержки, например, максимальную продолжительность запрета прерываний. Данный файл служит для записи такой информации. Значение в файле меняется лишь в том случае, когда измеренная задержка превышает записанную ранее, т. е. значение в файле никогда не уменьшается, пока не будет сброшено. Задержка (и другие временные параметры, упоминаемые далее) указывается в микросекундах. Значение максимальной задержки сохраняется также в файле trace.
tracing_thresh
Некоторые трассировщики задержки записывают значение в файл, если оно превышает величину, указанную в этом файле (порог). Опция активна лишь при указании в файле значения больше 0 (в микросекундах).
buffer_size_kb
Размер буфера для каждого CPU в килобайтах. По умолчанию буферы трассировки имеют одинаковый размер для всех процессоров. Буферы трассировки выделяются страницами (блоками памяти, используемыми ядром; размер страницы обычно составляет 4 килобайта). Если последняя выделенная страница имеет размер больше запрошенного, реальный буфер будет использовать все пространство, а не то, которое запрошено и отображается. Отметим, что размер буферов не обязан быть кратным размеру страницы из-за метаданных управления буферами.
Размеры буферов для отдельных CPU могут различаться (см. per_cpu/cpu0/buffer_size_kb ниже) и в этом случае файл будет содержать значение X.
buffer_total_size_kb
Общий размер буферов трассировки в килобайтах.
free_buffer
Если процесс выполняет трассировку и кольцевой буфер нужно «освободить» после завершения процесса, даже если процесс уничтожен по сигналу, для освобождения буфера можно использовать этот файл. При закрытии данного файла размер кольцевого буфера снижается до минимального. Выполняющий трассировку процесс открывает этот файл и при завершении процесса дескриптор файла будет освобождаться. В результате файл будет закрыт и кольцевой буфер «освободится».
При установке опции disable_on_free трассировка будет останавливаться.
tracing_cpumask
Маска, позволяющая пользователю задать трассировку лишь для нужных CPU. Маска указывается в шестнадцатеричном формате.
set_ftrace_filter
При динамической трассировке (см. dynamic ftrace ниже) код динамически изменяется для запрета вызова профилировщика функций (mcount). Это позволяет выполнить трассировку практически без влияния на производительность. Однако имеется побочное влияние на включение или отключение трассировки определенных функций. Отправка (echo) имен функций в этот файл ограничивает трассировку лишь включенными в список функциями. Это влияет на трассировщики function и function_graph, а также на профилировку функций (см. function_profile_enabled).
Функции, перечисленные в файле available_filter_functions могут быть записаны в файл фильтров.
Этот интерфейс можно использовать также для команд (см. Команды фильтрации).
set_ftrace_notrace
Это антипод set_ftrace_filter и любая функция, добавленная в файл, не будет трассироваться. При указании функции в set_ftrace_filter и set_ftrace_notrace трассировка этой функции выполняться не будет.
set_ftrace_pid
Указывает трассировщику function отслеживать лишь функции с PID, указанным в этом файле.
Если установлена опция function-fork, при ветвлении задачи с указанным в этом файле PID, значения PID дочерних процессов будут автоматически добавляться в этот файл и дочерние процессы также будут отслеживаться. При завершении задачи PID удаляется из файла.
set_event_pid
Задает
трассировку событий лишь для процессов,
PID
указан
в этом файле.
Отметим,
sched_switch и sched_wake_up также будут отслеживать события, указанные в этом файле.
что
Для добавления в файл PID дочерних процессов при ветвлении следует включить опцию event-fork (по завершении процесса PID будет удаляться из файла).
set_graph_function
Функции, указанные в этом файле и вызванные ими функции, будут отслеживаться трассировщиком function_ graph (см. Динамическая трассировка ftrace). Отметим, что set_ftrace_filter и set_ftrace_notrace сохраняют влияние на набор отслеживаемых функций.
set_graph_notrace
Похожа на set_graph_function, но отключает трассировщик function_graph до выхода из указанной в этом файле функции. Это позволяет пропустить трассировку функций, вызываемых из заданной функции.
available_filter_functions
Этот файл содержит список функций, для которых возможна трассировка, указанных по именам. Эти функции можно передать в опции (файлы) set_ftrace_filter, set_ftrace_notrace, set_graph_function и set_graph_notrace для управления их трассировкой (см. Динамическая трассировка ftrace)
dyn_ftrace_total_info
Этот файл служит для отладки и указывает число функций, которые были преобразованы в nop и доступны для трассировки.
enabled_functions
Этот файл больше подходит для отладки ftrace, но может быть полезен и для выяснения функций, с которыми связаны обратные вызовы (callback). Средства трассировки использует не только инфраструктура ftrace, но и другие подсистемы. В этом файле указаны все функции, с которыми связаны callback, а также число обратных вызовов. Отметим, что callback может также вызывать множество функций, которые не учитываются в этом числе.
Если обратный вызов зарегистрирован для трассировки функцией с атрибутом сохранения регистров (save regs), что ведет к добавочному росту издержек, в строке возвращающей регистры функции будет выводиться символ R.
Если обратный вызов зарегистрирован для трассировки функцией с атрибутом ip modify (т. е. regs->ip можно изменить), в строке с функцией, которая может быть переопределена, выводится символ I.
Если архитектура позволяет, будет также показано, для каких callback возможен прямой вызов из функции. Если значение счетчика больше 1, это скорей всего будет ftrace_ops_list_func().
Если обратный вызов функции имеет переход к trampoline2, специфичному для callback, а не к стандартному trampoline, будет выводиться адрес перехода, а также функция, которую вызывает trampoline.
function_profile_enabled
При установке включает отслеживание всех функций с помощью трассировщика function или (если настроен) function_graph. Будет сохраняться гистограмма числа вызовов функций, а при настроенном трассировщике function_graph – еще и время, проведенное в этих функциях. Содержимое гистограммы размещается в файлах по процессорам trace_stat/function<cpu> (function0, function1 и т. д.).
trace_stat
Каталог, в котором хранится статистика трассировки.
kprobe_events
Включает точки динамической трассировки (см. файл kprobetrace.txt в документации ядра).
kprobe_profile
Статистика точек динамической трассировки (см. файл kprobetrace.txt в документации ядра).
max_graph_depth
Применяется с трассировщиком function_graph, задавая максимальную глубину трассировки внутрь функции. При установке в файле значения 1 будет показываться только первый вызов функции ядра из пользовательского пространства.
printk_formats
Этот файл служит для инструментов, читающих необработанные (raw) файлы. Если событие в кольцевом буфере указывается строкой, в буфер записывается лишь указатель, а не сама строка. Это не позволяет инструментам узнать, что было в строке. Данный файл отображает строки и адреса, позволяя инструментам сопоставлять указатели со строками.
saved_cmdlines
В трек событий записывается лишь pid задачи, если событие специально не сохраняет и команду. Ftrace кэширует отображения pid на команды, чтобы попытаться указать для событий и команду. Если pid для команды не указа, выводится <…>.
Если для опции record-cmd установлено значение 0, команды задач не сохраняются при записи. По умолчанию сохранение включено.
saved_cmdlines_size
По умолчанию сохраняется 128 команд (см. saved_cmdlines выше). Для изменения числа кэшируемых команд в этот файл записывается соответствующее число.
saved_tgids
При установленной опции record-tgid для каждого запланированного переключения контекста сохраняется идентификатор группы (Task Group ID) для задачи в таблице сопоставления PID потока с TGID. По умолчанию опция record-tgid выключена.
snapshot
Показывает «моментальный снимок» (snapshot) буфера и позволяет пользователю сделать снимок текущей трассировки (см. раздел Мгновенные снимки).
stack_max_size
При активизации трассировщика стека здесь будет указан максимальный наблюдаемый размер стека (см. раздел Трассировка стека).
stack_trace
Здесь указывается обратная трассировка наибольшего стека, который встретился при активизации трассировщика стека (см. раздел Трассировка стека).
stack_trace_filter
Похоже на set_ftrace_filter, но ограничивает функции, которые будет проверять трассировщик стека.
trace_clock
При записи события в кольцевой буфер указывается временная метка, которая берется из определенных часов. По умолчанию ftrace использует локальные часы (local). Это очень быстрые часы, привязанные к каждому процессору, но они могут быть не синхронизированы между разными CPU.
# cat trace_clock [local] global counter x86-tsc
Используемые часы указываются в квадратных скобках.
local
Используемые по умолчанию часы, которые могут быть не синхронизированы между CPU.
global
Эти часы синхронизированы на всех CPU, но могут быть медленней часов local.
counter
Это не часы, а счетчик, увеличивающий значение на 1 и синхронизированный со всеми CPU. Счетчик может быть полезен, когда нужно знать точный порядок событий, связанных с разными CPU.
uptime
Это счетчик циклов jiffy и временная метка относительно момента последней загрузки системы.
perf
Задает для ftrace использование тех же часов, которые применяются perf. В конечном счете perf сможет считывать буферы ftrace и это поможет при чередовании данных.
x86-tsc
Архитектура может задавать свои часы. Например, в x86 применяются часы TSC.
ppc-tb
Значение регистра timebase в powerpc. Оно синхронизировано для всех CPU и может служить для сопоставления событий между гипервизором и гостевыми системами, если известно значение tb_offset.
mono
Быстрый, монотонно возрастающий счетчик (CLOCK_MONOTONIC), согласуемый с NTP .
mono_raw
Быстрый, монотонно возрастающий счетчик (CLOCK_MONOTONIC_RAW) без возможности согласования частоты, работающий от аппаратного генератора сигналов.
boot
Загрузочные часы (CLOCK_BOOTTIME) на основе быстрого монотонно возрастающего счетчика, учитывающие время ожидания. Поскольку доступ к этим часам рассчитан на использование при трассировке на пути с остановками, возможны побочные эффекты при доступе к часам после того, как было учтено время приостановки до обновления монотонно возрастающего счетчика. В 32-битовых системах 64-битовое смещение загрузки может обновляться частично. Эти эффекты возникают редко и постобработка может справляться с ними (см. комментарии в функции ядра ktime_get_boot_fast_ns()).
Для выбора часов просто помещается их имя в файл trace_clock
# echo global > trace_clock
trace_marker
Этот файл очень полезен для синхронизации пользовательского пространства с событиями в ядре. Запись строки в этот файл копируется в буфер трассировки ftrace.
Полезно открыть этот файл в приложении при его запуске и просто сослаться на дескриптор файла
void trace_write(const char *fmt, ...) { va_list ap; char buf[256]; int n; if (trace_fd < 0) return; va_start(ap, fmt); n = vsnprintf(buf, 256, fmt, ap); va_end(ap); write(trace_fd, buf, n); } start:: trace_fd = open("trace_marker", WR_ONLY);
Примечание. Запись в файл trace_marker будет также вызывать запись в /sys/kernel/tracing/events/ftrace/print/trigger (см. Event triggers в файле Documentation/trace/events.rst и пример в разделе 3 файла trace/histogram.rst)
trace_marker_raw
Похож на trace_marker, но предназначен для записи двоичных данных, когда можно использовать инструмент для анализа.
uprobe_events
Добавляет в программу динамические точки трассировки (см. uprobetracer.txt).
uprobe_profile
Статистика uprobe (см. uprobetracer.txt).
instances
Это способ создания множества буферов трассировки, куда могут записываться разные события (см. Экземпляры).
events
Это каталог событий трассировки, в котором хранятся события точек трассировки (статические точки трассировки), встроенных в ядро. Это показывает наличие точек трассировки и их группировку в системе. Здесь расположены файлы «разрешения» для различных уровней, которые позволяют включить точки трассировки при записи 1 в соответствующий файл (см. events.txt).
set_event
Запись события в этот файл будет разрешать трассировку этого события (см. events.txt).
available_events
Список событий, трассировка которых может быть включена (см. events.txt).
timestamp_mode
Некоторые трассировщики могут менять режим временных меток, используемый при записи событий в кольцевой буфер. События с разными режимами меток могут сосуществовать в буфере, но действующий режим влияет на временную метку, записываемую с событием. По умолчанию применяется режим меток delta (приращение).
# cat timestamp_mode [delta] absolute
В квадратных скобках указывается действующий режим временных меток.
delta
Указывается интервал времени от предшествующего события в данном буфере. Применяется по умолчанию.
absolute
Временная метка содержит полное время, а не приращение от предыдущего значения. Эти метки занимают больше места и менее эффективны.
hwlat_detector
Каталог для детектора задержек в оборудовании (см. Определение аппаратной задержки).
per_cpu
Каталог с данными трассировки для каждого процессора. В качестве примера показан процессор 0.
per_cpu/cpu0/buffer_size_kb
Размер буфера ftrace для данного процессора. Отдельные буферы позволяют каждому процессору записывать данные самостоятельно без общего кэширования. Буферы разных процессоров могут различаться по размеру. Этот файл поход на buffer_size_kb file, но показывает размер буфера отдельного процессора.
per_cpu/cpu0/trace
Похож на файл trace, но отображает данные лишь для одного CPU. При записи очищается буфер только данного CPU.
per_cpu/cpu0/trace_pipe
Похож на trace_pipe и «потребляет» данные при чтении, но отображает (и потребляет) данные лишь одного CPU.
per_cpu/cpu0/trace_pipe_raw
Для инструментов, понимающих двоичный формат кольцевого буфера ftrace, файл trace_pipe_raw может служить для извлечения информации напрямую из кольцевого буфера. С помощью системного вызова splice() данные из буфера можно быстро перенести в файл или сеть для сбора на сервере.
Подобно trace_pipe, это потребляющий считыватель, где каждое чтение будет возвращать новые данные.
per_cpu/cpu0/snapshot
Похож на файл snapshot, но дает снимок только для одного CPU (если это поддерживается). При записи в файл очищается только буфер данного CPU.
per_cpu/cpu0/snapshot_raw
Похож на trace_pipe_raw, но считывает двоичные данные лишь из «моментального снимка» для данного CPU.
per_cpu/cpu0/stats
Статистика из кольцевого буфера процессора.
entries
Число событий, остающихся в буфере.
overrun
Число перезаписанных событий в результате заполнения буфера.
commit overrun
Значение в файле устанавливается, если произошло много событий внутри вложенного события (повторный вход в кольцевой буфер), заполнивших буфер и приведших к отбрасыванию событий. В нормальных условиях содержит 0.
bytes
Число реально прочитанных байтов (не перезаписанных).
oldest event ts
Временная метка самого старого события в буфере.
now ts
Текущая временная метка.
dropped events
События, потерянные в результате выключения опции перезаписи.
read events
Число
считываний событий.
Трассировщики
Ниже приведен список трассировщиков, которые могут быть настроены в системе.
function
Трассировщик
вызовов для всех функций ядра.
function_graph
Похож на трассировщик function, но отличается дополнительным отслеживанием выхода из функции, которое первый не выполняет. Это позволяет построить граф вызовов функций, похожий на исходный код языка C.
blk
Трассировщик блоков, применяемый пользовательским приложением blktrace.
hwlat
Трассировщик аппаратной задержки, служащий для обнаружения задержки в оборудовании. (см. Определение аппаратной задержки).
irqsoff
Трассирует области, где запрещены прерывания и сохраняет вызов с самой большой максимальной задержкой, заменяя имеющееся значение, если новый максимум превышает его. Лучше всего использовать эту трассировку со включенной опцией latency-format, которая автоматически включается при выборе трассировщика.
Этот трассировщик похож на preemptirqsoff,
но отслеживает
.
лишь максимальный интервал запрета
прерываний
preemptoff
Похож на трассировщик preemptirqsoff,
но отслеживает
.
лишь максимальный интервал запрета
вытеснения
preemptirqsoff
Похож на irqsoff и preemptoff, но трассирует и записывает лишь наибольшее время, когда irq и/или вытеснение были отключены.
wakeup
Трассировка максимальной задержки между пробуждением процесса с наивысшим приоритетом и запланированным его использованием.
wakeup_
rt
Трассировка максимальной задержки между пробуждением процесса с наивысшим приоритетом и запланированным его использованием. Этот трассировщик отслеживает лишь задачи, работающие в режиме реального времени (RT3), а задачи SCHED_OTHER
игнорируются.
wakeup_dl
Отслеживает и записывает максимальную задержку для задач SCHED_DEADLINE (подобно wakeup и wakeup_rt).
mmiotrace
Специальный трассировщик, служащий для отслеживания двоичных модулей. Он будет отслеживать все вызовы, которые модуль делает для оборудования.
branch
Этот трассировщик можно настроит на вероятные и маловероятные вызовы внутри ядра. Он будет отслеживать вероятные и маловероятные ветвления и оценивать корректность их предсказания.
nop
Отключает трассировку. Для выключения всех трассировщиков достаточно просто записать nop в файл current_tracer.
Примеры использования трассировщиков
Здесь представлены типовые примеры использования трассировщиков с управлением лишь через интерфейс tracefs (без утилит пользовательского пространства).
Выходной формат
Ниже представлен пример выходного формата файла трассировки (trace).
# tracer: function # # entries-in-buffer/entries-written: 140080/250280 #P:4 # # _-----=> irqs-off # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / delay # TASK-PID CPU# |||| TIMESTAMP FUNCTION # | | | |||| | | bash-1977 [000] .... 17284.993652: sys_close <-system_call_fastpath bash-1977 [000] .... 17284.993653: __close_fd <-sys_close bash-1977 [000] .... 17284.993653: _raw_spin_lock <-__close_fd sshd-1974 [003] .... 17284.993653: __srcu_read_unlock <-fsnotify bash-1977 [000] .... 17284.993654: add_preempt_count <-_raw_spin_lock bash-1977 [000] ...1 17284.993655: _raw_spin_unlock <-__close_fd bash-1977 [000] ...1 17284.993656: sub_preempt_count <-_raw_spin_unlock bash-1977 [000] .... 17284.993657: filp_close <-__close_fd bash-1977 [000] .... 17284.993657: dnotify_flush <-filp_close sshd-1974 [003] .... 17284.993658: sys_select <-system_call_fastpath ....
Заголовок выводится с именем трассировщика (в примере function). Затем выводится число событий в буфере и общее число сделанных записей. Разница между этими значениями показывает число потерянных событий (250280 – 140080 = 110200 потерянных событий).
В заголовке разъясняется содержимое событий. Имя задачи bash, ее идентификатор PID – 1977, задача работает на CPU 000, задержка (формат описан ниже, временная метка в формате <сек>.<мксек>, имя отслеживаемой функции (sys_close и родительская функция system_call_fastpath). Временная метка показывает момент входа в функцию.
Формат трассировки задержек
При включенной опции latency-format или установке одного из трассировщиков задержки файл trace содержит дополнительную информацию, позволяющую увидеть причину задержки. Ниже приведен пример трассировки.
# tracer: irqsoff # # irqsoff latency trace v1.1.5 on 3.8.0-test+ # -------------------------------------------------------------------- # latency: 259 us, #4/4, CPU#2 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:4) # ----------------- # | task: ps-6143 (uid:0 nice:0 policy:0 rt_prio:0) # ----------------- # => started at: __lock_task_sighand # => ended at: _raw_spin_unlock_irqrestore # # # _------=> CPU# # / _-----=> irqs-off # | / _----=> need-resched # || / _---=> hardirq/softirq # ||| / _--=> preempt-depth # |||| / delay # cmd pid ||||| time | caller # \ / ||||| \ | / ps-6143 2d... 0us!: trace_hardirqs_off <-__lock_task_sighand ps-6143 2d..1 259us+: trace_hardirqs_on <-_raw_spin_unlock_irqrestore ps-6143 2d..1 263us+: time_hardirqs_on <-_raw_spin_unlock_irqrestore ps-6143 2d..1 306us : <stack trace> => trace_hardirqs_on_caller => trace_hardirqs_on => _raw_spin_unlock_irqrestore => do_task_stat => proc_tgid_stat => proc_single_show => seq_read => vfs_read => sys_read => system_call_fastpath
Вывод показывает, что трассировщик irqsoff отслеживает время, на которое отключались прерывания. Указана версия трассировки (никогда не меняется) и версия ядра (3.8). Затем указана максимальная задержка в микросекундах (259), число показанных событий и общее их число (#4/4). VP, KP, SP и HP всегда имеют значение 0 (резерв на будущее). #P указывает число работающих процессоров CPU (#P:4).
В качестве задачи указывается процесс, который работал при возникновении задержки (ps pid: 6143).
Строки started at и ended at указывают функции, в которых прерывания были отключены и включены (соответственно), что и привело к задержке.
-
В функции __lock_task_sighand прерывания были отключены.
-
В функции _raw_spin_unlock_irqrestore прерывания были снова включены.
Далее следует заголовок, разъясняющий формат вывода, и результаты трассировки.
cmd
Имя процесса в трассировке.
pid
PID этого процесса.
CPU#
Номер CPU, на котором выполнялся процесс.
irqs-off
Символ d указывает запрет прерываний, в остальных случаях выводится точка (.). Если архитектура не поддерживает считывание переменной флагов irq, в этом поле всегда указывается символ X.
need-resched
- N – установлены оба флага TIF_NEED_RESCHED и PREEMPT_NEED_RESCHED;
- n – установлен только флаг TIF_NEED_RESCHED;
- p – установлен только флаг PREEMPT_NEED_RESCHED;
- . – иное.
hardirq/softirq
- Z – прерывание NMI внутри hardirq;
- z – NMI выполняется;
- H – аппаратное irq внутри softirq;
- h – аппаратное irq выполняется;
- s – программное irq выполняется;
- . – обычный контекст.
preempt-depth
Уровень preempt_disabled.
Описанное выше предназначено в основном для разработчиков ядра.
time:
При включенной опции latency-format вывод файла trace включает временные метки относительно начала трассировки, а при выключенной опции latency-format метки указываются в абсолютном времени.
delay
Это просто метки для упрощения зрительного восприятия, относящиеся к одному CPU и показывающие разницу между текущей и следующей трассировкой.
- $ – больше 1 секунды;
- @ – больше 100 миллисекунд;
- * – больше 10 миллисекунд;
- # – больше 1000 микросекунд;
- ! – больше 100 микросекунд;
- + – больше 10 микросекунд;
- ‘ ‘ – не больше 10 микросекунд.
Остальная часть вывода такая же как для файла trace.
Отметим, что трассировщики задержки обычно завершаются трассировкой назад, чтобы было увидеть место задержки.
Опции трассировки – файл trace_options
Файл trace_options (или каталог options) служит для управления выводом трассировки или манипуляций с трассировщиками. Для просмотра доступного просто введите команду cat.
cat trace_options print-parent nosym-offset nosym-addr noverbose noraw nohex nobin noblock trace_printk annotate nouserstacktrace nosym-userobj noprintk-msg-only context-info nolatency-format record-cmd norecord-tgid overwrite nodisable_on_free irq-info markers noevent-fork function-trace nofunction-fork nodisplay-graph nostacktrace nobranch
Для отключения нужной опции добавьте к ее имени префикс no и отправьте в файл trace_options4
echo noprint-parent > trace_options
Для включения опции отправьте ее имя в файл trace_options
echo sym-offset > trace_options
Список доступных опций приведен ниже.
print-parent
При трассировке функций задает вывод вызывающей (родительской) функции вместе с трассируемой.
print-parent
bash-4000 [01] 1477.606694: simple_strtoul <-kstrtoul
noprint-parent
bash-4000 [01] 1477.606694: simple_strtoul
sym-offset
Вывод не только имени функции, но и ее смещения. Например, вместо ktime_get будет ktime_get+0xb/0x20.
sym-offset
bash-4000 [01] 1477.606694: simple_strtoul+0x6/0xa0
sym-addr
Вывод адреса функции вместе с ее именем.
sym-addr
bash-4000 [01] 1477.606694: simple_strtoul <c0339346>
verbose
Задает подробный вывод в файле trace при включенной опции latency-format.
bash 4000 1 0 00000000 00010a95 [58127d26] 1720.415ms (+0.000ms): simple_strtoul (kstrtoul)
raw
Вывод необработанных (raw) чисел. Эта опция удобна для использования с приложениями, транслирующими необработанные числа лучше, чем это делает ядро.
hex
Похожа на raw, но числа выводятся в шестнадцатеричном формате.
bin
Вывод в необработанном двоичном формате.
block
При установленной опции считывание файла trace_pipe не будет блокироваться при опросе.
trace_printk
Может запрещать запись trace_printk() в буфер.
annotate
При заполненных буферах CPU может возникать путаница, когда в одном буфере имеется много свежих событий, а у другого событий немного и буфер будет содержать более старые события. При выводе трассировки сначала указываются более старые события и может показаться, что работает лишь один процессор. При включенном аннотировании будет указываться запуск нового буфера CPU.
<idle>-0 [001] dNs4 21169.031481: wake_up_idle_cpu <-add_timer_on <idle>-0 [001] dNs4 21169.031482: _raw_spin_unlock_irqrestore <-add_timer_on <idle>-0 [001] .Ns4 21169.031484: sub_preempt_count <-_raw_spin_unlock_irqrestore ##### CPU 2 buffer started #### <idle>-0 [002] .N.1 21169.031484: rcu_idle_exit <-cpu_idle <idle>-0 [001] .Ns3 21169.031484: _raw_spin_unlock <-clocksource_watchdog <idle>-0 [001] .Ns3 21169.031485: sub_preempt_count <-_raw_spin_unlock
userstacktrace
Эта опция меняет трассировку и ведет к записи трассировки стека (stacktrace) текущего потока пользовательского пространства после каждого трассируемого события.
sym-userobj
При включенной опции userstacktrace определяется, к какому объекту относится адрес и выводится относительный адрес. Это особенно полезно при включенном ASLR, поскольку иначе не будет возможности преобразовать адрес в объект/файл/строку после того, как приложение прекратит работу. Поиск выполняется при чтении trace или trace_pipe.
a.out-1623 [000] 40874.465068: /root/a.out[+0x480] <-/root/a.out[+0 x494] <- /root/a.out[+0x4a8] <- /lib/libc-2.7.so[+0x1e1a6]
printk-msg-only
При установке этой опции trace_printk() будет показывать только формат, а не параметры (если использовался вызов trace_bprintk() или trace_bputs() для сохранения trace_printk()).
context-info
Задает вывод только событий без команд, PID, временных меток, CPU и других данных.
latency-format
Эта опция меняет вывод трассировки и при включении обеспечивает дополнительную информацию о задержке, как описано в разделе Формат трассировки задержек.
record-cmd
При включении любого события или трассировщика в точке трассировки sched_switch включается ловушка для заполнения кэша сопоставлений pid и команд. Однако это может приводить к издержкам и если вам достаточно pid без имен задач, отключение этой опции может снизить влияние на трассировку (см. saved_cmdlines).
record-tgid
При включении любого события или трассировщика в точке трассировки sched_switch включается ловушка для заполнения кэша сопоставлений pid и TGID5 (см. saved_tgids).
overwrite
Управляет поведением при заполнении буфера трассировки. Значение 1 (принято по умолчанию) ведет к перезаписи самых старых событий, 0 – к перезаписи самых новых (см. per_cpu/cpu0/stats, где указаны переполнения и отбрасывания).
disable_on_free
При закрытии free_buffer трассировка будет прекращаться (устанавливается tracing_on = 0).
irq-info
Показывает прерывание, счетчик вытеснения и необходимость перепланировки данных. При отключенной опции трассировка будет иметь вид
# tracer: function # # entries-in-buffer/entries-written: 144405/9452052 #P:4 # # TASK-PID CPU# TIMESTAMP FUNCTION # | | | | | <idle>-0 [002] 23636.756054: ttwu_do_activate.constprop.89 <-try_to_wake_up <idle>-0 [002] 23636.756054: activate_task <-ttwu_do_activate.constprop.89 <idle>-0 [002] 23636.756055: enqueue_task <-activate_task
markers
Установка опции открывает файл trace_marker для записи (пользователю root). При отключенной опции запись в trace_marker будет приводить к ошибке EINVAL.
event-fork
При установке опции для задач, PID которых указаны в set_event_pid, в этот файл будут добавляться при ветвлении PID дочерних процессов. При завершении задачи с PID, указанным в set_event_pid, значение PID будет удаляться из файла.
function-trace
При включенной опции трассировщики задержек будут включать трассировку функций (принято по умолчанию). Если опция не установлена при трассировке задержки функции отслеживаться не будут для снижения издержек.
function-fork
При установке опции для задач, PID которых указаны в set_ftrace_pid, в этот файл будут добавляться при ветвлении PID дочерних процессов. При завершении задачи с PID, указанным в set_ftrace_pid exit, значение PID будет удаляться из файла.
display-graph
При включенной опции трассировщик задержек (irqsoff, wakeup и т. п.) будет применять трассировку function_graph вместо function.
stacktrace
При включенной опции записывается трассировка стека после записи любого события.
branch
Разрешает трассировку ветвления, включая трассировщик branch вместе с текущим трассировщиком. Включение этой опции с трассировщиком nop эквивалентно простому включению трассировщика branch.
Примечание. У некоторых трассировщиков имеются свои опции, которые выводятся в файле только при их активизации. Однако они всегда присутствуют в каталоге options.
Ниже рассматриваются опции отдельных трассировщиков.
Опции трассировщика function
func_stack_trace
При установке этой опции записывается трассировка стека после записи каждой функции. Перед включением этой опции важно ограничить число отслеживаемых функций с помощью фильтра set_ftrace_filter, поскольку иначе производительность системы сильно снизится. Не забывает отключить эту опцию до отмены фильтра отслеживаемых функций.
Опции трассировщика function_graph
Поскольку трассировщик function_graph отличается выводом, у него имеются свои опции для контроля вывода.
funcgraph-overrun
При установке опции «переполнение» стека function_graph отображается после каждой отслеживаемой функции. Переполнение возникает, когда грубина стека вызовов превышает значение, зарезервированное для каждой задачи. Каждая задача имеет фиксированный массив функций для отслеживания в графе вызовов. Если глубина вызовов превышает предел, функция не отслеживается. Переполнение – это число функций, пропущенных в результате превышения массива.
funcgraph-cpu
При установке опции отображается номер CPU, где выполнялась трассировка.
funcgraph-overhead
При установленной опции отображается маркер задержки, если функция заняла время, превышающее некое значение.
funcgraph-proc
В отличие от других трассировщиков командная строка здесь по умолчанию не отображается и указывается лишь при отслеживании задачи во время переключения контекста. Включение этой опции ведет к выводу команды каждого процесса в каждой строке.
funcgraph-duration
В конце каждой функции (возврат) выводится продолжительность ее работы в микросекундах.
funcgraph-abstime
При установке опции в каждой строке выводится временная метка.
funcgraph-irqs
При отключенной опции функции внутри прерываний не отслеживаются.
funcgraph-tail
При установленной опции событие возврата будет включать представляемую им функцию. По умолчанию это отключено и при возврате из функции отображается лишь фигурная скобка }.
sleep-time
Служит для учета запланированного времени задачи при работе трассировщика function_graph. При включенной опции запланированное время будет учитываться как часть вызова функции.
graph-time
При запуске профилировщика функций с трассировщиком function_graph служит для включения времени вызова вложенных функций. Если опция не задана, указываемое для функции время будет включать только время выполнения самой функции без учета вызываемых ею функций.
Опции трассировщика blk
blk_classic
Задает минимальный
вывод.
Трассировщик irqsoff
При отключенных прерываниях CPU не может реагировать на другие внешние события, кроме NMI и SMI. В результате замедляется реакция системы на события.
Трассировщик irqsoff отслеживает периоды запрета прерываний. При достижении нового значения максимальной задержки трассировщик сохраняет трассировку к точке задержки, чтобы при достижении нового максимума прежнее значения отбрасывалось, а новое записывалось.
Для сброса максимума записывается значение 0 в файл tracing_max_latency. Ниже приведен пример.
# echo 0 > options/function-trace # echo irqsoff > current_tracer # echo 1 > tracing_on # echo 0 > tracing_max_latency # ls -ltr [...] # echo 0 > tracing_on # cat trace # tracer: irqsoff # # irqsoff latency trace v1.1.5 on 3.8.0-test+ # -------------------------------------------------------------------- # latency: 16 us, #4/4, CPU#0 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:4) # ----------------- # | task: swapper/0-0 (uid:0 nice:0 policy:0 rt_prio:0) # ----------------- # => started at: run_timer_softirq # => ended at: run_timer_softirq # # # _------=> CPU# # / _-----=> irqs-off # | / _----=> need-resched # || / _---=> hardirq/softirq # ||| / _--=> preempt-depth # |||| / delay # cmd pid ||||| time | caller # \ / ||||| \ | / <idle>-0 0d.s2 0us+: _raw_spin_lock_irq <-run_timer_softirq <idle>-0 0dNs3 17us : _raw_spin_unlock_irq <-run_timer_softirq <idle>-0 0dNs3 17us+: trace_hardirqs_on <-run_timer_softirq <idle>-0 0dNs3 25us : <stack trace> => _raw_spin_unlock_irq => run_timer_softirq => __do_softirq => call_softirq => do_softirq => irq_exit => smp_apic_timer_interrupt => apic_timer_interrupt => rcu_idle_exit => cpu_idle => rest_init => start_kernel => x86_64_start_reservations => x86_64_start_kernel
Вывод показывает задержку 16 мксек (очень хорошо). Прерывания отключены функцией _raw_spin_lock_irq в run_timer_softirq. Разница между задержкой в 16 мксек и показанной временной меткой 25us обусловлена инкрементированием часов между моментами записи максимальной задержки и функции, вызвавшей эту задержку.
Отметим, что в приведенном выше примере опция function-trace не была установлена и при ее установке вывод будет более подробным, как показано ниже.
with echo 1 > options/function-trace # tracer: irqsoff # # irqsoff latency trace v1.1.5 on 3.8.0-test+ # -------------------------------------------------------------------- # latency: 71 us, #168/168, CPU#3 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:4) # ----------------- # | task: bash-2042 (uid:0 nice:0 policy:0 rt_prio:0) # ----------------- # => started at: ata_scsi_queuecmd # => ended at: ata_scsi_queuecmd # # # _------=> CPU# # / _-----=> irqs-off # | / _----=> need-resched # || / _---=> hardirq/softirq # ||| / _--=> preempt-depth # |||| / delay # cmd pid ||||| time | caller # \ / ||||| \ | / bash-2042 3d... 0us : _raw_spin_lock_irqsave <-ata_scsi_queuecmd bash-2042 3d... 0us : add_preempt_count <-_raw_spin_lock_irqsave bash-2042 3d..1 1us : ata_scsi_find_dev <-ata_scsi_queuecmd bash-2042 3d..1 1us : __ata_scsi_find_dev <-ata_scsi_find_dev bash-2042 3d..1 2us : ata_find_dev.part.14 <-__ata_scsi_find_dev bash-2042 3d..1 2us : ata_qc_new_init <-__ata_scsi_queuecmd bash-2042 3d..1 3us : ata_sg_init <-__ata_scsi_queuecmd bash-2042 3d..1 4us : ata_scsi_rw_xlat <-__ata_scsi_queuecmd bash-2042 3d..1 4us : ata_build_rw_tf <-ata_scsi_rw_xlat [...] bash-2042 3d..1 67us : delay_tsc <-__delay bash-2042 3d..1 67us : add_preempt_count <-delay_tsc bash-2042 3d..2 67us : sub_preempt_count <-delay_tsc bash-2042 3d..1 67us : add_preempt_count <-delay_tsc bash-2042 3d..2 68us : sub_preempt_count <-delay_tsc bash-2042 3d..1 68us+: ata_bmdma_start <-ata_bmdma_qc_issue bash-2042 3d..1 71us : _raw_spin_unlock_irqrestore <-ata_scsi_queuecmd bash-2042 3d..1 71us : _raw_spin_unlock_irqrestore <-ata_scsi_queuecmd bash-2042 3d..1 72us+: trace_hardirqs_on <-ata_scsi_queuecmd bash-2042 3d..1 120us : <stack trace> => _raw_spin_unlock_irqrestore => ata_scsi_queuecmd => scsi_dispatch_cmd => scsi_request_fn => __blk_run_queue_uncond => __blk_run_queue => blk_queue_bio => generic_make_request => submit_bio => submit_bh => __ext3_get_inode_loc => ext3_iget => ext3_lookup => lookup_real => __lookup_hash => walk_component => lookup_last => path_lookupat => filename_lookup => user_path_at_empty => user_path_at => vfs_fstatat => vfs_stat => sys_newstat => system_call_fastpath
Здесь показана задержка в 71 мксек, но видны функции, которые вызвались в это время. Отметим, что включение трассировки функций увеличило издержки, которые могли привести к росту задержки. Тем не менее, эта трассировка содержит много полезной для отладки информации.
Трассировщик preemptoff
При отключенном вытеснении (preemption) прерывания будут работать, но задачи не могут вытеснять друг друга и в результате задаче с высоким приоритетом может потребоваться ожидать возможности вытеснения менее приоритетной задачи.
Трассировщик preemptoff отслеживает места, где вытеснение запрещено. Подобно трассировщику irqsoff, он записывает максимальную задержку, для которой вытеснение было отключено. Управление трассировщиком preemptoff похоже на управление irqsoff.
# echo 0 > options/function-trace # echo preemptoff > current_tracer # echo 1 > tracing_on # echo 0 > tracing_max_latency # ls -ltr [...] # echo 0 > tracing_on # cat trace # tracer: preemptoff # # preemptoff latency trace v1.1.5 on 3.8.0-test+ # -------------------------------------------------------------------- # latency: 46 us, #4/4, CPU#1 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:4) # ----------------- # | task: sshd-1991 (uid:0 nice:0 policy:0 rt_prio:0) # ----------------- # => started at: do_IRQ # => ended at: do_IRQ # # # _------=> CPU# # / _-----=> irqs-off # | / _----=> need-resched # || / _---=> hardirq/softirq # ||| / _--=> preempt-depth # |||| / delay # cmd pid ||||| time | caller # \ / ||||| \ | / sshd-1991 1d.h. 0us+: irq_enter <-do_IRQ sshd-1991 1d..1 46us : irq_exit <-do_IRQ sshd-1991 1d..1 47us+: trace_preempt_on <-do_IRQ sshd-1991 1d..1 52us : <stack trace> => sub_preempt_count => irq_exit => do_IRQ => ret_from_intr
Здесь видны некоторые отличия. Вытеснение было отключено во время прерывания (символ h) и восстановлено по завершении. Однако можно видеть, что прерывания были отключены при входе в раздел preempt off и на выходе из него (символ d). Неизвестно, были ли прерывания включены в это время или вскоре после него.
# tracer: preemptoff # # preemptoff latency trace v1.1.5 on 3.8.0-test+ # -------------------------------------------------------------------- # latency: 83 us, #241/241, CPU#1 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:4) # ----------------- # | task: bash-1994 (uid:0 nice:0 policy:0 rt_prio:0) # ----------------- # => started at: wake_up_new_task # => ended at: task_rq_unlock # # # _------=> CPU# # / _-----=> irqs-off # | / _----=> need-resched # || / _---=> hardirq/softirq # ||| / _--=> preempt-depth # |||| / delay # cmd pid ||||| time | caller # \ / ||||| \ | / bash-1994 1d..1 0us : _raw_spin_lock_irqsave <-wake_up_new_task bash-1994 1d..1 0us : select_task_rq_fair <-select_task_rq bash-1994 1d..1 1us : __rcu_read_lock <-select_task_rq_fair bash-1994 1d..1 1us : source_load <-select_task_rq_fair bash-1994 1d..1 1us : source_load <-select_task_rq_fair [...] bash-1994 1d..1 12us : irq_enter <-smp_apic_timer_interrupt bash-1994 1d..1 12us : rcu_irq_enter <-irq_enter bash-1994 1d..1 13us : add_preempt_count <-irq_enter bash-1994 1d.h1 13us : exit_idle <-smp_apic_timer_interrupt bash-1994 1d.h1 13us : hrtimer_interrupt <-smp_apic_timer_interrupt bash-1994 1d.h1 13us : _raw_spin_lock <-hrtimer_interrupt bash-1994 1d.h1 14us : add_preempt_count <-_raw_spin_lock bash-1994 1d.h2 14us : ktime_get_update_offsets <-hrtimer_interrupt [...] bash-1994 1d.h1 35us : lapic_next_event <-clockevents_program_event bash-1994 1d.h1 35us : irq_exit <-smp_apic_timer_interrupt bash-1994 1d.h1 36us : sub_preempt_count <-irq_exit bash-1994 1d..2 36us : do_softirq <-irq_exit bash-1994 1d..2 36us : __do_softirq <-call_softirq bash-1994 1d..2 36us : __local_bh_disable <-__do_softirq bash-1994 1d.s2 37us : add_preempt_count <-_raw_spin_lock_irq bash-1994 1d.s3 38us : _raw_spin_unlock <-run_timer_softirq bash-1994 1d.s3 39us : sub_preempt_count <-_raw_spin_unlock bash-1994 1d.s2 39us : call_timer_fn <-run_timer_softirq [...] bash-1994 1dNs2 81us : cpu_needs_another_gp <-rcu_process_callbacks bash-1994 1dNs2 82us : __local_bh_enable <-__do_softirq bash-1994 1dNs2 82us : sub_preempt_count <-__local_bh_enable bash-1994 1dN.2 82us : idle_cpu <-irq_exit bash-1994 1dN.2 83us : rcu_irq_exit <-irq_exit bash-1994 1dN.2 83us : sub_preempt_count <-irq_exit bash-1994 1.N.1 84us : _raw_spin_unlock_irqrestore <-task_rq_unlock bash-1994 1.N.1 84us+: trace_preempt_on <-task_rq_unlock bash-1994 1.N.1 104us : <stack trace> => sub_preempt_count => _raw_spin_unlock_irqrestore => task_rq_unlock => wake_up_new_task => do_fork => sys_clone => stub_clone
Выше приведен пример трассировки preemptoff с включенной опцией function-trace. Здесь видно, что прерывания были отключены не все время. Код irq_enter указывает вход в прерывание h. Перед этим отслеживаемые функции показывают, что они не находятся в прерывании, но из самих функций видно, что это не так.
Трассировщик preemptirqsoff
Полезно знать места, где прерывания или вытеснение были отключены на самое продолжительное время. Однако иногда нужно знать, когда прерывания и/или вытеснение были отключены. Рассмотрим пример кода.
local_irq_disable(); call_function_with_irqs_off(); preempt_disable(); call_function_with_irqs_and_preemption_off(); local_irq_enable(); call_function_with_preemption_off(); preempt_enable();
Трассировщик irqsoff записывает общую продолжительность вызовов call_function_with_irqs_off() и call_function_with_irqs_and_preemption_off(), preemptoff – общую продолжительность call_function_with_irqs_and_preemption_off() и call_function_with_preemption_off(). Но ни один из них не записывает время, в течение которого прерывания и/или вытеснение были отключены. Это общее время, которое мы не можем планировать. Для записи этого времени служит трассировщик preemptirqsoff.
Работа с этим трассировщиком похожа на действия с irqsoff и preemptoff.
# echo 0 > options/function-trace # echo preemptirqsoff > current_tracer # echo 1 > tracing_on # echo 0 > tracing_max_latency # ls -ltr [...] # echo 0 > tracing_on # cat trace # tracer: preemptirqsoff # # preemptirqsoff latency trace v1.1.5 on 3.8.0-test+ # -------------------------------------------------------------------- # latency: 100 us, #4/4, CPU#3 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:4) # ----------------- # | task: ls-2230 (uid:0 nice:0 policy:0 rt_prio:0) # ----------------- # => started at: ata_scsi_queuecmd # => ended at: ata_scsi_queuecmd # # # _------=> CPU# # / _-----=> irqs-off # | / _----=> need-resched # || / _---=> hardirq/softirq # ||| / _--=> preempt-depth # |||| / delay # cmd pid ||||| time | caller # \ / ||||| \ | / ls-2230 3d... 0us+: _raw_spin_lock_irqsave <-ata_scsi_queuecmd ls-2230 3...1 100us : _raw_spin_unlock_irqrestore <-ata_scsi_queuecmd ls-2230 3...1 101us+: trace_preempt_on <-ata_scsi_queuecmd ls-2230 3...1 111us : <stack trace> => sub_preempt_count => _raw_spin_unlock_irqrestore => ata_scsi_queuecmd => scsi_dispatch_cmd => scsi_request_fn => __blk_run_queue_uncond => __blk_run_queue => blk_queue_bio => generic_make_request => submit_bio => submit_bh => ext3_bread => ext3_dir_bread => htree_dirblock_to_tree => ext3_htree_fill_tree => ext3_readdir => vfs_readdir => sys_getdents => system_call_fastpath
Вызов trace_hardirqs_off_thunk выполняется из сборки кода x86 когда прерывания запрещены в этой сборке. Без трассировки функций мы не узнаем, были ли прерывания запрещены в пределах точек вытеснения. Видно, что это начинается с включения вытеснения. Ниже показана трассировка с включенной опцией function-trace.
# tracer: preemptirqsoff # # preemptirqsoff latency trace v1.1.5 on 3.8.0-test+ # -------------------------------------------------------------------- # latency: 161 us, #339/339, CPU#3 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:4) # ----------------- # | task: ls-2269 (uid:0 nice:0 policy:0 rt_prio:0) # ----------------- # => started at: schedule # => ended at: mutex_unlock # # # _------=> CPU# # / _-----=> irqs-off # | / _----=> need-resched # || / _---=> hardirq/softirq # ||| / _--=> preempt-depth # |||| / delay # cmd pid ||||| time | caller # \ / ||||| \ | / kworker/-59 3...1 0us : __schedule <-schedule kworker/-59 3d..1 0us : rcu_preempt_qs <-rcu_note_context_switch kworker/-59 3d..1 1us : add_preempt_count <-_raw_spin_lock_irq kworker/-59 3d..2 1us : deactivate_task <-__schedule kworker/-59 3d..2 1us : dequeue_task <-deactivate_task kworker/-59 3d..2 2us : update_rq_clock <-dequeue_task kworker/-59 3d..2 2us : dequeue_task_fair <-dequeue_task kworker/-59 3d..2 2us : update_curr <-dequeue_task_fair kworker/-59 3d..2 2us : update_min_vruntime <-update_curr kworker/-59 3d..2 3us : cpuacct_charge <-update_curr kworker/-59 3d..2 3us : __rcu_read_lock <-cpuacct_charge kworker/-59 3d..2 3us : __rcu_read_unlock <-cpuacct_charge kworker/-59 3d..2 3us : update_cfs_rq_blocked_load <-dequeue_task_fair kworker/-59 3d..2 4us : clear_buddies <-dequeue_task_fair kworker/-59 3d..2 4us : account_entity_dequeue <-dequeue_task_fair kworker/-59 3d..2 4us : update_min_vruntime <-dequeue_task_fair kworker/-59 3d..2 4us : update_cfs_shares <-dequeue_task_fair kworker/-59 3d..2 5us : hrtick_update <-dequeue_task_fair kworker/-59 3d..2 5us : wq_worker_sleeping <-__schedule kworker/-59 3d..2 5us : kthread_data <-wq_worker_sleeping kworker/-59 3d..2 5us : put_prev_task_fair <-__schedule kworker/-59 3d..2 6us : pick_next_task_fair <-pick_next_task kworker/-59 3d..2 6us : clear_buddies <-pick_next_task_fair kworker/-59 3d..2 6us : set_next_entity <-pick_next_task_fair kworker/-59 3d..2 6us : update_stats_wait_end <-set_next_entity ls-2269 3d..2 7us : finish_task_switch <-__schedule ls-2269 3d..2 7us : _raw_spin_unlock_irq <-finish_task_switch ls-2269 3d..2 8us : do_IRQ <-ret_from_intr ls-2269 3d..2 8us : irq_enter <-do_IRQ ls-2269 3d..2 8us : rcu_irq_enter <-irq_enter ls-2269 3d..2 9us : add_preempt_count <-irq_enter ls-2269 3d.h2 9us : exit_idle <-do_IRQ [...] ls-2269 3d.h3 20us : sub_preempt_count <-_raw_spin_unlock ls-2269 3d.h2 20us : irq_exit <-do_IRQ ls-2269 3d.h2 21us : sub_preempt_count <-irq_exit ls-2269 3d..3 21us : do_softirq <-irq_exit ls-2269 3d..3 21us : __do_softirq <-call_softirq ls-2269 3d..3 21us+: __local_bh_disable <-__do_softirq ls-2269 3d.s4 29us : sub_preempt_count <-_local_bh_enable_ip ls-2269 3d.s5 29us : sub_preempt_count <-_local_bh_enable_ip ls-2269 3d.s5 31us : do_IRQ <-ret_from_intr ls-2269 3d.s5 31us : irq_enter <-do_IRQ ls-2269 3d.s5 31us : rcu_irq_enter <-irq_enter [...] ls-2269 3d.s5 31us : rcu_irq_enter <-irq_enter ls-2269 3d.s5 32us : add_preempt_count <-irq_enter ls-2269 3d.H5 32us : exit_idle <-do_IRQ ls-2269 3d.H5 32us : handle_irq <-do_IRQ ls-2269 3d.H5 32us : irq_to_desc <-handle_irq ls-2269 3d.H5 33us : handle_fasteoi_irq <-handle_irq [...] ls-2269 3d.s5 158us : _raw_spin_unlock_irqrestore <-rtl8139_poll ls-2269 3d.s3 158us : net_rps_action_and_irq_enable.isra.65 <-net_rx_action ls-2269 3d.s3 159us : __local_bh_enable <-__do_softirq ls-2269 3d.s3 159us : sub_preempt_count <-__local_bh_enable ls-2269 3d..3 159us : idle_cpu <-irq_exit ls-2269 3d..3 159us : rcu_irq_exit <-irq_exit ls-2269 3d..3 160us : sub_preempt_count <-irq_exit ls-2269 3d... 161us : __mutex_unlock_slowpath <-mutex_unlock ls-2269 3d... 162us+: trace_hardirqs_on <-mutex_unlock ls-2269 3d... 186us : <stack trace> => __mutex_unlock_slowpath => mutex_unlock => process_output => n_tty_write => tty_write => vfs_write => sys_write => system_call_fastpath
Эта интересная трассировка, которая начинается с того, что процесс kworker работает и запланирован, но ls берет управление на себя. Как только ls освобождает rq и разрешает прерывания (но не вытеснение), сразу срабатывает прерывание. По завершении прерывания запускаются softirq, но во время работы softirq происходит другое прерывание, завершение которого внутри softirq указывается символом H.
Трассировщик wakeup
Одним из основных случаев, представляющих интерес для отслеживания, является время, требуемое для выполнения задачи, которая пробуждается для реальной работы. Это не задачи в реальном масштабе времени (RT), а обычные задачи. Но их трассировка не менее интересна.
Ниже приведена трассировка без отслеживания функций.
# echo 0 > options/function-trace # echo wakeup > current_tracer # echo 1 > tracing_on # echo 0 > tracing_max_latency # chrt -f 5 sleep 1 # echo 0 > tracing_on # cat trace # tracer: wakeup # # wakeup latency trace v1.1.5 on 3.8.0-test+ # -------------------------------------------------------------------- # latency: 15 us, #4/4, CPU#3 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:4) # ----------------- # | task: kworker/3:1H-312 (uid:0 nice:-20 policy:0 rt_prio:0) # ----------------- # # _------=> CPU# # / _-----=> irqs-off # | / _----=> need-resched # || / _---=> hardirq/softirq # ||| / _--=> preempt-depth # |||| / delay # cmd pid ||||| time | caller # \ / ||||| \ | / <idle>-0 3dNs7 0us : 0:120:R + [003] 312:100:R kworker/3:1H <idle>-0 3dNs7 1us+: ttwu_do_activate.constprop.87 <-try_to_wake_up <idle>-0 3d..3 15us : __schedule <-schedule <idle>-0 3d..3 15us : 0:120:R ==> [003] 312:100:R kworker/3:1H
Трассировщик отслеживает лишь задачи с наиболее высоким приоритетом в системе, не трассируя обычные ситуации. Можно видеть, что для задачи kworker с приоритетом nice = of -20 (не очень высокий) прошло лишь 15 мксек между пробуждением и началом работы.
Задачи, не работающие в режиме реального времени (RT), не так интересны, по сравнению с задачами RT.
Трассировщик wakeup_rt
В среде RT очень важно знать, какое время проходит между пробуждением задачи с наивысшим приоритетом и началом ее выполнения. Это время называют также задержкой планирования (schedule latency). Подчеркнем, что речь здесь идет только о задачах RT. Важно также понимать задержку планирования для задач не-RT, но для них лучше средняя задержка планирования. Для таких измерений лучше подходят такие инструменты, как LatencyTop.
Для сред RT интересна задержка в худшей ситуации. Это наибольшая, а не средняя задержка. У нас может быть очень быстрый планировщик, который будет редко приводить к значительным задержкам, но он может быть недостаточно хорош для задач RT. Трассировщик wakeup_rt был разработан для определения худшего варианта пробуждения задач RT. Другие (не-RT) задачи не отслеживаются, поскольку трассировщик записывает один наихудший случай, а отслеживание задач не-RT, дающее непредсказуемый результат, будет переписывать наихудшую задержку для задач RT (это легко увидеть при запуске трассировщика wakeup).
Поскольку этот трассировщик работает только с задачами RT, воспользуемся для него вместо привычной команды ls командой sleep 1 в chrt, где меняется приоритет задачи.
# echo 0 > options/function-trace # echo wakeup_rt > current_tracer # echo 1 > tracing_on # echo 0 > tracing_max_latency # chrt -f 5 sleep 1 # echo 0 > tracing_on # cat trace # tracer: wakeup # # tracer: wakeup_rt # # wakeup_rt latency trace v1.1.5 on 3.8.0-test+ # -------------------------------------------------------------------- # latency: 5 us, #4/4, CPU#3 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:4) # ----------------- # | task: sleep-2389 (uid:0 nice:0 policy:1 rt_prio:5) # ----------------- # # _------=> CPU# # / _-----=> irqs-off # | / _----=> need-resched # || / _---=> hardirq/softirq # ||| / _--=> preempt-depth # |||| / delay # cmd pid ||||| time | caller # \ / ||||| \ | / <idle>-0 3d.h4 0us : 0:120:R + [003] 2389: 94:R sleep <idle>-0 3d.h4 1us+: ttwu_do_activate.constprop.87 <-try_to_wake_up <idle>-0 3d..3 5us : __schedule <-schedule <idle>-0 3d..3 5us : 0:120:R ==> [003] 2389: 94:R sleep
Запустив это в режиме ожидания, видим, что для переключения задачи потребовалось лишь 5 мксек. Отметим, что трассировка была остановлена к моменту, когда записываемая задача была запланирована, поскольку точка трассировки в планировании расположена перед фактическим переключателем (switch). Это можно изменить, добавив новый маркер в конце планировщика.
Отметим, что записываемая задача «спит» (sleep) с PID = 2389 и имеет rt_prio = 5. Этот приоритет относится к пользовательскому пространству, а не к ядру. Для SCHED_FIFO используется правило 1, для SCHED_RR – 2.
Отметим, что данные трассировки показывают внутренний приоритет (99 – rt_prio).
<idle>-0 3d..3 5us : 0:120:R ==> [003] 2389: 94:R sleep
Запись 0:120:R означает бездействие с приоритетом nice = 0 (120 — 120), в рабочем состоянии (R). Спящая задача запланирована с 2389: 94:R. Т. е. приоритет является приоритетом ядра rt_prio (99 – 5 = 94) и задача также выполняется.
Повторим это с chrt -r 5 и установленной опцией function-trace.
echo 1 > options/function-trace # tracer: wakeup_rt # # wakeup_rt latency trace v1.1.5 on 3.8.0-test+ # -------------------------------------------------------------------- # latency: 29 us, #85/85, CPU#3 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:4) # ----------------- # | task: sleep-2448 (uid:0 nice:0 policy:1 rt_prio:5) # ----------------- # # _------=> CPU# # / _-----=> irqs-off # | / _----=> need-resched # || / _---=> hardirq/softirq # ||| / _--=> preempt-depth # |||| / delay # cmd pid ||||| time | caller # \ / ||||| \ | / <idle>-0 3d.h4 1us+: 0:120:R + [003] 2448: 94:R sleep <idle>-0 3d.h4 2us : ttwu_do_activate.constprop.87 <-try_to_wake_up <idle>-0 3d.h3 3us : check_preempt_curr <-ttwu_do_wakeup <idle>-0 3d.h3 3us : resched_curr <-check_preempt_curr <idle>-0 3dNh3 4us : task_woken_rt <-ttwu_do_wakeup <idle>-0 3dNh3 4us : _raw_spin_unlock <-try_to_wake_up <idle>-0 3dNh3 4us : sub_preempt_count <-_raw_spin_unlock <idle>-0 3dNh2 5us : ttwu_stat <-try_to_wake_up <idle>-0 3dNh2 5us : _raw_spin_unlock_irqrestore <-try_to_wake_up <idle>-0 3dNh2 6us : sub_preempt_count <-_raw_spin_unlock_irqrestore <idle>-0 3dNh1 6us : _raw_spin_lock <-__run_hrtimer <idle>-0 3dNh1 6us : add_preempt_count <-_raw_spin_lock <idle>-0 3dNh2 7us : _raw_spin_unlock <-hrtimer_interrupt <idle>-0 3dNh2 7us : sub_preempt_count <-_raw_spin_unlock <idle>-0 3dNh1 7us : tick_program_event <-hrtimer_interrupt <idle>-0 3dNh1 7us : clockevents_program_event <-tick_program_event <idle>-0 3dNh1 8us : ktime_get <-clockevents_program_event <idle>-0 3dNh1 8us : lapic_next_event <-clockevents_program_event <idle>-0 3dNh1 8us : irq_exit <-smp_apic_timer_interrupt <idle>-0 3dNh1 9us : sub_preempt_count <-irq_exit <idle>-0 3dN.2 9us : idle_cpu <-irq_exit <idle>-0 3dN.2 9us : rcu_irq_exit <-irq_exit <idle>-0 3dN.2 10us : rcu_eqs_enter_common.isra.45 <-rcu_irq_exit <idle>-0 3dN.2 10us : sub_preempt_count <-irq_exit <idle>-0 3.N.1 11us : rcu_idle_exit <-cpu_idle <idle>-0 3dN.1 11us : rcu_eqs_exit_common.isra.43 <-rcu_idle_exit <idle>-0 3.N.1 11us : tick_nohz_idle_exit <-cpu_idle <idle>-0 3dN.1 12us : menu_hrtimer_cancel <-tick_nohz_idle_exit <idle>-0 3dN.1 12us : ktime_get <-tick_nohz_idle_exit <idle>-0 3dN.1 12us : tick_do_update_jiffies64 <-tick_nohz_idle_exit <idle>-0 3dN.1 13us : cpu_load_update_nohz <-tick_nohz_idle_exit <idle>-0 3dN.1 13us : _raw_spin_lock <-cpu_load_update_nohz <idle>-0 3dN.1 13us : add_preempt_count <-_raw_spin_lock <idle>-0 3dN.2 13us : __cpu_load_update <-cpu_load_update_nohz <idle>-0 3dN.2 14us : sched_avg_update <-__cpu_load_update <idle>-0 3dN.2 14us : _raw_spin_unlock <-cpu_load_update_nohz <idle>-0 3dN.2 14us : sub_preempt_count <-_raw_spin_unlock <idle>-0 3dN.1 15us : calc_load_nohz_stop <-tick_nohz_idle_exit <idle>-0 3dN.1 15us : touch_softlockup_watchdog <-tick_nohz_idle_exit <idle>-0 3dN.1 15us : hrtimer_cancel <-tick_nohz_idle_exit <idle>-0 3dN.1 15us : hrtimer_try_to_cancel <-hrtimer_cancel <idle>-0 3dN.1 16us : lock_hrtimer_base.isra.18 <-hrtimer_try_to_cancel <idle>-0 3dN.1 16us : _raw_spin_lock_irqsave <-lock_hrtimer_base.isra.18 <idle>-0 3dN.1 16us : add_preempt_count <-_raw_spin_lock_irqsave <idle>-0 3dN.2 17us : __remove_hrtimer <-remove_hrtimer.part.16 <idle>-0 3dN.2 17us : hrtimer_force_reprogram <-__remove_hrtimer <idle>-0 3dN.2 17us : tick_program_event <-hrtimer_force_reprogram <idle>-0 3dN.2 18us : clockevents_program_event <-tick_program_event <idle>-0 3dN.2 18us : ktime_get <-clockevents_program_event <idle>-0 3dN.2 18us : lapic_next_event <-clockevents_program_event <idle>-0 3dN.2 19us : _raw_spin_unlock_irqrestore <-hrtimer_try_to_cancel <idle>-0 3dN.2 19us : sub_preempt_count <-_raw_spin_unlock_irqrestore <idle>-0 3dN.1 19us : hrtimer_forward <-tick_nohz_idle_exit <idle>-0 3dN.1 20us : ktime_add_safe <-hrtimer_forward <idle>-0 3dN.1 20us : ktime_add_safe <-hrtimer_forward <idle>-0 3dN.1 20us : hrtimer_start_range_ns <-hrtimer_start_expires.constprop.11 <idle>-0 3dN.1 20us : __hrtimer_start_range_ns <-hrtimer_start_range_ns <idle>-0 3dN.1 21us : lock_hrtimer_base.isra.18 <-__hrtimer_start_range_ns <idle>-0 3dN.1 21us : _raw_spin_lock_irqsave <-lock_hrtimer_base.isra.18 <idle>-0 3dN.1 21us : add_preempt_count <-_raw_spin_lock_irqsave <idle>-0 3dN.2 22us : ktime_add_safe <-__hrtimer_start_range_ns <idle>-0 3dN.2 22us : enqueue_hrtimer <-__hrtimer_start_range_ns <idle>-0 3dN.2 22us : tick_program_event <-__hrtimer_start_range_ns <idle>-0 3dN.2 23us : clockevents_program_event <-tick_program_event <idle>-0 3dN.2 23us : ktime_get <-clockevents_program_event <idle>-0 3dN.2 23us : lapic_next_event <-clockevents_program_event <idle>-0 3dN.2 24us : _raw_spin_unlock_irqrestore <-__hrtimer_start_range_ns <idle>-0 3dN.2 24us : sub_preempt_count <-_raw_spin_unlock_irqrestore <idle>-0 3dN.1 24us : account_idle_ticks <-tick_nohz_idle_exit <idle>-0 3dN.1 24us : account_idle_time <-account_idle_ticks <idle>-0 3.N.1 25us : sub_preempt_count <-cpu_idle <idle>-0 3.N.. 25us : schedule <-cpu_idle <idle>-0 3.N.. 25us : __schedule <-preempt_schedule <idle>-0 3.N.. 26us : add_preempt_count <-__schedule <idle>-0 3.N.1 26us : rcu_note_context_switch <-__schedule <idle>-0 3.N.1 26us : rcu_sched_qs <-rcu_note_context_switch <idle>-0 3dN.1 27us : rcu_preempt_qs <-rcu_note_context_switch <idle>-0 3.N.1 27us : _raw_spin_lock_irq <-__schedule <idle>-0 3dN.1 27us : add_preempt_count <-_raw_spin_lock_irq <idle>-0 3dN.2 28us : put_prev_task_idle <-__schedule <idle>-0 3dN.2 28us : pick_next_task_stop <-pick_next_task <idle>-0 3dN.2 28us : pick_next_task_rt <-pick_next_task <idle>-0 3dN.2 29us : dequeue_pushable_task <-pick_next_task_rt <idle>-0 3d..3 29us : __schedule <-preempt_schedule <idle>-0 3d..3 30us : 0:120:R ==> [003] 2448: 94:R sleep
Трек даже при отслеживании функций не слишком велик и приведен здесь полностью.
Прерывание произошло в момент бездействия системы. Где-то перед вызовом task_woken_rt() был установлен флаг NEED_RESCHED, что показано первым включением символа N.
Трассировка задержки и события
Трассировка функций может вносить значительные задержки, но без нее трудно установить причины задержки. Золотой серединой является включение трассировки событий, как показано ниже.
# echo 0 > options/function-trace # echo wakeup_rt > current_tracer # echo 1 > events/enable # echo 1 > tracing_on # echo 0 > tracing_max_latency # chrt -f 5 sleep 1 # echo 0 > tracing_on # cat trace # tracer: wakeup_rt # # wakeup_rt latency trace v1.1.5 on 3.8.0-test+ # -------------------------------------------------------------------- # latency: 6 us, #12/12, CPU#2 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:4) # ----------------- # | task: sleep-5882 (uid:0 nice:0 policy:1 rt_prio:5) # ----------------- # # _------=> CPU# # / _-----=> irqs-off # | / _----=> need-resched # || / _---=> hardirq/softirq # ||| / _--=> preempt-depth # |||| / delay # cmd pid ||||| time | caller # \ / ||||| \ | / <idle>-0 2d.h4 0us : 0:120:R + [002] 5882: 94:R sleep <idle>-0 2d.h4 0us : ttwu_do_activate.constprop.87 <-try_to_wake_up <idle>-0 2d.h4 1us : sched_wakeup: comm=sleep pid=5882 prio=94 success=1 target_cpu=002 <idle>-0 2dNh2 1us : hrtimer_expire_exit: hrtimer=ffff88007796feb8 <idle>-0 2.N.2 2us : power_end: cpu_id=2 <idle>-0 2.N.2 3us : cpu_idle: state=4294967295 cpu_id=2 <idle>-0 2dN.3 4us : hrtimer_cancel: hrtimer=ffff88007d50d5e0 <idle>-0 2dN.3 4us : hrtimer_start: hrtimer=ffff88007d50d5e0 function=tick_sched_timer expires=34311211000000 softexpires=34311211000000 <idle>-0 2.N.2 5us : rcu_utilization: Start context switch <idle>-0 2.N.2 5us : rcu_utilization: End context switch <idle>-0 2d..3 6us : __schedule <-schedule <idle>-0 2d..3 6us : 0:120:R ==> [002] 5882: 94:R sleep
Определение аппаратной задержки
Для определения задержек в оборудовании служит трассировщик hwlat. Следует отметить, что этот трассировщик влияет на производительность всей системы и может полностью загружать CPU с запретом прерываний.
# echo hwlat > current_tracer # sleep 100 # cat trace # tracer: hwlat # # _-----=> irqs-off # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / delay # TASK-PID CPU# |||| TIMESTAMP FUNCTION # | | | |||| | | <...>-3638 [001] d... 19452.055471: #1 inner/outer(us): 12/14 ts:1499801089.066141940 <...>-3638 [003] d... 19454.071354: #2 inner/outer(us): 11/9 ts:1499801091.082164365 <...>-3638 [002] dn.. 19461.126852: #3 inner/outer(us): 12/9 ts:1499801098.138150062 <...>-3638 [001] d... 19488.340960: #4 inner/outer(us): 8/12 ts:1499801125.354139633 <...>-3638 [003] d... 19494.388553: #5 inner/outer(us): 8/12 ts:1499801131.402150961 <...>-3638 [003] d... 19501.283419: #6 inner/outer(us): 0/12 ts:1499801138.297435289 nmi-total:4 nmi-count:1
Приведенный выше вывод содержит заголовок с описанием формата. Для всех событий прерывания запрещены (d). Вывод в колонке FUNCTION описан ниже.
#1
Число записанных событий, превышающих tracing_threshold (см. ниже).
inner/outer(us): 12/14
Два значения, показывающие внутреннюю (inner) и внешнюю (outer) задержку. Тест выполняется в цикле, проверяя временную метку дважды. Задержка между двумя метками одного цикла называется внутренней, а задержка между меткой предыдущего и текущего циклов – внешней.
ts:1499801089.066141940
Метка абсолютного времени события.
nmi-total:4 nmi-count:1
Для поддерживающей немаскируемые прерывания архитектуры при NMI в процессе тестирования время, проведенное в NMI, указывается в поле nmi-total (мксек).
Если архитектура поддерживает NMI, поле nmi-count будет показывать число NMI в процессе тестирования.
Файлы hwlat
tracing_thresh
Если в этом файле указано значение 0, автоматически устанавливается значение 10 (мксек), определяющее порог задержки, которая вызывает запись трассировки в кольцевой буфер.
Отметим, что по завершении трассировщика hwlat (записи другого трассировщика в current_tracer) в файле восстанавливается исходное значение tracing_threshold.
hwlat_detector/width
Продолжительность работы теста с отключенными прерываниями.
hwlat_detector/window
Продолжительность окна тестирования – тест выполнялся в течение width мксек в течение window мксек.
tracing_cpumask
При запуске теста создается поток (thread) ядра для работы теста. Этот поток будет передаваться между CPU, указанными в tracing_cpumask каждый интервал (окно – window). Для работы теста на определенных CPU в этом файле устанавливается нужная маска.
Трассировщик function
Этот трассировщик отслеживает функции. Включить трассировщик можно через файловую систему debugfs. Следует убедиться, что установлена переменная (файл) ftrace_enabled, поскольку в противном случае трассировка не будет делать ничего (см. ftrace_enabled ниже).
Ниже приведен пример команд и вывода при трассировке функций.
# sysctl kernel.ftrace_enabled=1 # echo function > current_tracer # echo 1 > tracing_on # usleep 1 # echo 0 > tracing_on # cat trace # tracer: function # # entries-in-buffer/entries-written: 24799/24799 #P:4 # # _-----=> irqs-off # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / delay # TASK-PID CPU# |||| TIMESTAMP FUNCTION # | | | |||| | | bash-1994 [002] .... 3082.063030: mutex_unlock <-rb_simple_write bash-1994 [002] .... 3082.063031: __mutex_unlock_slowpath <-mutex_unlock bash-1994 [002] .... 3082.063031: __fsnotify_parent <-fsnotify_modify bash-1994 [002] .... 3082.063032: fsnotify <-fsnotify_modify bash-1994 [002] .... 3082.063032: __srcu_read_lock <-fsnotify bash-1994 [002] .... 3082.063032: add_preempt_count <-__srcu_read_lock bash-1994 [002] ...1 3082.063032: sub_preempt_count <-__srcu_read_lock bash-1994 [002] .... 3082.063033: __srcu_read_unlock <-fsnotify
Примечание. Трассировщик функций использует кольцевые буферы для хранения показанных выше записей и новые данные могут переписывать более старые. Иногда использования команды echo не достаточно для прекращения трассировки, поскольку данные могут быть перезаписаны. По этой причине иногда лучше отключать трассировку из программы. Это позволит остановить трассировку в нужной точке. Для выключения трассировки непосредственно из программы на языке C можно воспользоваться кодом, подобным приведенному ниже.
int trace_fd; [...] int main(int argc, char *argv[]) { [...] trace_fd = open(tracing_file("tracing_on"), O_WRONLY); [...] if (condition_hit()) { write(trace_fd, "0", 1); } [...] }
Трассировка одного потока
Путем записи нужного значения в файл set_ftrace_pid можно ограничиться отслеживанием одного потока (thread).
# cat set_ftrace_pid no pid # echo 3111 > set_ftrace_pid # cat set_ftrace_pid 3111 # echo function > current_tracer # cat trace | head # tracer: function # # TASK-PID CPU# TIMESTAMP FUNCTION # | | | | | yum-updatesd-3111 [003] 1637.254676: finish_task_switch <-thread_return yum-updatesd-3111 [003] 1637.254681: hrtimer_cancel <-schedule_hrtimeout_range yum-updatesd-3111 [003] 1637.254682: hrtimer_try_to_cancel <-hrtimer_cancel yum-updatesd-3111 [003] 1637.254683: lock_hrtimer_base <-hrtimer_try_to_cancel yum-updatesd-3111 [003] 1637.254685: fget_light <-do_sys_poll yum-updatesd-3111 [003] 1637.254686: pipe_poll <-do_sys_poll # echo > set_ftrace_pid # cat trace |head # tracer: function # # TASK-PID CPU# TIMESTAMP FUNCTION # | | | | | ##### CPU 3 buffer started #### yum-updatesd-3111 [003] 1701.957688: free_poll_entry <-poll_freewait yum-updatesd-3111 [003] 1701.957689: remove_wait_queue <-free_poll_entry yum-updatesd-3111 [003] 1701.957691: fput <-free_poll_entry yum-updatesd-3111 [003] 1701.957692: audit_syscall_exit <-sysret_audit yum-updatesd-3111 [003] 1701.957693: path_put <-audit_syscall_exit
Если нужно отслеживать функцию при выполнении, можно воспользоваться чем-то вроде приведенной программы.
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #define _STR(x) #x #define STR(x) _STR(x) #define MAX_PATH 256 const char *find_tracefs(void) { static char tracefs[MAX_PATH+1]; static int tracefs_found; char type[100]; FILE *fp; if (tracefs_found) return tracefs; if ((fp = fopen("/proc/mounts","r")) == NULL) { perror("/proc/mounts"); return NULL; } while (fscanf(fp, "%*s %" STR(MAX_PATH) "s %99s %*s %*d %*d\n", tracefs, type) == 2) { if (strcmp(type, "tracefs") == 0) break; } fclose(fp); if (strcmp(type, "tracefs") != 0) { fprintf(stderr, "tracefs not mounted"); return NULL; } strcat(tracefs, "/tracing/"); tracefs_found = 1; return tracefs; } const char *tracing_file(const char *file_name) { static char trace_file[MAX_PATH+1]; snprintf(trace_file, MAX_PATH, "%s/%s", find_tracefs(), file_name); return trace_file; } int main (int argc, char **argv) { if (argc < 1) exit(-1); if (fork() > 0) { int fd, ffd; char line[64]; int s; ffd = open(tracing_file("current_tracer"), O_WRONLY); if (ffd < 0) exit(-1); write(ffd, "nop", 3); fd = open(tracing_file("set_ftrace_pid"), O_WRONLY); s = sprintf(line, "%d\n", getpid()); write(fd, line, s); write(ffd, "function", 8); close(fd); close(ffd); execvp(argv[1], argv+1); } return 0; }
Подойдет также простой сценарий, показанный ниже.
#!/bin/bash tracefs=`sed -ne 's/^tracefs \(.*\) tracefs.*/\1/p' /proc/mounts` echo nop > $tracefs/tracing/current_tracer echo 0 > $tracefs/tracing/tracing_on echo $$ > $tracefs/tracing/set_ftrace_pid echo function > $tracefs/tracing/current_tracer echo 1 > $tracefs/tracing/tracing_on exec "$@"
Трассировщик function_graph
Этот трассировщик похож на function, но проверяет функции на входе и выходе. Это делается с помощью динамически выделяемого стека адресов возврата в каждой task_struct. На входе функции трассировщик переписывает адрес возврата каждой отслеживаемой функции для установки специального «зонда». Исходный адрес возврата сохраняется в стеке возврата структуры task_struct.
Зондирование на входе и выходе функции позволяет определить время ее выполнения и получить надежный стек вызовов для создания графа вызовов функций.
Этот трассировщик полезен в нескольких ситуациях:
-
поиск причин странного поведения ядра;
-
обнаружение непонятных задержек;
-
быстрый поиск пути выполнение определенной функции;
-
исследование происходящего внутри ядра.
# tracer: function_graph # # CPU DURATION FUNCTION CALLS # | | | | | | | 0) | sys_open() { 0) | do_sys_open() { 0) | getname() { 0) | kmem_cache_alloc() { 0) 1.382 us | __might_sleep(); 0) 2.478 us | } 0) | strncpy_from_user() { 0) | might_fault() { 0) 1.389 us | __might_sleep(); 0) 2.553 us | } 0) 3.807 us | } 0) 7.876 us | } 0) | alloc_fd() { 0) 0.668 us | _spin_lock(); 0) 0.570 us | expand_files(); 0) 0.586 us | _spin_unlock();
Здесь имеется несколько колонок, которые можно динамически включать и выключать. Можно использовать любую подходящую комбинацию опций.
-
Номер процессора, на котором выполняется функция, по умолчанию выводится. Иногда может потребоваться отслеживание лишь одного процессора (см. файл tracing_cpu_mask) или отслеживать функции без привязки к процессорам.
-
Скрыть номер процессора – echo nofuncgraph-cpu > trace_options.
-
Показывать номер процессора – echo funcgraph-cpu > trace_options.
-
-
Продолжительность выполнения функции указывается после закрывающей (фигурной) скобки функции или в одной строке с функцией, если эта функция не делает других вызовов. По умолчанию отображение продолжительности включено.
-
Скрыть продолжительность вызова – echo nofuncgraph-duration > trace_options.
-
Показывать продолжительность вызова – echo funcgraph-duration > trace_options.
-
-
Поле служебных данных (overhead) указывается перед полем продолжительности в случае достижения порога длительности.
-
Скрыть служебные данные – echo nofuncgraph-overhead > trace_options.
-
Показывать служебные данные – echo funcgraph-overhead > trace_options
-
3) # 1837.709 us | } /* __switch_to */ 3) | finish_task_switch() { 3) 0.313 us | _raw_spin_unlock_irq(); 3) 3.177 us | } 3) # 1889.063 us | } /* __schedule */ 3) ! 140.417 us | } /* __schedule */ 3) # 2034.948 us | } /* schedule */ 3) * 33998.59 us | } /* schedule_preempt_disabled */ [...] 1) 0.260 us | msecs_to_jiffies(); 1) 0.313 us | __rcu_read_unlock(); 1) + 61.770 us | } 1) + 64.479 us | } 1) 0.313 us | rcu_bh_qs(); 1) 0.313 us | __local_bh_enable(); 1) ! 217.240 us | } 1) 0.365 us | idle_cpu(); 1) | rcu_irq_exit() { 1) 0.417 us | rcu_eqs_enter_common.isra.47(); 1) 3.125 us | } 1) ! 227.812 us | } 1) ! 457.395 us | } 1) @ 119760.2 us | } [...] 2) | handle_IPI() { 1) 6.979 us | } 2) 0.417 us | scheduler_ipi(); 1) 9.791 us | } 1) + 12.917 us | } 2) 3.490 us | } 1) + 15.729 us | } 1) + 18.542 us | } 2) $ 3594274 us | }
+ – больше 10 мксек.
! – больше 100 мксек.
# – больше 1000 мксек.
* – больше 10 мсек.
@ – больше 100 мсек.
$ – больше 1 сек.
-
Поле task/pid показывает строку команды и pid процесса, в котором функция выполняется. По умолчанию это поле не выводится.
-
Скрыть поле – echo nofuncgraph-proc > trace_options.
-
Показывать поле – echo funcgraph-proc > trace_options.
-
# tracer: function_graph # # CPU TASK/PID DURATION FUNCTION CALLS # | | | | | | | | | 0) sh-4802 | | d_free() { 0) sh-4802 | | call_rcu() { 0) sh-4802 | | __call_rcu() { 0) sh-4802 | 0.616 us | rcu_process_gp_end(); 0) sh-4802 | 0.586 us | check_for_new_grace_period(); 0) sh-4802 | 2.899 us | } 0) sh-4802 | 4.040 us | } 0) sh-4802 | 5.151 us | } 0) sh-4802 | + 49.370 us | }
-
Поле «абсолютного» времени содержит временную метку системных часов, отсчитываемую от момента загрузки системы. Время указывается на входе и выходе функции.
-
Скрыть время – echo nofuncgraph-abstime > trace_options
-
Показывать время – echo funcgraph-abstime > trace_options
-
# # TIME CPU DURATION FUNCTION CALLS # | | | | | | | | 360.774522 | 1) 0.541 us | } 360.774522 | 1) 4.663 us | } 360.774523 | 1) 0.541 us | __wake_up_bit(); 360.774524 | 1) 6.796 us | } 360.774524 | 1) 7.952 us | } 360.774525 | 1) 9.063 us | } 360.774525 | 1) 0.615 us | journal_mark_dirty(); 360.774527 | 1) 0.578 us | __brelse(); 360.774528 | 1) | reiserfs_prepare_for_journal() { 360.774528 | 1) | unlock_buffer() { 360.774529 | 1) | wake_up_bit() { 360.774529 | 1) | bit_waitqueue() { 360.774530 | 1) 0.594 us | __phys_addr();
Имя функции, начало которой не попадает в буфер трассировки выводится после закрывающей скобки для функции. Вывод этого имени можно включить для упрощения поиска продолжительности функций с помощью grep. По умолчанию вывод отключен
-
Скрыть имя – echo nofuncgraph-tail > trace_options.
-
Показывать имя – echo funcgraph-tail > trace_options.
Пример без вывода имен (nofuncgraph-tail), как принято по умолчанию.
0) | putname() { 0) | kmem_cache_free() { 0) 0.518 us | __phys_addr(); 0) 1.757 us | } 0) 2.861 us | }
Пример с выводом имен (funcgraph-tail)
0) | putname() { 0) | kmem_cache_free() { 0) 0.518 us | __phys_addr(); 0) 1.757 us | } /* kmem_cache_free() */ 0) 2.861 us | } /* putname() */
Можно добавить комментарии к конкретным функциям с помощью trace_printk(). Например, для включения комментариев в функцию __might_sleep() достаточно включить файл <linux/ftrace.h> и вызвать trace_printk() внутри __might_sleep(). Например,
trace_printk("I'm a comment!\n")
будет давать вывод
1) | __might_sleep() { 1) | /* I'm a comment! */ 1) 1.449 us | }
Другие варианты применения этого трассировщика описаны в следующем разделе.
Динамическая трассировка ftrace
Если установлена опция ядра CONFIG_DYNAMIC_FTRACE, система будет работать почти без связанных с трассировкой издержек при отключенной трассировке функций. Это работает за счет вызова функции mcount (помещается в начале каждой функции ядра с помощью опции -pg компилятора gcc6), указывающей простой возврат.
При компиляции каждый объект файла C запускается через программу recordmcount (находится в каталоге scripts), которая анализирует заголовки ELF в объекте C для поиска всех вызовов в разделе .text, вызывающих функцию mcount7.
Отметим, что отслеживаются не все разделы – трассировка может быть отключена notrace или заблокирована иным путем и все встроенные функции не будут отслеживаться. Для просмотра списка функций, которые могут трассироваться, следует обратиться к файлу available_filter_functions.
Создается раздел __mcount_loc, в котором содержатся ссылки на все вызовы mcount/fentry в разделе .text. Программа recordmcount заново связывает этот раздел с исходными объектами. На этапе финальной компоновки ядра все эти ссылки будут добавлены в одну таблицу.
При загрузке (до инициализации SMP) динамический код ftrace сканирует эту таблицу и заменяет все найденные ссылки на nop, а также записывает местоположения, добавленные в список available_filter_functions. Модули обрабатываются по мере их загрузки до начала выполнения. При выгрузке модуля реализованные в нем функции удаляются из списка функций ftrace. Эту процедуру автоматически выполняет код выгрузки модулей и автор модуля не должен беспокоиться об этом.
При включенной трассировке процесс изменения точек трассировки функций зависит от архитектуры. Старый метод использовал kstop_machine для предотвращения «гонок» CPU, выполняющих измененный код (что может заставлять CPU делать нежелательные вещи, особенно в тех случаях, когда измененный код пересекает границу кэше или страницы) и операции nop обратно помещались в вызовы. Но сейчас вместо mcount (сейчас это просто функция-заглушка) используются вызовы инфраструктуры ftrace.
Новый метод изменения точек трассировки функций заключается в размещении точки остановки (breakpoint) в изменяемом месте, синхронизации всех CPU, и изменении остальной части инструкции, не охваченной точкой остановки. CPU синхронизируются еще раз и точка установки удаляется из окончательной версии.
Некоторым вариантам архитектуры не нужно заботиться о синхронизации, они просто могут поместить новый код взамен прежнего без возникновения проблем с другими CPU, работающими в это же время.
Одним из побочных эффектов записи функций, которые будут отслеживаться, является то, что можно выбрать функции, которые мы хотим трассировать, и функции, для которых хотим, чтобы вызовы mcount оставались nop.
Используется два файла, один из которых служит для разрешения, другой для запрета трассировки заданных функций
set_ftrace_filter
и
set_ftrace_notrace
Список функций, которые можно включить в эти файлы содержится в файле available_filter_functions.
# cat available_filter_functions put_prev_task_idle kmem_cache_create pick_next_task_rt get_online_cpus pick_next_task_fair mutex_lock [...]
Если интересно отслеживать лишь sys_nanosleep и hrtimer_interrupt, можно воспользоваться приведенными ниже командами.
# echo sys_nanosleep hrtimer_interrupt > set_ftrace_filter # echo function > current_tracer # echo 1 > tracing_on # usleep 1 # echo 0 > tracing_on # cat trace # tracer: function # # entries-in-buffer/entries-written: 5/5 #P:4 # # _-----=> irqs-off # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / delay # TASK-PID CPU# |||| TIMESTAMP FUNCTION # | | | |||| | | usleep-2665 [001] .... 4186.475355: sys_nanosleep <-system_call_fastpath <idle>-0 [001] d.h1 4186.475409: hrtimer_interrupt <-smp_apic_timer_interrupt usleep-2665 [001] d.h1 4186.475426: hrtimer_interrupt <-smp_apic_timer_interrupt <idle>-0 [003] d.h1 4186.475426: hrtimer_interrupt <-smp_apic_timer_interrupt <idle>-0 [002] d.h1 4186.475427: hrtimer_interrupt <-smp_apic_timer_interrupt
Для просмотра функций, которые будут отслеживаться, можно воспользоваться командой
# cat set_ftrace_filter hrtimer_interrupt sys_nanosleep
Иногда явного указания имен всех функций не достаточно, поэтому фильтры поддерживают шаблоны сопоставлений (см. glob).
match*
соответствует всем функциям, начинающимся с match.
*match
соответствует всем функциям, заканчивающимся match.
*match*
соответствует всем функциям, содержащим match в имени.
match1*match2
соответствует всем функциям, начинающимся с match1 и заканчивающимися match2.
Примечание. Шаблоны сопоставления лучше указывать в кавычках, поскольку в противном случае интерпретатор команд может счесть параметры именами файлов в локальном каталоге.
Например,
# echo 'hrtimer_*' > set_ftrace_filter
Приведет к выводу, показанному ниже
# tracer: function # # entries-in-buffer/entries-written: 897/897 #P:4 # # _-----=> irqs-off # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / delay # TASK-PID CPU# |||| TIMESTAMP FUNCTION # | | | |||| | | <idle>-0 [003] dN.1 4228.547803: hrtimer_cancel <-tick_nohz_idle_exit <idle>-0 [003] dN.1 4228.547804: hrtimer_try_to_cancel <-hrtimer_cancel <idle>-0 [003] dN.2 4228.547805: hrtimer_force_reprogram <-__remove_hrtimer <idle>-0 [003] dN.1 4228.547805: hrtimer_forward <-tick_nohz_idle_exit <idle>-0 [003] dN.1 4228.547805: hrtimer_start_range_ns <-hrtimer_start_expires.constprop.11 <idle>-0 [003] d..1 4228.547858: hrtimer_get_next_event <-get_next_timer_interrupt <idle>-0 [003] d..1 4228.547859: hrtimer_start <-__tick_nohz_idle_enter <idle>-0 [003] d..2 4228.547860: hrtimer_force_reprogram <-__rem
Отметим, что трассировка sys_nanosleep была потеряна.
# cat set_ftrace_filter hrtimer_run_queues hrtimer_run_pending hrtimer_init hrtimer_cancel hrtimer_try_to_cancel hrtimer_forward hrtimer_start hrtimer_reprogram hrtimer_force_reprogram hrtimer_get_next_event hrtimer_interrupt hrtimer_nanosleep hrtimer_wakeup hrtimer_get_remaining hrtimer_get_res hrtimer_init_sleeper
Причиной этого стало указание символа > в команде echo. Одиночная угловая скобка (>) задает переписывание файла, а два символа (>>) указывают добавление в конец файла.
Для сброса фильтров с восстановлением записи всех функций можно воспользоваться командой
# echo > set_ftrace_filter # cat set_ftrace_filter #
Далее показано, как можно добавить фильтры, не удаляя имеющихся.
# echo sys_nanosleep > set_ftrace_filter # cat set_ftrace_filter sys_nanosleep # echo 'hrtimer_*' >> set_ftrace_filter # cat set_ftrace_filter hrtimer_run_queues hrtimer_run_pending hrtimer_init hrtimer_cancel hrtimer_try_to_cancel hrtimer_forward hrtimer_start hrtimer_reprogram hrtimer_force_reprogram hrtimer_get_next_event hrtimer_interrupt sys_nanosleep hrtimer_nanosleep hrtimer_wakeup hrtimer_get_remaining hrtimer_get_res hrtimer_init_sleeper
Фильтр set_ftrace_notrace исключает трассировку указанных в файле функций. Например, команда
# echo '*preempt*' '*lock*' > set_ftrace_notrace
Приведет к показанному ниже результату.
# tracer: function # # entries-in-buffer/entries-written: 39608/39608 #P:4 # # _-----=> irqs-off # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / delay # TASK-PID CPU# |||| TIMESTAMP FUNCTION # | | | |||| | | bash-1994 [000] .... 4342.324896: file_ra_state_init <-do_dentry_open bash-1994 [000] .... 4342.324897: open_check_o_direct <-do_last bash-1994 [000] .... 4342.324897: ima_file_check <-do_last bash-1994 [000] .... 4342.324898: process_measurement <-ima_file_check bash-1994 [000] .... 4342.324898: ima_get_action <-process_measurement bash-1994 [000] .... 4342.324898: ima_match_policy <-ima_get_action bash-1994 [000] .... 4342.324899: do_truncate <-do_last bash-1994 [000] .... 4342.324899: should_remove_suid <-do_truncate bash-1994 [000] .... 4342.324899: notify_change <-do_truncate bash-1994 [000] .... 4342.324900: current_fs_time <-notify_change bash-1994 [000] .... 4342.324900: current_kernel_time <-current_fs_time bash-1994 [000] .... 4342.324900: timespec_trunc <-current_fs_time
Откуда можно видеть, что отслеживания lock и preempt не происходит.
Динамическая трассировка с function_graph
Хотя приведенные выше разъяснения относятся к обоим трассировщикам function и function_graph, последний имеет некоторые особые возможности.
Если вы хотите отслеживать лишь одну функцию и всех ее потомком, достаточно включить имя этой функции с помощью команды echo в файл set_graph_function. Например,
echo __do_fault > set_graph_function
будет выводить «расширенную» трассировку функции __do_fault().
0) | __do_fault() { 0) | filemap_fault() { 0) | find_lock_page() { 0) 0.804 us | find_get_page(); 0) | __might_sleep() { 0) 1.329 us | } 0) 3.904 us | } 0) 4.979 us | } 0) 0.653 us | _spin_lock(); 0) 0.578 us | page_add_file_rmap(); 0) 0.525 us | native_set_pte_at(); 0) 0.585 us | _spin_unlock(); 0) | unlock_page() { 0) 0.541 us | page_waitqueue(); 0) 0.639 us | __wake_up_bit(); 0) 2.786 us | } 0) + 14.237 us | } 0) | __do_fault() { 0) | filemap_fault() { 0) | find_lock_page() { 0) 0.698 us | find_get_page(); 0) | __might_sleep() { 0) 1.412 us | } 0) 3.950 us | } 0) 5.098 us | } 0) 0.631 us | _spin_lock(); 0) 0.571 us | page_add_file_rmap(); 0) 0.526 us | native_set_pte_at(); 0) 0.586 us | _spin_unlock(); 0) | unlock_page() { 0) 0.533 us | page_waitqueue(); 0) 0.638 us | __wake_up_bit(); 0) 2.793 us | } 0) + 14.012 us | }
Можно таким способом отслеживать несколько функций, например,
echo sys_open > set_graph_function echo sys_close >> set_graph_function
Если после этого нужно вернуться к отслеживанию всех функций, можно сбросить фильтр командой
echo > set_graph_function
ftrace_enabled
Следует отметить, что sysctl ftrace_enable служит основным выключателем для трассировщика функций, который по умолчанию включен (если трассировка функций активизирована в ядре). При отключении трассировки выключается отслеживание всех функций (не только трассировщики функций для ftrace, но и другие – perf, kprobe, трассировка стека, профилирование и т. п.). Следует осторожно относиться к запрету трассировки. Команды управления приведены ниже.
sysctl kernel.ftrace_enabled=0 sysctl kernel.ftrace_enabled=1
или
echo 0 > /proc/sys/kernel/ftrace_enabled echo 1 > /proc/sys/kernel/ftrace_enabled
Команды фильтрации
Интерфейс set_ftrace_filter поддерживает несколько команд, имеющих формат
<function>:<command>:<parameter>
Ниже перечислены поддерживаемые команды.
mod
Включает фильтрацию функций на уровне модуля, задаваемого параметром. Например, если нужно отслеживать лишь функции write* в модуле ext3, можно воспользоваться командой
echo 'write*:mod:ext3' > set_ftrace_filter
Эта команда взаимодействует с фильтрами так же, как для фильтрации по именам функций, т. е. для добавления других функций применяется операция записи в конец файла (>>). Для удалении функции применяется восклицательный знак перед именем.
echo '!writeback*:mod:ext3' >> set_ftrace_filter
Команда позволяет указывать функции и модули с помощью шаблонов. Например, для запрета трассировки всех функций, за исключением указанного модуля, служит команд
echo '!*:mod:!ext3' >> set_ftrace_filter
Для запрета трассировки всех модулей с сохранением трассировки ядра служит команд
echo '!*:mod:*' >> set_ftrace_filter
Для фильтрации только функций ядра служат команды вида
echo '*write*:mod:!*' >> set_ftrace_filter
Для фильтрации набора модулей служат команды вида
echo '*write*:mod:*snd*' >> set_ftrace_filter
traceon/traceoff
Эти команды включают и выключают трассировку только при обращении к указанным функциям. Параметр определяет число включений и отключений трассировки, при отсутствии параметра число не ограничивается. Например, для запрета трассировки при первых 5 вызовах __schedule_bug можно воспользоваться командой
echo '__schedule_bug:traceoff:5' > set_ftrace_filter
Для запрета трассировки при вызове __schedule_bug служит команда
echo '__schedule_bug:traceoff' > set_ftrace_filter
Эти команды являются накопительными независимо от их добавления в set_ftrace_filter. Для удаления команды указывается символ ! Перед ее именем и значение 0 для параметра
echo '!__schedule_bug:traceoff:0' > set_ftrace_filter
Приведенная выше команда удаляет traceoff для функции __schedule_bug со счетчиком. Для удаления команд без счетчиков можно воспользоваться командой вида
echo '!__schedule_bug:traceoff' > set_ftrace_filter
snapshot
Будет вызывать создание моментального снимка (snapshot) при вызове указанной функции.
echo 'native_flush_tlb_others:snapshot' > set_ftrace_filter
Для однократного создания снимка служат команды вида
echo 'native_flush_tlb_others:snapshot:1' > set_ftrace_filter
Для удаления приведенных выше команд можно воспользоваться командами
echo '!native_flush_tlb_others:snapshot' > set_ftrace_filter echo '!native_flush_tlb_others:snapshot:0' > set_ftrace_filter
enable_event/disable_event
Эти команды могут включать и выключать трассировку событий. Отметим, что по причине «чувствительности» обратных вызовов трассировки функций при использовании этих команд точки трассировки активируются, но отключаются они в «мягком» (soft) режиме, т. е. точка трассировки будет вызываться, просто не будет отслеживаться ничего. Точка трассировки события остается в таком режиме, пока есть использующие ее команды.
echo 'try_to_wake_up:enable_event:sched:sched_switch:2' > set_ftrace_filter
Формат команд показан ниже.
<function>:enable_event:<system>:<event>[:count] <function>:disable_event:<system>:<event>[:count]
Для удаления связанных с событиями команд служат приведенные ниже команды
echo '!try_to_wake_up:enable_event:sched:sched_switch:0' > set_ftrace_filter echo '!schedule:disable_event:sched:sched_switch' > set_ftrace_filter
dump
При обращении к функции на консоль будет выводиться содержимое кольцевого буфера ftrace. Это полезно при отладке, когда вы хотите быстро увидеть трассировку при обращении к некой функции. Например, ее можно использовать для просмотра буфера перед повторяющимся отказом.
cpudump
При обращении к функции на консоль будет выводиться содержимое кольцевого буфера ftrace для текущего CPU. В отличие от команды dump выводиться будет лишь буфер CPU, на котором выполняется вызвавшая вывод функция.
trace_pipe
Файл trace_pipe выводит те же данные, что и trace, но влияние на трассировку различается. Каждая запись из trace_pipe «потребляется» при считывании, т. е. последовательные операции чтения будут давать новый результат.
# echo function > current_tracer # cat trace_pipe > /tmp/trace.out & [1] 4153 # echo 1 > tracing_on # usleep 1 # echo 0 > tracing_on # cat trace # tracer: function # # entries-in-buffer/entries-written: 0/0 #P:4 # # _-----=> irqs-off # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / delay # TASK-PID CPU# |||| TIMESTAMP FUNCTION # | | | |||| | | # # cat /tmp/trace.out bash-1994 [000] .... 5281.568961: mutex_unlock <-rb_simple_write bash-1994 [000] .... 5281.568963: __mutex_unlock_slowpath <-mutex_unlock bash-1994 [000] .... 5281.568963: __fsnotify_parent <-fsnotify_modify bash-1994 [000] .... 5281.568964: fsnotify <-fsnotify_modify bash-1994 [000] .... 5281.568964: __srcu_read_lock <-fsnotify bash-1994 [000] .... 5281.568964: add_preempt_count <-__srcu_read_lock bash-1994 [000] ...1 5281.568965: sub_preempt_count <-__srcu_read_lock bash-1994 [000] .... 5281.568965: __srcu_read_unlock <-fsnotify bash-1994 [000] .... 5281.568967: sys_dup2 <-system_call_fastpath
Отметим, что чтение файла trace_pipe будет блокироваться, пока в него не добавлено новых данных.
Записи трассировки
При диагностике проблем в ядре избыток или недостаток данных могут создавать проблемы. Файл buffer_size_kb служит для изменения размера внутренних буферов трассировки. Значение в файле указывает число записей, которые могут быть созданы для каждого CPU. Для определения полного размера это значение следует умножить на число процессоров.
# cat buffer_size_kb 1408 (units kilobytes)
Или просто прочитать файл buffer_total_size_kb
# cat buffer_total_size_kb 5632
Для изменения размера буфера просто записывается (echo) нужное число (в сегментах по 1024 байта).
# echo 10000 > buffer_size_kb # cat buffer_size_kb 10000 (units kilobytes)
При попытке указать слишком большой размер буферов сработает триггер Out-Of-Memory.
# echo 1000000000000 > buffer_size_kb -bash: echo: write error: Cannot allocate memory # cat buffer_size_kb 85
Буферы per_cpu можно изменять независимо, как показано ниже.
# echo 10000 > per_cpu/cpu0/buffer_size_kb # echo 100 > per_cpu/cpu1/buffer_size_kb
При разных размерах буферов per_cpu файл buffer_size_kb на верхнем уровне просто покажет X.
# cat buffer_size_kb X
В этом случае можно воспользоваться командой
# cat buffer_total_size_kb 12916
Запись в buffer_size_kb верхнего уровня будет сбрасывать буферы всех процессоров к одному размеру.
Мгновенные снимки
Опция ядра CONFIG_TRACER_SNAPSHOT включает возможность создания «моментальных снимков» для всех трассировщиков, кроме трассировщиков задержки. Трассировщики задержки, записывающие максимальное значение (такие как irqsoff или wakeup), не могут использовать эту возможность, поскольку в них уже применяется внутренний механизм snapshot mechanism.
Снимок сохраняет текущий буфер трассировки в конкретный момент времени без остановки отслеживания. Ftrace заменяет текущий буфер запасным и трассировка продолжается в новый (ранее резервный) буфер.
Ниже указаны файлы tracefs (tracing), связанные с этой функцией.
snapshot
используется для записи и считывания моментального снимка. Запись (echo) 1 в этот файл ведет к созданию запасного буфера, записи снимка и последующему его считыванию в формате файла (см. раздел Файловая система). Считывание снимка и трассировка выполняются параллельно. Когда запасной буфер создан, запись (echo) 0 в файл освобождает буфер, а запись других (положительных) значений очищает содержимое снимка.
-
состояние/ввод
0
1
иное
Не выделен
Ничего
Выделение и переключение
Ничего
Выделен
Освобождение
swap
Очистка
Ниже приведен пример использования моментального снимка.
# echo 1 > events/sched/enable # echo 1 > snapshot # cat snapshot # tracer: nop # # entries-in-buffer/entries-written: 71/71 #P:8 # # _-----=> irqs-off # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / delay # TASK-PID CPU# |||| TIMESTAMP FUNCTION # | | | |||| | | <idle>-0 [005] d... 2440.603828: sched_switch: prev_comm=swapper/5 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=snapshot-test-2 next_pid=2242 next_prio=120 sleep-2242 [005] d... 2440.603846: sched_switch: prev_comm=snapshot-test-2 prev_pid=2242 prev_prio=120 prev_state=R ==> next_comm=kworker/5:1 next_pid=60 next_prio=120 [...] <idle>-0 [002] d... 2440.707230: sched_switch: prev_comm=swapper/2 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=snapshot-test-2 next_pid=2229 next_prio=120 # cat trace # tracer: nop # # entries-in-buffer/entries-written: 77/77 #P:8 # # _-----=> irqs-off # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / delay # TASK-PID CPU# |||| TIMESTAMP FUNCTION # | | | |||| | | <idle>-0 [007] d... 2440.707395: sched_switch: prev_comm=swapper/7 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=snapshot-test-2 next_pid=2243 next_prio=120 snapshot-test-2-2229 [002] d... 2440.707438: sched_switch: prev_comm=snapshot-test-2 prev_pid=2229 prev_prio=120 prev_state=S ==> next_comm=swapper/2 next_pid=0 next_prio=120 [...]
Если попытаться использовать функцию snapshot для одного из трассировщиков задержек, возникнет ошибка.
# echo wakeup > current_tracer # echo 1 > snapshot bash: echo: write error: Device or resource busy # cat snapshot cat: snapshot: Device or resource busy
Экземпляры
В каталоге tracing файловой системы tracefs имеется папка instances, где можно создавать новые каталоги с помощью команды mkdir и удалять их командой rmdir. Создаваемые командой mkdir каталоги сразу будут содержать файлы и другие папки.
# mkdir instances/foo # ls instances/foo buffer_size_kb buffer_total_size_kb events free_buffer per_cpu set_event snapshot trace trace_clock trace_marker trace_options trace_pipe tracing_on
Как видно из представленного вывода созданный каталог похож на сам каталог tracing. Фактически он отличается лишь тем, что буфер и события не зависят от основного экземпляра и других созданных экземпляров.
Файлы в новом каталоге работают так же, как одноименные файлы каталога tracing, за исключением того, что для них применяется независимый новый буфер. Файлы влияют на этот буфер, но не воздействуют на основной буфер (за исключением trace_options). В настоящее время trace_options одинаково влияет на все экземпляры, включая буфер верхнего уровня, но это может измениться в будущих версиях. Т. е. опции могут стать независимыми для каждого экземпляра.
Отмети, что здесь нет ни одного трассировщика функций, а также файлов current_tracer и available_tracers. Это обусловлено тем, что в настоящее буферы могут включать лишь разрешенные для них события.
# mkdir instances/foo # mkdir instances/bar # mkdir instances/zoot # echo 100000 > buffer_size_kb # echo 1000 > instances/foo/buffer_size_kb # echo 5000 > instances/bar/per_cpu/cpu1/buffer_size_kb # echo function > current_trace # echo 1 > instances/foo/events/sched/sched_wakeup/enable # echo 1 > instances/foo/events/sched/sched_wakeup_new/enable # echo 1 > instances/foo/events/sched/sched_switch/enable # echo 1 > instances/bar/events/irq/enable # echo 1 > instances/zoot/events/syscalls/enable # cat trace_pipe CPU:2 [LOST 11745 EVENTS] bash-2044 [002] .... 10594.481032: _raw_spin_lock_irqsave <-get_page_from_freelist bash-2044 [002] d... 10594.481032: add_preempt_count <-_raw_spin_lock_irqsave bash-2044 [002] d..1 10594.481032: __rmqueue <-get_page_from_freelist bash-2044 [002] d..1 10594.481033: _raw_spin_unlock <-get_page_from_freelist bash-2044 [002] d..1 10594.481033: sub_preempt_count <-_raw_spin_unlock bash-2044 [002] d... 10594.481033: get_pageblock_flags_group <-get_pageblock_migratetype bash-2044 [002] d... 10594.481034: __mod_zone_page_state <-get_page_from_freelist bash-2044 [002] d... 10594.481034: zone_statistics <-get_page_from_freelist bash-2044 [002] d... 10594.481034: __inc_zone_state <-zone_statistics bash-2044 [002] d... 10594.481034: __inc_zone_state <-zone_statistics bash-2044 [002] .... 10594.481035: arch_dup_task_struct <-copy_process [...] # cat instances/foo/trace_pipe bash-1998 [000] d..4 136.676759: sched_wakeup: comm=kworker/0:1 pid=59 prio=120 success=1 target_cpu=000 bash-1998 [000] dN.4 136.676760: sched_wakeup: comm=bash pid=1998 prio=120 success=1 target_cpu=000 <idle>-0 [003] d.h3 136.676906: sched_wakeup: comm=rcu_preempt pid=9 prio=120 success=1 target_cpu=003 <idle>-0 [003] d..3 136.676909: sched_switch: prev_comm=swapper/3 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=rcu_preempt next_pid=9 next_prio=120 rcu_preempt-9 [003] d..3 136.676916: sched_switch: prev_comm=rcu_preempt prev_pid=9 prev_prio=120 prev_state=S ==> next_comm=swapper/3 next_pid=0 next_prio=120 bash-1998 [000] d..4 136.677014: sched_wakeup: comm=kworker/0:1 pid=59 prio=120 success=1 target_cpu=000 bash-1998 [000] dN.4 136.677016: sched_wakeup: comm=bash pid=1998 prio=120 success=1 target_cpu=000 bash-1998 [000] d..3 136.677018: sched_switch: prev_comm=bash prev_pid=1998 prev_prio=120 prev_state=R+ ==> next_comm=kworker/0:1 next_pid=59 next_prio=120 kworker/0:1-59 [000] d..4 136.677022: sched_wakeup: comm=sshd pid=1995 prio=120 success=1 target_cpu=001 kworker/0:1-59 [000] d..3 136.677025: sched_switch: prev_comm=kworker/0:1 prev_pid=59 prev_prio=120 prev_state=S ==> next_comm=bash next_pid=1998 next_prio=120 [...] # cat instances/bar/trace_pipe migration/1-14 [001] d.h3 138.732674: softirq_raise: vec=3 [action=NET_RX] <idle>-0 [001] dNh3 138.732725: softirq_raise: vec=3 [action=NET_RX] bash-1998 [000] d.h1 138.733101: softirq_raise: vec=1 [action=TIMER] bash-1998 [000] d.h1 138.733102: softirq_raise: vec=9 [action=RCU] bash-1998 [000] ..s2 138.733105: softirq_entry: vec=1 [action=TIMER] bash-1998 [000] ..s2 138.733106: softirq_exit: vec=1 [action=TIMER] bash-1998 [000] ..s2 138.733106: softirq_entry: vec=9 [action=RCU] bash-1998 [000] ..s2 138.733109: softirq_exit: vec=9 [action=RCU] sshd-1995 [001] d.h1 138.733278: irq_handler_entry: irq=21 name=uhci_hcd:usb4 sshd-1995 [001] d.h1 138.733280: irq_handler_exit: irq=21 ret=unhandled sshd-1995 [001] d.h1 138.733281: irq_handler_entry: irq=21 name=eth0 sshd-1995 [001] d.h1 138.733283: irq_handler_exit: irq=21 ret=handled [...] # cat instances/zoot/trace # tracer: nop # # entries-in-buffer/entries-written: 18996/18996 #P:4 # # _-----=> irqs-off # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / delay # TASK-PID CPU# |||| TIMESTAMP FUNCTION # | | | |||| | | bash-1998 [000] d... 140.733501: sys_write -> 0x2 bash-1998 [000] d... 140.733504: sys_dup2(oldfd: a, newfd: 1) bash-1998 [000] d... 140.733506: sys_dup2 -> 0x1 bash-1998 [000] d... 140.733508: sys_fcntl(fd: a, cmd: 1, arg: 0) bash-1998 [000] d... 140.733509: sys_fcntl -> 0x1 bash-1998 [000] d... 140.733510: sys_close(fd: a) bash-1998 [000] d... 140.733510: sys_close -> 0x0 bash-1998 [000] d... 140.733514: sys_rt_sigprocmask(how: 0, nset: 0, oset: 6e2768, sigsetsize: 8) bash-1998 [000] d... 140.733515: sys_rt_sigprocmask -> 0x0 bash-1998 [000] d... 140.733516: sys_rt_sigaction(sig: 2, act: 7fff718846f0, oact: 7fff71884650, sigsetsize: 8) bash-1998 [000] d... 140.733516: sys_rt_sigaction -> 0x0
Можно видеть, трассировка в буфере верхнего уровня показывает лишь отслеживание функций, а экземпляр foo показывает пробуждения (wakeup) и переключатели задач.
Для удаления экземпляров просто удалите соответствующие каталоги
# rmdir instances/foo # rmdir instances/bar # rmdir instances/zoot
Отметим, что при наличии у процесса открытого в удаляемом экземпляре файла rmdir будет приводить к отказу с ошибкой EBUSY.
Трассировка стека
Поскольку размер стека ядра фиксирован, важно не исчерпать этот размер вызовами функций. Разработчики ядра должны понимать, что они помещают в стек. Если добавлять туда слишком много, может произойти переполнение стека, которое обычно ведет к аварийному завершению работы системы.
Существуют инструменты, периодически проверяющие использование стека (обычно с прерываниями). Поскольку ftrace обеспечивает трассировку функций, это позволяет проверять размер стека при каждом вызове функции. Это включается через трассировщик стека. Для включения возможности трассировки стека служит конфигурационный параметр ядра CONFIG_STACK_TRACER.
Для включения трассировки стека записывается 1 в файл /proc/sys/kernel/stack_tracer_enabled
# echo 1 > /proc/sys/kernel/stack_tracer_enabled
Можно также включить трассировку из командной строки ядра для отслеживания стека в процессе загрузки ядра путем добавления команды stacktrace.
Через несколько минут после активизации трассировки можно посмотреть результаты.
# cat stack_max_size8 2928 # cat stack_trace Depth Size Location (18 entries) ----- ---- -------- 0) 2928 224 update_sd_lb_stats+0xbc/0x4ac 1) 2704 160 find_busiest_group+0x31/0x1f1 2) 2544 256 load_balance+0xd9/0x662 3) 2288 80 idle_balance+0xbb/0x130 4) 2208 128 __schedule+0x26e/0x5b9 5) 2080 16 schedule+0x64/0x66 6) 2064 128 schedule_timeout+0x34/0xe0 7) 1936 112 wait_for_common+0x97/0xf1 8) 1824 16 wait_for_completion+0x1d/0x1f 9) 1808 128 flush_work+0xfe/0x119 10) 1680 16 tty_flush_to_ldisc+0x1e/0x20 11) 1664 48 input_available_p+0x1d/0x5c 12) 1616 48 n_tty_poll+0x6d/0x134 13) 1568 64 tty_poll+0x64/0x7f 14) 1504 880 do_select+0x31e/0x511 15) 624 400 core_sys_select+0x177/0x216 16) 224 96 sys_select+0x91/0xb9 17) 128 128 system_call_fastpath+0x16/0x1b
Отметим, что при использовании опции -mfentry в компиляторе gcc функции будут отслеживаться до того, как они организуют кадр стека. Это означает, что функции конечного уровня (листья – leaf) не будут отслеживаться трассировщиком стека при использовании опции -mfentry. В настоящее время эта опция поддерживается gcc 4.6.0 и выше только на архитектуре x86.
По материалам документации к ядру Linux
Николай Малых
1Следует отметить, что в некоторых вариантах Linux каталог /sys/kernel/tracing остается пустым, а все упомянутые здесь файлы размещаются только в /sys/kernel/debug/tracing
2В GCC термин trampoline означает метод реализации указателей на вложенные функции. Trampoline – это небольшой фрагмент кода, который создается «на лету» в стеке, когда берется адрес вложенной функции. Trampoline устанавливает статический указатель на привязку (link), который позволяет вложенной функции обращаться к переменным вмещающей ее функции. Указателем на функцию является просто адрес trampoline. Это избавляет от применения «толстых» указателей на функции, передающих адрес кода и статическую ссылку. Однако это вступает в противоречие с тенденцией отказа от исполняемого стека по соображениям безопасности.
3Real time.
4Следует отметить, что, не смотря на одиночный символ >, содержимое файла не переписывается и меняется лишь строка указанной опции.
5Thread Group ID – идентификатор группы потоков.
6Включение FTRACE будет добавлять опции -pg при компиляции ядра
7Начиная с gcc версии 4.6 для процессоров x86 можно добавить опцию -mfentry, которая будет вызывать __fentry__ взамен mcount до создания кадра стека.
8Эта команда показывает не максимальный размер стека, а его максимальное использование к текущему моменту. Для просмотра максимального размера стека, установленного в ядре, служит команда ulimit -s.