public
Last active

Crier: simple introspection for long-running Python processes

  • Download Gist
crier.py
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
import pprint
import os
import sys
import threading
import time
import traceback
 
_crier = None
def init_crier(temp_dir='/tmp'):
"Initialzies Crier, ensuring it's only created once in the process"
global _crier
if _crier is None:
_crier = Crier(threading.current_thread(), temp_dir=temp_dir)
 
class Crier(object):
"""
Crier spawns a thread watching for the existence of crier request files
in the temp directory. Upon finding a request file, Crier writes a
traceback file to the temp directory. Traceback files include a stack trace
and a snapshot of local variables for each thread in the process.
"""
def __init__(self, app_thread, temp_dir='/tmp'):
self.app_thread = app_thread
self.watch_file = os.path.join(temp_dir, 'crier-%d' % os.getpid())
self.traceback_file = os.path.join(temp_dir, 'dump-%d' % os.getpid())
self.watch_thread = threading.Thread(target=self.watch_thread)
self.watch_thread.start()
 
def watch_thread(self):
"""
Check for dump requests while the thread that initialized
Crier is still alive
"""
while self.app_thread.isAlive():
if self.check_request():
self.write_traceback()
time.sleep(1)
 
def check_request(self):
"""
Checks the temp directory for the existence for a file named
'crier-{pid}', where {pid} is the ID of this process.
"""
if os.path.exists(self.watch_file):
os.unlink(self.watch_file)
return True
return False
 
def write_traceback(self):
"""
Writes a traceback file to the temp directory named 'traceback-{pid},
where {pid} is the ID of this process.
"""
tmap = {}
main_thread = None
# get a map of threads by their ID so we can print their names
# during the traceback dump
for t in threading.enumerate():
if t.ident:
tmap[t.ident] = t
else:
main_thread = t
out = open(self.traceback_file, 'w')
# Loop over each thread's current frame, writing info about it
for tid, frame in sys._current_frames().iteritems():
thread = tmap.get(tid, main_thread)
# no reason to write about Crier's thread.
if thread == self.watch_thread:
continue
out.write('%s\n' % thread.getName())
out.write('========================================\n')
traceback.print_stack(frame, file=out)
out.write('========================================\n')
out.write('LOCAL VARIABLES:\n')
out.write('========================================\n')
pprint.pprint(frame.f_locals, stream=out)
out.write('\n\n')
out.close()
example.py
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
import time
from crier import init_crier
 
init_crier()
 
def main():
i = 60
while i:
time.sleep(1)
i -= 1
 
if __name__ == '__main__':
main()
 
#########################################################
# assume example.py has a process id of 1234
#
# $> touch /tmp/crier-1234
# $> cat /tmp/dump-1234
# MainThread
# ========================================
# File "example.py", line 88, in <module>
# main()
# File "example.py", line 84, in main
# time.sleep(1)
# ========================================
# LOCAL VARIABLES:
# ========================================
# {'i': 34}
#

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.