summaryrefslogtreecommitdiff
path: root/Modules/_tracemalloc.c
diff options
context:
space:
mode:
Diffstat (limited to 'Modules/_tracemalloc.c')
-rw-r--r--Modules/_tracemalloc.c586
1 files changed, 483 insertions, 103 deletions
diff --git a/Modules/_tracemalloc.c b/Modules/_tracemalloc.c
index 65bf7a33d9..ec8bd960a7 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
+{
+ 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;
+}
+
+
+static 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,53 +518,161 @@ 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;
+ 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 (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, 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, (uintptr_t)(ptr))
+
+
+static int
+tracemalloc_add_trace(_PyTraceMalloc_domain_t domain, 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
+ (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 <= 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, (uintptr_t)(ptr), size)
+
+
static void*
tracemalloc_alloc(int use_calloc, void *ctx, size_t nelem, size_t elsize)
{
PyMemAllocatorEx *alloc = (PyMemAllocatorEx *)ctx;
void *ptr;
- assert(elsize == 0 || nelem <= PY_SIZE_MAX / elsize);
+ assert(elsize == 0 || nelem <= SIZE_MAX / elsize);
if (use_calloc)
ptr = alloc->calloc(alloc->ctx, nelem, 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 shrunk 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(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, 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, (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, (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, 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, 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, uintptr_t ptr)
+{
+ traceback_t *traceback;
+
+ traceback = tracemalloc_get_traceback(domain, ptr);
+ if (traceback == NULL)
+ Py_RETURN_NONE;
+
+ return traceback_to_pyobject(traceback, NULL);
+}