test_tracemalloc.py 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100
  1. import contextlib
  2. import os
  3. import sys
  4. import tracemalloc
  5. import unittest
  6. from unittest.mock import patch
  7. from test.support.script_helper import (assert_python_ok, assert_python_failure,
  8. interpreter_requires_environment)
  9. from test import support
  10. from test.support import os_helper
  11. try:
  12. import _testcapi
  13. except ImportError:
  14. _testcapi = None
  15. EMPTY_STRING_SIZE = sys.getsizeof(b'')
  16. INVALID_NFRAME = (-1, 2**30)
  17. def get_frames(nframe, lineno_delta):
  18. frames = []
  19. frame = sys._getframe(1)
  20. for index in range(nframe):
  21. code = frame.f_code
  22. lineno = frame.f_lineno + lineno_delta
  23. frames.append((code.co_filename, lineno))
  24. lineno_delta = 0
  25. frame = frame.f_back
  26. if frame is None:
  27. break
  28. return tuple(frames)
  29. def allocate_bytes(size):
  30. nframe = tracemalloc.get_traceback_limit()
  31. bytes_len = (size - EMPTY_STRING_SIZE)
  32. frames = get_frames(nframe, 1)
  33. data = b'x' * bytes_len
  34. return data, tracemalloc.Traceback(frames, min(len(frames), nframe))
  35. def create_snapshots():
  36. traceback_limit = 2
  37. # _tracemalloc._get_traces() returns a list of (domain, size,
  38. # traceback_frames) tuples. traceback_frames is a tuple of (filename,
  39. # line_number) tuples.
  40. raw_traces = [
  41. (0, 10, (('a.py', 2), ('b.py', 4)), 3),
  42. (0, 10, (('a.py', 2), ('b.py', 4)), 3),
  43. (0, 10, (('a.py', 2), ('b.py', 4)), 3),
  44. (1, 2, (('a.py', 5), ('b.py', 4)), 3),
  45. (2, 66, (('b.py', 1),), 1),
  46. (3, 7, (('<unknown>', 0),), 1),
  47. ]
  48. snapshot = tracemalloc.Snapshot(raw_traces, traceback_limit)
  49. raw_traces2 = [
  50. (0, 10, (('a.py', 2), ('b.py', 4)), 3),
  51. (0, 10, (('a.py', 2), ('b.py', 4)), 3),
  52. (0, 10, (('a.py', 2), ('b.py', 4)), 3),
  53. (2, 2, (('a.py', 5), ('b.py', 4)), 3),
  54. (2, 5000, (('a.py', 5), ('b.py', 4)), 3),
  55. (4, 400, (('c.py', 578),), 1),
  56. ]
  57. snapshot2 = tracemalloc.Snapshot(raw_traces2, traceback_limit)
  58. return (snapshot, snapshot2)
  59. def frame(filename, lineno):
  60. return tracemalloc._Frame((filename, lineno))
  61. def traceback(*frames):
  62. return tracemalloc.Traceback(frames)
  63. def traceback_lineno(filename, lineno):
  64. return traceback((filename, lineno))
  65. def traceback_filename(filename):
  66. return traceback_lineno(filename, 0)
  67. class TestTraceback(unittest.TestCase):
  68. def test_repr(self):
  69. def get_repr(*args) -> str:
  70. return repr(tracemalloc.Traceback(*args))
  71. self.assertEqual(get_repr(()), "<Traceback ()>")
  72. self.assertEqual(get_repr((), 0), "<Traceback () total_nframe=0>")
  73. frames = (("f1", 1), ("f2", 2))
  74. exp_repr_frames = (
  75. "(<Frame filename='f2' lineno=2>,"
  76. " <Frame filename='f1' lineno=1>)"
  77. )
  78. self.assertEqual(get_repr(frames),
  79. f"<Traceback {exp_repr_frames}>")
  80. self.assertEqual(get_repr(frames, 2),
  81. f"<Traceback {exp_repr_frames} total_nframe=2>")
  82. class TestTracemallocEnabled(unittest.TestCase):
  83. def setUp(self):
  84. if tracemalloc.is_tracing():
  85. self.skipTest("tracemalloc must be stopped before the test")
  86. tracemalloc.start(1)
  87. def tearDown(self):
  88. tracemalloc.stop()
  89. def test_get_tracemalloc_memory(self):
  90. data = [allocate_bytes(123) for count in range(1000)]
  91. size = tracemalloc.get_tracemalloc_memory()
  92. self.assertGreaterEqual(size, 0)
  93. tracemalloc.clear_traces()
  94. size2 = tracemalloc.get_tracemalloc_memory()
  95. self.assertGreaterEqual(size2, 0)
  96. self.assertLessEqual(size2, size)
  97. def test_get_object_traceback(self):
  98. tracemalloc.clear_traces()
  99. obj_size = 12345
  100. obj, obj_traceback = allocate_bytes(obj_size)
  101. traceback = tracemalloc.get_object_traceback(obj)
  102. self.assertEqual(traceback, obj_traceback)
  103. def test_new_reference(self):
  104. tracemalloc.clear_traces()
  105. # gc.collect() indirectly calls PyList_ClearFreeList()
  106. support.gc_collect()
  107. # Create a list and "destroy it": put it in the PyListObject free list
  108. obj = []
  109. obj = None
  110. # Create a list which should reuse the previously created empty list
  111. obj = []
  112. nframe = tracemalloc.get_traceback_limit()
  113. frames = get_frames(nframe, -3)
  114. obj_traceback = tracemalloc.Traceback(frames, min(len(frames), nframe))
  115. traceback = tracemalloc.get_object_traceback(obj)
  116. self.assertIsNotNone(traceback)
  117. self.assertEqual(traceback, obj_traceback)
  118. def test_set_traceback_limit(self):
  119. obj_size = 10
  120. tracemalloc.stop()
  121. self.assertRaises(ValueError, tracemalloc.start, -1)
  122. tracemalloc.stop()
  123. tracemalloc.start(10)
  124. obj2, obj2_traceback = allocate_bytes(obj_size)
  125. traceback = tracemalloc.get_object_traceback(obj2)
  126. self.assertEqual(len(traceback), 10)
  127. self.assertEqual(traceback, obj2_traceback)
  128. tracemalloc.stop()
  129. tracemalloc.start(1)
  130. obj, obj_traceback = allocate_bytes(obj_size)
  131. traceback = tracemalloc.get_object_traceback(obj)
  132. self.assertEqual(len(traceback), 1)
  133. self.assertEqual(traceback, obj_traceback)
  134. def find_trace(self, traces, traceback):
  135. for trace in traces:
  136. if trace[2] == traceback._frames:
  137. return trace
  138. self.fail("trace not found")
  139. def test_get_traces(self):
  140. tracemalloc.clear_traces()
  141. obj_size = 12345
  142. obj, obj_traceback = allocate_bytes(obj_size)
  143. traces = tracemalloc._get_traces()
  144. trace = self.find_trace(traces, obj_traceback)
  145. self.assertIsInstance(trace, tuple)
  146. domain, size, traceback, length = trace
  147. self.assertEqual(size, obj_size)
  148. self.assertEqual(traceback, obj_traceback._frames)
  149. tracemalloc.stop()
  150. self.assertEqual(tracemalloc._get_traces(), [])
  151. def test_get_traces_intern_traceback(self):
  152. # dummy wrappers to get more useful and identical frames in the traceback
  153. def allocate_bytes2(size):
  154. return allocate_bytes(size)
  155. def allocate_bytes3(size):
  156. return allocate_bytes2(size)
  157. def allocate_bytes4(size):
  158. return allocate_bytes3(size)
  159. # Ensure that two identical tracebacks are not duplicated
  160. tracemalloc.stop()
  161. tracemalloc.start(4)
  162. obj_size = 123
  163. obj1, obj1_traceback = allocate_bytes4(obj_size)
  164. obj2, obj2_traceback = allocate_bytes4(obj_size)
  165. traces = tracemalloc._get_traces()
  166. obj1_traceback._frames = tuple(reversed(obj1_traceback._frames))
  167. obj2_traceback._frames = tuple(reversed(obj2_traceback._frames))
  168. trace1 = self.find_trace(traces, obj1_traceback)
  169. trace2 = self.find_trace(traces, obj2_traceback)
  170. domain1, size1, traceback1, length1 = trace1
  171. domain2, size2, traceback2, length2 = trace2
  172. self.assertIs(traceback2, traceback1)
  173. def test_get_traced_memory(self):
  174. # Python allocates some internals objects, so the test must tolerate
  175. # a small difference between the expected size and the real usage
  176. max_error = 2048
  177. # allocate one object
  178. obj_size = 1024 * 1024
  179. tracemalloc.clear_traces()
  180. obj, obj_traceback = allocate_bytes(obj_size)
  181. size, peak_size = tracemalloc.get_traced_memory()
  182. self.assertGreaterEqual(size, obj_size)
  183. self.assertGreaterEqual(peak_size, size)
  184. self.assertLessEqual(size - obj_size, max_error)
  185. self.assertLessEqual(peak_size - size, max_error)
  186. # destroy the object
  187. obj = None
  188. size2, peak_size2 = tracemalloc.get_traced_memory()
  189. self.assertLess(size2, size)
  190. self.assertGreaterEqual(size - size2, obj_size - max_error)
  191. self.assertGreaterEqual(peak_size2, peak_size)
  192. # clear_traces() must reset traced memory counters
  193. tracemalloc.clear_traces()
  194. self.assertEqual(tracemalloc.get_traced_memory(), (0, 0))
  195. # allocate another object
  196. obj, obj_traceback = allocate_bytes(obj_size)
  197. size, peak_size = tracemalloc.get_traced_memory()
  198. self.assertGreaterEqual(size, obj_size)
  199. # stop() also resets traced memory counters
  200. tracemalloc.stop()
  201. self.assertEqual(tracemalloc.get_traced_memory(), (0, 0))
  202. def test_clear_traces(self):
  203. obj, obj_traceback = allocate_bytes(123)
  204. traceback = tracemalloc.get_object_traceback(obj)
  205. self.assertIsNotNone(traceback)
  206. tracemalloc.clear_traces()
  207. traceback2 = tracemalloc.get_object_traceback(obj)
  208. self.assertIsNone(traceback2)
  209. def test_reset_peak(self):
  210. # Python allocates some internals objects, so the test must tolerate
  211. # a small difference between the expected size and the real usage
  212. tracemalloc.clear_traces()
  213. # Example: allocate a large piece of memory, temporarily
  214. large_sum = sum(list(range(100000)))
  215. size1, peak1 = tracemalloc.get_traced_memory()
  216. # reset_peak() resets peak to traced memory: peak2 < peak1
  217. tracemalloc.reset_peak()
  218. size2, peak2 = tracemalloc.get_traced_memory()
  219. self.assertGreaterEqual(peak2, size2)
  220. self.assertLess(peak2, peak1)
  221. # check that peak continue to be updated if new memory is allocated:
  222. # peak3 > peak2
  223. obj_size = 1024 * 1024
  224. obj, obj_traceback = allocate_bytes(obj_size)
  225. size3, peak3 = tracemalloc.get_traced_memory()
  226. self.assertGreaterEqual(peak3, size3)
  227. self.assertGreater(peak3, peak2)
  228. self.assertGreaterEqual(peak3 - peak2, obj_size)
  229. def test_is_tracing(self):
  230. tracemalloc.stop()
  231. self.assertFalse(tracemalloc.is_tracing())
  232. tracemalloc.start()
  233. self.assertTrue(tracemalloc.is_tracing())
  234. def test_snapshot(self):
  235. obj, source = allocate_bytes(123)
  236. # take a snapshot
  237. snapshot = tracemalloc.take_snapshot()
  238. # This can vary
  239. self.assertGreater(snapshot.traces[1].traceback.total_nframe, 10)
  240. # write on disk
  241. snapshot.dump(os_helper.TESTFN)
  242. self.addCleanup(os_helper.unlink, os_helper.TESTFN)
  243. # load from disk
  244. snapshot2 = tracemalloc.Snapshot.load(os_helper.TESTFN)
  245. self.assertEqual(snapshot2.traces, snapshot.traces)
  246. # tracemalloc must be tracing memory allocations to take a snapshot
  247. tracemalloc.stop()
  248. with self.assertRaises(RuntimeError) as cm:
  249. tracemalloc.take_snapshot()
  250. self.assertEqual(str(cm.exception),
  251. "the tracemalloc module must be tracing memory "
  252. "allocations to take a snapshot")
  253. def test_snapshot_save_attr(self):
  254. # take a snapshot with a new attribute
  255. snapshot = tracemalloc.take_snapshot()
  256. snapshot.test_attr = "new"
  257. snapshot.dump(os_helper.TESTFN)
  258. self.addCleanup(os_helper.unlink, os_helper.TESTFN)
  259. # load() should recreate the attribute
  260. snapshot2 = tracemalloc.Snapshot.load(os_helper.TESTFN)
  261. self.assertEqual(snapshot2.test_attr, "new")
  262. def fork_child(self):
  263. if not tracemalloc.is_tracing():
  264. return 2
  265. obj_size = 12345
  266. obj, obj_traceback = allocate_bytes(obj_size)
  267. traceback = tracemalloc.get_object_traceback(obj)
  268. if traceback is None:
  269. return 3
  270. # everything is fine
  271. return 0
  272. @support.requires_fork()
  273. def test_fork(self):
  274. # check that tracemalloc is still working after fork
  275. pid = os.fork()
  276. if not pid:
  277. # child
  278. exitcode = 1
  279. try:
  280. exitcode = self.fork_child()
  281. finally:
  282. os._exit(exitcode)
  283. else:
  284. support.wait_process(pid, exitcode=0)
  285. def test_no_incomplete_frames(self):
  286. tracemalloc.stop()
  287. tracemalloc.start(8)
  288. def f(x):
  289. def g():
  290. return x
  291. return g
  292. obj = f(0).__closure__[0]
  293. traceback = tracemalloc.get_object_traceback(obj)
  294. self.assertIn("test_tracemalloc", traceback[-1].filename)
  295. self.assertNotIn("test_tracemalloc", traceback[-2].filename)
  296. class TestSnapshot(unittest.TestCase):
  297. maxDiff = 4000
  298. def test_create_snapshot(self):
  299. raw_traces = [(0, 5, (('a.py', 2),), 10)]
  300. with contextlib.ExitStack() as stack:
  301. stack.enter_context(patch.object(tracemalloc, 'is_tracing',
  302. return_value=True))
  303. stack.enter_context(patch.object(tracemalloc, 'get_traceback_limit',
  304. return_value=5))
  305. stack.enter_context(patch.object(tracemalloc, '_get_traces',
  306. return_value=raw_traces))
  307. snapshot = tracemalloc.take_snapshot()
  308. self.assertEqual(snapshot.traceback_limit, 5)
  309. self.assertEqual(len(snapshot.traces), 1)
  310. trace = snapshot.traces[0]
  311. self.assertEqual(trace.size, 5)
  312. self.assertEqual(trace.traceback.total_nframe, 10)
  313. self.assertEqual(len(trace.traceback), 1)
  314. self.assertEqual(trace.traceback[0].filename, 'a.py')
  315. self.assertEqual(trace.traceback[0].lineno, 2)
  316. def test_filter_traces(self):
  317. snapshot, snapshot2 = create_snapshots()
  318. filter1 = tracemalloc.Filter(False, "b.py")
  319. filter2 = tracemalloc.Filter(True, "a.py", 2)
  320. filter3 = tracemalloc.Filter(True, "a.py", 5)
  321. original_traces = list(snapshot.traces._traces)
  322. # exclude b.py
  323. snapshot3 = snapshot.filter_traces((filter1,))
  324. self.assertEqual(snapshot3.traces._traces, [
  325. (0, 10, (('a.py', 2), ('b.py', 4)), 3),
  326. (0, 10, (('a.py', 2), ('b.py', 4)), 3),
  327. (0, 10, (('a.py', 2), ('b.py', 4)), 3),
  328. (1, 2, (('a.py', 5), ('b.py', 4)), 3),
  329. (3, 7, (('<unknown>', 0),), 1),
  330. ])
  331. # filter_traces() must not touch the original snapshot
  332. self.assertEqual(snapshot.traces._traces, original_traces)
  333. # only include two lines of a.py
  334. snapshot4 = snapshot3.filter_traces((filter2, filter3))
  335. self.assertEqual(snapshot4.traces._traces, [
  336. (0, 10, (('a.py', 2), ('b.py', 4)), 3),
  337. (0, 10, (('a.py', 2), ('b.py', 4)), 3),
  338. (0, 10, (('a.py', 2), ('b.py', 4)), 3),
  339. (1, 2, (('a.py', 5), ('b.py', 4)), 3),
  340. ])
  341. # No filter: just duplicate the snapshot
  342. snapshot5 = snapshot.filter_traces(())
  343. self.assertIsNot(snapshot5, snapshot)
  344. self.assertIsNot(snapshot5.traces, snapshot.traces)
  345. self.assertEqual(snapshot5.traces, snapshot.traces)
  346. self.assertRaises(TypeError, snapshot.filter_traces, filter1)
  347. def test_filter_traces_domain(self):
  348. snapshot, snapshot2 = create_snapshots()
  349. filter1 = tracemalloc.Filter(False, "a.py", domain=1)
  350. filter2 = tracemalloc.Filter(True, "a.py", domain=1)
  351. original_traces = list(snapshot.traces._traces)
  352. # exclude a.py of domain 1
  353. snapshot3 = snapshot.filter_traces((filter1,))
  354. self.assertEqual(snapshot3.traces._traces, [
  355. (0, 10, (('a.py', 2), ('b.py', 4)), 3),
  356. (0, 10, (('a.py', 2), ('b.py', 4)), 3),
  357. (0, 10, (('a.py', 2), ('b.py', 4)), 3),
  358. (2, 66, (('b.py', 1),), 1),
  359. (3, 7, (('<unknown>', 0),), 1),
  360. ])
  361. # include domain 1
  362. snapshot3 = snapshot.filter_traces((filter1,))
  363. self.assertEqual(snapshot3.traces._traces, [
  364. (0, 10, (('a.py', 2), ('b.py', 4)), 3),
  365. (0, 10, (('a.py', 2), ('b.py', 4)), 3),
  366. (0, 10, (('a.py', 2), ('b.py', 4)), 3),
  367. (2, 66, (('b.py', 1),), 1),
  368. (3, 7, (('<unknown>', 0),), 1),
  369. ])
  370. def test_filter_traces_domain_filter(self):
  371. snapshot, snapshot2 = create_snapshots()
  372. filter1 = tracemalloc.DomainFilter(False, domain=3)
  373. filter2 = tracemalloc.DomainFilter(True, domain=3)
  374. # exclude domain 2
  375. snapshot3 = snapshot.filter_traces((filter1,))
  376. self.assertEqual(snapshot3.traces._traces, [
  377. (0, 10, (('a.py', 2), ('b.py', 4)), 3),
  378. (0, 10, (('a.py', 2), ('b.py', 4)), 3),
  379. (0, 10, (('a.py', 2), ('b.py', 4)), 3),
  380. (1, 2, (('a.py', 5), ('b.py', 4)), 3),
  381. (2, 66, (('b.py', 1),), 1),
  382. ])
  383. # include domain 2
  384. snapshot3 = snapshot.filter_traces((filter2,))
  385. self.assertEqual(snapshot3.traces._traces, [
  386. (3, 7, (('<unknown>', 0),), 1),
  387. ])
  388. def test_snapshot_group_by_line(self):
  389. snapshot, snapshot2 = create_snapshots()
  390. tb_0 = traceback_lineno('<unknown>', 0)
  391. tb_a_2 = traceback_lineno('a.py', 2)
  392. tb_a_5 = traceback_lineno('a.py', 5)
  393. tb_b_1 = traceback_lineno('b.py', 1)
  394. tb_c_578 = traceback_lineno('c.py', 578)
  395. # stats per file and line
  396. stats1 = snapshot.statistics('lineno')
  397. self.assertEqual(stats1, [
  398. tracemalloc.Statistic(tb_b_1, 66, 1),
  399. tracemalloc.Statistic(tb_a_2, 30, 3),
  400. tracemalloc.Statistic(tb_0, 7, 1),
  401. tracemalloc.Statistic(tb_a_5, 2, 1),
  402. ])
  403. # stats per file and line (2)
  404. stats2 = snapshot2.statistics('lineno')
  405. self.assertEqual(stats2, [
  406. tracemalloc.Statistic(tb_a_5, 5002, 2),
  407. tracemalloc.Statistic(tb_c_578, 400, 1),
  408. tracemalloc.Statistic(tb_a_2, 30, 3),
  409. ])
  410. # stats diff per file and line
  411. statistics = snapshot2.compare_to(snapshot, 'lineno')
  412. self.assertEqual(statistics, [
  413. tracemalloc.StatisticDiff(tb_a_5, 5002, 5000, 2, 1),
  414. tracemalloc.StatisticDiff(tb_c_578, 400, 400, 1, 1),
  415. tracemalloc.StatisticDiff(tb_b_1, 0, -66, 0, -1),
  416. tracemalloc.StatisticDiff(tb_0, 0, -7, 0, -1),
  417. tracemalloc.StatisticDiff(tb_a_2, 30, 0, 3, 0),
  418. ])
  419. def test_snapshot_group_by_file(self):
  420. snapshot, snapshot2 = create_snapshots()
  421. tb_0 = traceback_filename('<unknown>')
  422. tb_a = traceback_filename('a.py')
  423. tb_b = traceback_filename('b.py')
  424. tb_c = traceback_filename('c.py')
  425. # stats per file
  426. stats1 = snapshot.statistics('filename')
  427. self.assertEqual(stats1, [
  428. tracemalloc.Statistic(tb_b, 66, 1),
  429. tracemalloc.Statistic(tb_a, 32, 4),
  430. tracemalloc.Statistic(tb_0, 7, 1),
  431. ])
  432. # stats per file (2)
  433. stats2 = snapshot2.statistics('filename')
  434. self.assertEqual(stats2, [
  435. tracemalloc.Statistic(tb_a, 5032, 5),
  436. tracemalloc.Statistic(tb_c, 400, 1),
  437. ])
  438. # stats diff per file
  439. diff = snapshot2.compare_to(snapshot, 'filename')
  440. self.assertEqual(diff, [
  441. tracemalloc.StatisticDiff(tb_a, 5032, 5000, 5, 1),
  442. tracemalloc.StatisticDiff(tb_c, 400, 400, 1, 1),
  443. tracemalloc.StatisticDiff(tb_b, 0, -66, 0, -1),
  444. tracemalloc.StatisticDiff(tb_0, 0, -7, 0, -1),
  445. ])
  446. def test_snapshot_group_by_traceback(self):
  447. snapshot, snapshot2 = create_snapshots()
  448. # stats per file
  449. tb1 = traceback(('a.py', 2), ('b.py', 4))
  450. tb2 = traceback(('a.py', 5), ('b.py', 4))
  451. tb3 = traceback(('b.py', 1))
  452. tb4 = traceback(('<unknown>', 0))
  453. stats1 = snapshot.statistics('traceback')
  454. self.assertEqual(stats1, [
  455. tracemalloc.Statistic(tb3, 66, 1),
  456. tracemalloc.Statistic(tb1, 30, 3),
  457. tracemalloc.Statistic(tb4, 7, 1),
  458. tracemalloc.Statistic(tb2, 2, 1),
  459. ])
  460. # stats per file (2)
  461. tb5 = traceback(('c.py', 578))
  462. stats2 = snapshot2.statistics('traceback')
  463. self.assertEqual(stats2, [
  464. tracemalloc.Statistic(tb2, 5002, 2),
  465. tracemalloc.Statistic(tb5, 400, 1),
  466. tracemalloc.Statistic(tb1, 30, 3),
  467. ])
  468. # stats diff per file
  469. diff = snapshot2.compare_to(snapshot, 'traceback')
  470. self.assertEqual(diff, [
  471. tracemalloc.StatisticDiff(tb2, 5002, 5000, 2, 1),
  472. tracemalloc.StatisticDiff(tb5, 400, 400, 1, 1),
  473. tracemalloc.StatisticDiff(tb3, 0, -66, 0, -1),
  474. tracemalloc.StatisticDiff(tb4, 0, -7, 0, -1),
  475. tracemalloc.StatisticDiff(tb1, 30, 0, 3, 0),
  476. ])
  477. self.assertRaises(ValueError,
  478. snapshot.statistics, 'traceback', cumulative=True)
  479. def test_snapshot_group_by_cumulative(self):
  480. snapshot, snapshot2 = create_snapshots()
  481. tb_0 = traceback_filename('<unknown>')
  482. tb_a = traceback_filename('a.py')
  483. tb_b = traceback_filename('b.py')
  484. tb_a_2 = traceback_lineno('a.py', 2)
  485. tb_a_5 = traceback_lineno('a.py', 5)
  486. tb_b_1 = traceback_lineno('b.py', 1)
  487. tb_b_4 = traceback_lineno('b.py', 4)
  488. # per file
  489. stats = snapshot.statistics('filename', True)
  490. self.assertEqual(stats, [
  491. tracemalloc.Statistic(tb_b, 98, 5),
  492. tracemalloc.Statistic(tb_a, 32, 4),
  493. tracemalloc.Statistic(tb_0, 7, 1),
  494. ])
  495. # per line
  496. stats = snapshot.statistics('lineno', True)
  497. self.assertEqual(stats, [
  498. tracemalloc.Statistic(tb_b_1, 66, 1),
  499. tracemalloc.Statistic(tb_b_4, 32, 4),
  500. tracemalloc.Statistic(tb_a_2, 30, 3),
  501. tracemalloc.Statistic(tb_0, 7, 1),
  502. tracemalloc.Statistic(tb_a_5, 2, 1),
  503. ])
  504. def test_trace_format(self):
  505. snapshot, snapshot2 = create_snapshots()
  506. trace = snapshot.traces[0]
  507. self.assertEqual(str(trace), 'b.py:4: 10 B')
  508. traceback = trace.traceback
  509. self.assertEqual(str(traceback), 'b.py:4')
  510. frame = traceback[0]
  511. self.assertEqual(str(frame), 'b.py:4')
  512. def test_statistic_format(self):
  513. snapshot, snapshot2 = create_snapshots()
  514. stats = snapshot.statistics('lineno')
  515. stat = stats[0]
  516. self.assertEqual(str(stat),
  517. 'b.py:1: size=66 B, count=1, average=66 B')
  518. def test_statistic_diff_format(self):
  519. snapshot, snapshot2 = create_snapshots()
  520. stats = snapshot2.compare_to(snapshot, 'lineno')
  521. stat = stats[0]
  522. self.assertEqual(str(stat),
  523. 'a.py:5: size=5002 B (+5000 B), count=2 (+1), average=2501 B')
  524. def test_slices(self):
  525. snapshot, snapshot2 = create_snapshots()
  526. self.assertEqual(snapshot.traces[:2],
  527. (snapshot.traces[0], snapshot.traces[1]))
  528. traceback = snapshot.traces[0].traceback
  529. self.assertEqual(traceback[:2],
  530. (traceback[0], traceback[1]))
  531. def test_format_traceback(self):
  532. snapshot, snapshot2 = create_snapshots()
  533. def getline(filename, lineno):
  534. return ' <%s, %s>' % (filename, lineno)
  535. with unittest.mock.patch('tracemalloc.linecache.getline',
  536. side_effect=getline):
  537. tb = snapshot.traces[0].traceback
  538. self.assertEqual(tb.format(),
  539. [' File "b.py", line 4',
  540. ' <b.py, 4>',
  541. ' File "a.py", line 2',
  542. ' <a.py, 2>'])
  543. self.assertEqual(tb.format(limit=1),
  544. [' File "a.py", line 2',
  545. ' <a.py, 2>'])
  546. self.assertEqual(tb.format(limit=-1),
  547. [' File "b.py", line 4',
  548. ' <b.py, 4>'])
  549. self.assertEqual(tb.format(most_recent_first=True),
  550. [' File "a.py", line 2',
  551. ' <a.py, 2>',
  552. ' File "b.py", line 4',
  553. ' <b.py, 4>'])
  554. self.assertEqual(tb.format(limit=1, most_recent_first=True),
  555. [' File "a.py", line 2',
  556. ' <a.py, 2>'])
  557. self.assertEqual(tb.format(limit=-1, most_recent_first=True),
  558. [' File "b.py", line 4',
  559. ' <b.py, 4>'])
  560. class TestFilters(unittest.TestCase):
  561. maxDiff = 2048
  562. def test_filter_attributes(self):
  563. # test default values
  564. f = tracemalloc.Filter(True, "abc")
  565. self.assertEqual(f.inclusive, True)
  566. self.assertEqual(f.filename_pattern, "abc")
  567. self.assertIsNone(f.lineno)
  568. self.assertEqual(f.all_frames, False)
  569. # test custom values
  570. f = tracemalloc.Filter(False, "test.py", 123, True)
  571. self.assertEqual(f.inclusive, False)
  572. self.assertEqual(f.filename_pattern, "test.py")
  573. self.assertEqual(f.lineno, 123)
  574. self.assertEqual(f.all_frames, True)
  575. # parameters passed by keyword
  576. f = tracemalloc.Filter(inclusive=False, filename_pattern="test.py", lineno=123, all_frames=True)
  577. self.assertEqual(f.inclusive, False)
  578. self.assertEqual(f.filename_pattern, "test.py")
  579. self.assertEqual(f.lineno, 123)
  580. self.assertEqual(f.all_frames, True)
  581. # read-only attribute
  582. self.assertRaises(AttributeError, setattr, f, "filename_pattern", "abc")
  583. def test_filter_match(self):
  584. # filter without line number
  585. f = tracemalloc.Filter(True, "abc")
  586. self.assertTrue(f._match_frame("abc", 0))
  587. self.assertTrue(f._match_frame("abc", 5))
  588. self.assertTrue(f._match_frame("abc", 10))
  589. self.assertFalse(f._match_frame("12356", 0))
  590. self.assertFalse(f._match_frame("12356", 5))
  591. self.assertFalse(f._match_frame("12356", 10))
  592. f = tracemalloc.Filter(False, "abc")
  593. self.assertFalse(f._match_frame("abc", 0))
  594. self.assertFalse(f._match_frame("abc", 5))
  595. self.assertFalse(f._match_frame("abc", 10))
  596. self.assertTrue(f._match_frame("12356", 0))
  597. self.assertTrue(f._match_frame("12356", 5))
  598. self.assertTrue(f._match_frame("12356", 10))
  599. # filter with line number > 0
  600. f = tracemalloc.Filter(True, "abc", 5)
  601. self.assertFalse(f._match_frame("abc", 0))
  602. self.assertTrue(f._match_frame("abc", 5))
  603. self.assertFalse(f._match_frame("abc", 10))
  604. self.assertFalse(f._match_frame("12356", 0))
  605. self.assertFalse(f._match_frame("12356", 5))
  606. self.assertFalse(f._match_frame("12356", 10))
  607. f = tracemalloc.Filter(False, "abc", 5)
  608. self.assertTrue(f._match_frame("abc", 0))
  609. self.assertFalse(f._match_frame("abc", 5))
  610. self.assertTrue(f._match_frame("abc", 10))
  611. self.assertTrue(f._match_frame("12356", 0))
  612. self.assertTrue(f._match_frame("12356", 5))
  613. self.assertTrue(f._match_frame("12356", 10))
  614. # filter with line number 0
  615. f = tracemalloc.Filter(True, "abc", 0)
  616. self.assertTrue(f._match_frame("abc", 0))
  617. self.assertFalse(f._match_frame("abc", 5))
  618. self.assertFalse(f._match_frame("abc", 10))
  619. self.assertFalse(f._match_frame("12356", 0))
  620. self.assertFalse(f._match_frame("12356", 5))
  621. self.assertFalse(f._match_frame("12356", 10))
  622. f = tracemalloc.Filter(False, "abc", 0)
  623. self.assertFalse(f._match_frame("abc", 0))
  624. self.assertTrue(f._match_frame("abc", 5))
  625. self.assertTrue(f._match_frame("abc", 10))
  626. self.assertTrue(f._match_frame("12356", 0))
  627. self.assertTrue(f._match_frame("12356", 5))
  628. self.assertTrue(f._match_frame("12356", 10))
  629. def test_filter_match_filename(self):
  630. def fnmatch(inclusive, filename, pattern):
  631. f = tracemalloc.Filter(inclusive, pattern)
  632. return f._match_frame(filename, 0)
  633. self.assertTrue(fnmatch(True, "abc", "abc"))
  634. self.assertFalse(fnmatch(True, "12356", "abc"))
  635. self.assertFalse(fnmatch(True, "<unknown>", "abc"))
  636. self.assertFalse(fnmatch(False, "abc", "abc"))
  637. self.assertTrue(fnmatch(False, "12356", "abc"))
  638. self.assertTrue(fnmatch(False, "<unknown>", "abc"))
  639. def test_filter_match_filename_joker(self):
  640. def fnmatch(filename, pattern):
  641. filter = tracemalloc.Filter(True, pattern)
  642. return filter._match_frame(filename, 0)
  643. # empty string
  644. self.assertFalse(fnmatch('abc', ''))
  645. self.assertFalse(fnmatch('', 'abc'))
  646. self.assertTrue(fnmatch('', ''))
  647. self.assertTrue(fnmatch('', '*'))
  648. # no *
  649. self.assertTrue(fnmatch('abc', 'abc'))
  650. self.assertFalse(fnmatch('abc', 'abcd'))
  651. self.assertFalse(fnmatch('abc', 'def'))
  652. # a*
  653. self.assertTrue(fnmatch('abc', 'a*'))
  654. self.assertTrue(fnmatch('abc', 'abc*'))
  655. self.assertFalse(fnmatch('abc', 'b*'))
  656. self.assertFalse(fnmatch('abc', 'abcd*'))
  657. # a*b
  658. self.assertTrue(fnmatch('abc', 'a*c'))
  659. self.assertTrue(fnmatch('abcdcx', 'a*cx'))
  660. self.assertFalse(fnmatch('abb', 'a*c'))
  661. self.assertFalse(fnmatch('abcdce', 'a*cx'))
  662. # a*b*c
  663. self.assertTrue(fnmatch('abcde', 'a*c*e'))
  664. self.assertTrue(fnmatch('abcbdefeg', 'a*bd*eg'))
  665. self.assertFalse(fnmatch('abcdd', 'a*c*e'))
  666. self.assertFalse(fnmatch('abcbdefef', 'a*bd*eg'))
  667. # replace .pyc suffix with .py
  668. self.assertTrue(fnmatch('a.pyc', 'a.py'))
  669. self.assertTrue(fnmatch('a.py', 'a.pyc'))
  670. if os.name == 'nt':
  671. # case insensitive
  672. self.assertTrue(fnmatch('aBC', 'ABc'))
  673. self.assertTrue(fnmatch('aBcDe', 'Ab*dE'))
  674. self.assertTrue(fnmatch('a.pyc', 'a.PY'))
  675. self.assertTrue(fnmatch('a.py', 'a.PYC'))
  676. else:
  677. # case sensitive
  678. self.assertFalse(fnmatch('aBC', 'ABc'))
  679. self.assertFalse(fnmatch('aBcDe', 'Ab*dE'))
  680. self.assertFalse(fnmatch('a.pyc', 'a.PY'))
  681. self.assertFalse(fnmatch('a.py', 'a.PYC'))
  682. if os.name == 'nt':
  683. # normalize alternate separator "/" to the standard separator "\"
  684. self.assertTrue(fnmatch(r'a/b', r'a\b'))
  685. self.assertTrue(fnmatch(r'a\b', r'a/b'))
  686. self.assertTrue(fnmatch(r'a/b\c', r'a\b/c'))
  687. self.assertTrue(fnmatch(r'a/b/c', r'a\b\c'))
  688. else:
  689. # there is no alternate separator
  690. self.assertFalse(fnmatch(r'a/b', r'a\b'))
  691. self.assertFalse(fnmatch(r'a\b', r'a/b'))
  692. self.assertFalse(fnmatch(r'a/b\c', r'a\b/c'))
  693. self.assertFalse(fnmatch(r'a/b/c', r'a\b\c'))
  694. # as of 3.5, .pyo is no longer munged to .py
  695. self.assertFalse(fnmatch('a.pyo', 'a.py'))
  696. def test_filter_match_trace(self):
  697. t1 = (("a.py", 2), ("b.py", 3))
  698. t2 = (("b.py", 4), ("b.py", 5))
  699. t3 = (("c.py", 5), ('<unknown>', 0))
  700. unknown = (('<unknown>', 0),)
  701. f = tracemalloc.Filter(True, "b.py", all_frames=True)
  702. self.assertTrue(f._match_traceback(t1))
  703. self.assertTrue(f._match_traceback(t2))
  704. self.assertFalse(f._match_traceback(t3))
  705. self.assertFalse(f._match_traceback(unknown))
  706. f = tracemalloc.Filter(True, "b.py", all_frames=False)
  707. self.assertFalse(f._match_traceback(t1))
  708. self.assertTrue(f._match_traceback(t2))
  709. self.assertFalse(f._match_traceback(t3))
  710. self.assertFalse(f._match_traceback(unknown))
  711. f = tracemalloc.Filter(False, "b.py", all_frames=True)
  712. self.assertFalse(f._match_traceback(t1))
  713. self.assertFalse(f._match_traceback(t2))
  714. self.assertTrue(f._match_traceback(t3))
  715. self.assertTrue(f._match_traceback(unknown))
  716. f = tracemalloc.Filter(False, "b.py", all_frames=False)
  717. self.assertTrue(f._match_traceback(t1))
  718. self.assertFalse(f._match_traceback(t2))
  719. self.assertTrue(f._match_traceback(t3))
  720. self.assertTrue(f._match_traceback(unknown))
  721. f = tracemalloc.Filter(False, "<unknown>", all_frames=False)
  722. self.assertTrue(f._match_traceback(t1))
  723. self.assertTrue(f._match_traceback(t2))
  724. self.assertTrue(f._match_traceback(t3))
  725. self.assertFalse(f._match_traceback(unknown))
  726. f = tracemalloc.Filter(True, "<unknown>", all_frames=True)
  727. self.assertFalse(f._match_traceback(t1))
  728. self.assertFalse(f._match_traceback(t2))
  729. self.assertTrue(f._match_traceback(t3))
  730. self.assertTrue(f._match_traceback(unknown))
  731. f = tracemalloc.Filter(False, "<unknown>", all_frames=True)
  732. self.assertTrue(f._match_traceback(t1))
  733. self.assertTrue(f._match_traceback(t2))
  734. self.assertFalse(f._match_traceback(t3))
  735. self.assertFalse(f._match_traceback(unknown))
  736. class TestCommandLine(unittest.TestCase):
  737. def test_env_var_disabled_by_default(self):
  738. # not tracing by default
  739. code = 'import tracemalloc; print(tracemalloc.is_tracing())'
  740. ok, stdout, stderr = assert_python_ok('-c', code)
  741. stdout = stdout.rstrip()
  742. self.assertEqual(stdout, b'False')
  743. @unittest.skipIf(interpreter_requires_environment(),
  744. 'Cannot run -E tests when PYTHON env vars are required.')
  745. def test_env_var_ignored_with_E(self):
  746. """PYTHON* environment variables must be ignored when -E is present."""
  747. code = 'import tracemalloc; print(tracemalloc.is_tracing())'
  748. ok, stdout, stderr = assert_python_ok('-E', '-c', code, PYTHONTRACEMALLOC='1')
  749. stdout = stdout.rstrip()
  750. self.assertEqual(stdout, b'False')
  751. def test_env_var_disabled(self):
  752. # tracing at startup
  753. code = 'import tracemalloc; print(tracemalloc.is_tracing())'
  754. ok, stdout, stderr = assert_python_ok('-c', code, PYTHONTRACEMALLOC='0')
  755. stdout = stdout.rstrip()
  756. self.assertEqual(stdout, b'False')
  757. def test_env_var_enabled_at_startup(self):
  758. # tracing at startup
  759. code = 'import tracemalloc; print(tracemalloc.is_tracing())'
  760. ok, stdout, stderr = assert_python_ok('-c', code, PYTHONTRACEMALLOC='1')
  761. stdout = stdout.rstrip()
  762. self.assertEqual(stdout, b'True')
  763. def test_env_limit(self):
  764. # start and set the number of frames
  765. code = 'import tracemalloc; print(tracemalloc.get_traceback_limit())'
  766. ok, stdout, stderr = assert_python_ok('-c', code, PYTHONTRACEMALLOC='10')
  767. stdout = stdout.rstrip()
  768. self.assertEqual(stdout, b'10')
  769. def check_env_var_invalid(self, nframe):
  770. with support.SuppressCrashReport():
  771. ok, stdout, stderr = assert_python_failure(
  772. '-c', 'pass',
  773. PYTHONTRACEMALLOC=str(nframe))
  774. if b'ValueError: the number of frames must be in range' in stderr:
  775. return
  776. if b'PYTHONTRACEMALLOC: invalid number of frames' in stderr:
  777. return
  778. self.fail(f"unexpected output: {stderr!a}")
  779. def test_env_var_invalid(self):
  780. for nframe in INVALID_NFRAME:
  781. with self.subTest(nframe=nframe):
  782. self.check_env_var_invalid(nframe)
  783. def test_sys_xoptions(self):
  784. for xoptions, nframe in (
  785. ('tracemalloc', 1),
  786. ('tracemalloc=1', 1),
  787. ('tracemalloc=15', 15),
  788. ):
  789. with self.subTest(xoptions=xoptions, nframe=nframe):
  790. code = 'import tracemalloc; print(tracemalloc.get_traceback_limit())'
  791. ok, stdout, stderr = assert_python_ok('-X', xoptions, '-c', code)
  792. stdout = stdout.rstrip()
  793. self.assertEqual(stdout, str(nframe).encode('ascii'))
  794. def check_sys_xoptions_invalid(self, nframe):
  795. args = ('-X', 'tracemalloc=%s' % nframe, '-c', 'pass')
  796. with support.SuppressCrashReport():
  797. ok, stdout, stderr = assert_python_failure(*args)
  798. if b'ValueError: the number of frames must be in range' in stderr:
  799. return
  800. if b'-X tracemalloc=NFRAME: invalid number of frames' in stderr:
  801. return
  802. self.fail(f"unexpected output: {stderr!a}")
  803. def test_sys_xoptions_invalid(self):
  804. for nframe in INVALID_NFRAME:
  805. with self.subTest(nframe=nframe):
  806. self.check_sys_xoptions_invalid(nframe)
  807. @unittest.skipIf(_testcapi is None, 'need _testcapi')
  808. def test_pymem_alloc0(self):
  809. # Issue #21639: Check that PyMem_Malloc(0) with tracemalloc enabled
  810. # does not crash.
  811. code = 'import _testcapi; _testcapi.test_pymem_alloc0(); 1'
  812. assert_python_ok('-X', 'tracemalloc', '-c', code)
  813. @unittest.skipIf(_testcapi is None, 'need _testcapi')
  814. class TestCAPI(unittest.TestCase):
  815. maxDiff = 80 * 20
  816. def setUp(self):
  817. if tracemalloc.is_tracing():
  818. self.skipTest("tracemalloc must be stopped before the test")
  819. self.domain = 5
  820. self.size = 123
  821. self.obj = allocate_bytes(self.size)[0]
  822. # for the type "object", id(obj) is the address of its memory block.
  823. # This type is not tracked by the garbage collector
  824. self.ptr = id(self.obj)
  825. def tearDown(self):
  826. tracemalloc.stop()
  827. def get_traceback(self):
  828. frames = _testcapi.tracemalloc_get_traceback(self.domain, self.ptr)
  829. if frames is not None:
  830. return tracemalloc.Traceback(frames)
  831. else:
  832. return None
  833. def track(self, release_gil=False, nframe=1):
  834. frames = get_frames(nframe, 1)
  835. _testcapi.tracemalloc_track(self.domain, self.ptr, self.size,
  836. release_gil)
  837. return frames
  838. def untrack(self):
  839. _testcapi.tracemalloc_untrack(self.domain, self.ptr)
  840. def get_traced_memory(self):
  841. # Get the traced size in the domain
  842. snapshot = tracemalloc.take_snapshot()
  843. domain_filter = tracemalloc.DomainFilter(True, self.domain)
  844. snapshot = snapshot.filter_traces([domain_filter])
  845. return sum(trace.size for trace in snapshot.traces)
  846. def check_track(self, release_gil):
  847. nframe = 5
  848. tracemalloc.start(nframe)
  849. size = tracemalloc.get_traced_memory()[0]
  850. frames = self.track(release_gil, nframe)
  851. self.assertEqual(self.get_traceback(),
  852. tracemalloc.Traceback(frames))
  853. self.assertEqual(self.get_traced_memory(), self.size)
  854. def test_track(self):
  855. self.check_track(False)
  856. def test_track_without_gil(self):
  857. # check that calling _PyTraceMalloc_Track() without holding the GIL
  858. # works too
  859. self.check_track(True)
  860. def test_track_already_tracked(self):
  861. nframe = 5
  862. tracemalloc.start(nframe)
  863. # track a first time
  864. self.track()
  865. # calling _PyTraceMalloc_Track() must remove the old trace and add
  866. # a new trace with the new traceback
  867. frames = self.track(nframe=nframe)
  868. self.assertEqual(self.get_traceback(),
  869. tracemalloc.Traceback(frames))
  870. def test_untrack(self):
  871. tracemalloc.start()
  872. self.track()
  873. self.assertIsNotNone(self.get_traceback())
  874. self.assertEqual(self.get_traced_memory(), self.size)
  875. # untrack must remove the trace
  876. self.untrack()
  877. self.assertIsNone(self.get_traceback())
  878. self.assertEqual(self.get_traced_memory(), 0)
  879. # calling _PyTraceMalloc_Untrack() multiple times must not crash
  880. self.untrack()
  881. self.untrack()
  882. def test_stop_track(self):
  883. tracemalloc.start()
  884. tracemalloc.stop()
  885. with self.assertRaises(RuntimeError):
  886. self.track()
  887. self.assertIsNone(self.get_traceback())
  888. def test_stop_untrack(self):
  889. tracemalloc.start()
  890. self.track()
  891. tracemalloc.stop()
  892. with self.assertRaises(RuntimeError):
  893. self.untrack()
  894. if __name__ == "__main__":
  895. unittest.main()