Skip to content

Commit 3db607f

Browse files
committed
gh-130695: add count create/destroy refs to tracemalloc
1 parent 9211b3d commit 3db607f

File tree

6 files changed

+117
-2
lines changed

6 files changed

+117
-2
lines changed

Doc/library/tracemalloc.rst

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,8 @@ Functions
299299

300300
.. function:: clear_traces()
301301

302-
Clear traces of memory blocks allocated by Python.
302+
Clear traces of memory blocks allocated and counts of created and destroyed
303+
memory blocks by Python.
303304

304305
See also :func:`stop`.
305306

@@ -330,6 +331,12 @@ Functions
330331
:mod:`tracemalloc` module as a tuple: ``(current: int, peak: int)``.
331332

332333

334+
.. function:: get_traced_refs()
335+
336+
Get the current count of created and destroyed memory blocks.
337+
:mod:`tracemalloc` module as a tuple: ``(created: int, destroyed: int)``.
338+
339+
333340
.. function:: reset_peak()
334341

335342
Set the peak size of memory blocks traced by the :mod:`tracemalloc` module

Include/internal/pycore_tracemalloc.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ struct _tracemalloc_runtime_state {
9494
/* domain (unsigned int) => traces (_Py_hashtable_t).
9595
Protected by TABLES_LOCK(). */
9696
_Py_hashtable_t *domains;
97+
/* Number of references created.
98+
Protected by TABLES_LOCK(). */
99+
size_t refs_created;
100+
/* Number of references destroyed.
101+
Protected by TABLES_LOCK() and sometimes modified atomically. */
102+
size_t refs_destroyed;
97103

98104
struct tracemalloc_traceback empty_traceback;
99105

@@ -155,6 +161,9 @@ extern size_t _PyTraceMalloc_GetMemory(void);
155161
/* Get the current size and peak size of traced memory blocks as a 2-tuple */
156162
extern PyObject* _PyTraceMalloc_GetTracedMemory(void);
157163

164+
/* Get the current number of references created and destroyed as a 2-tuple */
165+
extern PyObject* _PyTraceMalloc_GetTracedRefs(void);
166+
158167
/* Set the peak size of traced memory blocks to the current size */
159168
extern void _PyTraceMalloc_ResetPeak(void);
160169

Lib/test/test_tracemalloc.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import textwrap
55
import tracemalloc
66
import unittest
7+
import warnings
78
from unittest.mock import patch
89
from test.support.script_helper import (assert_python_ok, assert_python_failure,
910
interpreter_requires_environment)
@@ -1141,6 +1142,37 @@ def __del__(self, untrack=_testcapi.tracemalloc_untrack):
11411142
""")
11421143
assert_python_ok("-c", code)
11431144

1145+
def test_trace_refs(self):
1146+
def f():
1147+
l = []
1148+
del l
1149+
1150+
def g():
1151+
l = [], []
1152+
del l
1153+
1154+
tracemalloc.start()
1155+
1156+
try:
1157+
tracemalloc.clear_traces()
1158+
f()
1159+
refs = tracemalloc.get_traced_refs()
1160+
if refs == (1, 0):
1161+
warnings.warn("ceval Py_DECREF doesn't emit PyRefTracer_DESTROY in this build")
1162+
else:
1163+
self.assertEqual(refs, (1, 1))
1164+
1165+
tracemalloc.clear_traces()
1166+
g()
1167+
refs = tracemalloc.get_traced_refs()
1168+
if refs == (3, 2):
1169+
warnings.warn("ceval Py_DECREF doesn't emit PyRefTracer_DESTROY in this build")
1170+
else:
1171+
self.assertEqual(refs, (3, 3))
1172+
1173+
finally:
1174+
tracemalloc.stop()
1175+
11441176

11451177
if __name__ == "__main__":
11461178
unittest.main()

Modules/_tracemalloc.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,23 @@ _tracemalloc_get_traced_memory_impl(PyObject *module)
167167
return _PyTraceMalloc_GetTracedMemory();
168168
}
169169

170+
171+
/*[clinic input]
172+
_tracemalloc.get_traced_refs
173+
174+
Get the current count of created and destroyed refs.
175+
176+
Returns a tuple: (created: int, destroyed: int).
177+
[clinic start generated code]*/
178+
179+
static PyObject *
180+
_tracemalloc_get_traced_refs_impl(PyObject *module)
181+
/*[clinic end generated code: output=81d36fdeb3ffc362 input=d0652f2592733b0e]*/
182+
{
183+
return _PyTraceMalloc_GetTracedRefs();
184+
}
185+
186+
170187
/*[clinic input]
171188
_tracemalloc.reset_peak
172189
@@ -195,6 +212,7 @@ static PyMethodDef module_methods[] = {
195212
_TRACEMALLOC_GET_TRACEBACK_LIMIT_METHODDEF
196213
_TRACEMALLOC_GET_TRACEMALLOC_MEMORY_METHODDEF
197214
_TRACEMALLOC_GET_TRACED_MEMORY_METHODDEF
215+
_TRACEMALLOC_GET_TRACED_REFS_METHODDEF
198216
_TRACEMALLOC_RESET_PEAK_METHODDEF
199217
/* sentinel */
200218
{NULL, NULL}

Modules/clinic/_tracemalloc.c.h

Lines changed: 21 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/tracemalloc.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ typedef struct {
7676
#define tracemalloc_tracebacks _PyRuntime.tracemalloc.tracebacks
7777
#define tracemalloc_traces _PyRuntime.tracemalloc.traces
7878
#define tracemalloc_domains _PyRuntime.tracemalloc.domains
79+
#define tracemalloc_refs_created _PyRuntime.tracemalloc.refs_created
80+
#define tracemalloc_refs_destroyed _PyRuntime.tracemalloc.refs_destroyed
7981

8082

8183
#ifdef TRACE_DEBUG
@@ -1258,6 +1260,10 @@ _PyTraceMalloc_TraceRef(PyObject *op, PyRefTracerEvent event,
12581260
void* Py_UNUSED(ignore))
12591261
{
12601262
if (event != PyRefTracer_CREATE) {
1263+
/* we don't want bother here with the lock for performance reasons */
1264+
if (_Py_atomic_load_int32_relaxed(&tracemalloc_config.tracing)) {
1265+
_Py_atomic_add_ssize((Py_ssize_t *)&tracemalloc_refs_destroyed, 1);
1266+
}
12611267
return 0;
12621268
}
12631269
if (get_reentrant()) {
@@ -1271,6 +1277,8 @@ _PyTraceMalloc_TraceRef(PyObject *op, PyRefTracerEvent event,
12711277
goto done;
12721278
}
12731279

1280+
tracemalloc_refs_created += 1;
1281+
12741282
PyTypeObject *type = Py_TYPE(op);
12751283
const size_t presize = _PyType_PreHeaderSize(type);
12761284
uintptr_t ptr = (uintptr_t)((char *)op - presize);
@@ -1326,6 +1334,8 @@ _PyTraceMalloc_ClearTraces(void)
13261334
TABLES_LOCK();
13271335
if (tracemalloc_config.tracing) {
13281336
tracemalloc_clear_traces_unlocked();
1337+
tracemalloc_refs_created = 0;
1338+
tracemalloc_refs_destroyed = 0;
13291339
}
13301340
TABLES_UNLOCK();
13311341
}
@@ -1464,6 +1474,25 @@ _PyTraceMalloc_GetTracedMemory(void)
14641474
return Py_BuildValue("nn", traced, peak);
14651475
}
14661476

1477+
1478+
PyObject *
1479+
_PyTraceMalloc_GetTracedRefs(void)
1480+
{
1481+
TABLES_LOCK();
1482+
Py_ssize_t created, destroyed;
1483+
if (tracemalloc_config.tracing) {
1484+
created = tracemalloc_refs_created;
1485+
destroyed = tracemalloc_refs_destroyed;
1486+
}
1487+
else {
1488+
created = 0;
1489+
destroyed = 0;
1490+
}
1491+
TABLES_UNLOCK();
1492+
1493+
return Py_BuildValue("nn", created, destroyed);
1494+
}
1495+
14671496
void
14681497
_PyTraceMalloc_ResetPeak(void)
14691498
{

0 commit comments

Comments
 (0)