''''''''''''
Local 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:
+at clearly defined locations. All local events can be disabled
+per location. The local events are:
* :monitoring-event:`PY_START`
* :monitoring-event:`PY_RESUME`
events will give much better performance as they can be disabled
independently.
+.. _monitoring-ancillary-events:
+
Ancillary events
''''''''''''''''
''''''''''''
Other events are not necessarily tied to a specific location in the
-program and cannot be individually disabled via :data:`DISABLE`.
+program and cannot be individually disabled per location.
The other events that can be monitored are:
* :monitoring-event:`PY_UNWIND`
* :monitoring-event:`RAISE`
* :monitoring-event:`EXCEPTION_HANDLED`
+* :monitoring-event:`RERAISE`
+
+.. versionchanged:: 3.15
+ Other events can now be turned on and disabled on a per code object
+ basis. Returning :data:`DISABLE` from a callback disables the event
+ for the entire code object (for the current tool).
The STOP_ITERATION event
To allow tools to monitor for real exceptions without slowing down generators
and coroutines, the :monitoring-event:`STOP_ITERATION` event is provided.
-:monitoring-event:`STOP_ITERATION` can be locally disabled, unlike
-:monitoring-event:`RAISE`.
+:monitoring-event:`STOP_ITERATION` can be locally disabled.
Note that the :monitoring-event:`STOP_ITERATION` event and the
:monitoring-event:`RAISE` event for a :exc:`StopIteration` exception are
This does not change which events are set, or any other code locations for the
same event.
-Disabling events for specific locations is very important for high
-performance monitoring. For example, a program can be run under a
-debugger with no overhead if the debugger disables all monitoring
-except for a few breakpoints.
+:ref:`Other events <monitoring-event-global>` can be disabled on a per code
+object basis by returning :data:`sys.monitoring.DISABLE` from a callback
+function. This disables the event for the entire code object (for the current
+tool).
-If :data:`DISABLE` is returned by a callback for a
-:ref:`global event <monitoring-event-global>`, :exc:`ValueError` will be raised
-by the interpreter in a non-specific location (that is, no traceback will be
-provided).
+Disabling events for specific locations is very important for high performance
+monitoring. For example, a program can be run under a debugger with no overhead
+if the debugger disables all monitoring except for a few breakpoints.
.. function:: restart_events() -> None
(Contributed by Klaus Zimmermann in :gh:`137476`.)
+sys.monitoring
+--------------
+
+* The :ref:`other events <monitoring-event-global>`
+ (:monitoring-event:`PY_THROW`, :monitoring-event:`PY_UNWIND`,
+ :monitoring-event:`RAISE`, :monitoring-event:`EXCEPTION_HANDLED`, and
+ :monitoring-event:`RERAISE`) can now be turned on and disabled on a per code
+ object basis. Returning :data:`~sys.monitoring.DISABLE` from a callback for
+ one of these events disables the event for the entire code object (for the
+ current tool), rather than raising :exc:`ValueError` as in prior versions.
+ (Contributed by Gabriele N. Tornetta in :gh:`146182`.)
+
+
tarfile
-------
#define PY_MONITORING_EVENT_STOP_ITERATION 10
#define PY_MONITORING_IS_INSTRUMENTED_EVENT(ev) \
- ((ev) < _PY_MONITORING_LOCAL_EVENTS)
+((ev) <= PY_MONITORING_EVENT_STOP_ITERATION)
-/* Other events, mainly exceptions */
+/* Other events, mainly exceptions.
+ * These can now be turned on and disabled on a per code object basis. */
-#define PY_MONITORING_EVENT_RAISE 11
+#define PY_MONITORING_EVENT_PY_UNWIND 11
#define PY_MONITORING_EVENT_EXCEPTION_HANDLED 12
-#define PY_MONITORING_EVENT_PY_UNWIND 13
+#define PY_MONITORING_EVENT_RAISE 13
#define PY_MONITORING_EVENT_PY_THROW 14
#define PY_MONITORING_EVENT_RERAISE 15
+#define _PY_MONITORING_IS_UNGROUPED_EVENT(ev) \
+((ev) < _PY_MONITORING_UNGROUPED_EVENTS)
+
/* Ancillary events */
PyAPI_FUNC(PyObject *)_PyEval_MatchClass(PyThreadState *tstate, PyObject *subject, PyObject *type, Py_ssize_t nargs, PyObject *kwargs);
PyAPI_FUNC(PyObject *)_PyEval_MatchKeys(PyThreadState *tstate, PyObject *map, PyObject *keys);
PyAPI_FUNC(void) _PyEval_MonitorRaise(PyThreadState *tstate, _PyInterpreterFrame *frame, _Py_CODEUNIT *instr);
-PyAPI_FUNC(bool) _PyEval_NoToolsForUnwind(PyThreadState *tstate);
+PyAPI_FUNC(bool) _PyEval_NoToolsForUnwind(PyThreadState *tstate, _PyInterpreterFrame *frame);
PyAPI_FUNC(int) _PyEval_UnpackIterableStackRef(PyThreadState *tstate, PyObject *v, int argcnt, int argcntafter, _PyStackRef *sp);
PyAPI_FUNC(void) _PyEval_FrameClearAndPop(PyThreadState *tstate, _PyInterpreterFrame *frame);
PyAPI_FUNC(PyObject **) _PyObjectArray_FromStackRefArray(_PyStackRef *input, Py_ssize_t nargs, PyObject **scratch);
/* Total tool ids available */
#define PY_MONITORING_TOOL_IDS 8
-/* Count of all local monitoring events */
-#define _PY_MONITORING_LOCAL_EVENTS 11
-/* Count of all "real" monitoring events (not derived from other events) */
+/* Count of all "real" monitoring events (not derived from other events).
+ * "Other" events can now be turned on/disabled per code object. */
#define _PY_MONITORING_UNGROUPED_EVENTS 16
/* Count of all monitoring events */
#define _PY_MONITORING_EVENTS 19
/* Tables of which tools are active for each monitored event. */
typedef struct _Py_LocalMonitors {
- uint8_t tools[_PY_MONITORING_LOCAL_EVENTS];
+ uint8_t tools[_PY_MONITORING_UNGROUPED_EVENTS];
} _Py_LocalMonitors;
typedef struct _Py_GlobalMonitors {
(E.BRANCH, "branch"),
]
-EXCEPT_EVENTS = [
+SIMPLE_EVENTS = INSTRUMENTED_EVENTS + [
(E.RAISE, "raise"),
- (E.PY_UNWIND, "unwind"),
(E.EXCEPTION_HANDLED, "exception_handled"),
-]
-
-SIMPLE_EVENTS = INSTRUMENTED_EVENTS + EXCEPT_EVENTS + [
+ (E.PY_UNWIND, "unwind"),
(E.C_RAISE, "c_raise"),
(E.C_RETURN, "c_return"),
]
sys.monitoring.register_callback(TEST_TOOL, event, None)
- def test_disable_illegal_events(self):
- for event, name in EXCEPT_EVENTS:
- try:
- counter = CounterWithDisable()
- counter.disable = True
- sys.monitoring.register_callback(TEST_TOOL, event, counter)
- sys.monitoring.set_events(TEST_TOOL, event)
- with self.assertRaises(ValueError):
- self.raise_handle_reraise()
- finally:
- sys.monitoring.set_events(TEST_TOOL, 0)
- sys.monitoring.register_callback(TEST_TOOL, event, None)
class ExceptionRecorder:
('line', 'func3', 6)])
def test_set_non_local_event(self):
+ # C_RETURN/C_RAISE are ancillary (derived) events — not settable as local
with self.assertRaises(ValueError):
- sys.monitoring.set_local_events(TEST_TOOL, just_call.__code__, E.RAISE)
+ sys.monitoring.set_local_events(TEST_TOOL, just_call.__code__, E.C_RETURN)
+
+ def test_local_reraise(self):
+ """RERAISE fires as a local event only for the instrumented code object."""
+
+ def foo():
+ try:
+ raise RuntimeError("test")
+ except RuntimeError:
+ raise
+
+ def bar():
+ try:
+ raise RuntimeError("test")
+ except RuntimeError:
+ raise
+
+ events = set()
+
+ def callback(code, offset, exc):
+ events.add(code.co_name)
+
+ try:
+ sys.monitoring.register_callback(TEST_TOOL, E.RERAISE, callback)
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.RERAISE)
+ try:
+ foo()
+ except RuntimeError:
+ pass
+ try:
+ bar() # should NOT trigger the callback
+ except RuntimeError:
+ pass
+ self.assertEqual(events, {'foo'})
+ finally:
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
+ sys.monitoring.register_callback(TEST_TOOL, E.RERAISE, None)
+
+ def test_local_reraise_disable(self):
+ """Returning DISABLE from a RERAISE callback disables it for that code object."""
+
+ call_count = 0
+
+ def foo():
+ try:
+ raise RuntimeError("test")
+ except RuntimeError:
+ raise
+
+ def callback(code, offset, exc):
+ nonlocal call_count
+ call_count += 1
+ return sys.monitoring.DISABLE
+
+ try:
+ sys.monitoring.register_callback(TEST_TOOL, E.RERAISE, callback)
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.RERAISE)
+ try:
+ foo()
+ except RuntimeError:
+ pass
+ self.assertEqual(call_count, 1)
+ try:
+ foo()
+ except RuntimeError:
+ pass
+ self.assertEqual(call_count, 1) # not fired again — disabled
+ finally:
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
+ sys.monitoring.register_callback(TEST_TOOL, E.RERAISE, None)
+
+ def test_local_py_throw(self):
+ """PY_THROW fires as a local event only for the instrumented code object."""
+
+ def gen_foo():
+ yield 1
+ yield 2
+
+ def gen_bar():
+ yield 1
+ yield 2
+
+ events = []
+
+ def callback(code, offset, exc):
+ events.append(code.co_name)
+
+ try:
+ sys.monitoring.register_callback(TEST_TOOL, E.PY_THROW, callback)
+ sys.monitoring.set_local_events(TEST_TOOL, gen_foo.__code__, E.PY_THROW)
+
+ g = gen_foo()
+ next(g)
+ try:
+ g.throw(RuntimeError("test"))
+ except RuntimeError:
+ pass
+
+ h = gen_bar()
+ next(h)
+ try:
+ h.throw(RuntimeError("test")) # should NOT trigger the callback
+ except RuntimeError:
+ pass
+
+ self.assertEqual(events, ['gen_foo'])
+ finally:
+ sys.monitoring.set_local_events(TEST_TOOL, gen_foo.__code__, 0)
+ sys.monitoring.register_callback(TEST_TOOL, E.PY_THROW, None)
+
+ def test_local_py_throw_disable(self):
+ """Returning DISABLE from a PY_THROW callback disables it for that code object."""
+
+ call_count = 0
+
+ def gen_foo():
+ yield 1
+ yield 2
+
+ def callback(code, offset, exc):
+ nonlocal call_count
+ call_count += 1
+ return sys.monitoring.DISABLE
+
+ try:
+ sys.monitoring.register_callback(TEST_TOOL, E.PY_THROW, callback)
+ sys.monitoring.set_local_events(TEST_TOOL, gen_foo.__code__, E.PY_THROW)
+
+ g = gen_foo()
+ next(g)
+ try:
+ g.throw(RuntimeError("test"))
+ except RuntimeError:
+ pass
+ self.assertEqual(call_count, 1)
+
+ g2 = gen_foo()
+ next(g2)
+ try:
+ g2.throw(RuntimeError("test"))
+ except RuntimeError:
+ pass
+ self.assertEqual(call_count, 1) # not fired again — disabled
+ finally:
+ sys.monitoring.set_local_events(TEST_TOOL, gen_foo.__code__, 0)
+ sys.monitoring.register_callback(TEST_TOOL, E.PY_THROW, None)
+
+ def test_local_raise(self):
+ """RAISE fires as a local event only for the instrumented code object."""
+
+ def foo():
+ try:
+ raise RuntimeError("test")
+ except RuntimeError:
+ pass
+
+ def bar():
+ try:
+ raise RuntimeError("test")
+ except RuntimeError:
+ pass
+
+ events = []
+
+ def callback(code, offset, exc):
+ events.append(code.co_name)
+
+ try:
+ sys.monitoring.register_callback(TEST_TOOL, E.RAISE, callback)
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.RAISE)
+ foo()
+ bar() # should NOT trigger the callback
+ self.assertEqual(events, ['foo'])
+ finally:
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
+ sys.monitoring.register_callback(TEST_TOOL, E.RAISE, None)
+
+ def test_local_raise_disable(self):
+ """Returning DISABLE from a RAISE callback disables it for that code object."""
+
+ call_count = 0
+
+ def foo():
+ try:
+ raise RuntimeError("test")
+ except RuntimeError:
+ pass
+
+ def callback(code, offset, exc):
+ nonlocal call_count
+ call_count += 1
+ return sys.monitoring.DISABLE
+
+ try:
+ sys.monitoring.register_callback(TEST_TOOL, E.RAISE, callback)
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.RAISE)
+ foo()
+ self.assertEqual(call_count, 1)
+ foo()
+ self.assertEqual(call_count, 1) # not fired again — disabled
+ finally:
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
+ sys.monitoring.register_callback(TEST_TOOL, E.RAISE, None)
+
+ def test_local_exception_handled(self):
+ """EXCEPTION_HANDLED fires as a local event only for the instrumented code object."""
+
+ def foo():
+ try:
+ raise RuntimeError("test")
+ except RuntimeError:
+ pass
+
+ def bar():
+ try:
+ raise RuntimeError("test")
+ except RuntimeError:
+ pass
+
+ events = []
+
+ def callback(code, offset, exc):
+ events.append(code.co_name)
+
+ try:
+ sys.monitoring.register_callback(TEST_TOOL, E.EXCEPTION_HANDLED, callback)
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.EXCEPTION_HANDLED)
+ foo()
+ bar() # should NOT trigger the callback
+ self.assertEqual(events, ['foo'])
+ finally:
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
+ sys.monitoring.register_callback(TEST_TOOL, E.EXCEPTION_HANDLED, None)
+
+ def test_local_exception_handled_disable(self):
+ """Returning DISABLE from an EXCEPTION_HANDLED callback disables it for that code object."""
+
+ call_count = 0
+
+ def foo():
+ try:
+ raise RuntimeError("test")
+ except RuntimeError:
+ pass
+
+ def callback(code, offset, exc):
+ nonlocal call_count
+ call_count += 1
+ return sys.monitoring.DISABLE
+
+ try:
+ sys.monitoring.register_callback(TEST_TOOL, E.EXCEPTION_HANDLED, callback)
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.EXCEPTION_HANDLED)
+ foo()
+ self.assertEqual(call_count, 1)
+ foo()
+ self.assertEqual(call_count, 1) # not fired again — disabled
+ finally:
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
+ sys.monitoring.register_callback(TEST_TOOL, E.EXCEPTION_HANDLED, None)
+
+ def test_local_py_unwind(self):
+ """PY_UNWIND fires as a local event only for the instrumented code object."""
+
+ def foo():
+ raise RuntimeError("test")
+
+ def bar():
+ raise RuntimeError("test")
+
+ events = []
+
+ def callback(code, offset, exc):
+ events.append(code.co_name)
+
+ try:
+ sys.monitoring.register_callback(TEST_TOOL, E.PY_UNWIND, callback)
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.PY_UNWIND)
+
+ try:
+ foo()
+ except RuntimeError:
+ pass
+
+ try:
+ bar() # should NOT trigger the callback
+ except RuntimeError:
+ pass
+
+ self.assertEqual(events, ['foo'])
+ finally:
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
+ sys.monitoring.register_callback(TEST_TOOL, E.PY_UNWIND, None)
+
+ def test_local_py_unwind_disable(self):
+ """Returning DISABLE from a PY_UNWIND callback disables it for that code object."""
+
+ call_count = 0
+
+ def foo():
+ raise RuntimeError("test")
+
+ def callback(code, offset, exc):
+ nonlocal call_count
+ call_count += 1
+ return sys.monitoring.DISABLE
+
+ try:
+ sys.monitoring.register_callback(TEST_TOOL, E.PY_UNWIND, callback)
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.PY_UNWIND)
+
+ try:
+ foo()
+ except RuntimeError:
+ pass
+ self.assertEqual(call_count, 1) # fired once
+
+ try:
+ foo()
+ except RuntimeError:
+ pass
+ self.assertEqual(call_count, 1) # not fired again — disabled by DISABLE return
+
+ finally:
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
+ sys.monitoring.register_callback(TEST_TOOL, E.PY_UNWIND, None)
def line_from_offset(code, offset):
for start, end, line in code.co_lines():
--- /dev/null
+Global :mod:`sys.monitoring` events can now be turned on and disabled on a
+per code object basis. Returning ``DISABLE`` from a callback disables the
+event for the entire code object (for the current tool).
}
if (is_resume(frame->instr_ptr)) {
- bool no_unwind_tools = _PyEval_NoToolsForUnwind(_PyThreadState_GET());
+ bool no_unwind_tools = _PyEval_NoToolsForUnwind(_PyThreadState_GET(), frame);
/* We can safely ignore the outermost try block
* as it is automatically generated to handle
* StopIteration. */
_PyEval_MonitorRaise(PyThreadState *tstate, _PyInterpreterFrame *frame,
_Py_CODEUNIT *instr)
{
- if (no_tools_for_global_event(tstate, PY_MONITORING_EVENT_RAISE)) {
+ if (no_tools_for_local_event(tstate, frame, PY_MONITORING_EVENT_RAISE)) {
return;
}
do_monitor_exc(tstate, frame, instr, PY_MONITORING_EVENT_RAISE);
}
bool
-_PyEval_NoToolsForUnwind(PyThreadState *tstate) {
- return no_tools_for_global_event(tstate, PY_MONITORING_EVENT_PY_UNWIND);
+_PyEval_NoToolsForUnwind(PyThreadState *tstate, _PyInterpreterFrame *frame)
+{
+ return no_tools_for_local_event(tstate, frame, PY_MONITORING_EVENT_PY_UNWIND);
}
static inline bool
no_tools_for_local_event(PyThreadState *tstate, _PyInterpreterFrame *frame, int event)
{
- assert(event < _PY_MONITORING_LOCAL_EVENTS);
+ assert(event < _PY_MONITORING_UNGROUPED_EVENTS);
_PyCoMonitoringData *data = _PyFrame_GetCode(frame)->_co_monitoring;
if (data) {
return data->active_monitors.tools[event] == 0;
_PyInterpreterFrame *frame,
_Py_CODEUNIT *instr, PyObject *exc)
{
- if (no_tools_for_global_event(tstate, PY_MONITORING_EVENT_EXCEPTION_HANDLED)) {
+ if (no_tools_for_local_event(tstate, frame, PY_MONITORING_EVENT_EXCEPTION_HANDLED)) {
return 0;
}
return _Py_call_instrumentation_arg(tstate, PY_MONITORING_EVENT_EXCEPTION_HANDLED, frame, instr, exc);
_PyInterpreterFrame *frame,
_Py_CODEUNIT *instr)
{
- if (no_tools_for_global_event(tstate, PY_MONITORING_EVENT_PY_THROW)) {
+ if (no_tools_for_local_event(tstate, frame, PY_MONITORING_EVENT_PY_THROW)) {
return;
}
do_monitor_exc(tstate, frame, instr, PY_MONITORING_EVENT_PY_THROW);
monitor_reraise(PyThreadState *tstate, _PyInterpreterFrame *frame,
_Py_CODEUNIT *instr)
{
- if (no_tools_for_global_event(tstate, PY_MONITORING_EVENT_RERAISE)) {
+ if (no_tools_for_local_event(tstate, frame, PY_MONITORING_EVENT_RERAISE)) {
return;
}
do_monitor_exc(tstate, frame, instr, PY_MONITORING_EVENT_RERAISE);
_PyInterpreterFrame *frame,
_Py_CODEUNIT *instr)
{
- if (no_tools_for_global_event(tstate, PY_MONITORING_EVENT_PY_UNWIND)) {
+ if (no_tools_for_local_event(tstate, frame, PY_MONITORING_EVENT_PY_UNWIND)) {
return;
}
do_monitor_exc(tstate, frame, instr, PY_MONITORING_EVENT_PY_UNWIND);
static inline bool
monitors_equals(_Py_LocalMonitors a, _Py_LocalMonitors b)
{
- for (int i = 0; i < _PY_MONITORING_LOCAL_EVENTS; i++) {
+ for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) {
if (a.tools[i] != b.tools[i]) {
return false;
}
monitors_sub(_Py_LocalMonitors a, _Py_LocalMonitors b)
{
_Py_LocalMonitors res;
- for (int i = 0; i < _PY_MONITORING_LOCAL_EVENTS; i++) {
+ for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) {
res.tools[i] = a.tools[i] & ~b.tools[i];
}
return res;
monitors_and(_Py_LocalMonitors a, _Py_LocalMonitors b)
{
_Py_LocalMonitors res;
- for (int i = 0; i < _PY_MONITORING_LOCAL_EVENTS; i++) {
+ for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) {
res.tools[i] = a.tools[i] & b.tools[i];
}
return res;
local_union(_Py_GlobalMonitors a, _Py_LocalMonitors b)
{
_Py_LocalMonitors res;
- for (int i = 0; i < _PY_MONITORING_LOCAL_EVENTS; i++) {
+ for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) {
res.tools[i] = a.tools[i] | b.tools[i];
}
return res;
static inline bool
monitors_are_empty(_Py_LocalMonitors m)
{
- for (int i = 0; i < _PY_MONITORING_LOCAL_EVENTS; i++) {
+ for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) {
if (m.tools[i]) {
return false;
}
static inline bool
multiple_tools(_Py_LocalMonitors *m)
{
- for (int i = 0; i < _PY_MONITORING_LOCAL_EVENTS; i++) {
+ for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) {
if (_Py_popcount32(m->tools[i]) > 1) {
return true;
}
get_local_events(_Py_LocalMonitors *m, int tool_id)
{
_PyMonitoringEventSet result = 0;
- for (int e = 0; e < _PY_MONITORING_LOCAL_EVENTS; e++) {
+ for (int e = 0; e < _PY_MONITORING_UNGROUPED_EVENTS; e++) {
if ((m->tools[e] >> tool_id) & 1) {
result |= (1 << e);
}
dump_local_monitors(const char *prefix, _Py_LocalMonitors monitors, FILE*out)
{
fprintf(out, "%s monitors:\n", prefix);
- for (int event = 0; event < _PY_MONITORING_LOCAL_EVENTS; event++) {
+ for (int event = 0; event < _PY_MONITORING_UNGROUPED_EVENTS; event++) {
fprintf(out, " Event %d: Tools %x\n", event, monitors.tools[event]);
}
}
event == PY_MONITORING_EVENT_C_RETURN);
event = PY_MONITORING_EVENT_CALL;
}
+ assert(_PY_MONITORING_IS_UNGROUPED_EVENT(event));
+ CHECK(debug_check_sanity(interp, code));
if (PY_MONITORING_IS_INSTRUMENTED_EVENT(event)) {
- CHECK(debug_check_sanity(interp, code));
+ /* Instrumented events use per-instruction tool bitmaps. */
if (code->_co_monitoring->tools) {
tools = code->_co_monitoring->tools[i];
}
}
}
else {
- tools = interp->monitors.tools[event];
+ /* Other (non-instrumented) events are not tied to specific instructions;
+ * use the code-object-level active_monitors bitmap instead. */
+ tools = code->_co_monitoring->active_monitors.tools[event];
}
return tools;
}
[PY_MONITORING_EVENT_STOP_ITERATION] = "STOP_ITERATION",
};
+/* Disable an "other" (non-instrumented) event (e.g. PY_UNWIND) for a single
+ * tool on this code object. Must be called with the world stopped or the
+ * code lock held. */
+static void
+remove_local_tool(PyCodeObject *code, PyInterpreterState *interp,
+ int event, int tool)
+{
+ ASSERT_WORLD_STOPPED_OR_LOCKED(code);
+ assert(_PY_MONITORING_IS_UNGROUPED_EVENT(event));
+ assert(!PY_MONITORING_IS_INSTRUMENTED_EVENT(event));
+ assert(code->_co_monitoring);
+ code->_co_monitoring->local_monitors.tools[event] &= ~(1 << tool);
+ /* Recompute active_monitors for this event as the union of global and
+ * (now updated) local monitors. */
+ code->_co_monitoring->active_monitors.tools[event] =
+ interp->monitors.tools[event] |
+ code->_co_monitoring->local_monitors.tools[event];
+}
+
static int
call_instrumentation_vector(
_Py_CODEUNIT *instr, PyThreadState *tstate, int event,
}
else {
/* DISABLE */
- if (!PY_MONITORING_IS_INSTRUMENTED_EVENT(event)) {
+ if (PY_MONITORING_IS_INSTRUMENTED_EVENT(event)) {
+ _PyEval_StopTheWorld(interp);
+ remove_tools(code, offset, event, 1 << tool);
+ _PyEval_StartTheWorld(interp);
+ }
+ else if (_PY_MONITORING_IS_UNGROUPED_EVENT(event)) {
+ /* Other (non-instrumented) event: disable for this code object. */
+ _PyEval_StopTheWorld(interp);
+ remove_local_tool(code, interp, event, tool);
+ _PyEval_StartTheWorld(interp);
+ }
+ else {
PyErr_Format(PyExc_ValueError,
"Cannot disable %s events. Callback removed.",
event_names[event]);
err = -1;
break;
}
- else {
- PyInterpreterState *interp = tstate->interp;
- _PyEval_StopTheWorld(interp);
- remove_tools(code, offset, event, 1 << tool);
- _PyEval_StartTheWorld(interp);
- }
}
}
Py_DECREF(arg2_obj);
_Py_LocalMonitors *local_monitors = &code->_co_monitoring->local_monitors;
for (int i = 0; i < PY_MONITORING_TOOL_IDS; i++) {
if (code->_co_monitoring->tool_versions[i] != interp->monitoring_tool_versions[i]) {
- for (int j = 0; j < _PY_MONITORING_LOCAL_EVENTS; j++) {
+ for (int j = 0; j < _PY_MONITORING_UNGROUPED_EVENTS; j++) {
local_monitors->tools[j] &= ~(1 << i);
}
}
set_local_events(_Py_LocalMonitors *m, int tool_id, _PyMonitoringEventSet events)
{
assert(0 <= tool_id && tool_id < PY_MONITORING_TOOL_IDS);
- for (int e = 0; e < _PY_MONITORING_LOCAL_EVENTS; e++) {
+ for (int e = 0; e < _PY_MONITORING_UNGROUPED_EVENTS; e++) {
uint8_t *tools = &m->tools[e];
int val = (events >> e) & 1;
*tools &= ~(1 << tool_id);
assert(0 <= tool_id && tool_id < PY_MONITORING_TOOL_IDS);
PyInterpreterState *interp = _PyInterpreterState_GET();
- assert(events < (1 << _PY_MONITORING_LOCAL_EVENTS));
+ assert(events < (1 << _PY_MONITORING_UNGROUPED_EVENTS));
if (code->_co_firsttraceable >= Py_SIZE(code)) {
PyErr_Format(PyExc_SystemError, "cannot instrument shim code object '%U'", code->co_name);
return -1;
_PyMonitoringEventSet event_set = 0;
_PyCoMonitoringData *data = ((PyCodeObject *)code)->_co_monitoring;
if (data != NULL) {
- for (int e = 0; e < _PY_MONITORING_LOCAL_EVENTS; e++) {
+ for (int e = 0; e < _PY_MONITORING_UNGROUPED_EVENTS; e++) {
if ((data->local_monitors.tools[e] >> tool_id) & 1) {
event_set |= (1 << e);
}
event_set &= ~(1 << PY_MONITORING_EVENT_BRANCH);
event_set |= (1 << PY_MONITORING_EVENT_BRANCH_RIGHT) | (1 << PY_MONITORING_EVENT_BRANCH_LEFT);
}
- if (event_set < 0 || event_set >= (1 << _PY_MONITORING_LOCAL_EVENTS)) {
+ if (event_set < 0 || event_set >= (1 << _PY_MONITORING_UNGROUPED_EVENTS)) {
PyErr_Format(PyExc_ValueError, "invalid local event set 0x%x", event_set);
return NULL;
}