Skip to content

Instantly share code, notes, and snippets.

@andymccurdy
Created December 11, 2010 01:09
Show Gist options
  • Save andymccurdy/737056 to your computer and use it in GitHub Desktop.
Save andymccurdy/737056 to your computer and use it in GitHub Desktop.
Crier: simple introspection for long-running Python processes
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()
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}
#
@hrieke
Copy link

hrieke commented Oct 20, 2023

Hello,

Can you tell me under what license this code is published?
Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment