GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||||||
GeezerVison is showing peps.python.org — PEP 669 – Low Impact Monitoring for CPython | peps.python.org
PEP 669 – Low Impact Monitoring for CPython
PEP 669 – Low Impact Monitoring for CPython
Table of Contents
Important This PEP is a historical document. The up-to-date, canonical documentation can now be found at × See PEP 1 for how to propose changes. AbstractUsing a profiler or debugger in CPython can have a severe impact on performance. Slowdowns by an order of magnitude are common. This PEP proposes an API for monitoring Python programs running on CPython that will enable monitoring at low cost. Although this PEP does not specify an implementation, it is expected that it will be implemented using the quickening step of PEP 659. A MotivationDevelopers should not have to pay an unreasonable cost to use debuggers, profilers and other similar tools. C++ and Java developers expect to be able to run a program at full speed (or very close to it) under a debugger. Python developers should expect that too. RationaleThe quickening mechanism provided by PEP 659 provides a way to dynamically modify executing Python bytecode. These modifications have little cost beyond the parts of the code that are modified and a relatively low cost to those parts that are modified. We can leverage this to provide an efficient mechanism for monitoring that was not possible in 3.10 or earlier. By using quickening, we expect that code run under a debugger on 3.12 should outperform code run without a debugger on 3.11. Profiling will still slow down execution, but by much less than in 3.11. SpecificationMonitoring of Python programs is done by registering callback functions for events and by activating a set of events. Activating events and registering callback functions are independent of each other. Both registering callbacks and activating events are done on a per-tool basis. It is possible to have multiple tools that respond to different sets of events. Note that, unlike EventsAs a code object executes various events occur that might be of interest to tools. By activating events and by registering callback functions tools can respond to these events in any way that suits them. Events can be set globally, or for individual code objects. For 3.12, CPython will support the following events:
More events may be added in the future. All events will be attributes of the Events are divided into three groups: Local eventsLocal events are associated with normal execution of the program and happen at clearly defined locations. All local events can be disabled. The local events are:
Ancilliary eventsAncillary events can be monitored like other events, but are controlled by another event:
The Other eventsOther events are not necessarily tied to a specific location in the program and cannot be individually disabled. The other events that can be monitored are:
The STOP_ITERATION eventPEP 380
specifies that a To allow tools to monitor for real exceptions without slowing down generators
and coroutines, the Tool identifiersThe VM can support up to 6 tools at once. Before registering or activating events, a tool should choose an identifier. Identifiers are integers in the range 0 to 5. sys.monitoring.use_tool_id(id, name:str) -> None sys.monitoring.free_tool_id(id) -> None sys.monitoring.get_tool(id) -> str | None
All IDs are treated the same by the VM with regard to events, but the following IDs are pre-defined to make co-operation of tools easier: sys.monitoring.DEBUGGER_ID = 0 sys.monitoring.COVERAGE_ID = 1 sys.monitoring.PROFILER_ID = 2 sys.monitoring.OPTIMIZER_ID = 5 There is no obligation to set an ID, nor is there anything preventing a tool from using an ID even it is already in use. However, tools are encouraged to use a unique ID and respect other tools. For example, if a debugger were attached and The Setting events globallyEvents can be controlled globally by modifying the set of events being monitored:
No events are active by default. Per code object eventsEvents can also be controlled on a per code object basis:
Local events add to global events, but do not mask them. In other words, all global events will trigger for a code object, regardless of the local events. Register callback functionsTo register a callable for events call: sys.monitoring.register_callback(tool_id:int, event: int, func: Callable | None) -> Callable | None If another callback was registered for the given Functions can be unregistered by calling
Callback functions can be registered and unregistered at any time. Registering or unregistering a callback function will generate a Callback function argumentsWhen an active event occurs, the registered callback function is called. Different events will provide the callback function with different arguments, as follows:
If a callback function returns Note that Events in callback functionsEvents are suspended in callback functions and their callees for the tool that registered that callback. That means that other tools will see events in the callback functions for other tools. This could be useful for debugging a profiling tool, but would produce misleading profiles, as the debugger tool would show up in the profile. Order of eventsIf an instructions triggers several events they occur in the following order:
Each event is delivered to tools in ascending order of ID. The “call” event groupMost events are independent; setting or disabling one event has no effect on the others.
However, the Attributes of the
| ||||||||||||||||
| Events | |||
|---|---|---|---|
| Tools | Others | LINE | INSTRUCTION |
| One | None | ≈40% | ≈80% |
| Two or more | ≈40% | ≈120% | ≈200% |
Allowing modification of running code has some security implications, but no more than the ability to generate and call new code.
All the new functions listed above will trigger audit hooks.
This outlines the proposed implementation for CPython 3.12. The actual implementation for later versions of CPython and other Python implementations may differ considerably.
The proposed implementation of this PEP will be built on top of the quickening step of CPython 3.11, as described in PEP 659. Instrumentation works in much the same way as quickening, bytecodes are replaced with instrumented ones as needed.
For example, if the CALL event is turned on,
then all call instructions will be
replaced with a INSTRUMENTED_CALL instruction.
Note that this will interfere with specialization, which will result in some performance degradation in addition to the overhead of calling the registered callable.
When the set of active events changes, the VM will immediately update all code objects present on the call stack of any thread. It will also set in place traps to ensure that all code objects are correctly instrumented when called. Consequently changing the set of active events should be done as infrequently as possible, as it could be quite an expensive operation.
Other events, such as RAISE can be turned on or off cheaply,
as they do not rely on code instrumentation, but runtime checks when the
underlying event occurs.
The exact set of events that require instrumentation is an implementation detail, but for the current design, the following events will require instrumentation:
Each instrumented bytecode will require an additional 8 bits of information to
note which tool the instrumentation applies to.
LINE and INSTRUCTION events require additional information, as they
need to store the original instruction, or even the instrumented instruction
if they overlap other instrumentation.
It is the philosophy of this PEP that it should be possible for third-party monitoring tools to achieve high-performance, not that it should be easy for them to do so.
Converting events into data that is meaningful to the users is the responsibility of the tool.
All events have a cost, and tools should attempt to the use set of events that trigger the least often and still provide the necessary information.
Breakpoints can be inserted setting per code object events, either LINE or INSTRUCTION,
and returning DISABLE for any events not matching a breakpoint.
Debuggers usually offer the ability to step execution by a single instruction or line.
Like breakpoints, stepping can be implemented by setting per code object events. As soon as normal execution is to be resumed, the local events can be unset.
Debuggers can use the PY_START and PY_RESUME events to be informed
when a code object is first encountered, so that any necessary breakpoints
can be inserted.
Coverage tools need to track which parts of the control graph have been
executed. To do this, they need to register for the PY_ events,
plus JUMP and BRANCH.
This information can be then be converted back into a line based report after execution has completed.
Simple profilers need to gather information about calls. To do this profilers should register for the following events:
Line based profilers can use the LINE and JUMP events.
Implementers of profilers should be aware that instrumenting LINE
events will have a large impact on performance.
Note
Instrumenting profilers have significant overhead and will distort the results of profiling. Unless you need exact call counts, consider using a statistical profiler.
A draft version of this PEP proposed making the user responsible for inserting the monitoring instructions, rather than have VM do it. However, that puts too much of a burden on the tools, and would make attaching a debugger nearly impossible.
An earlier version of this PEP, proposed storing events as enums:
class Event(enum.IntFlag): PY_START = ...
However, that would prevent monitoring of code before the enum module was
loaded and could cause unnecessary overhead.
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.