Skip to main content

Overview

Tripod is a module that samples running scripts, logging tracebacks to a temp file.

Most of the code was brought over from the Django watchdog middleware project, Dogslow.

Installation

Install tripod:

$ pip install tripod

Configuration

You can use the following environmental variables to adjust the sampler:

# Log traceback of running script every 5 seconds
TRIPOD_INTERVAL = 5

Usage

Run tripod:

$ python -m tripod.sampler ./some_script.py
[Tripod] Sampling every 5.000000 seconds
[Tripod] Writing output to: /tmp/slow_process_tkM6Rt.log

Every 5 seconds the watchdog is activated and takes a peek at the script’s stack and appends the backtrace (including all local stack variables) to the log file.

Every stack is appended to the log file and looks like this:

File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/runpy.py", line 162, in _run_module_as_main
    "__main__", fname, loader, pkg_name)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/runpy.py", line 72, in _run_code
    exec code in run_globals
File "/Users/shayne/Code/tripod/tripod/sampler.py", line 128, in <module>
    main()
File "/Users/shayne/Code/tripod/tripod/sampler.py", line 118, in main
    exec f.read() in globals(), globals()
File "<string>", line 5, in <module>
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py", line 351, in read
    data = self._sock.recv(rbufsize)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py", line 553, in read
    s = self.fp.read(amt)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py", line 380, in read
    data = self._sock.recv(left)

Full backtrace with local variables:

File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/runpy.py", line 162, in _run_module_as_main
    "__main__", fname, loader, pkg_name)

    Arguments: _run_module_as_main(mod_name='tripod.sampler', alter_argv=1)
    Local variables:

    {'alter_argv': 1,
    'code': <code object <module> at 0x10ba09a30, file "/Users/shayne/Code/tripod/tripod/sampler.py", line 1>,
    'fname': '/Users/shayne/Code/tripod/tripod/sampler.py',
    'loader': <pkgutil.ImpLoader instance at 0x10ba0a368>,
    'main_globals': {'SafePrettyPrinter': <class '__main__.SafePrettyPrinter'>,
                        'Timer': <class 'tripod.timer.Timer'>,
                        '__builtins__': <module '__builtin__' (built-in)>,
                        '__doc__': None,
                        '__file__': 'slow.py',
                        '__loader__': <pkgutil.ImpLoader instance at 0x10ba0a368>,
                        '__name__': '__main__',
                        '__package__': 'tripod',
                        'dt': <module 'datetime' from '/Users/shayne/pyenv/lib/python2.7/lib-dynload/datetime.so'>,
                        'f': <addinfourl at 4495809568 whose fp = <socket._fileobject object at 0x10bac70d0>>,
                        'formatvalue': <function formatvalue at 0x10bac1320>,
                        'inspect': <module 'inspect' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/inspect.py'>,
                        'linecache': <module 'linecache' from '/Users/shayne/pyenv/lib/python2.7/linecache.pyc'>,
                        'main': <function main at 0x10bac1488>,
                        'os': <module 'os' from '/Users/shayne/pyenv/lib/python2.7/os.pyc'>,
                        'peek': <function peek at 0x10bac1410>,
                        'pprint': <module 'pprint' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pprint.py'>,
                        'spformat': <function spformat at 0x10ba649b0>,
                        'stack': <function stack at 0x10bac1398>,
                        'sys': <module 'sys' (built-in)>,
                        'tempfile': <module 'tempfile' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/tempfile.pyc'>,
                        'thread': <module 'thread' (built-in)>,
                        'urllib2': <module 'urllib2' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.pyc'>},
    'mod_name': 'tripod.sampler',
    'pkg_name': 'tripod'}

File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/runpy.py", line 72, in _run_code
    exec code in run_globals

    Arguments: _run_code(code=<code object <module> at 0x10ba09a30, file "/Users/shayne/Code/tripod/tripod/sampler.py", line 1>, run_globals=<dict object at 0x7f9812c1dc40 (really long repr)>, init_globals=None, mod_name='__main__', mod_fname='/Users/shayne/Code/tripod/tripod/sampler.py', mod_loader=<pkgutil.ImpLoader instance at 0x10ba0a368>, pkg_name='tripod')
    Local variables:

    {'code': <code object <module> at 0x10ba09a30, file "/Users/shayne/Code/tripod/tripod/sampler.py", line 1>,
    'init_globals': None,
    'mod_fname': '/Users/shayne/Code/tripod/tripod/sampler.py',
    'mod_loader': <pkgutil.ImpLoader instance at 0x10ba0a368>,
    'mod_name': '__main__',

  ...loads more...

The example above shows that the request thread was blocked in f.read() at the time tripod took its snapshot.

Note that tripod only takes a peek at the thread’s stack. It does not interrupt the script, or influence it in any other way.

Caveats

Tripod, like Dogslow uses multithreading. It has a single background thread that handles the timer timeouts and takes the tracebacks, so that the original script threads are not interrupted. This has some consequences.

Multithreading and the GIL

In cPython, the GIL (Global Interpreter Lock) prevents multiple threads from executing Python code simultaneously. Only when a thread explicitly releases its lock on the GIL, can a second thread run.

Releasing the GIL is done automatically whenever a Python program makes blocking calls outside of the interpreter, for example when doing IO.

For tripod this means that it can only reliably sample scripts that are slow because they are doing IO, calling sleep or busy waiting to acquire locks themselves.

In most cases this is fine. A scenario where cPython’s GIL is problematic is when the request’s thread hits an infinite loop in Python code (or legitimate Python that is extremely expensive and takes a long time to execute), never releasing the GIL. Even though tripod’s watchdog timer does become runnable, it cannot log the stack.

Co-routines and Greenlets

Tripod is intended for use in a synchronous configuration. A process that uses dedicated threads (or single-threaded processes).

When running with a “co-routines framework” where multiple requests are served concurrently by one thread, backtraces might become nonsensical.

Release files for tripod 0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tripod 0.1
File Size Uploaded
tripod-0.1.tar.gz 15.6 kB Details

Release files / tripod-0.1.tar.gz

Download URL tripod-0.1.tar.gz
Size 15.6 kB
Tags Source
SHA-256 checksum
How to use checksums
ff5b1471cd910e1ed3be46ca3730ec9e24aa2db08bdd46730c7e6697d8fc9231
BLAKE2b-256 checksum
How to use checksums
db793b2e5677b33f011e61691a900050da26dbf4184f455d4321ca2670d2715a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No

Release history Release notifications | RSS feed

This release

0.1 This release

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page