Отображение трассировки стека из запущенного приложения Python
У меня есть это приложение Python, которое время от времени зависает, и я не могу узнать, где.
Есть ли какой-нибудь способ дать интерпретатору Python указание точного кода, который выполняется?
Какая-то трассировка стека на лету?
Смежные вопросы:
29 ответов
У меня есть модуль, который я использую для подобных ситуаций - когда процесс будет работать долго, но иногда застревает по неизвестным и невоспроизводимым причинам. Это немного странно, и работает только на Unix (требует сигналов):
import code, traceback, signal
def debug(sig, frame):
"""Interrupt running process, and provide a python prompt for
interactive debugging."""
d={'_frame':frame} # Allow access to frame object.
d.update(frame.f_globals) # Unless shadowed by global
d.update(frame.f_locals)
i = code.InteractiveConsole(d)
message = "Signal received : entering python shell.\nTraceback:\n"
message += ''.join(traceback.format_stack(frame))
i.interact(message)
def listen():
signal.signal(signal.SIGUSR1, debug) # Register handler
Для использования просто вызовите функцию listen() в какой-то момент, когда ваша программа запускается (вы можете даже вставить ее в site.py, чтобы все программы Python ее использовали), и позволить ей работать. В любой момент отправьте процессу сигнал SIGUSR1, используя kill или в python:
os.kill(pid, signal.SIGUSR1)
Это приведет к тому, что программа перейдет к консоли Python в точке, в которой она находится в данный момент, показывая трассировку стека и позволяя вам манипулировать переменными. Используйте control-d (EOF) для продолжения работы (хотя учтите, что вы, вероятно, прервете любой ввод-вывод и т. Д. В точке, о которой вы сигнализируете, так что это не является полностью ненавязчивым).
У меня есть другой скрипт, который делает то же самое, за исключением того, что он связывается с запущенным процессом по каналу (для отладки фоновых процессов и т. Д.). Это немного велико, чтобы публиковать здесь, но я добавил его как рецепт кулинарной книги питона.
Предложение об установке обработчика сигналов хорошее, и я им часто пользуюсь. Например, bzr по умолчанию устанавливает обработчик SIGQUIT, который вызывает pdb.set_trace()
немедленно бросить вас в приглашении pdb. (Точные подробности см. В источнике модуля bzrlib.breakin.) С помощью pdb вы можете не только получить текущую трассировку стека, но и проверить переменные и т. Д.
Однако иногда мне нужно отлаживать процесс, в котором у меня не было предвидения, чтобы установить обработчик сигналов. В Linux вы можете присоединить к процессу gdb и получить трассировку стека python с помощью некоторых макросов gdb. Поместите http://svn.python.org/projects/python/trunk/Misc/gdbinit в~/.gdbinit
, затем:
- Прикрепить GDB:
gdb -p
PID
- Получить трассировку стека Python:
pystack
К сожалению, это не совсем надежно, но работает большую часть времени.
Наконец, прикрепление strace
часто может дать вам хорошее представление о том, что делает процесс.
Я почти всегда имею дело с несколькими потоками, и основной поток, как правило, мало что делает, поэтому самое интересное - сбросить все стеки (что больше похоже на дамп Java). Вот реализация, основанная на этом блоге:
import threading, sys, traceback
def dumpstacks(signal, frame):
id2name = dict([(th.ident, th.name) for th in threading.enumerate()])
code = []
for threadId, stack in sys._current_frames().items():
code.append("\n# Thread: %s(%d)" % (id2name.get(threadId,""), threadId))
for filename, lineno, name, line in traceback.extract_stack(stack):
code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
if line:
code.append(" %s" % (line.strip()))
print "\n".join(code)
import signal
signal.signal(signal.SIGQUIT, dumpstacks)
Получение трассировки стека неподготовленной Python-программы, работающей на стандартном Python без отладочных символов, может быть выполнено с помощью Pyrasite. В Ubuntu Trusty для меня это сработало:
$ sudo pip install pyrasite
$ echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
$ sudo pyrasite 16262 dump_stacks.py # dumps stacks to stdout/stderr of the python program
(Шляпа на @Albert, ответ на который содержал указатель на это, среди других инструментов.)
>>> import traceback
>>> def x():
>>> print traceback.extract_stack()
>>> x()
[('<stdin>', 1, '<module>', None), ('<stdin>', 2, 'x', None)]
Вы также можете отформатировать трассировку стека, см. Документацию.
Редактировать: чтобы имитировать поведение Java, как предлагает @Douglas Leeder, добавьте это:
import signal
import traceback
signal.signal(signal.SIGUSR1, lambda sig, stack: traceback.print_stack(stack))
к коду запуска в вашем приложении. Затем вы можете распечатать стопку, отправив SIGUSR1
к запущенному процессу Python.
Модуль traceback имеет несколько приятных функций, среди которых: print_stack:
import traceback
traceback.print_stack()
Вы можете попробовать модуль обработчика ошибок. Установите его, используя pip install faulthandler
и добавить:
import faulthandler, signal
faulthandler.register(signal.SIGUSR1)
в начале вашей программы. Затем отправьте SIGUSR1 в ваш процесс (например: kill -USR1 42
) для отображения трассировки Python всех потоков на стандартный вывод. Прочитайте документацию для получения дополнительных опций (например, войти в файл) и других способов отображения трассировки.
Модуль теперь является частью Python 3.3. Для Python 2 см. http://faulthandler.readthedocs.org/
Что мне действительно здесь помогло, так это совет spiv (за который я бы проголосовал и прокомментировал, если бы у меня были очки репутации), чтобы получить трассировку стека из неподготовленного процесса Python. За исключением того, что он не работал, пока я не изменил скрипт gdbinit. Так:
загрузите http://svn.python.org/projects/python/trunk/Misc/gdbinit и вставьте его в
~/.gdbinit
редактировать его, изменяя[править: больше не нужно; связанный файл уже имеет это изменение по состоянию на 2010-01-14]PyEval_EvalFrame
вPyEval_EvalFrameEx
Прикрепить GDB:
gdb -p PID
Получить трассировку стека Python:
pystack
Это можно сделать с помощью отличного пи-шпиона. Это профилировщик выборки для программ Python, поэтому его задача - подключаться к процессам Python и создавать образцы их стеков вызовов. Следовательно,py-spy dump --pid $SOME_PID
все, что вам нужно сделать, чтобы сбросить стеки вызовов всех потоков в $SOME_PID
процесс. Обычно ему требуются повышенные привилегии (для чтения памяти целевого процесса).
Вот пример того, как это выглядит для многопоточного приложения Python.
$ sudo py-spy dump --pid 31080
Process 31080: python3.7 -m chronologer -e production serve -u www-data -m
Python v3.7.1 (/usr/local/bin/python3.7)
Thread 0x7FEF5E410400 (active): "MainThread"
_wait (cherrypy/process/wspbus.py:370)
wait (cherrypy/process/wspbus.py:384)
block (cherrypy/process/wspbus.py:321)
start (cherrypy/daemon.py:72)
serve (chronologer/cli.py:27)
main (chronologer/cli.py:84)
<module> (chronologer/__main__.py:5)
_run_code (runpy.py:85)
_run_module_as_main (runpy.py:193)
Thread 0x7FEF55636700 (active): "_TimeoutMonitor"
run (cherrypy/process/plugins.py:518)
_bootstrap_inner (threading.py:917)
_bootstrap (threading.py:885)
Thread 0x7FEF54B35700 (active): "HTTPServer Thread-2"
accept (socket.py:212)
tick (cherrypy/wsgiserver/__init__.py:2075)
start (cherrypy/wsgiserver/__init__.py:2021)
_start_http_thread (cherrypy/process/servers.py:217)
run (threading.py:865)
_bootstrap_inner (threading.py:917)
_bootstrap (threading.py:885)
...
Thread 0x7FEF2BFFF700 (idle): "CP Server Thread-10"
wait (threading.py:296)
get (queue.py:170)
run (cherrypy/wsgiserver/__init__.py:1586)
_bootstrap_inner (threading.py:917)
_bootstrap (threading.py:885)
python -dv yourscript.py
Это заставит интерпретатор работать в режиме отладки и даст вам представление о том, что делает интерпретатор.
Если вы хотите интерактивно отлаживать код, вы должны запустить его так:
python -m pdb yourscript.py
Это говорит интерпретатору python запускать ваш скрипт с модулем "pdb", который является отладчиком python, если вы запустите его так, что интерпретатор будет выполняться в интерактивном режиме, во многом как GDB
Я бы добавил это как комментарий к ответу haridsv, но мне не хватает репутации, чтобы сделать это:
Некоторые из нас все еще привязаны к версии Python старше 2.6 (требуется для Thread.ident), поэтому я получил код, работающий на Python 2.5 (хотя и без отображения имени потока), как таковой:
import traceback
import sys
def dumpstacks(signal, frame):
code = []
for threadId, stack in sys._current_frames().items():
code.append("\n# Thread: %d" % (threadId))
for filename, lineno, name, line in traceback.extract_stack(stack):
code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
if line:
code.append(" %s" % (line.strip()))
print "\n".join(code)
import signal
signal.signal(signal.SIGQUIT, dumpstacks)
Посмотрите на faulthandler
модуль, новый в Python 3.3. faulthandler
Backport для использования в Python 2 доступен на PyPI.
Некоторое время я искал решение для отладки своих потоков и нашел его здесь благодаря haridsv. Я использую слегка упрощенную версию с использованием traceback.print_stack():
import sys, traceback, signal
import threading
import os
def dumpstacks(signal, frame):
id2name = dict((th.ident, th.name) for th in threading.enumerate())
for threadId, stack in sys._current_frames().items():
print(id2name[threadId])
traceback.print_stack(f=stack)
signal.signal(signal.SIGQUIT, dumpstacks)
os.killpg(os.getpgid(0), signal.SIGQUIT)
Для своих нужд я также фильтрую темы по имени.
Вы можете использовать пакет hypno, например:
hypno <pid> "import traceback; traceback.print_stack()"
Это выведет трассировку стека в стандартный вывод программы.
В качестве альтернативы, если вы не хотите ничего выводить на стандартный вывод или у вас нет доступа к нему (например, демон), вы можете использовать пакет madbg, который представляет собой отладчик python, который позволяет вам подключаться к запущенному python и отлаживайте ее в своем текущем терминале. Это похоже наpyrasite
а также pyringe
, но новее, не требует GDB и использует IPython
для отладчика (что означает цвета и автозаполнение).
Чтобы увидеть трассировку стека запущенной программы, вы можете запустить:
madbg attach <pid>
И в оболочке отладчика введите:bt
Отказ от ответственности - я написал оба пакета
Если вы работаете в системе Linux, используйте удивительный gdb
с расширениями отладки Python (может быть в python-dbg
или же python-debuginfo
пакет). Это также помогает с многопоточными приложениями, приложениями GUI и модулями C.
Запустите вашу программу с:
$ gdb -ex r --args python <programname>.py [arguments]
Это инструктирует gdb
подготовить python <programname>.py <arguments>
а также r
Блок.
Теперь, когда ваша программа зависает, переключитесь на gdb
консоль, нажмите Ctr+C и выполните:
(gdb) thread apply all py-list
Смотрите пример сессии и больше информации здесь и здесь.
В Solaris вы можете использовать pstack(1). Нет необходимости вносить изменения в код Python. например.
# pstack 16000 | grep : | head
16000: /usr/bin/python2.6 /usr/lib/pkg.depotd --cfg svc:/application/pkg/serv
[ /usr/lib/python2.6/vendor-packages/cherrypy/process/wspbus.py:282 (_wait) ]
[ /usr/lib/python2.6/vendor-packages/cherrypy/process/wspbus.py:295 (wait) ]
[ /usr/lib/python2.6/vendor-packages/cherrypy/process/wspbus.py:242 (block) ]
[ /usr/lib/python2.6/vendor-packages/cherrypy/_init_.py:249 (quickstart) ]
[ /usr/lib/pkg.depotd:890 (<module>) ]
[ /usr/lib/python2.6/threading.py:256 (wait) ]
[ /usr/lib/python2.6/Queue.py:177 (get) ]
[ /usr/lib/python2.6/vendor-packages/pkg/server/depot.py:2142 (run) ]
[ /usr/lib/python2.6/threading.py:477 (run)
etc.
Я собрал воедино какой-то инструмент, который подключается к работающему процессу Python и внедряет некоторый код, чтобы получить оболочку Python.
Смотрите здесь: https://github.com/albertz/pydbattach
Стоит взглянуть на Pydb, "расширенную версию отладчика Python, свободно основанную на наборе команд gdb". Он включает в себя диспетчеры сигналов, которые могут позаботиться о запуске отладчика при отправке указанного сигнала.
В проекте Summer of Code 2006 года рассматривалось добавление функций удаленной отладки в pydb в модуле с именем mpdb.
Если вы хотите заглянуть в работающее приложение Python, чтобы увидеть «живой» стек вызовов в подобном топу стиле, вы можете использовать austin-tui (https://github.com/p403n1x87/austin-tui). Вы можете установить его из PyPI, например,
pipx install austin-tui
Обратите внимание, что для работы требуется двоичный файл austin (https://github.com/p403n1x87/austin), но затем вы можете подключиться к запущенному процессу Python с помощью
austin-tui -p <pid>
pyringe - это отладчик, который может взаимодействовать с запущенными процессами Python, трассировкой стека печати, переменными и т. д. без какой-либо предварительной настройки.
Хотя в прошлом я часто использовал решение для обработки сигналов, в некоторых средах по-прежнему бывает трудно воспроизвести проблему.
Я нахожусь в лагере GDB с расширениями Python. Следуйте https://wiki.python.org/moin/DebuggingWithGdb, что означает
dnf install gdb python-debuginfo
или жеsudo apt-get install gdb python2.7-dbg
gdb python <pid of running process>
py-bt
Также рассмотрим info threads
а также thread apply all py-bt
,
Как отладить любую функцию в консоли:
Создайте функцию, в которой вы используете pdb.set_trace (), затем функцию, которую вы хотите отладить.
>>> import pdb
>>> import my_function
>>> def f():
... pdb.set_trace()
... my_function()
...
Затем вызовите созданную функцию:
>>> f()
> <stdin>(3)f()
(Pdb) s
--Call--
> <stdin>(1)my_function()
(Pdb)
Удачной отладки:)
Нет способа подключиться к работающему процессу Python и получить разумные результаты. То, что я делаю, если процессы блокируются, это просто цепляет себя и пытается выяснить, что именно происходит.
К сожалению, часто страус является наблюдателем, который "фиксирует" условия гонки, так что результат там тоже бесполезен.
Для этого вы можете использовать PuDB, отладчик Python с интерфейсом curses. Просто добавь
from pudb import set_interrupt_handler; set_interrupt_handler()
к вашему коду и используйте Ctrl-C, когда вы хотите сломать. Вы можете продолжить с c
и сломать снова несколько раз, если вы пропустите это и хотите попробовать снова.
В Python 3 pdb автоматически установит обработчик сигнала при первом использовании c(ont(inue)) в отладчике. После этого нажмите Control-C, и вы снова окажетесь там. В Python 2 приведен однострочник, который должен работать даже в относительно старых версиях (протестировано в 2.7, но я проверил исходники Python до 2.4, и все выглядело нормально):
import pdb, signal
signal.signal(signal.SIGINT, lambda sig, frame: pdb.Pdb().set_trace(frame))
pdb стоит изучить, если вы потратите какое-то время на отладку Python. Интерфейс немного туповат, но должен быть знаком любому, кто использовал подобные инструменты, такие как gdb.
Я не знаю ничего похожего на ответ java на SIGQUIT, поэтому вам, возможно, придется встроить его в свое приложение. Может быть, вы могли бы создать сервер в другом потоке, который может получить трассировку стека при ответе на какое-либо сообщение?
В случае, если вам нужно сделать это с помощью uWSGI, в него встроен Python Tracebacker, и достаточно просто включить его в конфигурации (номер прикреплен к имени для каждого работника):
py-tracebacker=/var/run/uwsgi/pytrace
Сделав это, вы можете распечатать трассировку, просто подключившись к сокету:
uwsgi --connect-and-read /var/run/uwsgi/pytrace1
Используйте модуль проверки.
import inspect help (inspect.stack) Справка по стеку функций в модуле inspect:
stack (context = 1) Возвращает список записей для стека над фреймом вызывающего.
Я нахожу это очень полезным на самом деле.
В момент запуска кода вы можете вставить этот небольшой фрагмент, чтобы увидеть хорошо отформатированную трассировку напечатанного стека. Предполагается, что у вас есть папка с именемlogs
в корневом каталоге вашего проекта.
# DEBUG: START DEBUG -->
import traceback
with open('logs/stack-trace.log', 'w') as file:
traceback.print_stack(file=file)
# DEBUG: END DEBUG --!