Skip to content

Commit e567c3c

Browse files
committed
Add Python object reference tracking
This feature adds the capability to track Python objects throughout their lifecycle in memray. The implementation introduces a reference tracking system that records PyObject creation and destruction events, storing them as TrackedObject records in the memray capture file with associated stack traces. The Tracker class now accepts a reference_tracking flag to enable this functionality and provides a get_surviving_objects() method to retrieve live objects at the end of tracking. Object tracking works across threads and supports native, Python, and hybrid stack traces, providing insights into Python object lifetimes. This enhancement extends memray beyond memory allocation tracking to give developers a powerful tool for understanding object lifecycles and identifying potential reference leaks. With this addition, memray can now correlate memory allocations with the Python objects that trigger them, enabling more precise debugging of memory usage patterns. The feature is particularly useful for identifying objects that persist longer than expected or detecting unexpected object creation patterns. By tracking both memory allocations and object references, memray provides a more complete picture of a program's memory behavior, helping developers optimize memory usage and resolve leaks more effectively. This implementation leverages the new Python reference tracing API that was introduced in CPython 3.13. Signed-off-by: Pablo Galindo <[email protected]>
1 parent 87ff508 commit e567c3c

21 files changed

+1367
-25
lines changed

news/752.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add a mode that can be used in Python 3.13.3 and newer where Memray will track Python object creation and destruction events, which can be used to find leaked objects (ones that were created during a tracking session and not destroyed before the end of that tracking session).

src/memray/_memray.pyi

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,35 @@ class AllocationRecord:
6767
def __lt__(self, other: Any) -> Any: ...
6868
def __ne__(self, other: Any) -> Any: ...
6969

70+
class TrackedObjectRecord:
71+
@property
72+
def tid(self) -> int: ...
73+
@property
74+
def address(self) -> int: ...
75+
@property
76+
def is_created(self) -> bool: ...
77+
@property
78+
def frame_index(self) -> int: ...
79+
@property
80+
def native_frame_id(self) -> int: ...
81+
@property
82+
def native_segment_generation(self) -> int: ...
83+
def toPythonObject(self) -> Any: ...
84+
def hybrid_stack_trace(
85+
self,
86+
max_stacks: Optional[int] = None,
87+
) -> List[Union[PythonStackElement, NativeStackElement]]: ...
88+
def native_stack_trace(
89+
self, max_stacks: Optional[int] = None
90+
) -> List[NativeStackElement]: ...
91+
def stack_trace(
92+
self, max_stacks: Optional[int] = None
93+
) -> List[PythonStackElement]: ...
94+
def __eq__(self, other: Any) -> Any: ...
95+
def __hash__(self) -> Any: ...
96+
def __repr__(self) -> str: ...
97+
def __str__(self) -> str: ...
98+
7099
class Interval:
71100
def __init__(
72101
self,
@@ -171,6 +200,9 @@ class FileReader:
171200
@property
172201
def closed(self) -> bool: ...
173202
def close(self) -> None: ...
203+
def get_tracked_objects(
204+
self, filter_objs: Optional[Iterable[Any]] = None
205+
) -> Iterable[TrackedObjectRecord]: ...
174206

175207
def compute_statistics(
176208
file_name: Union[str, Path],
@@ -214,6 +246,7 @@ class Tracker:
214246
follow_fork: bool = ...,
215247
trace_python_allocators: bool = ...,
216248
file_format: FileFormat = ...,
249+
reference_tracking: bool = ...,
217250
) -> None: ...
218251
@overload
219252
def __init__(
@@ -225,6 +258,7 @@ class Tracker:
225258
follow_fork: bool = ...,
226259
trace_python_allocators: bool = ...,
227260
file_format: FileFormat = ...,
261+
reference_tracking: bool = ...,
228262
) -> None: ...
229263
def __enter__(self) -> Any: ...
230264
def __exit__(

0 commit comments

Comments
 (0)