mirror of
				https://github.com/python/cpython.git
				synced 2025-11-04 07:31:38 +00:00 
			
		
		
		
	bpo-37961, tracemalloc: add Traceback.total_nframe (GH-15545)
Add a total_nframe field to the traces collected by the tracemalloc module. This field indicates the original number of frames before it was truncated.
This commit is contained in:
		
							parent
							
								
									f3ef06a7cb
								
							
						
					
					
						commit
						8d59eb1b66
					
				
					 5 changed files with 114 additions and 64 deletions
				
			
		| 
						 | 
					@ -313,6 +313,9 @@ Functions
 | 
				
			||||||
   frames. By default, a trace of a memory block only stores the most recent
 | 
					   frames. By default, a trace of a memory block only stores the most recent
 | 
				
			||||||
   frame: the limit is ``1``. *nframe* must be greater or equal to ``1``.
 | 
					   frame: the limit is ``1``. *nframe* must be greater or equal to ``1``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   You can still read the original number of total frames that composed the
 | 
				
			||||||
 | 
					   traceback by looking at the :attr:`Traceback.total_nframe` attribute.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   Storing more than ``1`` frame is only useful to compute statistics grouped
 | 
					   Storing more than ``1`` frame is only useful to compute statistics grouped
 | 
				
			||||||
   by ``'traceback'`` or to compute cumulative statistics: see the
 | 
					   by ``'traceback'`` or to compute cumulative statistics: see the
 | 
				
			||||||
   :meth:`Snapshot.compare_to` and :meth:`Snapshot.statistics` methods.
 | 
					   :meth:`Snapshot.compare_to` and :meth:`Snapshot.statistics` methods.
 | 
				
			||||||
| 
						 | 
					@ -659,6 +662,9 @@ Traceback
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   When a snapshot is taken, tracebacks of traces are limited to
 | 
					   When a snapshot is taken, tracebacks of traces are limited to
 | 
				
			||||||
   :func:`get_traceback_limit` frames. See the :func:`take_snapshot` function.
 | 
					   :func:`get_traceback_limit` frames. See the :func:`take_snapshot` function.
 | 
				
			||||||
 | 
					   The original number of frames of the traceback is stored in the
 | 
				
			||||||
 | 
					   :attr:`Traceback.total_nframe` attribute. That allows to know if a traceback
 | 
				
			||||||
 | 
					   has been truncated by the traceback limit.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   The :attr:`Trace.traceback` attribute is an instance of :class:`Traceback`
 | 
					   The :attr:`Trace.traceback` attribute is an instance of :class:`Traceback`
 | 
				
			||||||
   instance.
 | 
					   instance.
 | 
				
			||||||
| 
						 | 
					@ -666,6 +672,15 @@ Traceback
 | 
				
			||||||
   .. versionchanged:: 3.7
 | 
					   .. versionchanged:: 3.7
 | 
				
			||||||
      Frames are now sorted from the oldest to the most recent, instead of most recent to oldest.
 | 
					      Frames are now sorted from the oldest to the most recent, instead of most recent to oldest.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   .. attribute:: total_nframe
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      Total number of frames that composed the traceback before truncation.
 | 
				
			||||||
 | 
					      This attribute can be set to ``None`` if the information is not
 | 
				
			||||||
 | 
					      available.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   .. versionchanged:: 3.9
 | 
				
			||||||
 | 
					      The :attr:`Traceback.total_nframe` attribute was added.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   .. method:: format(limit=None, most_recent_first=False)
 | 
					   .. method:: format(limit=None, most_recent_first=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      Format the traceback as a list of lines with newlines. Use the
 | 
					      Format the traceback as a list of lines with newlines. Use the
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,7 +36,7 @@ def allocate_bytes(size):
 | 
				
			||||||
    bytes_len = (size - EMPTY_STRING_SIZE)
 | 
					    bytes_len = (size - EMPTY_STRING_SIZE)
 | 
				
			||||||
    frames = get_frames(nframe, 1)
 | 
					    frames = get_frames(nframe, 1)
 | 
				
			||||||
    data = b'x' * bytes_len
 | 
					    data = b'x' * bytes_len
 | 
				
			||||||
    return data, tracemalloc.Traceback(frames)
 | 
					    return data, tracemalloc.Traceback(frames, min(len(frames), nframe))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def create_snapshots():
 | 
					def create_snapshots():
 | 
				
			||||||
    traceback_limit = 2
 | 
					    traceback_limit = 2
 | 
				
			||||||
| 
						 | 
					@ -45,27 +45,27 @@ def create_snapshots():
 | 
				
			||||||
    # traceback_frames) tuples. traceback_frames is a tuple of (filename,
 | 
					    # traceback_frames) tuples. traceback_frames is a tuple of (filename,
 | 
				
			||||||
    # line_number) tuples.
 | 
					    # line_number) tuples.
 | 
				
			||||||
    raw_traces = [
 | 
					    raw_traces = [
 | 
				
			||||||
        (0, 10, (('a.py', 2), ('b.py', 4))),
 | 
					        (0, 10, (('a.py', 2), ('b.py', 4)), 3),
 | 
				
			||||||
        (0, 10, (('a.py', 2), ('b.py', 4))),
 | 
					        (0, 10, (('a.py', 2), ('b.py', 4)), 3),
 | 
				
			||||||
        (0, 10, (('a.py', 2), ('b.py', 4))),
 | 
					        (0, 10, (('a.py', 2), ('b.py', 4)), 3),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        (1, 2, (('a.py', 5), ('b.py', 4))),
 | 
					        (1, 2, (('a.py', 5), ('b.py', 4)), 3),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        (2, 66, (('b.py', 1),)),
 | 
					        (2, 66, (('b.py', 1),), 1),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        (3, 7, (('<unknown>', 0),)),
 | 
					        (3, 7, (('<unknown>', 0),), 1),
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
    snapshot = tracemalloc.Snapshot(raw_traces, traceback_limit)
 | 
					    snapshot = tracemalloc.Snapshot(raw_traces, traceback_limit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    raw_traces2 = [
 | 
					    raw_traces2 = [
 | 
				
			||||||
        (0, 10, (('a.py', 2), ('b.py', 4))),
 | 
					        (0, 10, (('a.py', 2), ('b.py', 4)), 3),
 | 
				
			||||||
        (0, 10, (('a.py', 2), ('b.py', 4))),
 | 
					        (0, 10, (('a.py', 2), ('b.py', 4)), 3),
 | 
				
			||||||
        (0, 10, (('a.py', 2), ('b.py', 4))),
 | 
					        (0, 10, (('a.py', 2), ('b.py', 4)), 3),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        (2, 2, (('a.py', 5), ('b.py', 4))),
 | 
					        (2, 2, (('a.py', 5), ('b.py', 4)), 3),
 | 
				
			||||||
        (2, 5000, (('a.py', 5), ('b.py', 4))),
 | 
					        (2, 5000, (('a.py', 5), ('b.py', 4)), 3),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        (4, 400, (('c.py', 578),)),
 | 
					        (4, 400, (('c.py', 578),), 1),
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
    snapshot2 = tracemalloc.Snapshot(raw_traces2, traceback_limit)
 | 
					    snapshot2 = tracemalloc.Snapshot(raw_traces2, traceback_limit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -125,7 +125,7 @@ def test_new_reference(self):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        nframe = tracemalloc.get_traceback_limit()
 | 
					        nframe = tracemalloc.get_traceback_limit()
 | 
				
			||||||
        frames = get_frames(nframe, -3)
 | 
					        frames = get_frames(nframe, -3)
 | 
				
			||||||
        obj_traceback = tracemalloc.Traceback(frames)
 | 
					        obj_traceback = tracemalloc.Traceback(frames, min(len(frames), nframe))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        traceback = tracemalloc.get_object_traceback(obj)
 | 
					        traceback = tracemalloc.get_object_traceback(obj)
 | 
				
			||||||
        self.assertIsNotNone(traceback)
 | 
					        self.assertIsNotNone(traceback)
 | 
				
			||||||
| 
						 | 
					@ -167,7 +167,7 @@ def test_get_traces(self):
 | 
				
			||||||
        trace = self.find_trace(traces, obj_traceback)
 | 
					        trace = self.find_trace(traces, obj_traceback)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertIsInstance(trace, tuple)
 | 
					        self.assertIsInstance(trace, tuple)
 | 
				
			||||||
        domain, size, traceback = trace
 | 
					        domain, size, traceback, length = trace
 | 
				
			||||||
        self.assertEqual(size, obj_size)
 | 
					        self.assertEqual(size, obj_size)
 | 
				
			||||||
        self.assertEqual(traceback, obj_traceback._frames)
 | 
					        self.assertEqual(traceback, obj_traceback._frames)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -197,8 +197,8 @@ def allocate_bytes4(size):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        trace1 = self.find_trace(traces, obj1_traceback)
 | 
					        trace1 = self.find_trace(traces, obj1_traceback)
 | 
				
			||||||
        trace2 = self.find_trace(traces, obj2_traceback)
 | 
					        trace2 = self.find_trace(traces, obj2_traceback)
 | 
				
			||||||
        domain1, size1, traceback1 = trace1
 | 
					        domain1, size1, traceback1, length1 = trace1
 | 
				
			||||||
        domain2, size2, traceback2 = trace2
 | 
					        domain2, size2, traceback2, length2 = trace2
 | 
				
			||||||
        self.assertIs(traceback2, traceback1)
 | 
					        self.assertIs(traceback2, traceback1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_get_traced_memory(self):
 | 
					    def test_get_traced_memory(self):
 | 
				
			||||||
| 
						 | 
					@ -259,6 +259,9 @@ def test_snapshot(self):
 | 
				
			||||||
        # take a snapshot
 | 
					        # take a snapshot
 | 
				
			||||||
        snapshot = tracemalloc.take_snapshot()
 | 
					        snapshot = tracemalloc.take_snapshot()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # This can vary
 | 
				
			||||||
 | 
					        self.assertGreater(snapshot.traces[1].traceback.total_nframe, 10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # write on disk
 | 
					        # write on disk
 | 
				
			||||||
        snapshot.dump(support.TESTFN)
 | 
					        snapshot.dump(support.TESTFN)
 | 
				
			||||||
        self.addCleanup(support.unlink, support.TESTFN)
 | 
					        self.addCleanup(support.unlink, support.TESTFN)
 | 
				
			||||||
| 
						 | 
					@ -321,7 +324,7 @@ class TestSnapshot(unittest.TestCase):
 | 
				
			||||||
    maxDiff = 4000
 | 
					    maxDiff = 4000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_create_snapshot(self):
 | 
					    def test_create_snapshot(self):
 | 
				
			||||||
        raw_traces = [(0, 5, (('a.py', 2),))]
 | 
					        raw_traces = [(0, 5, (('a.py', 2),), 10)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with contextlib.ExitStack() as stack:
 | 
					        with contextlib.ExitStack() as stack:
 | 
				
			||||||
            stack.enter_context(patch.object(tracemalloc, 'is_tracing',
 | 
					            stack.enter_context(patch.object(tracemalloc, 'is_tracing',
 | 
				
			||||||
| 
						 | 
					@ -336,6 +339,7 @@ def test_create_snapshot(self):
 | 
				
			||||||
            self.assertEqual(len(snapshot.traces), 1)
 | 
					            self.assertEqual(len(snapshot.traces), 1)
 | 
				
			||||||
            trace = snapshot.traces[0]
 | 
					            trace = snapshot.traces[0]
 | 
				
			||||||
            self.assertEqual(trace.size, 5)
 | 
					            self.assertEqual(trace.size, 5)
 | 
				
			||||||
 | 
					            self.assertEqual(trace.traceback.total_nframe, 10)
 | 
				
			||||||
            self.assertEqual(len(trace.traceback), 1)
 | 
					            self.assertEqual(len(trace.traceback), 1)
 | 
				
			||||||
            self.assertEqual(trace.traceback[0].filename, 'a.py')
 | 
					            self.assertEqual(trace.traceback[0].filename, 'a.py')
 | 
				
			||||||
            self.assertEqual(trace.traceback[0].lineno, 2)
 | 
					            self.assertEqual(trace.traceback[0].lineno, 2)
 | 
				
			||||||
| 
						 | 
					@ -351,11 +355,11 @@ def test_filter_traces(self):
 | 
				
			||||||
        # exclude b.py
 | 
					        # exclude b.py
 | 
				
			||||||
        snapshot3 = snapshot.filter_traces((filter1,))
 | 
					        snapshot3 = snapshot.filter_traces((filter1,))
 | 
				
			||||||
        self.assertEqual(snapshot3.traces._traces, [
 | 
					        self.assertEqual(snapshot3.traces._traces, [
 | 
				
			||||||
            (0, 10, (('a.py', 2), ('b.py', 4))),
 | 
					            (0, 10, (('a.py', 2), ('b.py', 4)), 3),
 | 
				
			||||||
            (0, 10, (('a.py', 2), ('b.py', 4))),
 | 
					            (0, 10, (('a.py', 2), ('b.py', 4)), 3),
 | 
				
			||||||
            (0, 10, (('a.py', 2), ('b.py', 4))),
 | 
					            (0, 10, (('a.py', 2), ('b.py', 4)), 3),
 | 
				
			||||||
            (1, 2, (('a.py', 5), ('b.py', 4))),
 | 
					            (1, 2, (('a.py', 5), ('b.py', 4)), 3),
 | 
				
			||||||
            (3, 7, (('<unknown>', 0),)),
 | 
					            (3, 7, (('<unknown>', 0),), 1),
 | 
				
			||||||
        ])
 | 
					        ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # filter_traces() must not touch the original snapshot
 | 
					        # filter_traces() must not touch the original snapshot
 | 
				
			||||||
| 
						 | 
					@ -364,10 +368,10 @@ def test_filter_traces(self):
 | 
				
			||||||
        # only include two lines of a.py
 | 
					        # only include two lines of a.py
 | 
				
			||||||
        snapshot4 = snapshot3.filter_traces((filter2, filter3))
 | 
					        snapshot4 = snapshot3.filter_traces((filter2, filter3))
 | 
				
			||||||
        self.assertEqual(snapshot4.traces._traces, [
 | 
					        self.assertEqual(snapshot4.traces._traces, [
 | 
				
			||||||
            (0, 10, (('a.py', 2), ('b.py', 4))),
 | 
					            (0, 10, (('a.py', 2), ('b.py', 4)), 3),
 | 
				
			||||||
            (0, 10, (('a.py', 2), ('b.py', 4))),
 | 
					            (0, 10, (('a.py', 2), ('b.py', 4)), 3),
 | 
				
			||||||
            (0, 10, (('a.py', 2), ('b.py', 4))),
 | 
					            (0, 10, (('a.py', 2), ('b.py', 4)), 3),
 | 
				
			||||||
            (1, 2, (('a.py', 5), ('b.py', 4))),
 | 
					            (1, 2, (('a.py', 5), ('b.py', 4)), 3),
 | 
				
			||||||
        ])
 | 
					        ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # No filter: just duplicate the snapshot
 | 
					        # No filter: just duplicate the snapshot
 | 
				
			||||||
| 
						 | 
					@ -388,21 +392,21 @@ def test_filter_traces_domain(self):
 | 
				
			||||||
        # exclude a.py of domain 1
 | 
					        # exclude a.py of domain 1
 | 
				
			||||||
        snapshot3 = snapshot.filter_traces((filter1,))
 | 
					        snapshot3 = snapshot.filter_traces((filter1,))
 | 
				
			||||||
        self.assertEqual(snapshot3.traces._traces, [
 | 
					        self.assertEqual(snapshot3.traces._traces, [
 | 
				
			||||||
            (0, 10, (('a.py', 2), ('b.py', 4))),
 | 
					            (0, 10, (('a.py', 2), ('b.py', 4)), 3),
 | 
				
			||||||
            (0, 10, (('a.py', 2), ('b.py', 4))),
 | 
					            (0, 10, (('a.py', 2), ('b.py', 4)), 3),
 | 
				
			||||||
            (0, 10, (('a.py', 2), ('b.py', 4))),
 | 
					            (0, 10, (('a.py', 2), ('b.py', 4)), 3),
 | 
				
			||||||
            (2, 66, (('b.py', 1),)),
 | 
					            (2, 66, (('b.py', 1),), 1),
 | 
				
			||||||
            (3, 7, (('<unknown>', 0),)),
 | 
					            (3, 7, (('<unknown>', 0),), 1),
 | 
				
			||||||
        ])
 | 
					        ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # include domain 1
 | 
					        # include domain 1
 | 
				
			||||||
        snapshot3 = snapshot.filter_traces((filter1,))
 | 
					        snapshot3 = snapshot.filter_traces((filter1,))
 | 
				
			||||||
        self.assertEqual(snapshot3.traces._traces, [
 | 
					        self.assertEqual(snapshot3.traces._traces, [
 | 
				
			||||||
            (0, 10, (('a.py', 2), ('b.py', 4))),
 | 
					            (0, 10, (('a.py', 2), ('b.py', 4)), 3),
 | 
				
			||||||
            (0, 10, (('a.py', 2), ('b.py', 4))),
 | 
					            (0, 10, (('a.py', 2), ('b.py', 4)), 3),
 | 
				
			||||||
            (0, 10, (('a.py', 2), ('b.py', 4))),
 | 
					            (0, 10, (('a.py', 2), ('b.py', 4)), 3),
 | 
				
			||||||
            (2, 66, (('b.py', 1),)),
 | 
					            (2, 66, (('b.py', 1),), 1),
 | 
				
			||||||
            (3, 7, (('<unknown>', 0),)),
 | 
					            (3, 7, (('<unknown>', 0),), 1),
 | 
				
			||||||
        ])
 | 
					        ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_filter_traces_domain_filter(self):
 | 
					    def test_filter_traces_domain_filter(self):
 | 
				
			||||||
| 
						 | 
					@ -413,17 +417,17 @@ def test_filter_traces_domain_filter(self):
 | 
				
			||||||
        # exclude domain 2
 | 
					        # exclude domain 2
 | 
				
			||||||
        snapshot3 = snapshot.filter_traces((filter1,))
 | 
					        snapshot3 = snapshot.filter_traces((filter1,))
 | 
				
			||||||
        self.assertEqual(snapshot3.traces._traces, [
 | 
					        self.assertEqual(snapshot3.traces._traces, [
 | 
				
			||||||
            (0, 10, (('a.py', 2), ('b.py', 4))),
 | 
					            (0, 10, (('a.py', 2), ('b.py', 4)), 3),
 | 
				
			||||||
            (0, 10, (('a.py', 2), ('b.py', 4))),
 | 
					            (0, 10, (('a.py', 2), ('b.py', 4)), 3),
 | 
				
			||||||
            (0, 10, (('a.py', 2), ('b.py', 4))),
 | 
					            (0, 10, (('a.py', 2), ('b.py', 4)), 3),
 | 
				
			||||||
            (1, 2, (('a.py', 5), ('b.py', 4))),
 | 
					            (1, 2, (('a.py', 5), ('b.py', 4)), 3),
 | 
				
			||||||
            (2, 66, (('b.py', 1),)),
 | 
					            (2, 66, (('b.py', 1),), 1),
 | 
				
			||||||
        ])
 | 
					        ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # include domain 2
 | 
					        # include domain 2
 | 
				
			||||||
        snapshot3 = snapshot.filter_traces((filter2,))
 | 
					        snapshot3 = snapshot.filter_traces((filter2,))
 | 
				
			||||||
        self.assertEqual(snapshot3.traces._traces, [
 | 
					        self.assertEqual(snapshot3.traces._traces, [
 | 
				
			||||||
            (3, 7, (('<unknown>', 0),)),
 | 
					            (3, 7, (('<unknown>', 0),), 1),
 | 
				
			||||||
        ])
 | 
					        ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_snapshot_group_by_line(self):
 | 
					    def test_snapshot_group_by_line(self):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -182,15 +182,20 @@ class Traceback(Sequence):
 | 
				
			||||||
    Sequence of Frame instances sorted from the oldest frame
 | 
					    Sequence of Frame instances sorted from the oldest frame
 | 
				
			||||||
    to the most recent frame.
 | 
					    to the most recent frame.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    __slots__ = ("_frames",)
 | 
					    __slots__ = ("_frames", '_total_nframe')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, frames):
 | 
					    def __init__(self, frames, total_nframe=None):
 | 
				
			||||||
        Sequence.__init__(self)
 | 
					        Sequence.__init__(self)
 | 
				
			||||||
        # frames is a tuple of frame tuples: see Frame constructor for the
 | 
					        # frames is a tuple of frame tuples: see Frame constructor for the
 | 
				
			||||||
        # format of a frame tuple; it is reversed, because _tracemalloc
 | 
					        # format of a frame tuple; it is reversed, because _tracemalloc
 | 
				
			||||||
        # returns frames sorted from most recent to oldest, but the
 | 
					        # returns frames sorted from most recent to oldest, but the
 | 
				
			||||||
        # Python API expects oldest to most recent
 | 
					        # Python API expects oldest to most recent
 | 
				
			||||||
        self._frames = tuple(reversed(frames))
 | 
					        self._frames = tuple(reversed(frames))
 | 
				
			||||||
 | 
					        self._total_nframe = total_nframe
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def total_nframe(self):
 | 
				
			||||||
 | 
					        return self._total_nframe
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __len__(self):
 | 
					    def __len__(self):
 | 
				
			||||||
        return len(self._frames)
 | 
					        return len(self._frames)
 | 
				
			||||||
| 
						 | 
					@ -221,7 +226,12 @@ def __str__(self):
 | 
				
			||||||
        return str(self[0])
 | 
					        return str(self[0])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __repr__(self):
 | 
					    def __repr__(self):
 | 
				
			||||||
        return "<Traceback %r>" % (tuple(self),)
 | 
					        s = "<Traceback %r" % tuple(self)
 | 
				
			||||||
 | 
					        if self._total_nframe is None:
 | 
				
			||||||
 | 
					            s += ">"
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            s += f" total_nframe={self.total_nframe}>"
 | 
				
			||||||
 | 
					        return s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def format(self, limit=None, most_recent_first=False):
 | 
					    def format(self, limit=None, most_recent_first=False):
 | 
				
			||||||
        lines = []
 | 
					        lines = []
 | 
				
			||||||
| 
						 | 
					@ -280,7 +290,7 @@ def size(self):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def traceback(self):
 | 
					    def traceback(self):
 | 
				
			||||||
        return Traceback(self._trace[2])
 | 
					        return Traceback(*self._trace[2:])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __eq__(self, other):
 | 
					    def __eq__(self, other):
 | 
				
			||||||
        if not isinstance(other, Trace):
 | 
					        if not isinstance(other, Trace):
 | 
				
			||||||
| 
						 | 
					@ -378,7 +388,7 @@ def _match_traceback(self, traceback):
 | 
				
			||||||
            return self._match_frame(filename, lineno)
 | 
					            return self._match_frame(filename, lineno)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _match(self, trace):
 | 
					    def _match(self, trace):
 | 
				
			||||||
        domain, size, traceback = trace
 | 
					        domain, size, traceback, total_nframe = trace
 | 
				
			||||||
        res = self._match_traceback(traceback)
 | 
					        res = self._match_traceback(traceback)
 | 
				
			||||||
        if self.domain is not None:
 | 
					        if self.domain is not None:
 | 
				
			||||||
            if self.inclusive:
 | 
					            if self.inclusive:
 | 
				
			||||||
| 
						 | 
					@ -398,7 +408,7 @@ def domain(self):
 | 
				
			||||||
        return self._domain
 | 
					        return self._domain
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _match(self, trace):
 | 
					    def _match(self, trace):
 | 
				
			||||||
        domain, size, traceback = trace
 | 
					        domain, size, traceback, total_nframe = trace
 | 
				
			||||||
        return (domain == self.domain) ^ (not self.inclusive)
 | 
					        return (domain == self.domain) ^ (not self.inclusive)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -475,7 +485,7 @@ def _group_by(self, key_type, cumulative):
 | 
				
			||||||
        tracebacks = {}
 | 
					        tracebacks = {}
 | 
				
			||||||
        if not cumulative:
 | 
					        if not cumulative:
 | 
				
			||||||
            for trace in self.traces._traces:
 | 
					            for trace in self.traces._traces:
 | 
				
			||||||
                domain, size, trace_traceback = trace
 | 
					                domain, size, trace_traceback, total_nframe = trace
 | 
				
			||||||
                try:
 | 
					                try:
 | 
				
			||||||
                    traceback = tracebacks[trace_traceback]
 | 
					                    traceback = tracebacks[trace_traceback]
 | 
				
			||||||
                except KeyError:
 | 
					                except KeyError:
 | 
				
			||||||
| 
						 | 
					@ -496,7 +506,7 @@ def _group_by(self, key_type, cumulative):
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            # cumulative statistics
 | 
					            # cumulative statistics
 | 
				
			||||||
            for trace in self.traces._traces:
 | 
					            for trace in self.traces._traces:
 | 
				
			||||||
                domain, size, trace_traceback = trace
 | 
					                domain, size, trace_traceback, total_nframe = trace
 | 
				
			||||||
                for frame in trace_traceback:
 | 
					                for frame in trace_traceback:
 | 
				
			||||||
                    try:
 | 
					                    try:
 | 
				
			||||||
                        traceback = tracebacks[frame]
 | 
					                        traceback = tracebacks[frame]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					Add a ``total_nframe`` field to the traces collected by the tracemalloc module.
 | 
				
			||||||
 | 
					This field indicates the original number of frames before it was truncated.
 | 
				
			||||||
| 
						 | 
					@ -78,15 +78,20 @@ __attribute__((packed))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct {
 | 
					typedef struct {
 | 
				
			||||||
    Py_uhash_t hash;
 | 
					    Py_uhash_t hash;
 | 
				
			||||||
    int nframe;
 | 
					    /* Number of frames stored */
 | 
				
			||||||
 | 
					    uint16_t nframe;
 | 
				
			||||||
 | 
					    /* Total number of frames the traceback had */
 | 
				
			||||||
 | 
					    uint16_t total_nframe;
 | 
				
			||||||
    frame_t frames[1];
 | 
					    frame_t frames[1];
 | 
				
			||||||
} traceback_t;
 | 
					} traceback_t;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define TRACEBACK_SIZE(NFRAME) \
 | 
					#define TRACEBACK_SIZE(NFRAME) \
 | 
				
			||||||
        (sizeof(traceback_t) + sizeof(frame_t) * (NFRAME - 1))
 | 
					        (sizeof(traceback_t) + sizeof(frame_t) * (NFRAME - 1))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define MAX_NFRAME \
 | 
					/* The maximum number of frames is either:
 | 
				
			||||||
        ((INT_MAX - (int)sizeof(traceback_t)) / (int)sizeof(frame_t) + 1)
 | 
					 - The maximum number of frames we can store in `traceback_t.nframe`
 | 
				
			||||||
 | 
					 - The maximum memory size_t we can allocate */
 | 
				
			||||||
 | 
					static const unsigned long MAX_NFRAME = Py_MIN(UINT16_MAX, ((SIZE_MAX - sizeof(traceback_t)) / sizeof(frame_t) + 1));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static PyObject *unknown_filename = NULL;
 | 
					static PyObject *unknown_filename = NULL;
 | 
				
			||||||
| 
						 | 
					@ -308,6 +313,9 @@ hashtable_compare_traceback(_Py_hashtable_t *ht, const void *pkey,
 | 
				
			||||||
    if (traceback1->nframe != traceback2->nframe)
 | 
					    if (traceback1->nframe != traceback2->nframe)
 | 
				
			||||||
        return 0;
 | 
					        return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (traceback1->total_nframe != traceback2->total_nframe)
 | 
				
			||||||
 | 
					        return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (i=0; i < traceback1->nframe; i++) {
 | 
					    for (i=0; i < traceback1->nframe; i++) {
 | 
				
			||||||
        frame1 = &traceback1->frames[i];
 | 
					        frame1 = &traceback1->frames[i];
 | 
				
			||||||
        frame2 = &traceback2->frames[i];
 | 
					        frame2 = &traceback2->frames[i];
 | 
				
			||||||
| 
						 | 
					@ -416,6 +424,7 @@ traceback_hash(traceback_t *traceback)
 | 
				
			||||||
        /* the cast might truncate len; that doesn't change hash stability */
 | 
					        /* the cast might truncate len; that doesn't change hash stability */
 | 
				
			||||||
        mult += (Py_uhash_t)(82520UL + len + len);
 | 
					        mult += (Py_uhash_t)(82520UL + len + len);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    x ^= traceback->total_nframe;
 | 
				
			||||||
    x += 97531UL;
 | 
					    x += 97531UL;
 | 
				
			||||||
    return x;
 | 
					    return x;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -436,11 +445,13 @@ traceback_get_frames(traceback_t *traceback)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (pyframe = tstate->frame; pyframe != NULL; pyframe = pyframe->f_back) {
 | 
					    for (pyframe = tstate->frame; pyframe != NULL; pyframe = pyframe->f_back) {
 | 
				
			||||||
 | 
					        if (traceback->nframe < _Py_tracemalloc_config.max_nframe) {
 | 
				
			||||||
            tracemalloc_get_frame(pyframe, &traceback->frames[traceback->nframe]);
 | 
					            tracemalloc_get_frame(pyframe, &traceback->frames[traceback->nframe]);
 | 
				
			||||||
            assert(traceback->frames[traceback->nframe].filename != NULL);
 | 
					            assert(traceback->frames[traceback->nframe].filename != NULL);
 | 
				
			||||||
            traceback->nframe++;
 | 
					            traceback->nframe++;
 | 
				
			||||||
        if (traceback->nframe == _Py_tracemalloc_config.max_nframe)
 | 
					        }
 | 
				
			||||||
            break;
 | 
					        if (traceback->total_nframe < UINT16_MAX)
 | 
				
			||||||
 | 
					            traceback->total_nframe++;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -456,6 +467,7 @@ traceback_new(void)
 | 
				
			||||||
    /* get frames */
 | 
					    /* get frames */
 | 
				
			||||||
    traceback = tracemalloc_traceback;
 | 
					    traceback = tracemalloc_traceback;
 | 
				
			||||||
    traceback->nframe = 0;
 | 
					    traceback->nframe = 0;
 | 
				
			||||||
 | 
					    traceback->total_nframe = 0;
 | 
				
			||||||
    traceback_get_frames(traceback);
 | 
					    traceback_get_frames(traceback);
 | 
				
			||||||
    if (traceback->nframe == 0)
 | 
					    if (traceback->nframe == 0)
 | 
				
			||||||
        return &tracemalloc_empty_traceback;
 | 
					        return &tracemalloc_empty_traceback;
 | 
				
			||||||
| 
						 | 
					@ -1001,6 +1013,7 @@ tracemalloc_init(void)
 | 
				
			||||||
    PyUnicode_InternInPlace(&unknown_filename);
 | 
					    PyUnicode_InternInPlace(&unknown_filename);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    tracemalloc_empty_traceback.nframe = 1;
 | 
					    tracemalloc_empty_traceback.nframe = 1;
 | 
				
			||||||
 | 
					    tracemalloc_empty_traceback.total_nframe = 1;
 | 
				
			||||||
    /* borrowed reference */
 | 
					    /* borrowed reference */
 | 
				
			||||||
    tracemalloc_empty_traceback.frames[0].filename = unknown_filename;
 | 
					    tracemalloc_empty_traceback.frames[0].filename = unknown_filename;
 | 
				
			||||||
    tracemalloc_empty_traceback.frames[0].lineno = 0;
 | 
					    tracemalloc_empty_traceback.frames[0].lineno = 0;
 | 
				
			||||||
| 
						 | 
					@ -1046,10 +1059,10 @@ tracemalloc_start(int max_nframe)
 | 
				
			||||||
    PyMemAllocatorEx alloc;
 | 
					    PyMemAllocatorEx alloc;
 | 
				
			||||||
    size_t size;
 | 
					    size_t size;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (max_nframe < 1 || max_nframe > MAX_NFRAME) {
 | 
					    if (max_nframe < 1 || (unsigned long) max_nframe > MAX_NFRAME) {
 | 
				
			||||||
        PyErr_Format(PyExc_ValueError,
 | 
					        PyErr_Format(PyExc_ValueError,
 | 
				
			||||||
                     "the number of frames must be in range [1; %i]",
 | 
					                     "the number of frames must be in range [1; %lu]",
 | 
				
			||||||
                     (int)MAX_NFRAME);
 | 
					                     MAX_NFRAME);
 | 
				
			||||||
        return -1;
 | 
					        return -1;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1062,7 +1075,6 @@ tracemalloc_start(int max_nframe)
 | 
				
			||||||
        return 0;
 | 
					        return 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    assert(1 <= max_nframe && max_nframe <= MAX_NFRAME);
 | 
					 | 
				
			||||||
    _Py_tracemalloc_config.max_nframe = max_nframe;
 | 
					    _Py_tracemalloc_config.max_nframe = max_nframe;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* allocate a buffer to store a new traceback */
 | 
					    /* allocate a buffer to store a new traceback */
 | 
				
			||||||
| 
						 | 
					@ -1234,7 +1246,7 @@ trace_to_pyobject(unsigned int domain, trace_t *trace,
 | 
				
			||||||
    PyObject *trace_obj = NULL;
 | 
					    PyObject *trace_obj = NULL;
 | 
				
			||||||
    PyObject *obj;
 | 
					    PyObject *obj;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    trace_obj = PyTuple_New(3);
 | 
					    trace_obj = PyTuple_New(4);
 | 
				
			||||||
    if (trace_obj == NULL)
 | 
					    if (trace_obj == NULL)
 | 
				
			||||||
        return NULL;
 | 
					        return NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1259,6 +1271,13 @@ trace_to_pyobject(unsigned int domain, trace_t *trace,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    PyTuple_SET_ITEM(trace_obj, 2, obj);
 | 
					    PyTuple_SET_ITEM(trace_obj, 2, obj);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    obj = PyLong_FromUnsignedLong(trace->traceback->total_nframe);
 | 
				
			||||||
 | 
					    if (obj == NULL) {
 | 
				
			||||||
 | 
					        Py_DECREF(trace_obj);
 | 
				
			||||||
 | 
					        return NULL;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    PyTuple_SET_ITEM(trace_obj, 3, obj);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return trace_obj;
 | 
					    return trace_obj;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue