Skip to content

Commit 411b169

Browse files
authored
GH-103082: Implementation of PEP 669: Low Impact Monitoring for CPython (GH-103083)
* The majority of the monitoring code is in instrumentation.c * The new instrumentation bytecodes are in bytecodes.c * legacy_tracing.c adapts the new API to the old sys.setrace and sys.setprofile APIs
1 parent dce2d38 commit 411b169

44 files changed

Lines changed: 6021 additions & 1617 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎Include/cpython/code.h‎

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,22 @@
33
#ifndefPy_LIMITED_API
44
#ifndefPy_CODE_H
55
#definePy_CODE_H
6+
67
#ifdef__cplusplus
78
extern"C" {
89
#endif
910

11+
12+
/* Count of all "real" monitoring events (not derived from other events) */
13+
#definePY_MONITORING_UNGROUPED_EVENTS 14
14+
/* Count of all monitoring events */
15+
#definePY_MONITORING_EVENTS 16
16+
17+
/* Table of which tools are active for each monitored event. */
18+
typedefstruct_Py_Monitors {
19+
uint8_ttools[PY_MONITORING_UNGROUPED_EVENTS];
20+
} _Py_Monitors;
21+
1022
/* Each instruction in a code object is a fixed-width value,
1123
* currently 2 bytes: 1-byte opcode + 1-byte oparg. The EXTENDED_ARG
1224
* opcode allows for larger values but the current limit is 3 uses
@@ -56,6 +68,35 @@ typedef struct {
5668
PyObject*_co_freevars;
5769
} _PyCoCached;
5870

71+
/* Ancilliary data structure used for instrumentation.
72+
Line instrumentation creates an array of
73+
these. One entry per code unit.*/
74+
typedefstruct {
75+
uint8_toriginal_opcode;
76+
int8_tline_delta;
77+
} _PyCoLineInstrumentationData;
78+
79+
/* Main data structure used for instrumentation.
80+
* This is allocated when needed for instrumentation
81+
*/
82+
typedefstruct {
83+
/* Monitoring specific to this code object */
84+
_Py_Monitorslocal_monitors;
85+
/* Monitoring that is active on this code object */
86+
_Py_Monitorsactive_monitors;
87+
/* The tools that are to be notified for events for the matching code unit */
88+
uint8_t*tools;
89+
/* Information to support line events */
90+
_PyCoLineInstrumentationData*lines;
91+
/* The tools that are to be notified for line events for the matching code unit */
92+
uint8_t*line_tools;
93+
/* Information to support instruction events */
94+
/* The underlying instructions, which can themselves be instrumented */
95+
uint8_t*per_instruction_opcodes;
96+
/* The tools that are to be notified for instruction events for the matching code unit */
97+
uint8_t*per_instruction_tools;
98+
} _PyCoMonitoringData;
99+
59100
// To avoid repeating ourselves in deepfreeze.py, all PyCodeObject members are
60101
// defined in this macro:
61102
#define_PyCode_DEF(SIZE) { \
@@ -87,7 +128,6 @@ typedef struct {
87128
PyObject*co_exceptiontable; /* Byte string encoding exception handling \
88129
table */ \
89130
intco_flags; /* CO_..., see below */ \
90-
short_co_linearray_entry_size; /* Size of each entry in _co_linearray */ \
91131
\
92132
/* The rest are not so impactful on performance. */ \
93133
intco_argcount; /* #arguments, except *args */ \
@@ -114,8 +154,9 @@ typedef struct {
114154
PyObject*co_linetable; /* bytes object that holds location info */ \
115155
PyObject*co_weakreflist; /* to support weakrefs to code objects */ \
116156
_PyCoCached*_co_cached; /* cached co_* attributes */ \
157+
uint64_t_co_instrumentation_version; /* current instrumentation version */ \
158+
_PyCoMonitoringData*_co_monitoring; /* Monitoring data */ \
117159
int_co_firsttraceable; /* index of first traceable instruction */ \
118-
char*_co_linearray; /* array of line offsets */ \
119160
/* Scratch space for extra data relating to the code object. \
120161
Type is a void* to keep the format private in codeobject.c to force \
121162
people to go through the proper APIs. */ \

‎Include/cpython/pystate.h‎

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,6 @@ typedef int (*Py_tracefunc)(PyObject *, PyFrameObject *, int, PyObject *);
5858
#definePyTrace_C_RETURN 6
5959
#definePyTrace_OPCODE 7
6060

61-
62-
typedefstruct {
63-
PyCodeObject*code; // The code object for the bounds. May be NULL.
64-
PyCodeAddressRangebounds; // Only valid if code != NULL.
65-
} PyTraceInfo;
66-
6761
// Internal structure: you should not use it directly, but use public functions
6862
// like PyThreadState_EnterTracing() and PyThreadState_LeaveTracing().
6963
typedefstruct_PyCFrame {
@@ -77,7 +71,6 @@ typedef struct _PyCFrame {
7771
* discipline and make sure that instances of this struct cannot
7872
* accessed outside of their lifetime.
7973
*/
80-
uint8_tuse_tracing; // 0 or 255 (or'ed into opcode, hence 8-bit type)
8174
/* Pointer to the currently executing frame (it can be NULL) */
8275
struct_PyInterpreterFrame*current_frame;
8376
struct_PyCFrame*previous;
@@ -157,7 +150,7 @@ struct _ts {
157150
This is to prevent the actual trace/profile code from being recorded in
158151
the trace/profile. */
159152
inttracing;
160-
inttracing_what; /* The event currently being traced, if any. */
153+
intwhat_event; /* The event currently being monitored, if any. */
161154

162155
/* Pointer to current _PyCFrame in the C stack frame of the currently,
163156
* or most recently, executing _PyEval_EvalFrameDefault. */
@@ -228,8 +221,6 @@ struct _ts {
228221
/* Unique thread state id. */
229222
uint64_tid;
230223

231-
PyTraceInfotrace_info;
232-
233224
_PyStackChunk*datastack_chunk;
234225
PyObject**datastack_top;
235226
PyObject**datastack_limit;

‎Include/internal/pycore_code.h‎

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -441,32 +441,6 @@ adaptive_counter_backoff(uint16_t counter) {
441441

442442
/* Line array cache for tracing */
443443

444-
externint_PyCode_CreateLineArray(PyCodeObject*co);
445-
446-
staticinlineint
447-
_PyCode_InitLineArray(PyCodeObject*co)
448-
{
449-
if (co->_co_linearray) {
450-
return0;
451-
}
452-
return_PyCode_CreateLineArray(co);
453-
}
454-
455-
staticinlineint
456-
_PyCode_LineNumberFromArray(PyCodeObject*co, intindex)
457-
{
458-
assert(co->_co_linearray!=NULL);
459-
assert(index >= 0);
460-
assert(index<Py_SIZE(co));
461-
if (co->_co_linearray_entry_size==2) {
462-
return ((int16_t*)co->_co_linearray)[index];
463-
}
464-
else {
465-
assert(co->_co_linearray_entry_size==4);
466-
return ((int32_t*)co->_co_linearray)[index];
467-
}
468-
}
469-
470444
typedefstruct_PyShimCodeDef {
471445
constuint8_t*code;
472446
intcodelen;
@@ -500,6 +474,10 @@ extern uint32_t _Py_next_func_version;
500474

501475
#defineCOMPARISON_NOT_EQUALS (COMPARISON_UNORDERED | COMPARISON_LESS_THAN | COMPARISON_GREATER_THAN)
502476

477+
externint_Py_Instrument(PyCodeObject*co, PyInterpreterState*interp);
478+
479+
externint_Py_GetBaseOpcode(PyCodeObject*code, intoffset);
480+
503481

504482
#ifdef__cplusplus
505483
}

‎Include/internal/pycore_frame.h‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ struct _frame {
1919
struct_PyInterpreterFrame*f_frame; /* points to the frame data */
2020
PyObject*f_trace; /* Trace function */
2121
intf_lineno; /* Current line number. Only valid if non-zero */
22+
intf_last_traced_line; /* The last line traced for this frame */
2223
charf_trace_lines; /* Emit per-line trace events? */
2324
charf_trace_opcodes; /* Emit per-opcode trace events? */
2425
charf_fast_as_locals; /* Have the fast locals of this frame been converted to a dict? */
@@ -137,10 +138,16 @@ _PyFrame_GetLocalsArray(_PyInterpreterFrame *frame)
137138
returnframe->localsplus;
138139
}
139140

141+
/* Fetches the stack pointer, and sets stacktop to -1.
142+
Having stacktop <= 0 ensures that invalid
143+
values are not visible to the cycle GC.
144+
We choose -1 rather than 0 to assist debugging. */
140145
staticinlinePyObject**
141146
_PyFrame_GetStackPointer(_PyInterpreterFrame*frame)
142147
{
143-
returnframe->localsplus+frame->stacktop;
148+
PyObject**sp=frame->localsplus+frame->stacktop;
149+
frame->stacktop=-1;
150+
returnsp;
144151
}
145152

146153
staticinlinevoid
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
2+
#ifndefPy_INTERNAL_INSTRUMENT_H
3+
#definePy_INTERNAL_INSTRUMENT_H
4+
5+
6+
#include"pycore_bitutils.h"// _Py_popcount32
7+
#include"pycore_frame.h"
8+
9+
#include"cpython/code.h"
10+
11+
#ifdef__cplusplus
12+
extern"C" {
13+
#endif
14+
15+
#definePY_MONITORING_TOOL_IDS 8
16+
17+
/* Local events.
18+
* These require bytecode instrumentation */
19+
20+
#definePY_MONITORING_EVENT_PY_START 0
21+
#definePY_MONITORING_EVENT_PY_RESUME 1
22+
#definePY_MONITORING_EVENT_PY_RETURN 2
23+
#definePY_MONITORING_EVENT_PY_YIELD 3
24+
#definePY_MONITORING_EVENT_CALL 4
25+
#definePY_MONITORING_EVENT_LINE 5
26+
#definePY_MONITORING_EVENT_INSTRUCTION 6
27+
#definePY_MONITORING_EVENT_JUMP 7
28+
#definePY_MONITORING_EVENT_BRANCH 8
29+
#definePY_MONITORING_EVENT_STOP_ITERATION 9
30+
31+
#definePY_MONITORING_INSTRUMENTED_EVENTS 10
32+
33+
/* Other events, mainly exceptions */
34+
35+
#definePY_MONITORING_EVENT_RAISE 10
36+
#definePY_MONITORING_EVENT_EXCEPTION_HANDLED 11
37+
#definePY_MONITORING_EVENT_PY_UNWIND 12
38+
#definePY_MONITORING_EVENT_PY_THROW 13
39+
40+
41+
/* Ancilliary events */
42+
43+
#definePY_MONITORING_EVENT_C_RETURN 14
44+
#definePY_MONITORING_EVENT_C_RAISE 15
45+
46+
47+
typedefuint32_t_PyMonitoringEventSet;
48+
49+
/* Tool IDs */
50+
51+
/* These are defined in PEP 669 for convenience to avoid clashes */
52+
#definePY_MONITORING_DEBUGGER_ID 0
53+
#definePY_MONITORING_COVERAGE_ID 1
54+
#definePY_MONITORING_PROFILER_ID 2
55+
#definePY_MONITORING_OPTIMIZER_ID 5
56+
57+
/* Internal IDs used to suuport sys.setprofile() and sys.settrace() */
58+
#definePY_MONITORING_SYS_PROFILE_ID 6
59+
#definePY_MONITORING_SYS_TRACE_ID 7
60+
61+
62+
PyObject*_PyMonitoring_RegisterCallback(inttool_id, intevent_id, PyObject*obj);
63+
64+
int_PyMonitoring_SetEvents(inttool_id, _PyMonitoringEventSetevents);
65+
66+
externint
67+
_Py_call_instrumentation(PyThreadState*tstate, intevent,
68+
_PyInterpreterFrame*frame, _Py_CODEUNIT*instr);
69+
70+
externint
71+
_Py_call_instrumentation_line(PyThreadState*tstate, _PyInterpreterFrame*frame,
72+
_Py_CODEUNIT*instr);
73+
74+
externint
75+
_Py_call_instrumentation_instruction(
76+
PyThreadState*tstate, _PyInterpreterFrame*frame, _Py_CODEUNIT*instr);
77+
78+
int
79+
_Py_call_instrumentation_jump(
80+
PyThreadState*tstate, intevent,
81+
_PyInterpreterFrame*frame, _Py_CODEUNIT*instr, _Py_CODEUNIT*target);
82+
83+
externint
84+
_Py_call_instrumentation_arg(PyThreadState*tstate, intevent,
85+
_PyInterpreterFrame*frame, _Py_CODEUNIT*instr, PyObject*arg);
86+
87+
externint
88+
_Py_call_instrumentation_2args(PyThreadState*tstate, intevent,
89+
_PyInterpreterFrame*frame, _Py_CODEUNIT*instr, PyObject*arg0, PyObject*arg1);
90+
91+
externvoid
92+
_Py_call_instrumentation_exc0(PyThreadState*tstate, intevent,
93+
_PyInterpreterFrame*frame, _Py_CODEUNIT*instr);
94+
95+
externvoid
96+
_Py_call_instrumentation_exc2(PyThreadState*tstate, intevent,
97+
_PyInterpreterFrame*frame, _Py_CODEUNIT*instr, PyObject*arg0, PyObject*arg1);
98+
99+
externint
100+
_Py_Instrumentation_GetLine(PyCodeObject*code, intindex);
101+
102+
externPyObject_PyInstrumentation_MISSING;
103+
104+
#ifdef__cplusplus
105+
}
106+
#endif
107+
#endif/* !Py_INTERNAL_INSTRUMENT_H */

‎Include/internal/pycore_interp.h‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ extern "C" {
2424
#include"pycore_genobject.h"// struct _Py_async_gen_state
2525
#include"pycore_gc.h"// struct _gc_runtime_state
2626
#include"pycore_import.h"// struct _import_state
27+
#include"pycore_instruments.h"// PY_MONITORING_EVENTS
2728
#include"pycore_list.h"// struct _Py_list_state
2829
#include"pycore_global_objects.h"// struct _Py_interp_static_objects
2930
#include"pycore_object_state.h"// struct _py_object_state
@@ -37,7 +38,6 @@ struct _Py_long_state {
3738
intmax_str_digits;
3839
};
3940

40-
4141
/* interpreter state */
4242

4343
/* PyInterpreterState holds the global state for one of the runtime's
@@ -49,6 +49,9 @@ struct _is {
4949

5050
PyInterpreterState*next;
5151

52+
uint64_tmonitoring_version;
53+
uint64_tlast_restart_version;
54+
5255
structpythreads {
5356
uint64_tnext_unique_id;
5457
/* The linked list of threads, newest first. */
@@ -148,6 +151,15 @@ struct _is {
148151
structcallable_cachecallable_cache;
149152
PyCodeObject*interpreter_trampoline;
150153

154+
_Py_Monitorsmonitors;
155+
boolf_opcode_trace_set;
156+
boolsys_profile_initialized;
157+
boolsys_trace_initialized;
158+
Py_ssize_tsys_profiling_threads; /* Count of threads with c_profilefunc set */
159+
Py_ssize_tsys_tracing_threads; /* Count of threads with c_tracefunc set */
160+
PyObject*monitoring_callables[PY_MONITORING_TOOL_IDS][PY_MONITORING_EVENTS];
161+
PyObject*monitoring_tool_names[PY_MONITORING_TOOL_IDS];
162+
151163
struct_Py_interp_cached_objectscached_objects;
152164
struct_Py_interp_static_objectsstatic_objects;
153165

0 commit comments

Comments
 (0)