Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GH-103082: Implementation of PEP 669: Low Impact Monitoring for CPython #103083

Merged
merged 127 commits into from
Apr 12, 2023
Merged
Show file tree
Hide file tree
Changes from 78 commits
Commits
Show all changes
127 commits
Select commit Hold shift + click to select a range
e85d910
Initial experimental implementation of PEP 669.
markshannon Nov 9, 2022
6283ee8
First draft implementing small subset of PEP 669.
markshannon Nov 18, 2022
4edb7b7
Support disabling and restarting.
markshannon Nov 28, 2022
852c40b
Support multiple tools per event.
markshannon Nov 28, 2022
416b314
Tidy up of monitoring internals
markshannon Dec 2, 2022
7971979
Fix legacy tracing.
markshannon Dec 4, 2022
9896902
Implement support for multiple tools.
markshannon Dec 8, 2022
f432a66
Get support for line tracing mostly working. Needs to wait for regist…
markshannon Dec 22, 2022
fb29b34
Merge branch 'main' into pep-669-incremental
markshannon Jan 13, 2023
15a1ccd
Fix up INSTRUMENTED_OPCODES vector.
markshannon Jan 13, 2023
9abb339
Fix instrumented branches to call instrumentation with correct target.
markshannon Jan 16, 2023
3ba6a39
Merge main and assorted fixups to handle new instruction.
markshannon Jan 17, 2023
aa09895
Add PY_THROW event handling and fix up line table.
markshannon Jan 17, 2023
9e5d87d
LINE events working for sys.setrace.
markshannon Jan 17, 2023
e52cbe6
Add lots of internal debugging for instrumentation.
markshannon Jan 18, 2023
6c8be7e
Add more tests. Get those and some other passing.
markshannon Jan 18, 2023
c9e1e21
Fix LINE instrumentation and frame.set_lineno support (mostly)
markshannon Jan 19, 2023
e52e8d3
Refining line event generation.
markshannon Jan 20, 2023
f434ec7
Get sys.settrace tests passing.
markshannon Jan 20, 2023
b680084
Monitor 'internal' StopIteration raises.
markshannon Jan 20, 2023
e6e7cf1
Check for NULLs.
markshannon Jan 20, 2023
5bbc83e
Fix up a few tests
markshannon Jan 20, 2023
7fe9a43
Turn off debugging output by default.
markshannon Jan 23, 2023
8b9f996
Remove debugging printfs
markshannon Jan 26, 2023
9b02640
Avoid refleak.
markshannon Jan 26, 2023
2cadf32
Record last traced line on frame object.
markshannon Jan 26, 2023
1f54d77
Get a couple more top-level tests passing.
markshannon Jan 30, 2023
43a3f3e
Update magic number
markshannon Jan 30, 2023
3d436cf
Remove debug print statement.
markshannon Jan 30, 2023
691bcf5
Raise SystemError if frame is missing.
markshannon Jan 30, 2023
f07be07
Restore a few tests and better handle EXTENDED_ARG.
markshannon Jan 31, 2023
8b8f67e
Make sure instrumentation respects instruction boundaries.
markshannon Feb 1, 2023
d0a2228
Fix handling of errors in jump instruments
markshannon Feb 1, 2023
2a3a85e
Fix memory leaks.
markshannon Feb 1, 2023
c3724ab
Instrument FOR_ITER.
markshannon Feb 1, 2023
d64823c
Fix legacy profiling of calls.
markshannon Feb 2, 2023
284d0b1
Fix up instrumented yield.
markshannon Feb 3, 2023
b9e1f3b
Merge branch 'main' into pep-669-incremental
markshannon Feb 8, 2023
1440473
Fix instrumentation of SEND and tidy up instrumented bytecode numbering.
markshannon Feb 9, 2023
825f42a
Handle line numbers in exception handled event.
markshannon Feb 9, 2023
ce5ddb3
Instrument END_FOR to mimic PEP 380 StopIteration and add more tests.
markshannon Feb 10, 2023
5740c47
Merge branch 'main' into pep-669-incremental
markshannon Feb 13, 2023
da83abe
Add END_SEND for instrumentation.
markshannon Feb 14, 2023
cdb2bda
Correctly set StopIteration in INSTRUMENTED_END_FOR.
markshannon Feb 14, 2023
0148fa3
Set last traced line when jumping in debugger.
markshannon Feb 14, 2023
c5fb4f4
Fix test_dis to account for END_SEND.
markshannon Feb 17, 2023
7fa431b
Add a few more tests.
markshannon Feb 17, 2023
25bbc61
delete commented out code
markshannon Feb 17, 2023
5629a3e
Fix last (known) inconsistency in sys.settrace behaviour
markshannon Feb 17, 2023
477cc53
More clearly differentiate between instrumented events and non-instru…
markshannon Feb 20, 2023
23b5f5e
Add support for sys.monitoring.MISSING
markshannon Feb 21, 2023
dfc18c5
Add support for local (per-code-object) event monitoring.
markshannon Feb 23, 2023
566adbe
Merge branch 'main' into pep-669
markshannon Feb 23, 2023
cfb17ed
Make sure that tracing/profiling thread counts are correct.
markshannon Feb 23, 2023
c535f76
Remove linearray field
markshannon Feb 23, 2023
d579d2e
Rename some fields, shortening names.
markshannon Feb 23, 2023
0982e5e
Remove out of dat comments.
markshannon Feb 23, 2023
0693423
Remove instrumentation for JUMP_IF_[FALS|TRUE]_OR_POP
markshannon Mar 22, 2023
d6453e5
Merge branch 'main' into pep-669
markshannon Mar 22, 2023
0ee2aee
Minor fixups from merge.
markshannon Mar 23, 2023
acdca93
Remove instrumentation for COMPARE_AND_BRANCH.
markshannon Mar 23, 2023
f17ef14
Merge branch 'main' into pep-669
markshannon Mar 23, 2023
e7f6c37
Group instrumented opcodes.
markshannon Mar 23, 2023
e44ebc5
Remove last references to DO_TRACING
markshannon Mar 23, 2023
ece51e6
Merge branch 'main' into pep-669
markshannon Mar 23, 2023
e88921e
Remove unused function.
markshannon Mar 23, 2023
2d9f22c
Streamline de-instrumentation a little.
markshannon Mar 23, 2023
b7579ac
Use modern API for saving and restoring exception.
markshannon Mar 23, 2023
5a089a6
Refactor instrumentation calls to reduce duplication.
markshannon Mar 24, 2023
d5fdec8
Fix refleaks
markshannon Mar 24, 2023
c88741d
Remove commented out code.
markshannon Mar 24, 2023
b6744ca
Make sure that stacktop == -1 when stack_pointer is cached.
markshannon Mar 24, 2023
6c3473a
Minor cleanups.
markshannon Mar 24, 2023
899aecd
Remove useless asserts.
markshannon Mar 24, 2023
80d2e2e
Make functions static
markshannon Mar 24, 2023
94d35d8
Update Windows build files.
markshannon Mar 24, 2023
5aa0805
Make _PyLegacyEventHandler_Type immortal
markshannon Mar 24, 2023
b39edd3
Make arrays const.
markshannon Mar 24, 2023
c9c40cb
Rename _PyFrame_GetStackPointer to _PyFrame_FetchStackPointer and mak…
markshannon Mar 24, 2023
6611c72
Fixups from code review
markshannon Mar 24, 2023
50d28f1
Improve debugging output and make a few things more robust.
markshannon Mar 24, 2023
7165f52
Fix up frame.set_lineno().
markshannon Mar 25, 2023
65c548e
Make line instrumentation able to handle multiple inits.
markshannon Mar 25, 2023
575f7d1
Add brief comments on instrumentation data structures.
markshannon Mar 25, 2023
415741d
Add symbol constant for min instrumented opcode.
markshannon Mar 25, 2023
d70a1a4
Code cleanup from review.
markshannon Mar 25, 2023
d580de6
A bit more cleanup.
markshannon Mar 25, 2023
2076d5f
Add a few more comments.
markshannon Mar 25, 2023
7b32d79
Remove unused code.
markshannon Mar 25, 2023
662c16c
Fix leak of monitoring blocks.
markshannon Mar 25, 2023
d0139e9
Merge branch 'main' into pep-669
markshannon Mar 25, 2023
51a93e7
Make function static.
markshannon Mar 25, 2023
64bf37f
Use symbolic constants in line computations.
markshannon Mar 26, 2023
5faec77
Make data static.
markshannon Mar 26, 2023
0be1562
Add extra check.
markshannon Mar 26, 2023
b32a075
Make sure that instrumentation of line and instructions at the same t…
markshannon Mar 26, 2023
85d6923
Remove incorrect assertion.
markshannon Mar 31, 2023
ebcc42f
Turn off instrumentation debugging.
markshannon Mar 31, 2023
7cfbc7e
Fix a couple of errors.
markshannon Mar 31, 2023
d366364
Add minimal news entry.
markshannon Mar 31, 2023
fdb4860
Pacify the global object checker.
markshannon Mar 31, 2023
aee722f
Minor fixups.
markshannon Apr 3, 2023
bba53b8
Fix refleak when error in RETURN instrumentation.
markshannon Apr 3, 2023
718fbc8
Merge branch 'main' into pep-669
markshannon Apr 3, 2023
97ec1c5
Fix another refleak in error handling in instrumented instruction.
markshannon Apr 3, 2023
edc6709
Remove debug code ready for final review.
markshannon Apr 3, 2023
e40a68f
Fix another (and hopefully final) refleak when handling an error in a…
markshannon Apr 3, 2023
d9f8192
Comment and rename #defines to better reflect names in PEP.
markshannon Apr 4, 2023
2d9a380
Add various IDs as defined in the PEP.
markshannon Apr 4, 2023
29f41e7
Remove _PyThreadState_UpdateTracingState.
markshannon Apr 4, 2023
38b7f43
Formatting fixes, and check error codes.
markshannon Apr 4, 2023
9a40dad
Make sure tool API function names match PEP.
markshannon Apr 4, 2023
a551d65
A bit more cleanup.
markshannon Apr 4, 2023
4951306
Tidy up imports.
markshannon Apr 4, 2023
44a031e
Use clinic return converters.
markshannon Apr 4, 2023
c2155b7
Address code review.
markshannon Apr 4, 2023
b218428
Address further review comments.
markshannon Apr 4, 2023
9c0429e
Use byte offset, to conform to the convention used in other APIs taki…
markshannon Apr 5, 2023
c6bf38e
Address code review.
markshannon Apr 5, 2023
82fe16c
Merge branch 'main' into pep-669
markshannon Apr 5, 2023
43618a9
Keep check-c-globals happy.
markshannon Apr 5, 2023
821ae52
Don't crash if locals events aren't set.
markshannon Apr 9, 2023
505a08d
Add NO_EVENTS.
markshannon Apr 9, 2023
f63da91
Flip order of LINE and INSTRUCTION events.
markshannon Apr 9, 2023
c324344
Check tool is in use when setting events.
markshannon Apr 9, 2023
168b34a
Reset last traced line number when setting frame.f_trace only if set …
markshannon Apr 12, 2023
f07a080
Tidy up test case.
markshannon Apr 12, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions Include/cpython/code.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,18 @@
#ifndef Py_LIMITED_API
#ifndef Py_CODE_H
#define Py_CODE_H

#ifdef __cplusplus
extern "C" {
#endif

#define PY_MONITORING_EVENTS 16
#define PY_MONITORING_UNGROUPED_EVENTS 14
markshannon marked this conversation as resolved.
Show resolved Hide resolved

typedef struct _Py_Monitors {
uint8_t tools[PY_MONITORING_UNGROUPED_EVENTS];
} _Py_Monitors;

/* Each instruction in a code object is a fixed-width value,
* currently 2 bytes: 1-byte opcode + 1-byte oparg. The EXTENDED_ARG
* opcode allows for larger values but the current limit is 3 uses
Expand Down Expand Up @@ -56,6 +64,21 @@ typedef struct {
PyObject *_co_freevars;
} _PyCoCached;

typedef struct {
uint8_t original_opcode;
int8_t line_delta;
} _PyCoLineInstrumentationData;

typedef struct {
markshannon marked this conversation as resolved.
Show resolved Hide resolved
_Py_Monitors local_monitors;
_Py_Monitors active_monitors;
uint8_t *tools;
_PyCoLineInstrumentationData *lines;
uint8_t *line_tools;
uint8_t *per_instruction_opcodes;
uint8_t *per_instruction_tools;
} _PyCoMonitoringData;

// To avoid repeating ourselves in deepfreeze.py, all PyCodeObject members are
// defined in this macro:
#define _PyCode_DEF(SIZE) { \
Expand Down Expand Up @@ -87,7 +110,6 @@ typedef struct {
PyObject *co_exceptiontable; /* Byte string encoding exception handling \
table */ \
int co_flags; /* CO_..., see below */ \
short _co_linearray_entry_size; /* Size of each entry in _co_linearray */ \
\
/* The rest are not so impactful on performance. */ \
int co_argcount; /* #arguments, except *args */ \
Expand All @@ -114,8 +136,9 @@ typedef struct {
PyObject *co_linetable; /* bytes object that holds location info */ \
PyObject *co_weakreflist; /* to support weakrefs to code objects */ \
_PyCoCached *_co_cached; /* cached co_* attributes */ \
uint64_t _co_instrumentation_version; /* current instrumentation version */ \
_PyCoMonitoringData *_co_monitoring; /* Monitoring data */ \
int _co_firsttraceable; /* index of first traceable instruction */ \
char *_co_linearray; /* array of line offsets */ \
/* Scratch space for extra data relating to the code object. \
Type is a void* to keep the format private in codeobject.c to force \
people to go through the proper APIs. */ \
Expand Down
11 changes: 1 addition & 10 deletions Include/cpython/pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,6 @@ typedef int (*Py_tracefunc)(PyObject *, PyFrameObject *, int, PyObject *);
#define PyTrace_C_RETURN 6
#define PyTrace_OPCODE 7


typedef struct {
PyCodeObject *code; // The code object for the bounds. May be NULL.
PyCodeAddressRange bounds; // Only valid if code != NULL.
} PyTraceInfo;

// Internal structure: you should not use it directly, but use public functions
// like PyThreadState_EnterTracing() and PyThreadState_LeaveTracing().
typedef struct _PyCFrame {
Expand All @@ -77,7 +71,6 @@ typedef struct _PyCFrame {
* discipline and make sure that instances of this struct cannot
* accessed outside of their lifetime.
*/
uint8_t use_tracing; // 0 or 255 (or'ed into opcode, hence 8-bit type)
/* Pointer to the currently executing frame (it can be NULL) */
struct _PyInterpreterFrame *current_frame;
struct _PyCFrame *previous;
Expand Down Expand Up @@ -157,7 +150,7 @@ struct _ts {
This is to prevent the actual trace/profile code from being recorded in
the trace/profile. */
int tracing;
int tracing_what; /* The event currently being traced, if any. */
int what_event; /* The event currently being monitored, if any. */
markshannon marked this conversation as resolved.
Show resolved Hide resolved

/* Pointer to current _PyCFrame in the C stack frame of the currently,
* or most recently, executing _PyEval_EvalFrameDefault. */
Expand Down Expand Up @@ -228,8 +221,6 @@ struct _ts {
/* Unique thread state id. */
uint64_t id;

PyTraceInfo trace_info;

_PyStackChunk *datastack_chunk;
PyObject **datastack_top;
PyObject **datastack_limit;
Expand Down
30 changes: 4 additions & 26 deletions Include/internal/pycore_code.h
Original file line number Diff line number Diff line change
Expand Up @@ -444,32 +444,6 @@ adaptive_counter_backoff(uint16_t counter) {

/* Line array cache for tracing */

extern int _PyCode_CreateLineArray(PyCodeObject *co);

static inline int
_PyCode_InitLineArray(PyCodeObject *co)
{
if (co->_co_linearray) {
return 0;
}
return _PyCode_CreateLineArray(co);
}

static inline int
_PyCode_LineNumberFromArray(PyCodeObject *co, int index)
{
assert(co->_co_linearray != NULL);
assert(index >= 0);
assert(index < Py_SIZE(co));
if (co->_co_linearray_entry_size == 2) {
return ((int16_t *)co->_co_linearray)[index];
}
else {
assert(co->_co_linearray_entry_size == 4);
return ((int32_t *)co->_co_linearray)[index];
}
}

typedef struct _PyShimCodeDef {
const uint8_t *code;
int codelen;
Expand Down Expand Up @@ -503,6 +477,10 @@ extern uint32_t _Py_next_func_version;

#define COMPARISON_NOT_EQUALS (COMPARISON_UNORDERED | COMPARISON_LESS_THAN | COMPARISON_GREATER_THAN)

extern int _Py_Instrument(PyCodeObject *co, PyInterpreterState *interp);

extern int _Py_GetBaseOpcode(PyCodeObject *code, int offset);


#ifdef __cplusplus
}
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ struct _frame {
struct _PyInterpreterFrame *f_frame; /* points to the frame data */
PyObject *f_trace; /* Trace function */
int f_lineno; /* Current line number. Only valid if non-zero */
int f_last_traced_line; /* The last line traced for this frame */
char f_trace_lines; /* Emit per-line trace events? */
char f_trace_opcodes; /* Emit per-opcode trace events? */
char f_fast_as_locals; /* Have the fast locals of this frame been converted to a dict? */
Expand Down
105 changes: 105 additions & 0 deletions Include/internal/pycore_instruments.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@

#ifndef Py_INTERNAL_INSTRUMENT_H
#define Py_INTERNAL_INSTRUMENT_H


#include "pycore_bitutils.h" // _Py_popcount32
#include "pycore_frame.h"

#include "cpython/code.h"

#ifdef __cplusplus
extern "C" {
#endif

#define PY_MONITORING_TOOL_IDS 8

/* Require bytecode instrumentation */

#define PY_MONITORING_EVENT_PY_START 0
#define PY_MONITORING_EVENT_PY_RESUME 1
#define PY_MONITORING_EVENT_PY_RETURN 2
#define PY_MONITORING_EVENT_PY_YIELD 3
#define PY_MONITORING_EVENT_CALL 4
#define PY_MONITORING_EVENT_LINE 5
#define PY_MONITORING_EVENT_INSTRUCTION 6
#define PY_MONITORING_EVENT_JUMP 7
#define PY_MONITORING_EVENT_BRANCH 8
#define PY_MONITORING_EVENT_STOP_ITERATION 9

#define PY_MONITORING_INSTRUMENTED_EVENTS 10

/* Exceptional events */

#define PY_MONITORING_EVENT_RAISE 10
#define PY_MONITORING_EVENT_EXCEPTION_HANDLED 11
#define PY_MONITORING_EVENT_PY_UNWIND 12
#define PY_MONITORING_EVENT_PY_THROW 13


/* Grouped events */

#define PY_MONITORING_EVENT_C_RETURN 14
#define PY_MONITORING_EVENT_C_RAISE 15


/* #define INSTRUMENT_EVENT_BRANCH_NOT_TAKEN xxx -- If we can afford this */


typedef uint32_t _PyMonitoringEventSet;

/* Reserved IDs */

#define PY_INSTRUMENT_PEP_523 5
#define PY_INSTRUMENT_SYS_PROFILE 6
#define PY_INSTRUMENT_SYS_TRACE 7


/* API functions */

PyObject *_PyMonitoring_RegisterCallback(int tool_id, int event_id, PyObject *obj);

void _PyMonitoring_SetEvents(int tool_id, _PyMonitoringEventSet events);

extern int
_Py_call_instrumentation(PyThreadState *tstate, int event,
_PyInterpreterFrame *frame, _Py_CODEUNIT *instr);

extern int
_Py_call_instrumentation_line(PyThreadState *tstate, _PyInterpreterFrame* frame,
_Py_CODEUNIT *instr);

extern int
_Py_call_instrumentation_instruction(
PyThreadState *tstate, _PyInterpreterFrame* frame, _Py_CODEUNIT *instr);

int
_Py_call_instrumentation_jump(
PyThreadState *tstate, int event,
_PyInterpreterFrame *frame, _Py_CODEUNIT *instr, _Py_CODEUNIT *target);

extern int
_Py_call_instrumentation_arg(PyThreadState *tstate, int event,
_PyInterpreterFrame *frame, _Py_CODEUNIT *instr, PyObject *arg);

extern int
_Py_call_instrumentation_2args(PyThreadState *tstate, int event,
_PyInterpreterFrame *frame, _Py_CODEUNIT *instr, PyObject *arg0, PyObject *arg1);

extern void
_Py_call_instrumentation_exc0(PyThreadState *tstate, int event,
_PyInterpreterFrame *frame, _Py_CODEUNIT *instr);

extern void
_Py_call_instrumentation_exc2(PyThreadState *tstate, int event,
_PyInterpreterFrame *frame, _Py_CODEUNIT *instr, PyObject *arg0, PyObject *arg1);

extern int
_Py_Instrumentation_GetLine(PyCodeObject *code, int index);

extern PyObject _PyInstrumentation_MISSING;

#ifdef __cplusplus
}
#endif
#endif /* !Py_INTERNAL_INSTRUMENT_H */
15 changes: 14 additions & 1 deletion Include/internal/pycore_interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ extern "C" {
#include "pycore_genobject.h" // struct _Py_async_gen_state
#include "pycore_gc.h" // struct _gc_runtime_state
#include "pycore_import.h" // struct _import_state
#include "pycore_instruments.h" // PY_MONITORING_EVENTS
#include "pycore_list.h" // struct _Py_list_state
#include "pycore_global_objects.h" // struct _Py_interp_static_objects
#include "pycore_object_state.h" // struct _py_object_state
Expand Down Expand Up @@ -50,7 +51,6 @@ struct _Py_long_state {
int max_str_digits;
};


/* interpreter state */

/* PyInterpreterState holds the global state for one of the runtime's
Expand All @@ -62,6 +62,9 @@ struct _is {

PyInterpreterState *next;

uint64_t monitoring_version;
uint64_t last_restart_version;

struct pythreads {
uint64_t next_unique_id;
/* The linked list of threads, newest first. */
Expand Down Expand Up @@ -161,6 +164,15 @@ struct _is {
struct callable_cache callable_cache;
PyCodeObject *interpreter_trampoline;

_Py_Monitors monitors;
bool f_opcode_trace_set;
bool sys_profile_initialized;
bool sys_trace_initialized;
Py_ssize_t sys_profiling_threads; /* Count of threads with c_profilefunc set */
Py_ssize_t sys_tracing_threads; /* Count of threads with c_tracefunc set */
PyObject *monitoring_callables[PY_MONITORING_TOOL_IDS][PY_MONITORING_EVENTS];
PyObject *monitoring_tool_names[PY_MONITORING_TOOL_IDS];

struct _Py_interp_cached_objects cached_objects;
struct _Py_interp_static_objects static_objects;

Expand Down Expand Up @@ -207,6 +219,7 @@ PyAPI_FUNC(int) _PyInterpreterState_IDInitref(PyInterpreterState *);
PyAPI_FUNC(int) _PyInterpreterState_IDIncref(PyInterpreterState *);
PyAPI_FUNC(void) _PyInterpreterState_IDDecref(PyInterpreterState *);


markshannon marked this conversation as resolved.
Show resolved Hide resolved
#ifdef __cplusplus
}
#endif
Expand Down
Loading