Skip to content

Commit

Permalink
Merge pull request #198 from P403n1x87/feat/sub-interpreters-support
Browse files Browse the repository at this point in the history
feat: sub-interpreters support
  • Loading branch information
P403n1x87 authored Sep 11, 2023
2 parents 068dd8b + c43198d commit 6455c21
Show file tree
Hide file tree
Showing 14 changed files with 79 additions and 48 deletions.
2 changes: 2 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
2023-xx-xx v3.6.0

Added support for sub-interpreters.

Dropped support for Python 2, 3.3, 3.4, 3.5, 3.6 and 3.7.

Bugfix: ensure that threads are resumed by austinp when an error occurs during
Expand Down
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -367,11 +367,12 @@ for some further processing.
By default, each line has the following structure:

~~~
P<pid>;T<tid>[;[frame]]* [metric]*
P<pid>;T<iid>:<tid>[;[frame]]* [metric]*
~~~

where the structure of `[frame]` and the number and type of metrics on each line
depend on the mode.
depend on the mode. The `<pid>`, `<iid>` and `<tid>` component represent the
process ID, the sub-interpreter ID, and the thread ID respectively.


## Environment variables
Expand Down Expand Up @@ -449,6 +450,15 @@ Austin can be told to profile multi-process applications with the `-C` or
process.


## Sub-interpreters

Austin has support for Python applications that make use of sub-interpreters.
This means that Austin will sample all the sub-interpreters that are running
within each process making up the Python application.

*Since Austin 3.6.0*.


## Garbage Collector Sampling

Austin can sample the Python garbage collector state for applications running
Expand Down
2 changes: 1 addition & 1 deletion scripts/requirements-bm.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
austin-python~=1.4.1
austin-python~=1.6
scipy~=1.10.1
2 changes: 1 addition & 1 deletion scripts/requirements-val.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
austin-python~=1.5
austin-python~=1.6
numpy
scipy
8 changes: 4 additions & 4 deletions src/argparse.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ const char SAMPLE_FORMAT_KERNEL[] = ";kernel:%s:0";
const char SAMPLE_FORMAT_WHERE_KERNEL[]= " \033[38;5;159m%s\033[0m 🐧\n";
#endif
#if defined PL_WIN
const char HEAD_FORMAT_DEFAULT[] = "P%I64d;T%I64x";
const char HEAD_FORMAT_WHERE[] = "\n\n%3$s%4$s Process \033[35;1m%1$I64d\033[0m 🧵 Thread \033[34;1m%2$I64d\033[0m\n\n";
const char HEAD_FORMAT_DEFAULT[] = "P%I64d;T%I64x:%I64x";
const char HEAD_FORMAT_WHERE[] = "\n\n%4$s%5$s Process \033[35;1m%1$I64d\033[0m 🧵 Thread \033[34;1m%2$I64d:%3$I64d\033[0m\n\n";
#else
const char HEAD_FORMAT_DEFAULT[] = "P%d;T%ld";
const char HEAD_FORMAT_WHERE[] = "\n\n%3$s%4$s Process \033[35;1m%1$d\033[0m 🧵 Thread \033[34;1m%2$ld\033[0m\n\n";
const char HEAD_FORMAT_DEFAULT[] = "P%d;T%ld:%ld";
const char HEAD_FORMAT_WHERE[] = "\n\n%4$s%5$s Process \033[35;1m%1$d\033[0m 🧵 Thread \033[34;1m%2$ld:%3$ld\033[0m\n\n";
#endif


Expand Down
14 changes: 7 additions & 7 deletions src/events.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,13 @@
} \
}

#define emit_stack(format, pid, tid, ...) \
{ \
if (pargs.binary) { \
mojo_stack(pid, tid); \
} else { \
fprintfp(pargs.output_file, format, pid, tid, __VA_ARGS__); \
} \
#define emit_stack(format, pid, iid, tid, ...) \
{ \
if (pargs.binary) { \
mojo_stack(pid, iid, tid); \
} else { \
fprintfp(pargs.output_file, format, pid, iid, tid, __VA_ARGS__); \
} \
}

#define emit_frame_ref(format, frame) \
Expand Down
9 changes: 5 additions & 4 deletions src/mojo.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
#include "cache.h"
#include "platform.h"

#define MOJO_VERSION 2
#define MOJO_VERSION 3

enum {
MOJO_RESERVED,
Expand Down Expand Up @@ -119,9 +119,10 @@ static inline void mojo_integer(mojo_int_t integer, int sign) {
mojo_string(label); \
mojo_fstring(__VA_ARGS__);

#define mojo_stack(pid, tid) \
mojo_event(MOJO_STACK); \
mojo_integer(pid, 0); \
#define mojo_stack(pid, iid, tid) \
mojo_event(MOJO_STACK); \
mojo_integer(pid, 0); \
mojo_integer(iid, 0); \
mojo_fstring(FORMAT_TID, tid);

#define mojo_frame(frame) \
Expand Down
61 changes: 36 additions & 25 deletions src/py_proc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1166,6 +1166,7 @@ _py_proc__sample_interpreter(py_proc_t * self, PyInterpreterState * is, ctime_t
current_thread = _py_proc__get_current_thread_state_raddr(self);
}

int64_t interp_id = V_FIELD_PTR(int64_t, is, py_is, o_id);
do {
if (pargs.memory) {
mem_delta = 0;
Expand All @@ -1183,6 +1184,7 @@ _py_proc__sample_interpreter(py_proc_t * self, PyInterpreterState * is, ctime_t

py_thread__emit_collapsed_stack(
&py_thread,
interp_id,
time_delta,
mem_delta
);
Expand All @@ -1200,45 +1202,54 @@ _py_proc__sample_interpreter(py_proc_t * self, PyInterpreterState * is, ctime_t
// ----------------------------------------------------------------------------
int
py_proc__sample(py_proc_t * self) {
ctime_t time_delta = gettime() - self->timestamp; // Time delta since last sample.
ctime_t time_delta = gettime() - self->timestamp; // Time delta since last sample.
void * current_interp = self->is_raddr;

V_DESC(self->py_v);

PyInterpreterState is;
if (fail(py_proc__get_type(self, self->is_raddr, is))) {
log_ie("Failed to get interpreter state while sampling");
FAIL;
}

void * tstate_head = V_FIELD(void *, is, py_is, o_tstate_head);
if (!isvalid(tstate_head))
// Maybe the interpreter state is in an invalid state. We'll try again
// unless there is a fatal error.
SUCCESS;
do {
if (fail(py_proc__get_type(self, current_interp, is))) {
log_ie("Failed to get interpreter state while sampling");
FAIL;
}

#ifdef NATIVE
raddr_t raddr = { .pref = self->proc_ref, .addr = tstate_head };
if (fail(_py_proc__interrupt_threads(self, &raddr))) {
log_ie("Failed to interrupt threads");
FAIL;
}
time_delta = gettime() - self->timestamp;
#endif
void * tstate_head = V_FIELD(void *, is, py_is, o_tstate_head);
if (!isvalid(tstate_head))
// Maybe the interpreter state is in an invalid state. We'll try again
// unless there is a fatal error.
SUCCESS;

int result = _py_proc__sample_interpreter(self, &is, time_delta);
#ifdef NATIVE
raddr_t raddr = { .pref = self->proc_ref, .addr = tstate_head };
if (fail(_py_proc__interrupt_threads(self, &raddr))) {
log_ie("Failed to interrupt threads");
FAIL;
}
time_delta = gettime() - self->timestamp;
#endif

int result = _py_proc__sample_interpreter(self, &is, time_delta);

#ifdef NATIVE
if (fail(_py_proc__resume_threads(self, &raddr))) {
log_ie("Failed to resume threads");
FAIL;
}
#endif

if (fail(result))
FAIL;
} while (isvalid(current_interp = V_FIELD(void *, is, py_is, o_next)));

#ifdef NATIVE
self->timestamp = gettime();

if (fail(_py_proc__resume_threads(self, &raddr))) {
log_ie("Failed to resume threads");
FAIL;
}
#else
self->timestamp += time_delta;
#endif

return result;
SUCCESS;
} /* py_proc__sample */


Expand Down
4 changes: 2 additions & 2 deletions src/py_thread.c
Original file line number Diff line number Diff line change
Expand Up @@ -893,7 +893,7 @@ py_thread__next(py_thread_t * self) {

// ----------------------------------------------------------------------------
void
py_thread__emit_collapsed_stack(py_thread_t * self, ctime_t time_delta, ssize_t mem_delta) {
py_thread__emit_collapsed_stack(py_thread_t * self, int64_t interp_id, ctime_t time_delta, ssize_t mem_delta) {
if (!pargs.full && pargs.memory && mem_delta == 0)
return;

Expand Down Expand Up @@ -924,7 +924,7 @@ py_thread__emit_collapsed_stack(py_thread_t * self, ctime_t time_delta, ssize_t

// Group entries by thread.
emit_stack(
pargs.head_format, self->proc->pid, self->tid,
pargs.head_format, self->proc->pid, interp_id, self->tid,
// These are relevant only in `where` mode
is_idle ? "💤" : "🚀",
self->proc->child ? "🧒" : ""
Expand Down
3 changes: 2 additions & 1 deletion src/py_thread.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,12 @@ py_thread__next(py_thread_t *);
* Print the frame stack using the collapsed format.
*
* @param py_thread_t self.
* @param int64_t the interpreter ID.
* @param ctime_t the time delta.
* @param ssize_t the memory delta.
*/
void
py_thread__emit_collapsed_stack(py_thread_t *, ctime_t, ssize_t);
py_thread__emit_collapsed_stack(py_thread_t *, int64_t, ctime_t, ssize_t);


/**
Expand Down
1 change: 1 addition & 0 deletions src/python/interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ struct _ts; /* Forward */
typedef struct _is2 {
struct _is2 *next;
struct _ts *tstate_head;
int64_t id;
void* gc; /* Dummy */
} PyInterpreterState2;

Expand Down
4 changes: 4 additions & 0 deletions src/version.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ typedef struct {

offset_t o_next;
offset_t o_tstate_head;
offset_t o_id;
offset_t o_gc;
offset_t o_gil_state;
} py_is_v;
Expand Down Expand Up @@ -303,20 +304,23 @@ typedef struct {
sizeof(s), \
offsetof(s, next), \
offsetof(s, tstate_head), \
offsetof(s, id), \
offsetof(s, gc), \
}

#define PY_IS_311(s) { \
sizeof(s), \
offsetof(s, next), \
offsetof(s, threads.head), \
offsetof(s, id), \
offsetof(s, gc), \
}

#define PY_IS_312(s) { \
sizeof(s), \
offsetof(s, next), \
offsetof(s, threads.head), \
offsetof(s, id), \
offsetof(s, gc), \
offsetof(s, ceval.gil), \
}
Expand Down
1 change: 1 addition & 0 deletions test/functional/test_fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def test_fork_wall_time(austin, py, heap, mojo):
assert len(processes(result.stdout)) == 1, compress(result.stdout)
ts = threads(result.stdout)
assert len(ts) == 2, compress(result.stdout)
assert all(len(t[1].split(":")) == 2 for t in ts), "threads have interpreter ID"

assert has_pattern(result.stdout, "target34.py:keep_cpu_busy:3"), compress(
result.stdout
Expand Down
2 changes: 1 addition & 1 deletion test/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
austin-python~=1.5
austin-python~=1.6
flaky
pytest
pytest-xdist
Expand Down

0 comments on commit 6455c21

Please sign in to comment.