diff options
Diffstat (limited to 'Modules/_tracemalloc.c')
-rw-r--r-- | Modules/_tracemalloc.c | 584 |
1 files changed, 482 insertions, 102 deletions
diff --git a/Modules/_tracemalloc.c b/Modules/_tracemalloc.c index 796ac0f..beda2b6 100644 --- a/Modules/_tracemalloc.c +++ b/Modules/_tracemalloc.c @@ -39,7 +39,11 @@ static struct { /* limit of the number of frames in a traceback, 1 by default. Variable protected by the GIL. */ int max_nframe; -} tracemalloc_config = {TRACEMALLOC_NOT_INITIALIZED, 0, 1}; + + /* use domain in trace key? + Variable protected by the GIL. */ + int use_domain; +} tracemalloc_config = {TRACEMALLOC_NOT_INITIALIZED, 0, 1, 0}; #if defined(TRACE_RAW_MALLOC) && defined(WITH_THREAD) /* This lock is needed because tracemalloc_free() is called without @@ -54,10 +58,21 @@ static PyThread_type_lock tables_lock; # define TABLES_UNLOCK() #endif + +#define DEFAULT_DOMAIN 0 + +/* Pack the frame_t structure to reduce the memory footprint. */ +typedef struct +#ifdef __GNUC__ +__attribute__((packed)) +#endif +{ + Py_uintptr_t ptr; + _PyTraceMalloc_domain_t domain; +} pointer_t; + /* Pack the frame_t structure to reduce the memory footprint on 64-bit - architectures: 12 bytes instead of 16. This optimization might produce - SIGBUS on architectures not supporting unaligned memory accesses (64-bit - MIPS CPU?): on such architecture, the structure must not be packed. */ + architectures: 12 bytes instead of 16. */ typedef struct #ifdef __GNUC__ __attribute__((packed)) @@ -65,10 +80,13 @@ __attribute__((packed)) _declspec(align(4)) #endif { + /* filename cannot be NULL: "<unknown>" is used if the Python frame + filename is NULL */ PyObject *filename; unsigned int lineno; } frame_t; + typedef struct { Py_uhash_t hash; int nframe; @@ -81,6 +99,7 @@ typedef struct { #define MAX_NFRAME \ ((INT_MAX - (int)sizeof(traceback_t)) / (int)sizeof(frame_t) + 1) + static PyObject *unknown_filename = NULL; static traceback_t tracemalloc_empty_traceback; @@ -93,6 +112,7 @@ typedef struct { traceback_t *traceback; } trace_t; + /* Size in bytes of currently traced memory. Protected by TABLES_LOCK(). */ static size_t tracemalloc_traced_memory = 0; @@ -119,6 +139,7 @@ static _Py_hashtable_t *tracemalloc_tracebacks = NULL; Protected by TABLES_LOCK(). */ static _Py_hashtable_t *tracemalloc_traces = NULL; + #ifdef TRACE_DEBUG static void tracemalloc_error(const char *format, ...) @@ -133,6 +154,7 @@ tracemalloc_error(const char *format, ...) } #endif + #if defined(WITH_THREAD) && defined(TRACE_RAW_MALLOC) #define REENTRANT_THREADLOCAL @@ -143,7 +165,7 @@ tracemalloc_error(const char *format, ...) # error "need native thread local storage (TLS)" #endif -static int tracemalloc_reentrant_key; +static int tracemalloc_reentrant_key = -1; /* Any non-NULL pointer can be used */ #define REENTRANT Py_True @@ -151,7 +173,10 @@ static int tracemalloc_reentrant_key; static int get_reentrant(void) { - void *ptr = PyThread_get_key_value(tracemalloc_reentrant_key); + void *ptr; + + assert(tracemalloc_reentrant_key != -1); + ptr = PyThread_get_key_value(tracemalloc_reentrant_key); if (ptr != NULL) { assert(ptr == REENTRANT); return 1; @@ -164,12 +189,14 @@ static void set_reentrant(int reentrant) { assert(reentrant == 0 || reentrant == 1); + assert(tracemalloc_reentrant_key != -1); + if (reentrant) { - assert(PyThread_get_key_value(tracemalloc_reentrant_key) == NULL); + assert(!get_reentrant()); PyThread_set_key_value(tracemalloc_reentrant_key, REENTRANT); } else { - assert(PyThread_get_key_value(tracemalloc_reentrant_key) == REENTRANT); + assert(get_reentrant()); PyThread_set_key_value(tracemalloc_reentrant_key, NULL); } } @@ -194,27 +221,75 @@ set_reentrant(int reentrant) } #endif + +static Py_uhash_t +hashtable_hash_pyobject(_Py_hashtable_t *ht, const void *pkey) +{ + PyObject *obj; + + _Py_HASHTABLE_READ_KEY(ht, pkey, obj); + return PyObject_Hash(obj); +} + + static int -hashtable_compare_unicode(const void *key, const _Py_hashtable_entry_t *entry) +hashtable_compare_unicode(_Py_hashtable_t *ht, const void *pkey, + const _Py_hashtable_entry_t *entry) { - if (key != NULL && entry->key != NULL) - return (PyUnicode_Compare((PyObject *)key, (PyObject *)entry->key) == 0); + PyObject *key1, *key2; + + _Py_HASHTABLE_READ_KEY(ht, pkey, key1); + _Py_HASHTABLE_ENTRY_READ_KEY(ht, entry, key2); + + if (key1 != NULL && key2 != NULL) + return (PyUnicode_Compare(key1, key2) == 0); else - return key == entry->key; + return key1 == key2; +} + + +static Py_uhash_t +hashtable_hash_pointer_t(_Py_hashtable_t *ht, const void *pkey) +{ + pointer_t ptr; + Py_uhash_t hash; + + _Py_HASHTABLE_READ_KEY(ht, pkey, ptr); + + hash = (Py_uhash_t)_Py_HashPointer((void*)ptr.ptr); + hash ^= ptr.domain; + return hash; +} + + +int +hashtable_compare_pointer_t(_Py_hashtable_t *ht, const void *pkey, + const _Py_hashtable_entry_t *entry) +{ + pointer_t ptr1, ptr2; + + _Py_HASHTABLE_READ_KEY(ht, pkey, ptr1); + _Py_HASHTABLE_ENTRY_READ_KEY(ht, entry, ptr2); + + /* compare pointer before domain, because pointer is more likely to be + different */ + return (ptr1.ptr == ptr2.ptr && ptr1.domain == ptr2.domain); + } -static _Py_hashtable_allocator_t hashtable_alloc = {malloc, free}; static _Py_hashtable_t * -hashtable_new(size_t data_size, +hashtable_new(size_t key_size, size_t data_size, _Py_hashtable_hash_func hash_func, _Py_hashtable_compare_func compare_func) { - return _Py_hashtable_new_full(data_size, 0, + _Py_hashtable_allocator_t hashtable_alloc = {malloc, free}; + return _Py_hashtable_new_full(key_size, data_size, 0, hash_func, compare_func, - NULL, NULL, NULL, &hashtable_alloc); + &hashtable_alloc); } + static void* raw_malloc(size_t size) { @@ -227,21 +302,28 @@ raw_free(void *ptr) allocators.raw.free(allocators.raw.ctx, ptr); } + static Py_uhash_t -hashtable_hash_traceback(const void *key) +hashtable_hash_traceback(_Py_hashtable_t *ht, const void *pkey) { - const traceback_t *traceback = key; + traceback_t *traceback; + + _Py_HASHTABLE_READ_KEY(ht, pkey, traceback); return traceback->hash; } + static int -hashtable_compare_traceback(const traceback_t *traceback1, - const _Py_hashtable_entry_t *he) +hashtable_compare_traceback(_Py_hashtable_t *ht, const void *pkey, + const _Py_hashtable_entry_t *entry) { - const traceback_t *traceback2 = he->key; + traceback_t *traceback1, *traceback2; const frame_t *frame1, *frame2; int i; + _Py_HASHTABLE_READ_KEY(ht, pkey, traceback1); + _Py_HASHTABLE_ENTRY_READ_KEY(ht, entry, traceback2); + if (traceback1->nframe != traceback2->nframe) return 0; @@ -260,6 +342,7 @@ hashtable_compare_traceback(const traceback_t *traceback1, return 1; } + static void tracemalloc_get_frame(PyFrameObject *pyframe, frame_t *frame) { @@ -310,15 +393,15 @@ tracemalloc_get_frame(PyFrameObject *pyframe, frame_t *frame) } /* intern the filename */ - entry = _Py_hashtable_get_entry(tracemalloc_filenames, filename); + entry = _Py_HASHTABLE_GET_ENTRY(tracemalloc_filenames, filename); if (entry != NULL) { - filename = (PyObject *)entry->key; + _Py_HASHTABLE_ENTRY_READ_KEY(tracemalloc_filenames, entry, filename); } else { /* tracemalloc_filenames is responsible to keep a reference to the filename */ Py_INCREF(filename); - if (_Py_hashtable_set(tracemalloc_filenames, filename, NULL, 0) < 0) { + if (_Py_HASHTABLE_SET_NODATA(tracemalloc_filenames, filename) < 0) { Py_DECREF(filename); #ifdef TRACE_DEBUG tracemalloc_error("failed to intern the filename"); @@ -331,6 +414,7 @@ tracemalloc_get_frame(PyFrameObject *pyframe, frame_t *frame) frame->filename = filename; } + static Py_uhash_t traceback_hash(traceback_t *traceback) { @@ -355,6 +439,7 @@ traceback_hash(traceback_t *traceback) return x; } + static void traceback_get_frames(traceback_t *traceback) { @@ -382,6 +467,7 @@ traceback_get_frames(traceback_t *traceback) } } + static traceback_t * traceback_new(void) { @@ -401,9 +487,9 @@ traceback_new(void) traceback->hash = traceback_hash(traceback); /* intern the traceback */ - entry = _Py_hashtable_get_entry(tracemalloc_tracebacks, traceback); + entry = _Py_HASHTABLE_GET_ENTRY(tracemalloc_tracebacks, traceback); if (entry != NULL) { - traceback = (traceback_t *)entry->key; + _Py_HASHTABLE_ENTRY_READ_KEY(tracemalloc_tracebacks, entry, traceback); } else { traceback_t *copy; @@ -420,7 +506,7 @@ traceback_new(void) } memcpy(copy, traceback, traceback_size); - if (_Py_hashtable_set(tracemalloc_tracebacks, copy, NULL, 0) < 0) { + if (_Py_HASHTABLE_SET_NODATA(tracemalloc_tracebacks, copy) < 0) { raw_free(copy); #ifdef TRACE_DEBUG tracemalloc_error("failed to intern the traceback: putdata failed"); @@ -432,46 +518,154 @@ traceback_new(void) return traceback; } + static int -tracemalloc_add_trace(void *ptr, size_t size) +tracemalloc_use_domain_cb(_Py_hashtable_t *old_traces, + _Py_hashtable_entry_t *entry, void *user_data) { - traceback_t *traceback; - trace_t trace; - int res; + Py_uintptr_t ptr; + pointer_t key; + _Py_hashtable_t *new_traces = (_Py_hashtable_t *)user_data; + const void *pdata = _Py_HASHTABLE_ENTRY_PDATA(old_traces, entry); + + _Py_HASHTABLE_ENTRY_READ_KEY(old_traces, entry, ptr); + key.ptr = ptr; + key.domain = DEFAULT_DOMAIN; + + return _Py_hashtable_set(new_traces, + sizeof(key), &key, + old_traces->data_size, pdata); +} -#ifdef WITH_THREAD - assert(PyGILState_Check()); -#endif - traceback = traceback_new(); - if (traceback == NULL) - return -1; +/* Convert tracemalloc_traces from compact key (Py_uintptr_t) to pointer_t key. + * Return 0 on success, -1 on error. */ +static int +tracemalloc_use_domain(void) +{ + _Py_hashtable_t *new_traces = NULL; - trace.size = size; - trace.traceback = traceback; + assert(!tracemalloc_config.use_domain); - res = _Py_HASHTABLE_SET(tracemalloc_traces, ptr, trace); - if (res == 0) { - assert(tracemalloc_traced_memory <= PY_SIZE_MAX - size); - tracemalloc_traced_memory += size; - if (tracemalloc_traced_memory > tracemalloc_peak_traced_memory) - tracemalloc_peak_traced_memory = tracemalloc_traced_memory; + new_traces = hashtable_new(sizeof(pointer_t), + sizeof(trace_t), + hashtable_hash_pointer_t, + hashtable_compare_pointer_t); + if (new_traces == NULL) { + return -1; } - return res; + if (_Py_hashtable_foreach(tracemalloc_traces, tracemalloc_use_domain_cb, + new_traces) < 0) + { + _Py_hashtable_destroy(new_traces); + return -1; + } + + _Py_hashtable_destroy(tracemalloc_traces); + tracemalloc_traces = new_traces; + + tracemalloc_config.use_domain = 1; + + return 0; } + static void -tracemalloc_remove_trace(void *ptr) +tracemalloc_remove_trace(_PyTraceMalloc_domain_t domain, Py_uintptr_t ptr) +{ + trace_t trace; + int removed; + + assert(tracemalloc_config.tracing); + + if (tracemalloc_config.use_domain) { + pointer_t key = {ptr, domain}; + removed = _Py_HASHTABLE_POP(tracemalloc_traces, key, trace); + } + else { + removed = _Py_HASHTABLE_POP(tracemalloc_traces, ptr, trace); + } + if (!removed) { + return; + } + + assert(tracemalloc_traced_memory >= trace.size); + tracemalloc_traced_memory -= trace.size; +} + +#define REMOVE_TRACE(ptr) \ + tracemalloc_remove_trace(DEFAULT_DOMAIN, (Py_uintptr_t)(ptr)) + + +static int +tracemalloc_add_trace(_PyTraceMalloc_domain_t domain, Py_uintptr_t ptr, + size_t size) { + pointer_t key = {ptr, domain}; + traceback_t *traceback; trace_t trace; + _Py_hashtable_entry_t* entry; + int res; - if (_Py_hashtable_pop(tracemalloc_traces, ptr, &trace, sizeof(trace))) { + assert(tracemalloc_config.tracing); + + traceback = traceback_new(); + if (traceback == NULL) { + return -1; + } + + if (!tracemalloc_config.use_domain && domain != DEFAULT_DOMAIN) { + /* first trace using a non-zero domain whereas traces use compact + (Py_uintptr_t) keys: switch to pointer_t keys. */ + if (tracemalloc_use_domain() < 0) { + return -1; + } + } + + if (tracemalloc_config.use_domain) { + entry = _Py_HASHTABLE_GET_ENTRY(tracemalloc_traces, key); + } + else { + entry = _Py_HASHTABLE_GET_ENTRY(tracemalloc_traces, ptr); + } + + if (entry != NULL) { + /* the memory block is already tracked */ + _Py_HASHTABLE_ENTRY_READ_DATA(tracemalloc_traces, entry, trace); assert(tracemalloc_traced_memory >= trace.size); tracemalloc_traced_memory -= trace.size; + + trace.size = size; + trace.traceback = traceback; + _Py_HASHTABLE_ENTRY_WRITE_DATA(tracemalloc_traces, entry, trace); } + else { + trace.size = size; + trace.traceback = traceback; + + if (tracemalloc_config.use_domain) { + res = _Py_HASHTABLE_SET(tracemalloc_traces, key, trace); + } + else { + res = _Py_HASHTABLE_SET(tracemalloc_traces, ptr, trace); + } + if (res != 0) { + return res; + } + } + + assert(tracemalloc_traced_memory <= PY_SIZE_MAX - size); + tracemalloc_traced_memory += size; + if (tracemalloc_traced_memory > tracemalloc_peak_traced_memory) + tracemalloc_peak_traced_memory = tracemalloc_traced_memory; + return 0; } +#define ADD_TRACE(ptr, size) \ + tracemalloc_add_trace(DEFAULT_DOMAIN, (Py_uintptr_t)(ptr), size) + + static void* tracemalloc_alloc(int use_calloc, void *ctx, size_t nelem, size_t elsize) { @@ -488,7 +682,7 @@ tracemalloc_alloc(int use_calloc, void *ctx, size_t nelem, size_t elsize) return NULL; TABLES_LOCK(); - if (tracemalloc_add_trace(ptr, nelem * elsize) < 0) { + if (ADD_TRACE(ptr, nelem * elsize) < 0) { /* Failed to allocate a trace for the new memory block */ TABLES_UNLOCK(); alloc->free(alloc->ctx, ptr); @@ -498,6 +692,7 @@ tracemalloc_alloc(int use_calloc, void *ctx, size_t nelem, size_t elsize) return ptr; } + static void* tracemalloc_realloc(void *ctx, void *ptr, size_t new_size) { @@ -512,9 +707,14 @@ tracemalloc_realloc(void *ctx, void *ptr, size_t new_size) /* an existing memory block has been resized */ TABLES_LOCK(); - tracemalloc_remove_trace(ptr); - if (tracemalloc_add_trace(ptr2, new_size) < 0) { + /* tracemalloc_add_trace() updates the trace if there is already + a trace at address (domain, ptr2) */ + if (ptr2 != ptr) { + REMOVE_TRACE(ptr); + } + + if (ADD_TRACE(ptr2, new_size) < 0) { /* Memory allocation failed. The error cannot be reported to the caller, because realloc() may already have shrinked the memory block and so removed bytes. @@ -532,7 +732,7 @@ tracemalloc_realloc(void *ctx, void *ptr, size_t new_size) /* new allocation */ TABLES_LOCK(); - if (tracemalloc_add_trace(ptr2, new_size) < 0) { + if (ADD_TRACE(ptr2, new_size) < 0) { /* Failed to allocate a trace for the new memory block */ TABLES_UNLOCK(); alloc->free(alloc->ctx, ptr2); @@ -543,6 +743,7 @@ tracemalloc_realloc(void *ctx, void *ptr, size_t new_size) return ptr2; } + static void tracemalloc_free(void *ctx, void *ptr) { @@ -557,10 +758,11 @@ tracemalloc_free(void *ctx, void *ptr) alloc->free(alloc->ctx, ptr); TABLES_LOCK(); - tracemalloc_remove_trace(ptr); + REMOVE_TRACE(ptr); TABLES_UNLOCK(); } + static void* tracemalloc_alloc_gil(int use_calloc, void *ctx, size_t nelem, size_t elsize) { @@ -585,18 +787,21 @@ tracemalloc_alloc_gil(int use_calloc, void *ctx, size_t nelem, size_t elsize) return ptr; } + static void* tracemalloc_malloc_gil(void *ctx, size_t size) { return tracemalloc_alloc_gil(0, ctx, 1, size); } + static void* tracemalloc_calloc_gil(void *ctx, size_t nelem, size_t elsize) { return tracemalloc_alloc_gil(1, ctx, nelem, elsize); } + static void* tracemalloc_realloc_gil(void *ctx, void *ptr, size_t new_size) { @@ -612,7 +817,7 @@ tracemalloc_realloc_gil(void *ctx, void *ptr, size_t new_size) ptr2 = alloc->realloc(alloc->ctx, ptr, new_size); if (ptr2 != NULL && ptr != NULL) { TABLES_LOCK(); - tracemalloc_remove_trace(ptr); + REMOVE_TRACE(ptr); TABLES_UNLOCK(); } return ptr2; @@ -629,6 +834,7 @@ tracemalloc_realloc_gil(void *ctx, void *ptr, size_t new_size) return ptr2; } + #ifdef TRACE_RAW_MALLOC static void* tracemalloc_raw_alloc(int use_calloc, void *ctx, size_t nelem, size_t elsize) @@ -663,18 +869,21 @@ tracemalloc_raw_alloc(int use_calloc, void *ctx, size_t nelem, size_t elsize) return ptr; } + static void* tracemalloc_raw_malloc(void *ctx, size_t size) { return tracemalloc_raw_alloc(0, ctx, 1, size); } + static void* tracemalloc_raw_calloc(void *ctx, size_t nelem, size_t elsize) { return tracemalloc_raw_alloc(1, ctx, nelem, elsize); } + static void* tracemalloc_raw_realloc(void *ctx, void *ptr, size_t new_size) { @@ -691,7 +900,7 @@ tracemalloc_raw_realloc(void *ctx, void *ptr, size_t new_size) if (ptr2 != NULL && ptr != NULL) { TABLES_LOCK(); - tracemalloc_remove_trace(ptr); + REMOVE_TRACE(ptr); TABLES_UNLOCK(); } return ptr2; @@ -715,22 +924,31 @@ tracemalloc_raw_realloc(void *ctx, void *ptr, size_t new_size) } #endif /* TRACE_RAW_MALLOC */ + static int -tracemalloc_clear_filename(_Py_hashtable_entry_t *entry, void *user_data) +tracemalloc_clear_filename(_Py_hashtable_t *ht, _Py_hashtable_entry_t *entry, + void *user_data) { - PyObject *filename = (PyObject *)entry->key; + PyObject *filename; + + _Py_HASHTABLE_ENTRY_READ_KEY(ht, entry, filename); Py_DECREF(filename); return 0; } + static int -traceback_free_traceback(_Py_hashtable_entry_t *entry, void *user_data) +traceback_free_traceback(_Py_hashtable_t *ht, _Py_hashtable_entry_t *entry, + void *user_data) { - traceback_t *traceback = (traceback_t *)entry->key; + traceback_t *traceback; + + _Py_HASHTABLE_ENTRY_READ_KEY(ht, entry, traceback); raw_free(traceback); return 0; } + /* reentrant flag must be set to call this function and GIL must be held */ static void tracemalloc_clear_traces(void) @@ -753,6 +971,7 @@ tracemalloc_clear_traces(void) _Py_hashtable_clear(tracemalloc_filenames); } + static int tracemalloc_init(void) { @@ -789,21 +1008,29 @@ tracemalloc_init(void) } #endif - tracemalloc_filenames = hashtable_new(0, - (_Py_hashtable_hash_func)PyObject_Hash, + tracemalloc_filenames = hashtable_new(sizeof(PyObject *), 0, + hashtable_hash_pyobject, hashtable_compare_unicode); - tracemalloc_tracebacks = hashtable_new(0, - (_Py_hashtable_hash_func)hashtable_hash_traceback, - (_Py_hashtable_compare_func)hashtable_compare_traceback); + tracemalloc_tracebacks = hashtable_new(sizeof(traceback_t *), 0, + hashtable_hash_traceback, + hashtable_compare_traceback); - tracemalloc_traces = hashtable_new(sizeof(trace_t), - _Py_hashtable_hash_ptr, - _Py_hashtable_compare_direct); + if (tracemalloc_config.use_domain) { + tracemalloc_traces = hashtable_new(sizeof(pointer_t), + sizeof(trace_t), + hashtable_hash_pointer_t, + hashtable_compare_pointer_t); + } + else { + tracemalloc_traces = hashtable_new(sizeof(Py_uintptr_t), + sizeof(trace_t), + _Py_hashtable_hash_ptr, + _Py_hashtable_compare_direct); + } if (tracemalloc_filenames == NULL || tracemalloc_tracebacks == NULL - || tracemalloc_traces == NULL) - { + || tracemalloc_traces == NULL) { PyErr_NoMemory(); return -1; } @@ -823,6 +1050,7 @@ tracemalloc_init(void) return 0; } + static void tracemalloc_deinit(void) { @@ -833,9 +1061,9 @@ tracemalloc_deinit(void) tracemalloc_stop(); /* destroy hash tables */ - _Py_hashtable_destroy(tracemalloc_traces); _Py_hashtable_destroy(tracemalloc_tracebacks); _Py_hashtable_destroy(tracemalloc_filenames); + _Py_hashtable_destroy(tracemalloc_traces); #if defined(WITH_THREAD) && defined(TRACE_RAW_MALLOC) if (tables_lock != NULL) { @@ -846,11 +1074,13 @@ tracemalloc_deinit(void) #ifdef REENTRANT_THREADLOCAL PyThread_delete_key(tracemalloc_reentrant_key); + tracemalloc_reentrant_key = -1; #endif Py_XDECREF(unknown_filename); } + static int tracemalloc_start(int max_nframe) { @@ -907,6 +1137,7 @@ tracemalloc_start(int max_nframe) return 0; } + static void tracemalloc_stop(void) { @@ -923,8 +1154,9 @@ tracemalloc_stop(void) PyMem_SetAllocator(PYMEM_DOMAIN_MEM, &allocators.mem); PyMem_SetAllocator(PYMEM_DOMAIN_OBJ, &allocators.obj); - /* release memory */ tracemalloc_clear_traces(); + + /* release memory */ raw_free(tracemalloc_traceback); tracemalloc_traceback = NULL; } @@ -935,6 +1167,7 @@ PyDoc_STRVAR(tracemalloc_is_tracing_doc, "True if the tracemalloc module is tracing Python memory allocations,\n" "False otherwise."); + static PyObject* py_tracemalloc_is_tracing(PyObject *self) { @@ -946,6 +1179,7 @@ PyDoc_STRVAR(tracemalloc_clear_traces_doc, "\n" "Clear traces of memory blocks allocated by Python."); + static PyObject* py_tracemalloc_clear_traces(PyObject *self) { @@ -959,6 +1193,7 @@ py_tracemalloc_clear_traces(PyObject *self) Py_RETURN_NONE; } + static PyObject* frame_to_pyobject(frame_t *frame) { @@ -968,8 +1203,6 @@ frame_to_pyobject(frame_t *frame) if (frame_obj == NULL) return NULL; - if (frame->filename == NULL) - frame->filename = Py_None; Py_INCREF(frame->filename); PyTuple_SET_ITEM(frame_obj, 0, frame->filename); @@ -983,6 +1216,7 @@ frame_to_pyobject(frame_t *frame) return frame_obj; } + static PyObject* traceback_to_pyobject(traceback_t *traceback, _Py_hashtable_t *intern_table) { @@ -1021,33 +1255,43 @@ traceback_to_pyobject(traceback_t *traceback, _Py_hashtable_t *intern_table) return frames; } + static PyObject* -trace_to_pyobject(trace_t *trace, _Py_hashtable_t *intern_tracebacks) +trace_to_pyobject(_PyTraceMalloc_domain_t domain, trace_t *trace, + _Py_hashtable_t *intern_tracebacks) { PyObject *trace_obj = NULL; - PyObject *size, *traceback; + PyObject *obj; - trace_obj = PyTuple_New(2); + trace_obj = PyTuple_New(3); if (trace_obj == NULL) return NULL; - size = PyLong_FromSize_t(trace->size); - if (size == NULL) { + obj = PyLong_FromSize_t(domain); + if (obj == NULL) { Py_DECREF(trace_obj); return NULL; } - PyTuple_SET_ITEM(trace_obj, 0, size); + PyTuple_SET_ITEM(trace_obj, 0, obj); - traceback = traceback_to_pyobject(trace->traceback, intern_tracebacks); - if (traceback == NULL) { + obj = PyLong_FromSize_t(trace->size); + if (obj == NULL) { Py_DECREF(trace_obj); return NULL; } - PyTuple_SET_ITEM(trace_obj, 1, traceback); + PyTuple_SET_ITEM(trace_obj, 1, obj); + + obj = traceback_to_pyobject(trace->traceback, intern_tracebacks); + if (obj == NULL) { + Py_DECREF(trace_obj); + return NULL; + } + PyTuple_SET_ITEM(trace_obj, 2, obj); return trace_obj; } + typedef struct { _Py_hashtable_t *traces; _Py_hashtable_t *tracebacks; @@ -1055,16 +1299,26 @@ typedef struct { } get_traces_t; static int -tracemalloc_get_traces_fill(_Py_hashtable_entry_t *entry, void *user_data) +tracemalloc_get_traces_fill(_Py_hashtable_t *traces, _Py_hashtable_entry_t *entry, + void *user_data) { get_traces_t *get_traces = user_data; - trace_t *trace; + _PyTraceMalloc_domain_t domain; + trace_t trace; PyObject *tracemalloc_obj; int res; - trace = (trace_t *)_Py_HASHTABLE_ENTRY_DATA(entry); + if (tracemalloc_config.use_domain) { + pointer_t key; + _Py_HASHTABLE_ENTRY_READ_KEY(traces, entry, key); + domain = key.domain; + } + else { + domain = DEFAULT_DOMAIN; + } + _Py_HASHTABLE_ENTRY_READ_DATA(traces, entry, trace); - tracemalloc_obj = trace_to_pyobject(trace, get_traces->tracebacks); + tracemalloc_obj = trace_to_pyobject(domain, &trace, get_traces->tracebacks); if (tracemalloc_obj == NULL) return 1; @@ -1076,14 +1330,19 @@ tracemalloc_get_traces_fill(_Py_hashtable_entry_t *entry, void *user_data) return 0; } + static int -tracemalloc_pyobject_decref_cb(_Py_hashtable_entry_t *entry, void *user_data) +tracemalloc_pyobject_decref_cb(_Py_hashtable_t *tracebacks, + _Py_hashtable_entry_t *entry, + void *user_data) { - PyObject *obj = (PyObject *)_Py_HASHTABLE_ENTRY_DATA_AS_VOID_P(entry); + PyObject *obj; + _Py_HASHTABLE_ENTRY_READ_DATA(tracebacks, entry, obj); Py_DECREF(obj); return 0; } + PyDoc_STRVAR(tracemalloc_get_traces_doc, "_get_traces() -> list\n" "\n" @@ -1110,7 +1369,8 @@ py_tracemalloc_get_traces(PyObject *self, PyObject *obj) /* the traceback hash table is used temporarily to intern traceback tuple of (filename, lineno) tuples */ - get_traces.tracebacks = hashtable_new(sizeof(PyObject *), + get_traces.tracebacks = hashtable_new(sizeof(traceback_t *), + sizeof(PyObject *), _Py_hashtable_hash_ptr, _Py_hashtable_compare_direct); if (get_traces.tracebacks == NULL) { @@ -1142,15 +1402,43 @@ error: finally: if (get_traces.tracebacks != NULL) { _Py_hashtable_foreach(get_traces.tracebacks, - tracemalloc_pyobject_decref_cb, NULL); + tracemalloc_pyobject_decref_cb, NULL); _Py_hashtable_destroy(get_traces.tracebacks); } - if (get_traces.traces != NULL) + if (get_traces.traces != NULL) { _Py_hashtable_destroy(get_traces.traces); + } return get_traces.list; } + +static traceback_t* +tracemalloc_get_traceback(_PyTraceMalloc_domain_t domain, Py_uintptr_t ptr) +{ + trace_t trace; + int found; + + if (!tracemalloc_config.tracing) + return NULL; + + TABLES_LOCK(); + if (tracemalloc_config.use_domain) { + pointer_t key = {ptr, domain}; + found = _Py_HASHTABLE_GET(tracemalloc_traces, key, trace); + } + else { + found = _Py_HASHTABLE_GET(tracemalloc_traces, ptr, trace); + } + TABLES_UNLOCK(); + + if (!found) + return NULL; + + return trace.traceback; +} + + PyDoc_STRVAR(tracemalloc_get_object_traceback_doc, "_get_object_traceback(obj)\n" "\n" @@ -1165,11 +1453,7 @@ py_tracemalloc_get_object_traceback(PyObject *self, PyObject *obj) { PyTypeObject *type; void *ptr; - trace_t trace; - int found; - - if (!tracemalloc_config.tracing) - Py_RETURN_NONE; + traceback_t *traceback; type = Py_TYPE(obj); if (PyType_IS_GC(type)) @@ -1177,16 +1461,48 @@ py_tracemalloc_get_object_traceback(PyObject *self, PyObject *obj) else ptr = (void *)obj; - TABLES_LOCK(); - found = _Py_HASHTABLE_GET(tracemalloc_traces, ptr, trace); - TABLES_UNLOCK(); - - if (!found) + traceback = tracemalloc_get_traceback(DEFAULT_DOMAIN, (Py_uintptr_t)ptr); + if (traceback == NULL) Py_RETURN_NONE; - return traceback_to_pyobject(trace.traceback, NULL); + return traceback_to_pyobject(traceback, NULL); } + +#define PUTS(fd, str) _Py_write_noraise(fd, str, (int)strlen(str)) + +static void +_PyMem_DumpFrame(int fd, frame_t * frame) +{ + PUTS(fd, " File \""); + _Py_DumpASCII(fd, frame->filename); + PUTS(fd, "\", line "); + _Py_DumpDecimal(fd, frame->lineno); + PUTS(fd, "\n"); +} + +/* Dump the traceback where a memory block was allocated into file descriptor + fd. The function may block on TABLES_LOCK() but it is unlikely. */ +void +_PyMem_DumpTraceback(int fd, const void *ptr) +{ + traceback_t *traceback; + int i; + + traceback = tracemalloc_get_traceback(DEFAULT_DOMAIN, (Py_uintptr_t)ptr); + if (traceback == NULL) + return; + + PUTS(fd, "Memory block allocated at (most recent call first):\n"); + for (i=0; i < traceback->nframe; i++) { + _PyMem_DumpFrame(fd, &traceback->frames[i]); + } + PUTS(fd, "\n"); +} + +#undef PUTS + + PyDoc_STRVAR(tracemalloc_start_doc, "start(nframe: int=1)\n" "\n" @@ -1222,6 +1538,7 @@ PyDoc_STRVAR(tracemalloc_stop_doc, "Stop tracing Python memory allocations and clear traces\n" "of memory blocks allocated by Python."); + static PyObject* py_tracemalloc_stop(PyObject *self) { @@ -1229,6 +1546,7 @@ py_tracemalloc_stop(PyObject *self) Py_RETURN_NONE; } + PyDoc_STRVAR(tracemalloc_get_traceback_limit_doc, "get_traceback_limit() -> int\n" "\n" @@ -1244,6 +1562,7 @@ py_tracemalloc_get_traceback_limit(PyObject *self) return PyLong_FromLong(tracemalloc_config.max_nframe); } + PyDoc_STRVAR(tracemalloc_get_tracemalloc_memory_doc, "get_tracemalloc_memory() -> int\n" "\n" @@ -1267,6 +1586,7 @@ tracemalloc_get_tracemalloc_memory(PyObject *self) return Py_BuildValue("N", size_obj); } + PyDoc_STRVAR(tracemalloc_get_traced_memory_doc, "get_traced_memory() -> (int, int)\n" "\n" @@ -1292,6 +1612,7 @@ tracemalloc_get_traced_memory(PyObject *self) return Py_BuildValue("NN", size_obj, peak_size_obj); } + static PyMethodDef module_methods[] = { {"is_tracing", (PyCFunction)py_tracemalloc_is_tracing, METH_NOARGS, tracemalloc_is_tracing_doc}, @@ -1342,6 +1663,7 @@ PyInit__tracemalloc(void) return m; } + static int parse_sys_xoptions(PyObject *value) { @@ -1370,6 +1692,7 @@ parse_sys_xoptions(PyObject *value) return Py_SAFE_DOWNCAST(nframe, long, int); } + int _PyTraceMalloc_Init(void) { @@ -1428,6 +1751,7 @@ _PyTraceMalloc_Init(void) return tracemalloc_start(nframe); } + void _PyTraceMalloc_Fini(void) { @@ -1437,3 +1761,59 @@ _PyTraceMalloc_Fini(void) tracemalloc_deinit(); } +int +_PyTraceMalloc_Track(_PyTraceMalloc_domain_t domain, Py_uintptr_t ptr, + size_t size) +{ + int res; +#ifdef WITH_THREAD + PyGILState_STATE gil_state; +#endif + + if (!tracemalloc_config.tracing) { + /* tracemalloc is not tracing: do nothing */ + return -2; + } + +#ifdef WITH_THREAD + gil_state = PyGILState_Ensure(); +#endif + + TABLES_LOCK(); + res = tracemalloc_add_trace(domain, ptr, size); + TABLES_UNLOCK(); + +#ifdef WITH_THREAD + PyGILState_Release(gil_state); +#endif + return res; +} + + +int +_PyTraceMalloc_Untrack(_PyTraceMalloc_domain_t domain, Py_uintptr_t ptr) +{ + if (!tracemalloc_config.tracing) { + /* tracemalloc is not tracing: do nothing */ + return -2; + } + + TABLES_LOCK(); + tracemalloc_remove_trace(domain, ptr); + TABLES_UNLOCK(); + + return 0; +} + + +PyObject* +_PyTraceMalloc_GetTraceback(_PyTraceMalloc_domain_t domain, Py_uintptr_t ptr) +{ + traceback_t *traceback; + + traceback = tracemalloc_get_traceback(domain, ptr); + if (traceback == NULL) + Py_RETURN_NONE; + + return traceback_to_pyobject(traceback, NULL); +} |