mirror of https://github.com/python/cpython
gh-103615: Use local events for opcode tracing (GH-109472)
* Use local monitoring for opcode trace * Remove f_opcode_trace_set * Add test for setting f_trace_opcodes after settrace
This commit is contained in:
parent
2bc01cc0c7
commit
e0afed7e27
|
@ -22,6 +22,8 @@ PyAPI_FUNC(int) _PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc func, PyO
|
||||||
|
|
||||||
extern int _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg);
|
extern int _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg);
|
||||||
|
|
||||||
|
extern int _PyEval_SetOpcodeTrace(PyFrameObject *f, bool enable);
|
||||||
|
|
||||||
// Helper to look up a builtin object
|
// Helper to look up a builtin object
|
||||||
// Export for 'array' shared extension
|
// Export for 'array' shared extension
|
||||||
PyAPI_FUNC(PyObject*) _PyEval_GetBuiltin(PyObject *);
|
PyAPI_FUNC(PyObject*) _PyEval_GetBuiltin(PyObject *);
|
||||||
|
|
|
@ -63,6 +63,8 @@ typedef uint32_t _PyMonitoringEventSet;
|
||||||
PyObject *_PyMonitoring_RegisterCallback(int tool_id, int event_id, PyObject *obj);
|
PyObject *_PyMonitoring_RegisterCallback(int tool_id, int event_id, PyObject *obj);
|
||||||
|
|
||||||
int _PyMonitoring_SetEvents(int tool_id, _PyMonitoringEventSet events);
|
int _PyMonitoring_SetEvents(int tool_id, _PyMonitoringEventSet events);
|
||||||
|
int _PyMonitoring_SetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEventSet events);
|
||||||
|
int _PyMonitoring_GetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEventSet *events);
|
||||||
|
|
||||||
extern int
|
extern int
|
||||||
_Py_call_instrumentation(PyThreadState *tstate, int event,
|
_Py_call_instrumentation(PyThreadState *tstate, int event,
|
||||||
|
|
|
@ -200,7 +200,6 @@ struct _is {
|
||||||
uint32_t next_func_version;
|
uint32_t next_func_version;
|
||||||
|
|
||||||
_Py_GlobalMonitors monitors;
|
_Py_GlobalMonitors monitors;
|
||||||
bool f_opcode_trace_set;
|
|
||||||
bool sys_profile_initialized;
|
bool sys_profile_initialized;
|
||||||
bool sys_trace_initialized;
|
bool sys_trace_initialized;
|
||||||
Py_ssize_t sys_profiling_threads; /* Count of threads with c_profilefunc set */
|
Py_ssize_t sys_profiling_threads; /* Count of threads with c_profilefunc set */
|
||||||
|
|
|
@ -9,6 +9,10 @@ from functools import wraps
|
||||||
import asyncio
|
import asyncio
|
||||||
from test.support import import_helper
|
from test.support import import_helper
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import textwrap
|
||||||
|
import subprocess
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
support.requires_working_socket(module=True)
|
support.requires_working_socket(module=True)
|
||||||
|
@ -1802,6 +1806,39 @@ class TraceOpcodesTestCase(TraceTestCase):
|
||||||
def make_tracer():
|
def make_tracer():
|
||||||
return Tracer(trace_opcode_events=True)
|
return Tracer(trace_opcode_events=True)
|
||||||
|
|
||||||
|
def test_trace_opcodes_after_settrace(self):
|
||||||
|
"""Make sure setting f_trace_opcodes after starting trace works even
|
||||||
|
if it's the first time f_trace_opcodes is being set. GH-103615"""
|
||||||
|
|
||||||
|
code = textwrap.dedent("""
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def opcode_trace_func(frame, event, arg):
|
||||||
|
if event == "opcode":
|
||||||
|
print("opcode trace triggered")
|
||||||
|
return opcode_trace_func
|
||||||
|
|
||||||
|
sys.settrace(opcode_trace_func)
|
||||||
|
sys._getframe().f_trace = opcode_trace_func
|
||||||
|
sys._getframe().f_trace_opcodes = True
|
||||||
|
a = 1
|
||||||
|
""")
|
||||||
|
|
||||||
|
# We can't use context manager because Windows can't execute a file while
|
||||||
|
# it's being written
|
||||||
|
tmp = tempfile.NamedTemporaryFile(delete=False, suffix='.py')
|
||||||
|
tmp.write(code.encode('utf-8'))
|
||||||
|
tmp.close()
|
||||||
|
try:
|
||||||
|
p = subprocess.Popen([sys.executable, tmp.name], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
|
p.wait()
|
||||||
|
out = p.stdout.read()
|
||||||
|
finally:
|
||||||
|
os.remove(tmp.name)
|
||||||
|
p.stdout.close()
|
||||||
|
p.stderr.close()
|
||||||
|
self.assertIn(b"opcode trace triggered", out)
|
||||||
|
|
||||||
|
|
||||||
class RaisingTraceFuncTestCase(unittest.TestCase):
|
class RaisingTraceFuncTestCase(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Use local events for opcode tracing
|
|
@ -127,10 +127,13 @@ frame_settrace_opcodes(PyFrameObject *f, PyObject* value, void *Py_UNUSED(ignore
|
||||||
}
|
}
|
||||||
if (value == Py_True) {
|
if (value == Py_True) {
|
||||||
f->f_trace_opcodes = 1;
|
f->f_trace_opcodes = 1;
|
||||||
_PyInterpreterState_GET()->f_opcode_trace_set = true;
|
if (f->f_trace) {
|
||||||
|
return _PyEval_SetOpcodeTrace(f, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
f->f_trace_opcodes = 0;
|
f->f_trace_opcodes = 0;
|
||||||
|
return _PyEval_SetOpcodeTrace(f, false);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -842,6 +845,9 @@ frame_settrace(PyFrameObject *f, PyObject* v, void *closure)
|
||||||
}
|
}
|
||||||
if (v != f->f_trace) {
|
if (v != f->f_trace) {
|
||||||
Py_XSETREF(f->f_trace, Py_XNewRef(v));
|
Py_XSETREF(f->f_trace, Py_XNewRef(v));
|
||||||
|
if (v != NULL && f->f_trace_opcodes) {
|
||||||
|
return _PyEval_SetOpcodeTrace(f, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1833,6 +1833,23 @@ _PyMonitoring_SetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEvent
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
_PyMonitoring_GetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEventSet *events)
|
||||||
|
{
|
||||||
|
assert(0 <= tool_id && tool_id < PY_MONITORING_TOOL_IDS);
|
||||||
|
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||||
|
if (check_tool(interp, tool_id)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (code->_co_monitoring == NULL) {
|
||||||
|
*events = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
_Py_LocalMonitors *local = &code->_co_monitoring->local_monitors;
|
||||||
|
*events = get_local_events(local, tool_id);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
module monitoring
|
module monitoring
|
||||||
[clinic start generated code]*/
|
[clinic start generated code]*/
|
||||||
|
|
|
@ -117,6 +117,35 @@ sys_profile_call_or_return(
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
_PyEval_SetOpcodeTrace(
|
||||||
|
PyFrameObject *frame,
|
||||||
|
bool enable
|
||||||
|
) {
|
||||||
|
assert(frame != NULL);
|
||||||
|
assert(PyCode_Check(frame->f_frame->f_executable));
|
||||||
|
|
||||||
|
PyCodeObject *code = (PyCodeObject *)frame->f_frame->f_executable;
|
||||||
|
_PyMonitoringEventSet events = 0;
|
||||||
|
|
||||||
|
if (_PyMonitoring_GetLocalEvents(code, PY_MONITORING_SYS_TRACE_ID, &events) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enable) {
|
||||||
|
if (events & (1 << PY_MONITORING_EVENT_INSTRUCTION)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
events |= (1 << PY_MONITORING_EVENT_INSTRUCTION);
|
||||||
|
} else {
|
||||||
|
if (!(events & (1 << PY_MONITORING_EVENT_INSTRUCTION))) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
events &= (~(1 << PY_MONITORING_EVENT_INSTRUCTION));
|
||||||
|
}
|
||||||
|
return _PyMonitoring_SetLocalEvents(code, PY_MONITORING_SYS_TRACE_ID, events);
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
call_trace_func(_PyLegacyEventHandler *self, PyObject *arg)
|
call_trace_func(_PyLegacyEventHandler *self, PyObject *arg)
|
||||||
{
|
{
|
||||||
|
@ -130,6 +159,12 @@ call_trace_func(_PyLegacyEventHandler *self, PyObject *arg)
|
||||||
"Missing frame when calling trace function.");
|
"Missing frame when calling trace function.");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
if (frame->f_trace_opcodes) {
|
||||||
|
if (_PyEval_SetOpcodeTrace(frame, true) != 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Py_INCREF(frame);
|
Py_INCREF(frame);
|
||||||
int err = tstate->c_tracefunc(tstate->c_traceobj, frame, self->event, arg);
|
int err = tstate->c_tracefunc(tstate->c_traceobj, frame, self->event, arg);
|
||||||
Py_DECREF(frame);
|
Py_DECREF(frame);
|
||||||
|
@ -230,11 +265,14 @@ sys_trace_instruction_func(
|
||||||
"Missing frame when calling trace function.");
|
"Missing frame when calling trace function.");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
if (!frame->f_trace_opcodes) {
|
PyThreadState *tstate = _PyThreadState_GET();
|
||||||
|
if (!tstate->c_tracefunc || !frame->f_trace_opcodes) {
|
||||||
|
if (_PyEval_SetOpcodeTrace(frame, false) != 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
Py_INCREF(frame);
|
Py_INCREF(frame);
|
||||||
PyThreadState *tstate = _PyThreadState_GET();
|
|
||||||
int err = tstate->c_tracefunc(tstate->c_traceobj, frame, self->event, Py_None);
|
int err = tstate->c_tracefunc(tstate->c_traceobj, frame, self->event, Py_None);
|
||||||
frame->f_lineno = 0;
|
frame->f_lineno = 0;
|
||||||
Py_DECREF(frame);
|
Py_DECREF(frame);
|
||||||
|
@ -531,9 +569,15 @@ _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
|
||||||
(1 << PY_MONITORING_EVENT_PY_UNWIND) | (1 << PY_MONITORING_EVENT_PY_THROW) |
|
(1 << PY_MONITORING_EVENT_PY_UNWIND) | (1 << PY_MONITORING_EVENT_PY_THROW) |
|
||||||
(1 << PY_MONITORING_EVENT_STOP_ITERATION) |
|
(1 << PY_MONITORING_EVENT_STOP_ITERATION) |
|
||||||
(1 << PY_MONITORING_EVENT_EXCEPTION_HANDLED);
|
(1 << PY_MONITORING_EVENT_EXCEPTION_HANDLED);
|
||||||
if (tstate->interp->f_opcode_trace_set) {
|
|
||||||
events |= (1 << PY_MONITORING_EVENT_INSTRUCTION);
|
PyFrameObject* frame = PyEval_GetFrame();
|
||||||
|
if (frame->f_trace_opcodes) {
|
||||||
|
int ret = _PyEval_SetOpcodeTrace(frame, true);
|
||||||
|
if (ret != 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return _PyMonitoring_SetEvents(PY_MONITORING_SYS_TRACE_ID, events);
|
return _PyMonitoring_SetEvents(PY_MONITORING_SYS_TRACE_ID, events);
|
||||||
}
|
}
|
||||||
|
|
|
@ -708,7 +708,6 @@ init_interpreter(PyInterpreterState *interp,
|
||||||
/* Fix the self-referential, statically initialized fields. */
|
/* Fix the self-referential, statically initialized fields. */
|
||||||
interp->dtoa = (struct _dtoa_state)_dtoa_state_INIT(interp);
|
interp->dtoa = (struct _dtoa_state)_dtoa_state_INIT(interp);
|
||||||
}
|
}
|
||||||
interp->f_opcode_trace_set = false;
|
|
||||||
|
|
||||||
interp->_initialized = 1;
|
interp->_initialized = 1;
|
||||||
return _PyStatus_OK();
|
return _PyStatus_OK();
|
||||||
|
@ -958,7 +957,6 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
|
||||||
interp->code_watchers[i] = NULL;
|
interp->code_watchers[i] = NULL;
|
||||||
}
|
}
|
||||||
interp->active_code_watchers = 0;
|
interp->active_code_watchers = 0;
|
||||||
interp->f_opcode_trace_set = false;
|
|
||||||
// XXX Once we have one allocator per interpreter (i.e.
|
// XXX Once we have one allocator per interpreter (i.e.
|
||||||
// per-interpreter GC) we must ensure that all of the interpreter's
|
// per-interpreter GC) we must ensure that all of the interpreter's
|
||||||
// objects have been cleaned up at the point.
|
// objects have been cleaned up at the point.
|
||||||
|
|
Loading…
Reference in New Issue