Вы можете изменить формат файла для создания профилей спидскопа или необработанных данных с помощью параметра --format
. См. py-spy record --help
для получения информации о других параметрах, включая изменение частоты выборки, фильтрацию для включения только потоков, содержащих GIL, профилирование собственных расширений C, отображение идентификаторов потоков, профилирование подпроцессов и многое другое.
Top показывает в реальном времени, какие функции занимают больше всего времени в вашей программе Python, аналогично команде top в Unix. Запуск py-spy с помощью:
py-spy top --pid 12345
# OR
py-spy top -- python myprogram.py
отобразит интерактивное обновление высокого уровня вашей программы Python:
py-spy также может отображать текущий стек вызовов для каждого потока Python с помощью команды dump
:
py-spy dump --pid 12345
Это выведет на консоль стеки вызовов для каждого потока и некоторую другую базовую информацию о процессе:
Это полезно в случае, когда вам нужен всего лишь один стек вызовов, чтобы выяснить, где зависла ваша программа Python. Эта команда также имеет возможность распечатать локальные переменные, связанные с каждым кадром стека, установив флаг --locals
.
Цель этого проекта — предоставить вам возможность профилировать и отлаживать любую работающую программу Python, даже если она обслуживает производственный трафик.
Хотя существует множество других проектов профилирования Python, почти все они требуют той или иной модификации профилируемой программы. Обычно код профилирования выполняется внутри целевого процесса Python, что замедляет работу программы и изменяет ее работу. Это означает, что в целом небезопасно использовать эти профилировщики для отладки проблем в производственных сервисах, поскольку они обычно оказывают заметное влияние на производительность.
py-spy работает путем прямого чтения памяти программы Python с помощью системного вызоваprocess_vm_readv в Linux, вызова vm_read в OSX или вызова ReadProcessMemory в Windows.
Выяснение стека вызовов программы Python выполняется путем просмотра глобальной переменной PyInterpreterState, чтобы получить все потоки Python, работающие в интерпретаторе, а затем перебора каждого PyFrameObject в каждом потоке, чтобы получить стек вызовов. Поскольку ABI Python меняется в зависимости от версии, мы используем привязку ржавчины для создания различных структур ржавчины для каждого класса интерпретатора Python, который нас интересует, и используем эти сгенерированные структуры для определения структуры памяти в программе Python.
Получить адрес памяти интерпретатора Python может быть немного сложнее из-за рандомизации структуры адресного пространства. Если целевой интерпретатор Python поставляется с символами, довольно легко определить адрес памяти интерпретатора, разыменовав переменные interp_head
или _PyRuntime
в зависимости от версии Python. Однако многие версии Python поставляются либо с удаленными двоичными файлами, либо без соответствующих файлов символов PDB в Windows. В этих случаях мы просматриваем раздел BSS на предмет адресов, которые выглядят так, как будто они могут указывать на действительный PyInterpreterState, и проверяем, соответствует ли расположение этого адреса тому, что мы ожидаем.
Да! py-spy поддерживает профилирование собственных расширений Python, написанных на таких языках, как C/C++ или Cython, в x86_64 Linux и Windows. Вы можете включить этот режим, передав --native
в командной строке. Для достижения наилучших результатов вам следует скомпилировать расширение Python с символами. Для программ Cython также стоит отметить, что py-spy нужен сгенерированный файл C или C++, чтобы возвращать номера строк исходного файла .pyx. Прочтите сообщение в блоге для получения дополнительной информации.
Передавая флаг --subprocesses
либо в запись, либо в вид сверху, py-spy также включит выходные данные любого процесса Python, который является дочерним процессом целевой программы. Это полезно для профилирования приложений, использующих многопроцессорные пулы или рабочие пулы. py-spy будет отслеживать новые создаваемые процессы, автоматически присоединяться к ним и включать образцы из них в выходные данные. Представление записи будет включать PID и командную строку каждой программы в стеке вызовов, при этом подпроцессы будут отображаться как дочерние процессы своих родительских процессов.
py-spy работает, считывая память из другого процесса Python, и это может быть запрещено по соображениям безопасности в зависимости от вашей ОС и настроек системы. Во многих случаях работа от имени пользователя root (с помощью sudo или чего-то подобного) позволяет обойти эти ограничения безопасности. OSX всегда требует запуска от имени пользователя root, но в Linux это зависит от того, как вы запускаете py-spy, и настроек безопасности системы.
В Linux конфигурация по умолчанию требует прав root при подключении к процессу, который не является дочерним. Для py-spy это означает, что вы можете профилировать без root-доступа, заставив py-spy создать процесс ( py-spy record -- python myprogram.py
), но для подключения к существующему процессу с указанием PID обычно требуется root ( sudo py-spy record --pid 123456
). Вы можете снять это ограничение в Linux, установив sysctl переменную ptrace_scope.
py-spy пытается включать трассировки стека только из потоков, в которых активно выполняется код, и исключать потоки, которые находятся в режиме ожидания или иным образом простаивают. Когда это возможно, py-spy пытается получить информацию об активности этого потока из ОС: читая /proc/PID/stat
в Linux, используя вызов mach thread_basic_info в OSX и проверяя, известен ли текущий SysCall как бездействующий. в Windows.
Однако у этого подхода есть некоторые ограничения, из-за которых простаивающие потоки могут по-прежнему помечаться как активные. Прежде всего, мы должны получить информацию об активности этого потока перед приостановкой программы, потому что получение этой информации из приостановленной программы приведет к тому, что она всегда будет возвращать сообщение о том, что она простаивает. Это означает, что существует потенциальное состояние гонки, когда мы получаем активность потока, а затем поток находится в другом состоянии, когда мы получаем трассировку стека. Запрос активности потоков у ОС также еще не реализован для процессоров FreeBSD и i686/ARM в Linux. В Windows вызовы, заблокированные при вводе-выводе, также не будут помечены как бездействующие, например, при чтении ввода со стандартного ввода. Наконец, в некоторых Linux вызовы ptrace Attach, которые мы используем, могут привести к мгновенному пробуждению простаивающих потоков, вызывая ложные срабатывания при чтении из procfs. По этим причинам у нас также есть эвристический запасной вариант, который помечает известные определенные известные вызовы в Python как бездействующие.
Вы можете отключить эту функцию, установив флаг --idle
, который будет включать кадры, которые py-spy считает бездействующими.
Мы получаем активность GIL, просматривая значение threadid, на которое указывает символ _PyThreadState_Current
для Python 3.6 и более ранних версий, и находя эквивалент из структуры _PyRuntime
в Python 3.7 и более поздних версиях. Эти символы могут не быть включены в ваш дистрибутив Python, что приведет к сбою определения того, какой поток удерживает GIL. Текущее использование GIL также отображается в виде top
как %GIL.
Передача флага --gil
будет включать трассировку только для потоков, удерживающих глобальную блокировку интерпретатора. В некоторых случаях это может быть более точным представлением о том, как ваша программа Python тратит свое время, хотя вы должны знать, что при этом будет пропущена активность в расширениях, которые выпускают GIL, пока они еще активны.
В OSX есть функция под названием «Защита целостности системы», которая не позволяет даже пользователю root читать память из любого двоичного файла, расположенного в /usr/bin. К сожалению, сюда входит интерпретатор Python, поставляемый с OSX.
Есть несколько способов справиться с этим:
Запуск py-spy внутри Docker-контейнера также обычно приводит к ошибке отказа в разрешениях, даже при запуске от имени пользователя root.
Эта ошибка вызвана тем, что Docker ограничивает используемый нами системный вызовprocess_vm_readv. Это можно переопределить, установив --cap-add SYS_PTRACE
при запуске Docker-контейнера.
Альтернативно вы можете отредактировать файл yaml docker-compose.
your_service:
cap_add:
- SYS_PTRACE
Обратите внимание: вам потребуется перезапустить Docker-контейнер, чтобы этот параметр вступил в силу.
Вы также можете использовать py-spy из ОС хоста для профилирования запущенного процесса, работающего внутри контейнера докеров.
py-spy требуется SYS_PTRACE
, чтобы иметь возможность читать память процесса. Kubernetes по умолчанию отключает эту возможность, что приводит к ошибке.
Permission Denied: Try running again with elevated permissions by going 'sudo env "PATH=$PATH" !!'
Рекомендуемый способ справиться с этой проблемой — отредактировать спецификацию и добавить эту возможность. Для развертывания это делается путем добавления этого в Deployment.spec.template.spec.containers
securityContext:
capabilities:
add:
- SYS_PTRACE
Более подробную информацию об этом можно найти здесь: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-capabilities-for-a-container. Обратите внимание, что при этом существующие модули будут удалены и созданы заново. .
Alpine Python отказывается от использования колес manylinux
: pypa/pip#3969 (комментарий). Вы можете переопределить это поведение, чтобы использовать pip для установки py-spy на Alpine, выполнив:
echo 'manylinux1_compatible = True' > /usr/local/lib/python3.7/site-packages/_manylinux.py
Альтернативно вы можете загрузить двоичный файл musl со страницы выпусков GitHub.
Установив опцию --nonblocking
, py-spy не будет приостанавливать целевой Python, из которого вы выполняете профилирование. Хотя влияние выборки из процесса с помощью py-spy на производительность обычно чрезвычайно низкое, установка этой опции позволит полностью избежать прерывания работы вашей программы Python.
Если эта опция установлена, py-spy вместо этого будет считывать состояние интерпретатора из процесса Python во время его работы. Поскольку вызовы, которые мы используем для чтения памяти, не являются атомарными, и нам приходится выполнять несколько вызовов, чтобы получить трассировку стека, это означает, что иногда мы получаем ошибки при выборке. Это может проявляться в увеличении частоты ошибок при выборке или в виде частичных кадров стека, включенных в выходные данные.
Пока нет =).
Если есть функции, которые вы хотели бы видеть в py-spy, либо отметьте соответствующую проблему, либо создайте новую, описывающую, какая функциональность отсутствует.
py-spy соответствует спецификации CLICOLOR, поэтому установка CLICOLOR_FORCE=1
в вашей среде будет обеспечивать цветную печать py-spy даже при передаче на пейджер.
py-spy во многом вдохновлен превосходной работой Джулии Эванс над rbspy. В частности, код для создания файлов Flamegraph и Speedscope взят непосредственно из rbspy, а в этом проекте используются крейты read-process-memory и proc-maps, выделенные из rbspy.
py-spy выпускается под лицензией MIT, полный текст см. в файле LICENSE.