1
1
#include "Python.h"
2
+ #include "ceval.h"
3
+ #if PY_MINOR_VERSION < 11
2
4
#include "code.h"
5
+ #else
6
+ #include "internal/pycore_code.h"
7
+ #include "internal/pycore_frame.h"
8
+ #endif
9
+ #include "frameobject.h"
3
10
#include "object.h"
11
+
4
12
#ifndef _GNU_SOURCE
5
13
#define _GNU_SOURCE
6
14
#endif
7
- #include "frameobject.h"
8
15
#include <dlfcn.h>
9
16
#include <pthread.h>
10
17
#include <stdatomic.h>
16
23
#include <stdbool.h>
17
24
#include <errno.h>
18
25
26
+ #if PY_MINOR_VERSION < 9
27
+ PyFrameObject * PyFrame_GetBack (PyFrameObject * frame ) {
28
+ if (frame -> f_back != NULL ) {
29
+ Py_INCREF (frame -> f_back );
30
+ }
31
+ return frame -> f_back ;
32
+ }
33
+
34
+ PyCodeObject * PyFrame_GetCode (PyFrameObject * frame ) {
35
+ Py_INCREF (frame -> f_code );
36
+ return frame -> f_code ;
37
+ }
38
+ #endif
39
+
19
40
// Macro to create the publicly exposed symbol:
20
41
#ifdef __APPLE__
21
42
#define SYMBOL_PREFIX (func ) reimplemented_##func
33
54
#define likely (x ) __builtin_expect(!!(x), 1)
34
55
#define unlikely (x ) __builtin_expect(!!(x), 0)
35
56
36
- // Underlying APIs we're wrapping:
37
- static void * (* underlying_real_mmap )(void * addr , size_t length , int prot ,
38
- int flags , int fd , off_t offset ) = 0 ;
57
+ // Underlying APIs we're wrapping:
58
+ static void * (* underlying_real_mmap )(void * addr , size_t length , int prot ,
59
+ int flags , int fd , off_t offset ) = 0 ;
39
60
static int (* underlying_real_pthread_create )(pthread_t * thread ,
40
61
const pthread_attr_t * attr ,
41
62
void * (* start_routine )(void * ),
@@ -118,8 +139,22 @@ static inline int should_track_memory() {
118
139
return (likely (initialized ) && atomic_load_explicit (& tracking_allocations , memory_order_acquire ) && !am_i_reentrant ());
119
140
}
120
141
121
- // Current thread's Python state:
122
- static _Thread_local PyFrameObject * current_frame = NULL ;
142
+ // Current thread's Python state; typically only set in C functions where GIL
143
+ // might be released.
144
+ static _Thread_local int current_line_number = -1 ;
145
+
146
+ static inline int get_current_line_number () {
147
+ if (PyGILState_Check ()) {
148
+ PyFrameObject * frame = PyEval_GetFrame ();
149
+ if (frame != NULL ) {
150
+ return PyFrame_GetLineNumber (frame );
151
+ }
152
+ }
153
+ if (current_line_number != -1 ) {
154
+ return current_line_number ;
155
+ }
156
+ return 0 ;
157
+ }
123
158
124
159
// The file and function name responsible for an allocation.
125
160
struct FunctionLocation {
@@ -200,13 +235,16 @@ static void __attribute__((constructor)) constructor() {
200
235
initialized = 1 ;
201
236
}
202
237
203
- static void start_call (uint64_t function_id , uint16_t line_number ) {
238
+ static void start_call (uint64_t function_id , uint16_t line_number , PyFrameObject * current_frame ) {
204
239
if (should_track_memory ()) {
205
240
increment_reentrancy ();
206
241
uint16_t parent_line_number = 0 ;
207
- if (current_frame != NULL && current_frame -> f_back != NULL ) {
208
- PyFrameObject * f = current_frame -> f_back ;
209
- parent_line_number = PyFrame_GetLineNumber (f );
242
+ if (current_frame != NULL ) {
243
+ PyFrameObject * parent = PyFrame_GetBack (current_frame );
244
+ if (parent != NULL ){
245
+ parent_line_number = PyFrame_GetLineNumber (parent );
246
+ Py_DECREF (parent );
247
+ }
210
248
}
211
249
pymemprofile_start_call (parent_line_number , function_id , line_number );
212
250
decrement_reentrancy ();
@@ -226,39 +264,53 @@ __attribute__((visibility("hidden"))) int
226
264
fil_tracer (PyObject * obj , PyFrameObject * frame , int what , PyObject * arg ) {
227
265
switch (what ) {
228
266
case PyTrace_CALL :
229
- // Store the current frame, so malloc() can look up line number:
230
- current_frame = frame ;
231
-
232
267
/*
233
268
We want an efficient identifier for filename+fuction name. So we register
234
269
the function + filename with some Rust code that gives back its ID, and
235
270
then store the ID. Due to bad API design, value 0 indicates "no result",
236
271
so we actually store the result + 1.
237
272
*/
273
+ current_line_number = frame -> f_lineno ;
238
274
uint64_t function_id = 0 ;
239
275
assert (extra_code_index != -1 );
240
- _PyCode_GetExtra (( PyObject * ) frame -> f_code , extra_code_index ,
241
- (void * * )& function_id );
276
+ PyCodeObject * code = PyFrame_GetCode ( frame );
277
+ _PyCode_GetExtra (( PyObject * ) code , extra_code_index , (void * * )& function_id );
242
278
if (function_id == 0 ) {
243
279
Py_ssize_t filename_length , function_length ;
244
- const char * filename = PyUnicode_AsUTF8AndSize (frame -> f_code -> co_filename ,
280
+ const char * filename = PyUnicode_AsUTF8AndSize (code -> co_filename ,
245
281
& filename_length );
246
- const char * function_name = PyUnicode_AsUTF8AndSize (frame -> f_code -> co_name ,
282
+ const char * function_name = PyUnicode_AsUTF8AndSize (code -> co_name ,
247
283
& function_length );
248
284
increment_reentrancy ();
249
285
function_id = pymemprofile_add_function_location (filename , (uint64_t )filename_length , function_name , (uint64_t )function_length );
250
286
decrement_reentrancy ();
251
- _PyCode_SetExtra ((PyObject * )frame -> f_code , extra_code_index ,
287
+ _PyCode_SetExtra ((PyObject * )code , extra_code_index ,
252
288
(void * )function_id + 1 );
289
+ Py_DECREF (code );
253
290
} else {
254
291
function_id -= 1 ;
255
292
}
256
- start_call (function_id , frame -> f_lineno );
293
+ start_call (function_id , current_line_number , frame );
257
294
break ;
258
295
case PyTrace_RETURN :
259
296
finish_call ();
260
- // We're done with this frame, so set the parent frame:
261
- current_frame = frame -> f_back ;
297
+ if (frame != NULL ) {
298
+ PyFrameObject * parent = PyFrame_GetBack (frame );
299
+ if (parent == NULL ) {
300
+ current_line_number = -1 ;
301
+ } else {
302
+ current_line_number = PyFrame_GetLineNumber (parent );
303
+ Py_DECREF (parent );
304
+ }
305
+ }
306
+ break ;
307
+ case PyTrace_C_CALL :
308
+ // C calls might release GIL, in which case they won't change the line
309
+ // number, so record it.
310
+ current_line_number = PyFrame_GetLineNumber (frame );
311
+ break ;
312
+ case PyTrace_C_RETURN :
313
+ current_line_number = -1 ;
262
314
break ;
263
315
default :
264
316
break ;
@@ -315,20 +367,12 @@ fil_dump_peak_to_flamegraph(const char *path) {
315
367
316
368
// *** End APIs called by Python ***
317
369
static void add_allocation (size_t address , size_t size ) {
318
- uint16_t line_number = 0 ;
319
- PyFrameObject * f = current_frame ;
320
- if (f != NULL ) {
321
- line_number = PyFrame_GetLineNumber (f );
322
- }
370
+ uint16_t line_number = get_current_line_number ();
323
371
pymemprofile_add_allocation (address , size , line_number );
324
372
}
325
373
326
374
static void add_anon_mmap (size_t address , size_t size ) {
327
- uint16_t line_number = 0 ;
328
- PyFrameObject * f = current_frame ;
329
- if (f != NULL ) {
330
- line_number = PyFrame_GetLineNumber (f );
331
- }
375
+ uint16_t line_number = get_current_line_number ();
332
376
pymemprofile_add_anon_mmap (address , size , line_number );
333
377
}
334
378
0 commit comments