Tracing and monitoring execution

sys · Expert · 14 min

Building or understanding developer tools requires knowing how Python reports calls, lines and returns.

Python lets tools register callbacks that the interpreter calls as code runs.

sys.settrace(func) installs a trace function for the current thread. It is called with (frame, event, arg) for 'call' events; whatever it returns becomes the local tracer for that frame, receiving 'line', 'return' and 'exception' events. pdb and older coverage tools are built on it. sys.setprofile is similar but only reports calls and returns, including C functions. threading.settrace installs a tracer for new threads. Both slow code down heavily because every line goes through a Python callback.

sys.monitoring (3.12, PEP 669) is the modern replacement. A tool claims an ID (DEBUGGER_ID, COVERAGE_ID, PROFILER_ID, OPTIMIZER_ID), registers callbacks per event (PY_START, PY_RETURN, LINE, BRANCH, CALL, RAISE...) and enables only the events it needs, globally or per code object. A callback can return sys.monitoring.DISABLE to switch off an event at a specific location, so a coverage tool pays for each line only once. Coverage.py and debuggers on 3.12+ use it.

Frames expose f_code.co_name, f_lineno and f_locals, which is how debuggers show variables. Always remove hooks when done and free the tool ID.

Example

import sys

mon = sys.monitoring
TOOL = mon.PROFILER_ID
mon.use_tool_id(TOOL, "call-counter")
calls = {}

def on_start(code, offset):
    calls[code.co_name] = calls.get(code.co_name, 0) + 1
def fib(n):
    return n if n < 2 else fib(n - 1) + fib(n - 2)

mon.register_callback(TOOL, mon.events.PY_START, on_start)
mon.set_events(TOOL, mon.events.PY_START)
fib(10)
mon.set_events(TOOL, 0)
mon.free_tool_id(TOOL)
print(calls["fib"])            # 177
lines = []
def tracer(frame, event, arg):
    if event == "line":
        lines.append(frame.f_lineno)
    return tracer
sys.settrace(tracer); fib(1); sys.settrace(None)
print(len(lines))              # 1

Remember

  • sys.settrace calls a Python function on call, line, return and exception events
  • sys.monitoring (3.12+) enables only chosen events per tool, with far lower overhead
  • Return DISABLE from a monitoring callback to stop events at that location

Watch out for

  • Leaving a trace function installed; it slows every line of the program.

Check yourself

  1. Why can a coverage tool built on sys.monitoring be much faster than one built on sys.settrace?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/sys.monitoring.html