Python works by using reference counting and a garbage collector of its own that reclaims back memory. It also manages its own heap memory, separately from the system heap. The heap is organized in fixed-size blocks, which are organized into pools, which are organized into arenas.

tracemalloc is a python package that allows us to programatically collect information about the memory.

tracemalloc.take_snapshot() will generate a snapshot element. Snapshots can be compared with their compare_to() method, which includes a key_type, to group the results by filename, lineno or traceback.

Example

Fast API endpoints

import tracemalloc  
tracemalloc.start(25)  
  
FIRST_SNAPSHOT: tracemalloc.Snapshot = None  
LAST_SNAPSHOT: tracemalloc.Snapshot = None
 
@router.get('/some/operation')
async def some_operation():
	response = {...}
	
	gc.collect()  
	global FIRST_SNAPSHOT  
	global LAST_SNAPSHOT  
	LAST_SNAPSHOT = tracemalloc.take_snapshot()  
	if FIRST_SNAPSHOT is None:  
	    FIRST_SNAPSHOT = LAST_SNAPSHOT  
 
	return response
 
@router.get('/memory/snapshots', response_class=PlainTextResponse)  
async def get_memory_snapshots(top: int = 10) -> str:  
    result = ''  
  
    global FIRST_SNAPSHOT  
    global LAST_SNAPSHOT  
    diff = LAST_SNAPSHOT.compare_to(FIRST_SNAPSHOT, 'filename')  
  
    for stat in diff[:top]:  
		result += f'{stat.size_diff / 1024:.2f} new KiB, '\  
		          f'{stat.size / 1024:.2f} total KiB, '\  
		          f'{stat.count_diff} new blocks, '\  
		          f'{stat.count} total blocks\n''  
        for line in stat.traceback.format():  
            result += line + '\n'  
        result += '\n'  
  
    return result

This will yield a result like the following when calling the GET /memory/snapshots endpoint:

0.00 new KiB, 602.31 total KiB, 0 new blocks, 6161 total blocks
  File "<frozen importlib._bootstrap_external>", line 0

0.00 new KiB, 264.03 total KiB, 0 new blocks, 18 total blocks
  File "/usr/local/lib/python3.8/gzip.py", line 0

0.00 new KiB, 254.15 total KiB, 0 new blocks, 25 total blocks
  File "/usr/local/lib/python3.8/site-packages/uvloop/__init__.py", line 0

0.00 new KiB, 179.01 total KiB, 0 new blocks, 1777 total blocks
  File "/usr/local/lib/python3.8/abc.py", line 0

0.00 new KiB, 147.27 total KiB, 0 new blocks, 1510 total blocks
  File "<frozen importlib._bootstrap>", line 0

0.00 new KiB, 134.92 total KiB, 0 new blocks, 1163 total blocks
  File "/usr/local/lib/python3.8/site-packages/fastapi/utils.py", line 0

0.00 new KiB, 74.03 total KiB, 0 new blocks, 959 total blocks
  File "/usr/local/lib/python3.8/site-packages/uvicorn/protocols/http/httptools_impl.py", line 0

0.00 new KiB, 59.04 total KiB, 0 new blocks, 605 total blocks
  File "/usr/local/lib/python3.8/site-packages/pytz/__init__.py", line 0

0.00 new KiB, 50.82 total KiB, 0 new blocks, 303 total blocks
  File "/usr/local/lib/python3.8/site-packages/websockets/exceptions.py", line 0

0.00 new KiB, 48.31 total KiB, 0 new blocks, 709 total blocks
  File "/usr/local/lib/python3.8/site-packages/fastapi/routing.py", line 0

Sources