Skip to content

Commit

Permalink
Merge pull request #187 from P403n1x87/feat/python312
Browse files Browse the repository at this point in the history
feat: add support for Python 3.12
  • Loading branch information
P403n1x87 authored Sep 6, 2023
2 parents ff51dad + ebe7deb commit 0036681
Show file tree
Hide file tree
Showing 20 changed files with 461 additions and 39 deletions.
7 changes: 3 additions & 4 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,7 @@ jobs:
sudo apt-get update
sudo apt-get -y install \
valgrind \
python2.7 \
python3.{5..11} \
python3.{8..12} \
python3.10-full python3.10-dev
python3.10 -m venv .venv
Expand All @@ -175,8 +174,8 @@ jobs:
run: |
echo "core.%p" | sudo tee /proc/sys/kernel/core_pattern
ulimit -c unlimited
.venv/bin/pytest --pastebin=failed -sr fE -n auto || true
sudo -E env PATH="$PATH" .venv/bin/pytest --pastebin=failed -sr fE -n auto || true
.venv/bin/pytest -sr fE -n auto || true
sudo -E env PATH="$PATH" .venv/bin/pytest -sr fE -n auto || true
- name: Generate Cobertura report
run: gcovr --xml ./cobertura.xml -r src/
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]

env:
AUSTIN_TESTS_PYTHON_VERSIONS: ${{ matrix.python-version }}
Expand Down Expand Up @@ -251,7 +251,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]

env:
AUSTIN_TESTS_PYTHON_VERSIONS: ${{ matrix.python-version }}
Expand Down Expand Up @@ -377,7 +377,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]

env:
AUSTIN_TESTS_PYTHON_VERSIONS: ${{ matrix.python-version }}
Expand Down Expand Up @@ -466,7 +466,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]

env:
AUSTIN_TESTS_PYTHON_VERSIONS: ${{ matrix.python-version }}
Expand Down
2 changes: 1 addition & 1 deletion doc/cheatsheet.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions scripts/build-wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
],
"Project-URL": [
"Homepage, https://github.com/P403n1x87/austin",
Expand Down
2 changes: 1 addition & 1 deletion src/argparse.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
#else
#define DEFAULT_SAMPLING_INTERVAL 100
#endif
#define DEFAULT_INIT_TIMEOUT_MS 1000 // 1 seconds
#define DEFAULT_INIT_TIMEOUT_MS 1000 // 1 second
#define DEFAULT_HEAP_SIZE 0

const char SAMPLE_FORMAT_NORMAL[] = ";%s:%s:%d";
Expand Down
16 changes: 14 additions & 2 deletions src/py_proc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1157,13 +1157,25 @@ py_proc__sample(py_proc_t * self) {

if (pargs.memory) {
// Use the current thread to determine which thread is manipulating memory
current_thread = _py_proc__get_current_thread_state_raddr(self);
if (V_MIN(3, 12)) {
void * gil_state_raddr = V_FIELD(void *, is, py_is, o_gil_state);
if (!isvalid(gil_state_raddr))
SUCCESS;
gil_state_t gil_state;
if (fail(copy_datatype(self->proc_ref, gil_state_raddr, gil_state))) {
log_ie("Failed to copy GIL state");
FAIL;
}
current_thread = (void *) gil_state.last_holder._value;
}
else
current_thread = _py_proc__get_current_thread_state_raddr(self);
}

do {
if (pargs.memory) {
mem_delta = 0;
if (self->symbols[DYNSYM_RUNTIME] != NULL && current_thread == (void *) -1) {
if (V_MAX(3, 11) && self->symbols[DYNSYM_RUNTIME] != NULL && current_thread == (void *) -1) {
if (_py_proc__find_current_thread_offset(self, py_thread.raddr.addr))
continue;
else
Expand Down
19 changes: 13 additions & 6 deletions src/py_string.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

#define MAGIC_TINY 7
#define MAGIC_BIG 1000003
#define p_ascii_data(raddr) (raddr + sizeof(PyASCIIObject))
#define p_ascii_data(raddr, size) (raddr + size)


// ----------------------------------------------------------------------------
Expand All @@ -57,7 +57,7 @@ string__hash(char * string) {
// ----------------------------------------------------------------------------
static inline char *
_string_from_raddr(proc_ref_t pref, void * raddr, python_v * py_v) {
PyUnicodeObject3 unicode;
PyUnicodeObject unicode;
char * buffer = NULL;
ssize_t len = 0;

Expand All @@ -66,15 +66,22 @@ _string_from_raddr(proc_ref_t pref, void * raddr, python_v * py_v) {
goto failed;
}

PyASCIIObject ascii = unicode._base._base;
PyASCIIObject ascii = unicode.v3._base._base;

if (ascii.state.kind != 1) {
set_error(ECODEFMT);
goto failed;
}

void * data = ascii.state.compact ? p_ascii_data(raddr) : unicode._base.utf8;
len = ascii.state.compact ? ascii.length : unicode._base.utf8_length;

// Because changes to PyASCIIObject are rare, we handle the version manually
// instead of using a version offset descriptor.
ssize_t ascii_size = V_MIN(3, 12) ? sizeof(unicode.v3_12._base._base) : sizeof(unicode.v3._base._base);
void * data = ascii.state.compact
? p_ascii_data(raddr, ascii_size)
: (V_MIN(3, 12) ? unicode.v3_12._base.utf8 : unicode.v3._base.utf8);
len = ascii.state.compact
? ascii.length
: (V_MIN(3, 12) ? unicode.v3_12._base.utf8_length : unicode.v3._base.utf8_length);

if (len < 0 || len > 4096) {
log_e("Invalid string length");
Expand Down
16 changes: 14 additions & 2 deletions src/py_thread.c
Original file line number Diff line number Diff line change
Expand Up @@ -317,14 +317,24 @@ _py_thread__push_iframe_from_addr(py_thread_t * self, PyInterpreterFrame * ifram
FAIL;
}

if (V_MIN(3, 12) && V_FIELD_PTR(char, iframe, py_iframe, o_owner) == FRAME_OWNED_BY_CSTACK) {
// This is a shim frame that we can ignore
#ifdef NATIVE
// In native mode we take this as the marker for the beginning of the stack
// for a call to PyEval_EvalFrameDefault.
stack_py_push_cframe();
#endif
SUCCESS;
}

stack_py_push(
origin,
code_raddr,
(((int)(V_FIELD_PTR(void *, iframe, py_iframe, o_prev_instr) - code_raddr)) - py_v->py_code.o_code) / sizeof(_Py_CODEUNIT)
);

#ifdef NATIVE
if (V_MIN(3, 11) && V_FIELD_PTR(int, iframe, py_iframe, o_is_entry)) {
if (V_EQ(3, 11) && V_FIELD_PTR(int, iframe, py_iframe, o_is_entry)) {
// This marks the end of a CFrame
stack_py_push_cframe();
}
Expand Down Expand Up @@ -797,7 +807,9 @@ py_thread__fill_from_raddr(py_thread_t * self, raddr_t * raddr, py_proc_t * proc
self->raddr = *raddr;

self->top_frame = V_FIELD(void*, ts, py_thread, o_frame);


self->status = V_FIELD(tstate_status_t, ts, py_thread, o_status);

self->next_raddr = (raddr_t) {
raddr->pref,
V_FIELD(void*, ts, py_thread, o_next) == raddr->addr \
Expand Down
2 changes: 2 additions & 0 deletions src/py_thread.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ typedef struct thread {
/* The per-thread datastack was introduced in Python 3.11 */
void * stack;
size_t stack_size;

tstate_status_t status;
} py_thread_t;


Expand Down
8 changes: 8 additions & 0 deletions src/python/cframe.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,16 @@ typedef struct _PyCFrame3_11 {
} _PyCFrame3_11;


typedef struct {
/* Pointer to the currently executing frame (it can be NULL) */
void *current_frame;
void *previous;
} _PyCFrame3_12;


typedef union {
_PyCFrame3_11 v3_11;
_PyCFrame3_12 v3_12;
} PyCFrame;

#endif
48 changes: 48 additions & 0 deletions src/python/code.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,57 @@ typedef struct {
typedef struct _PyCode_DEF_311(1) PyCodeObject3_11;


#define _PyCode_DEF_312(SIZE) { \
PyObject_VAR_HEAD \
\
PyObject *co_consts; /* list (constants used) */ \
PyObject *co_names; /* list of strings (names used) */ \
PyObject *co_exceptiontable; /* Byte string encoding exception handling \
table */ \
int co_flags; /* CO_..., see below */ \
\
/* The rest are not so impactful on performance. */ \
int co_argcount; /* #arguments, except *args */ \
int co_posonlyargcount; /* #positional only arguments */ \
int co_kwonlyargcount; /* #keyword only arguments */ \
int co_stacksize; /* #entries needed for evaluation stack */ \
int co_firstlineno; /* first source line number */ \
\
/* redundant values (derived from co_localsplusnames and \
co_localspluskinds) */ \
int co_nlocalsplus; /* number of local + cell + free variables */ \
int co_framesize; /* Size of frame in words */ \
int co_nlocals; /* number of local variables */ \
int co_ncellvars; /* total number of cell variables */ \
int co_nfreevars; /* number of free variables */ \
uint32_t co_version; /* version number */ \
\
PyObject *co_localsplusnames; /* tuple mapping offsets to names */ \
PyObject *co_localspluskinds; /* Bytes mapping to local kinds (one byte \
per variable) */ \
PyObject *co_filename; /* unicode (where it was loaded from) */ \
PyObject *co_name; /* unicode (name, for reference) */ \
PyObject *co_qualname; /* unicode (qualname, for reference) */ \
PyObject *co_linetable; /* bytes object that holds location info */ \
PyObject *co_weakreflist; /* to support weakrefs to code objects */ \
void *co_executors; /* executors from optimizer */ \
void *_co_cached; /* cached co_* attributes */ \
uint64_t _co_instrumentation_version; /* current instrumentation version */ \
void *_co_monitoring; /* Monitoring data */ \
int _co_firsttraceable; /* index of first traceable instruction */ \
/* 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. */ \
void *co_extra; \
char co_code_adaptive[(SIZE)]; \
}
typedef struct _PyCode_DEF_312(1) PyCodeObject3_12;


typedef union {
PyCodeObject3_8 v3_8;
PyCodeObject3_11 v3_11;
PyCodeObject3_12 v3_12;
} PyCodeObject;

#endif
40 changes: 39 additions & 1 deletion src/python/gc.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,46 @@ struct _gc_runtime_state3_8 {
int collecting;
};


struct _gc_runtime_state3_12 {
/* List of objects that still need to be cleaned up, singly linked
* via their gc headers' gc_prev pointers. */
PyObject *trash_delete_later;
/* Current call-stack depth of tp_dealloc calls. */
int trash_delete_nesting;

/* Is automatic collection enabled? */
int enabled;
int debug;
/* linked lists of container objects */
struct gc_generation3_8 generations[NUM_GENERATIONS];
void *generation0;
/* a permanent generation which won't be collected */
struct gc_generation3_8 permanent_generation;
struct gc_generation_stats generation_stats[NUM_GENERATIONS];
/* true if we are currently running the collector */
int collecting;
/* list of uncollectable objects */
PyObject *garbage;
/* a list of callbacks to be invoked when collection is performed */
PyObject *callbacks;
/* This is the number of objects that survived the last full
collection. It approximates the number of long lived objects
tracked by the GC.
(by "full collection", we mean a collection of the oldest
generation). */
Py_ssize_t long_lived_total;
/* This is the number of objects that survived all "non-full"
collections, and are awaiting to undergo a full collection for
the first time. */
Py_ssize_t long_lived_pending;
};


typedef union {
struct _gc_runtime_state3_8 v3_8;
struct _gc_runtime_state3_8 v3_8;
struct _gc_runtime_state3_12 v3_12;
} GCRuntimeState;

#endif
11 changes: 2 additions & 9 deletions src/python/gil.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,8 @@ struct _gil_runtime_state3_11 {
unsigned long interval;
_Py_atomic_address last_holder;
_Py_atomic_int locked;
unsigned long switch_number;
PyCOND_T cond;
PyMUTEX_T mutex;
#ifdef FORCE_SWITCHING
/* This condition variable helps the GIL-releasing thread wait for
a GIL-awaiting thread to be scheduled and take the GIL. */
PyCOND_T switch_cond;
PyMUTEX_T switch_mutex;
#endif
};

typedef struct _gil_runtime_state3_11 gil_state_t;

#endif
30 changes: 30 additions & 0 deletions src/python/iframe.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,38 @@ typedef struct _PyInterpreterFrame3_11 {
} _PyInterpreterFrame3_11;


#define FRAME_OWNED_BY_CSTACK 3

typedef struct _PyInterpreterFrame3_12 {
PyCodeObject *f_code; /* Strong reference */
struct _PyInterpreterFrame3_12 *previous;
PyObject *f_funcobj; /* Strong reference. Only valid if not on C stack */
PyObject *f_globals; /* Borrowed reference. Only valid if not on C stack */
PyObject *f_builtins; /* Borrowed reference. Only valid if not on C stack */
PyObject *f_locals; /* Strong reference, may be NULL. Only valid if not on C stack */
PyFrameObject *frame_obj; /* Strong reference, may be NULL. Only valid if not on C stack */
// NOTE: This is not necessarily the last instruction started in the given
// frame. Rather, it is the code unit *prior to* the *next* instruction. For
// example, it may be an inline CACHE entry, an instruction we just jumped
// over, or (in the case of a newly-created frame) a totally invalid value:
_Py_CODEUNIT *prev_instr;
int stacktop; /* Offset of TOS from localsplus */
/* The return_offset determines where a `RETURN` should go in the caller,
* relative to `prev_instr`.
* It is only meaningful to the callee,
* so it needs to be set in any CALL (to a Python function)
* or SEND (to a coroutine or generator).
* If there is no callee, then it is meaningless. */
uint16_t return_offset;
char owner;
/* Locals and stack */
PyObject *localsplus[1];
} _PyInterpreterFrame3_12;


typedef union {
_PyInterpreterFrame3_11 v3_11;
_PyInterpreterFrame3_12 v3_12;
} PyInterpreterFrame;

#endif
Loading

0 comments on commit 0036681

Please sign in to comment.