Skip to content

Commit 5026c7f

Browse files
committed
feat: sub-interpreters support
We add support for sub-interpreters.
1 parent 3da1159 commit 5026c7f

13 files changed

+67
-46
lines changed

ChangeLog

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
2023-xx-xx v3.6.0
22

3+
Added support for sub-interpreters.
4+
35
Dropped support for Python 2, 3.3, 3.4, 3.5, 3.6 and 3.7.
46

57
Bugfix: ensure that threads are resumed by austinp when an error occurs during

scripts/requirements-bm.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
austin-python~=1.4.1
1+
austin-python~=1.6
22
scipy~=1.10.1

scripts/requirements-val.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
austin-python~=1.5
1+
austin-python~=1.6
22
numpy
33
scipy

src/argparse.c

+4-4
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,11 @@ const char SAMPLE_FORMAT_KERNEL[] = ";kernel:%s:0";
5656
const char SAMPLE_FORMAT_WHERE_KERNEL[]= " \033[38;5;159m%s\033[0m 🐧\n";
5757
#endif
5858
#if defined PL_WIN
59-
const char HEAD_FORMAT_DEFAULT[] = "P%I64d;T%I64x";
60-
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";
59+
const char HEAD_FORMAT_DEFAULT[] = "P%I64d;T%I64x:%I64x";
60+
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";
6161
#else
62-
const char HEAD_FORMAT_DEFAULT[] = "P%d;T%ld";
63-
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";
62+
const char HEAD_FORMAT_DEFAULT[] = "P%d;T%ld:%ld";
63+
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";
6464
#endif
6565

6666

src/events.h

+7-7
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,13 @@
7575
} \
7676
}
7777

78-
#define emit_stack(format, pid, tid, ...) \
79-
{ \
80-
if (pargs.binary) { \
81-
mojo_stack(pid, tid); \
82-
} else { \
83-
fprintfp(pargs.output_file, format, pid, tid, __VA_ARGS__); \
84-
} \
78+
#define emit_stack(format, pid, iid, tid, ...) \
79+
{ \
80+
if (pargs.binary) { \
81+
mojo_stack(pid, iid, tid); \
82+
} else { \
83+
fprintfp(pargs.output_file, format, pid, iid, tid, __VA_ARGS__); \
84+
} \
8585
}
8686

8787
#define emit_frame_ref(format, frame) \

src/mojo.h

+5-4
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
#include "cache.h"
3030
#include "platform.h"
3131

32-
#define MOJO_VERSION 2
32+
#define MOJO_VERSION 3
3333

3434
enum {
3535
MOJO_RESERVED,
@@ -119,9 +119,10 @@ static inline void mojo_integer(mojo_int_t integer, int sign) {
119119
mojo_string(label); \
120120
mojo_fstring(__VA_ARGS__);
121121

122-
#define mojo_stack(pid, tid) \
123-
mojo_event(MOJO_STACK); \
124-
mojo_integer(pid, 0); \
122+
#define mojo_stack(pid, iid, tid) \
123+
mojo_event(MOJO_STACK); \
124+
mojo_integer(pid, 0); \
125+
mojo_integer(iid, 0); \
125126
mojo_fstring(FORMAT_TID, tid);
126127

127128
#define mojo_frame(frame) \

src/py_proc.c

+36-25
Original file line numberDiff line numberDiff line change
@@ -1166,6 +1166,7 @@ _py_proc__sample_interpreter(py_proc_t * self, PyInterpreterState * is, ctime_t
11661166
current_thread = _py_proc__get_current_thread_state_raddr(self);
11671167
}
11681168

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

11841185
py_thread__emit_collapsed_stack(
11851186
&py_thread,
1187+
interp_id,
11861188
time_delta,
11871189
mem_delta
11881190
);
@@ -1200,45 +1202,54 @@ _py_proc__sample_interpreter(py_proc_t * self, PyInterpreterState * is, ctime_t
12001202
// ----------------------------------------------------------------------------
12011203
int
12021204
py_proc__sample(py_proc_t * self) {
1203-
ctime_t time_delta = gettime() - self->timestamp; // Time delta since last sample.
1205+
ctime_t time_delta = gettime() - self->timestamp; // Time delta since last sample.
1206+
void * current_interp = self->is_raddr;
12041207

12051208
V_DESC(self->py_v);
12061209

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

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

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

1228-
int result = _py_proc__sample_interpreter(self, &is, time_delta);
1224+
#ifdef NATIVE
1225+
raddr_t raddr = { .pref = self->proc_ref, .addr = tstate_head };
1226+
if (fail(_py_proc__interrupt_threads(self, &raddr))) {
1227+
log_ie("Failed to interrupt threads");
1228+
FAIL;
1229+
}
1230+
time_delta = gettime() - self->timestamp;
1231+
#endif
1232+
1233+
int result = _py_proc__sample_interpreter(self, &is, time_delta);
12291234

1235+
#ifdef NATIVE
1236+
if (fail(_py_proc__resume_threads(self, &raddr))) {
1237+
log_ie("Failed to resume threads");
1238+
FAIL;
1239+
}
1240+
#endif
1241+
1242+
if (fail(result))
1243+
FAIL;
1244+
} while (isvalid(current_interp = V_FIELD(void *, is, py_is, o_next)));
1245+
12301246
#ifdef NATIVE
12311247
self->timestamp = gettime();
1232-
1233-
if (fail(_py_proc__resume_threads(self, &raddr))) {
1234-
log_ie("Failed to resume threads");
1235-
FAIL;
1236-
}
12371248
#else
12381249
self->timestamp += time_delta;
12391250
#endif
12401251

1241-
return result;
1252+
SUCCESS;
12421253
} /* py_proc__sample */
12431254

12441255

src/py_thread.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -893,7 +893,7 @@ py_thread__next(py_thread_t * self) {
893893

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

@@ -924,7 +924,7 @@ py_thread__emit_collapsed_stack(py_thread_t * self, ctime_t time_delta, ssize_t
924924

925925
// Group entries by thread.
926926
emit_stack(
927-
pargs.head_format, self->proc->pid, self->tid,
927+
pargs.head_format, self->proc->pid, interp_id, self->tid,
928928
// These are relevant only in `where` mode
929929
is_idle ? "💤" : "🚀",
930930
self->proc->child ? "🧒" : ""

src/py_thread.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,12 @@ py_thread__next(py_thread_t *);
9292
* Print the frame stack using the collapsed format.
9393
*
9494
* @param py_thread_t self.
95+
* @param int64_t the interpreter ID.
9596
* @param ctime_t the time delta.
9697
* @param ssize_t the memory delta.
9798
*/
9899
void
99-
py_thread__emit_collapsed_stack(py_thread_t *, ctime_t, ssize_t);
100+
py_thread__emit_collapsed_stack(py_thread_t *, int64_t, ctime_t, ssize_t);
100101

101102

102103
/**

src/python/interp.h

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ struct _ts; /* Forward */
4141
typedef struct _is2 {
4242
struct _is2 *next;
4343
struct _ts *tstate_head;
44+
int64_t id;
4445
void* gc; /* Dummy */
4546
} PyInterpreterState2;
4647

src/version.h

+4
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ typedef struct {
163163

164164
offset_t o_next;
165165
offset_t o_tstate_head;
166+
offset_t o_id;
166167
offset_t o_gc;
167168
offset_t o_gil_state;
168169
} py_is_v;
@@ -303,20 +304,23 @@ typedef struct {
303304
sizeof(s), \
304305
offsetof(s, next), \
305306
offsetof(s, tstate_head), \
307+
offsetof(s, id), \
306308
offsetof(s, gc), \
307309
}
308310

309311
#define PY_IS_311(s) { \
310312
sizeof(s), \
311313
offsetof(s, next), \
312314
offsetof(s, threads.head), \
315+
offsetof(s, id), \
313316
offsetof(s, gc), \
314317
}
315318

316319
#define PY_IS_312(s) { \
317320
sizeof(s), \
318321
offsetof(s, next), \
319322
offsetof(s, threads.head), \
323+
offsetof(s, id), \
320324
offsetof(s, gc), \
321325
offsetof(s, ceval.gil), \
322326
}

test/functional/test_fork.py

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def test_fork_wall_time(austin, py, heap, mojo):
5353
assert len(processes(result.stdout)) == 1, compress(result.stdout)
5454
ts = threads(result.stdout)
5555
assert len(ts) == 2, compress(result.stdout)
56+
assert all(len(t[1].split(":")) == 2 for t in ts), "threads have interpreter ID"
5657

5758
assert has_pattern(result.stdout, "target34.py:keep_cpu_busy:3"), compress(
5859
result.stdout

test/requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
austin-python~=1.5
1+
austin-python~=1.6
22
flaky
33
pytest
44
pytest-xdist

0 commit comments

Comments
 (0)