Thỉnh thoảng tôi có ứng dụng Python này bị kẹt và tôi không thể tìm ra nơi nào.
Có cách nào để báo hiệu trình thông dịch Python để hiển thị cho bạn mã chính xác đang chạy không?
Một số loại stacktrace trên bay?
Câu hỏi liên quan:
Thỉnh thoảng tôi có ứng dụng Python này bị kẹt và tôi không thể tìm ra nơi nào.
Có cách nào để báo hiệu trình thông dịch Python để hiển thị cho bạn mã chính xác đang chạy không?
Một số loại stacktrace trên bay?
Câu hỏi liên quan:
Câu trả lời:
Tôi có mô-đun tôi sử dụng cho các tình huống như thế này - trong đó một quy trình sẽ chạy trong một thời gian dài nhưng đôi khi bị kẹt vì những lý do không xác định và không thể thực hiện được. Nó hơi hack và chỉ hoạt động trên unix (yêu cầu tín hiệu):
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
Để sử dụng, chỉ cần gọi hàm nghe () tại một số điểm khi chương trình của bạn khởi động (Bạn thậm chí có thể dán nó vào trang web.py để tất cả các chương trình python sử dụng nó) và để nó chạy. Tại bất kỳ thời điểm nào, hãy gửi quá trình tín hiệu SIGUSR1, sử dụng kill hoặc trong python:
os.kill(pid, signal.SIGUSR1)
Điều này sẽ khiến chương trình chuyển sang bảng điều khiển python tại thời điểm hiện tại, hiển thị cho bạn dấu vết ngăn xếp và cho phép bạn thao tác với các biến. Sử dụng control-d (EOF) để tiếp tục chạy (mặc dù lưu ý rằng bạn có thể sẽ làm gián đoạn bất kỳ I / O nào v.v. tại điểm bạn báo hiệu, vì vậy nó không hoàn toàn không xâm phạm.
Tôi có một tập lệnh khác thực hiện điều tương tự, ngoại trừ nó giao tiếp với quy trình đang chạy thông qua một đường ống (để cho phép gỡ lỗi các quy trình nền, v.v.). Nó hơi lớn để đăng ở đây, nhưng tôi đã thêm nó như một công thức nấu ăn trăn .
faulthandler
mô-đun (và cổng sau của nó được tìm thấy trên PyPI) cho trình xử lý tín hiệu mức C sẽ in ngăn xếp Python mà không yêu cầu vòng lặp trình thông dịch phải phản hồi.
Đề xuất để cài đặt một trình xử lý tín hiệu là một điều tốt, và tôi sử dụng nó rất nhiều. Ví dụ, bzr theo mặc định sẽ cài đặt trình xử lý SIGQUIT để gọi pdb.set_trace()
ngay lập tức đưa bạn vào dấu nhắc pdb . (Xem nguồn của mô-đun bzrlib.breakin để biết chi tiết chính xác.) Với pdb, bạn không chỉ có thể theo dõi ngăn xếp hiện tại mà còn kiểm tra các biến, v.v.
Tuy nhiên, đôi khi tôi cần gỡ lỗi một quy trình mà tôi không có tầm nhìn xa để cài đặt trình xử lý tín hiệu. Trên linux, bạn có thể đính kèm gdb vào quy trình và lấy dấu vết ngăn xếp python với một số macro gdb. Đặt http://svn.python.org/projects/python/trunk/Misc/gdbinit vào ~/.gdbinit
, sau đó:
gdb -p
PID
pystack
Thật không may, nó không hoàn toàn đáng tin cậy, nhưng nó hoạt động hầu hết thời gian.
Cuối cùng, việc đính kèm strace
thường có thể cho bạn ý tưởng tốt về quy trình đang làm.
python-dbg
). Không có những biểu tượng đó, bạn dường như không nhận được nhiều thông tin hữu ích.
Unable to locate python frame
với mỗi lệnh
Tôi hầu như luôn luôn xử lý nhiều luồng và luồng chính thường không hoạt động nhiều, vì vậy điều thú vị nhất là kết xuất tất cả các ngăn xếp (giống như kết xuất của Java). Đây là một triển khai dựa trên blog này :
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)
Bắt một dấu vết ngăn xếp của một chương trình python chưa chuẩn bị , chạy trong một python stock mà không cần gỡ lỗi các biểu tượng có thể được thực hiện với pyrasite . Làm việc như một cơ duyên đối với tôi trong 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
(Hat tip cho @Albert, người có câu trả lời chứa con trỏ tới cái này, trong số các công cụ khác.)
dump_stacks.py
đơn giản làimport traceback; traceback.print_stack()
traceback -l
cung cấp cho bạn một danh sách các kịch bản python được xác định trước mà bạn có thể sử dụng và dump_stacks.py
là một trong số chúng. Nếu bạn đang sử dụng của riêng bạn (ví dụ để viết theo dõi ngăn xếp vào một tệp), có thể là khôn ngoan khi sử dụng một tên khác.
apt-get install gdb python-dbg
(hoặc tương đương) trước khi chạy pyrasite, nếu không nó sẽ âm thầm thất bại. Hoạt động như một sự quyến rũ khác!
>>> import traceback
>>> def x():
>>> print traceback.extract_stack()
>>> x()
[('<stdin>', 1, '<module>', None), ('<stdin>', 2, 'x', None)]
Bạn cũng có thể định dạng độc đáo theo dõi ngăn xếp, xem tài liệu .
Chỉnh sửa : Để mô phỏng hành vi của Java, như được đề xuất bởi @Doumund Leeder, hãy thêm điều này:
import signal
import traceback
signal.signal(signal.SIGUSR1, lambda sig, stack: traceback.print_stack(stack))
mã khởi động trong ứng dụng của bạn. Sau đó, bạn có thể in ngăn xếp bằng cách gửi SIGUSR1
đến quy trình Python đang chạy.
Các traceback mô-đun có một số chức năng tốt đẹp, trong đó có: print_stack:
import traceback
traceback.print_stack()
import traceback; f = open('/tmp/stack-trace.log', 'w') traceback.print_stack(file=f) f.close()
Bạn có thể thử mô-đun errorhandler . Cài đặt nó bằng cách sử dụng pip install faulthandler
và thêm:
import faulthandler, signal
faulthandler.register(signal.SIGUSR1)
vào đầu chương trình của bạn. Sau đó gửi SIGUSR1 đến tiến trình của bạn (ví dụ kill -USR1 42
:) để hiển thị truy nguyên Python của tất cả các luồng cho đầu ra tiêu chuẩn. Đọc tài liệu để có thêm tùy chọn (ví dụ: đăng nhập vào một tệp) và các cách khác để hiển thị truy nguyên.
Mô-đun này là một phần của Python 3.3. Đối với Python 2, xem http://faulthandler.readthedocs.org/
Điều thực sự giúp tôi ở đây là mẹo của spiv (mà tôi sẽ bỏ phiếu và nhận xét nếu tôi có điểm danh tiếng) để có được một dấu vết ngăn xếp trong quá trình Python chưa chuẩn bị . Ngoại trừ nó không hoạt động cho đến khi tôi sửa đổi tập lệnh gdbinit . Vì thế:
tải xuống http://svn.python.org/projects/python/trunk/Misc/gdbinit và đặt nó vào~/.gdbinit
chỉnh sửa nó, thay đổi [chỉnh sửa: không còn cần thiết; tệp được liên kết đã có thay đổi này kể từ 2010-01-14]PyEval_EvalFrame
thànhPyEval_EvalFrameEx
Đính kèm gdb: gdb -p PID
Lấy dấu vết ngăn xếp python: pystack
No symbol "co" in current context.
Tôi sẽ thêm điều này như một bình luận cho phản hồi của haridsv , nhưng tôi thiếu danh tiếng để làm như vậy:
Một số người trong chúng ta vẫn bị mắc kẹt trên một phiên bản Python cũ hơn 2.6 (bắt buộc cho Thread.ident), vì vậy tôi đã nhận được mã làm việc trong Python 2.5 (mặc dù không có tên luồng được hiển thị) như vậy:
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)
python -dv yourcript.py
Điều đó sẽ làm cho trình thông dịch chạy trong chế độ gỡ lỗi và cung cấp cho bạn một dấu vết về những gì trình thông dịch đang làm.
Nếu bạn muốn gỡ lỗi tương tác mã, bạn nên chạy nó như thế này:
python -m pdb của bạn
Điều đó nói cho trình thông dịch python chạy tập lệnh của bạn với mô-đun "pdb", đó là trình gỡ lỗi python, nếu bạn chạy nó như thế thì trình thông dịch sẽ được thực thi trong chế độ tương tác, giống như GDB
Hãy xem faulthandler
mô-đun, mới trong Python 3.3. Một faulthandler
backport để sử dụng trong Python 2 có sẵn trên PyPI.
Trên Solaris, bạn có thể sử dụng pstack (1) Không cần thay đổi mã python. ví dụ.
# 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.
pstack
thực hiện điều tương tự
Nếu bạn đang sử dụng hệ thống Linux, hãy sử dụng các gdb
phần mở rộng gỡ lỗi của Python (có thể ở trong python-dbg
hoặc python-debuginfo
gói). Nó cũng giúp với các ứng dụng đa luồng, ứng dụng GUI và mô-đun C.
Chạy chương trình của bạn với:
$ gdb -ex r --args python <programname>.py [arguments]
Điều này hướng dẫn gdb
để chuẩn bị python <programname>.py <arguments>
và r
un nó.
Bây giờ khi bạn lập trình bị treo, hãy chuyển sang gdb
bàn điều khiển, nhấn Ctr+Cvà thực hiện:
(gdb) thread apply all py-list
Xem phiên ví dụ và thêm thông tin ở đây và ở đây .
Tôi đã tìm kiếm một giải pháp để gỡ lỗi các chủ đề của mình và tôi đã tìm thấy nó ở đây nhờ haridsv. Tôi sử dụng phiên bản đơn giản hóa một chút bằng cách sử dụng tracBack.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)
Đối với nhu cầu của tôi, tôi cũng lọc chủ đề theo tên.
Thật đáng để xem xét Pydb , "một phiên bản mở rộng của trình gỡ lỗi Python lỏng lẻo dựa trên tập lệnh gdb". Nó bao gồm các trình quản lý tín hiệu có thể đảm nhiệm việc khởi động trình gỡ lỗi khi tín hiệu được chỉ định được gửi.
Một dự án Summer of Code năm 2006 đã xem xét việc thêm các tính năng sửa lỗi từ xa vào pydb trong một mô-đun gọi là mpdb .
Tôi đã hack cùng một số công cụ gắn vào một quy trình Python đang chạy và tiêm một số mã để lấy shell Python.
Xem tại đây: https://github.com/albertz/pydbattach
pyrasite
hoạt động hoàn hảo!
Nó có thể được thực hiện với py-spy tuyệt vời . Đây là một trình lược tả mẫu cho các chương trình Python , vì vậy công việc của nó là gắn vào các quy trình Python và lấy mẫu các ngăn xếp cuộc gọi của chúng. Do đó, py-spy dump --pid $SOME_PID
là tất cả những gì bạn cần làm để loại bỏ ngăn xếp cuộc gọi của tất cả các luồng trong $SOME_PID
quy trình. Thông thường, nó cần các đặc quyền leo thang (để đọc bộ nhớ của quá trình đích).
Đây là một ví dụ về giao diện của ứng dụng Python theo luồng.
$ 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)
pyenses là một trình gỡ lỗi có thể tương tác với việc chạy các quy trình python, in dấu vết ngăn xếp, các biến, v.v. mà không cần bất kỳ thiết lập tiên nghiệm nào.
Mặc dù trước đây tôi thường sử dụng giải pháp xử lý tín hiệu, nhưng vẫn có thể khó tái tạo vấn đề trong một số môi trường nhất định.
pyrasite
, làm việc như một cơ duyên đối với tôi.
Không có cách nào để nối vào một quy trình python đang chạy và nhận được kết quả hợp lý. Những gì tôi làm nếu các quy trình bị khóa là nối chuỗi và cố gắng tìm hiểu chính xác những gì đang xảy ra.
Thật không may, strace thường là người quan sát "sửa chữa" các điều kiện cuộc đua để đầu ra cũng vô dụng ở đó.
Bạn có thể sử dụng PuDB , trình gỡ lỗi Python có giao diện nguyền rủa để làm điều này. Chỉ cần thêm
from pudb import set_interrupt_handler; set_interrupt_handler()
mã của bạn và sử dụng Ctrl-C khi bạn muốn phá vỡ. Bạn có thể tiếp tục c
và phá vỡ lại nhiều lần nếu bạn bỏ lỡ nó và muốn thử lại.
Tôi đang ở trong trại GDB với phần mở rộng python. Theo dõi https://wiki.python.org/moin/DebuggingWithGdb , có nghĩa là
dnf install gdb python-debuginfo
hoặc là sudo apt-get install gdb python2.7-dbg
gdb python <pid of running process>
py-bt
Cũng xem xét info threads
và thread apply all py-bt
.
Traceback (most recent call first): Python Exception <class 'gdb.error'> No frame is currently selected.: Error occurred in Python command: No frame is currently selected.
khi chạy py-bt
vào gdb
không?
sudo
. tôi cũng cần phải chạy gdb pyton <pid>
như sudo.
Cách gỡ lỗi bất kỳ chức năng nào trong bảng điều khiển :
Tạo hàm nơi bạn sử dụng pdb.set_trace () , sau đó là hàm bạn muốn gỡ lỗi.
>>> import pdb
>>> import my_function
>>> def f():
... pdb.set_trace()
... my_function()
...
Sau đó gọi hàm đã tạo:
>>> f()
> <stdin>(3)f()
(Pdb) s
--Call--
> <stdin>(1)my_function()
(Pdb)
Chúc mừng gỡ lỗi :)
Tôi không biết bất cứ điều gì tương tự như phản hồi của java đối với SIGQUIT , vì vậy bạn có thể phải xây dựng nó vào ứng dụng của mình. Có lẽ bạn có thể tạo một máy chủ trong một luồng khác có thể nhận được một stacktrace khi phản hồi một tin nhắn nào đó?
Trong Python 3, pdb sẽ tự động cài đặt trình xử lý tín hiệu vào lần đầu tiên bạn sử dụng c (ont (inue)) trong trình gỡ lỗi. Nhấn Control-C sau đó sẽ đưa bạn trở lại ngay tại đó. Trong Python 2, đây là một lớp lót nên hoạt động ngay cả trong các phiên bản tương đối cũ (đã được thử nghiệm trong 2.7 nhưng tôi đã kiểm tra nguồn Python trở lại 2.4 và nó có vẻ ổn):
import pdb, signal
signal.signal(signal.SIGINT, lambda sig, frame: pdb.Pdb().set_trace(frame))
pdb đáng để học hỏi nếu bạn dành bất kỳ khoảng thời gian nào để gỡ lỗi Python. Giao diện hơi khó hiểu nhưng nên quen thuộc với bất kỳ ai đã sử dụng các công cụ tương tự, chẳng hạn như gdb.
Trong trường hợp bạn cần thực hiện việc này với uWSGI, nó có Python Tracebacker tích hợp và vấn đề là cho phép nó trong cấu hình (số được gắn vào tên cho mỗi nhân viên):
py-tracebacker=/var/run/uwsgi/pytrace
Khi bạn đã hoàn thành việc này, bạn có thể in backtrace chỉ bằng cách kết nối với ổ cắm:
uwsgi --connect-and-read /var/run/uwsgi/pytrace1
Tại điểm mã được chạy, bạn có thể chèn đoạn mã nhỏ này để xem dấu vết ngăn xếp được in được định dạng độc đáo. Nó giả định rằng bạn có một thư mục được gọi logs
tại thư mục gốc của dự án.
# DEBUG: START DEBUG -->
import traceback
with open('logs/stack-trace.log', 'w') as file:
traceback.print_stack(file=file)
# DEBUG: END DEBUG --!