Non-intrusive FastAPI memory leak profiler with web UI
Project description
🔧 LeakySpanner
Zero-interference memory leak profiler for FastAPI.
Pure spectator mode — watches everything, touches nothing.
The Problem
FastAPI + Python async apps can silently grow to 4–5 GB RAM at peak load.
Finding where is painful because the leak could be anywhere — images held in memory,
uncleaned async tasks, connection pools, Gemini API response objects.
The Solution
LeakySpanner attaches to your FastAPI app as a pure spectator:
- ✅ Watches every request
- ✅ Samples memory in a background thread every N seconds
- ✅ Tracks memory delta per endpoint
- ✅ Uses
tracemallocto find top allocation sites - ✅ Writes a clean log file you can grep through
- ❌ Never blocks a request
- ❌ Never adds latency
- ❌ Never modifies request/response
- ❌ If LeakySpanner crashes — your app keeps running
Install
pip install leakyspanner
Usage
from fastapi import FastAPI
from leakyspanner import leakyspanner
app = FastAPI()
# One line — that's it
leakyspanner(app)
With options:
leakyspanner(
app,
enabled=True, # set False in tests
log_path="./leak_logs", # where to write logs
level="deep", # basic | mid | deep
interval_seconds=5.0, # memory snapshot every 5s
leak_threshold_mb=50.0, # warn if RSS grows 50MB above baseline
)
Levels
| Level | What it tracks |
|---|---|
basic |
Endpoint-level memory delta per request |
mid |
Endpoint deltas + tracemalloc top allocation stats (default) |
deep |
Everything + per-snapshot top allocation traces with file/line |
Log Output
LeakySpanner writes to ./leakyspanner_logs/leakyspanner_YYYYMMDD_HHMMSS.log
Snapshot log (every 5s):
[14:32:05] RSS=312.4MB VMS=890.1MB delta=+12.1MB GC=(142, 8, 1)
[14:32:10] RSS=318.7MB VMS=890.1MB delta=+18.4MB GC=(198, 8, 1)
Endpoint delta log (when delta > 1MB):
[14:32:07] ENDPOINT POST /ocr/process mem_delta=+6.23MB status=200
[14:32:08] ENDPOINT POST /ocr/process mem_delta=+5.91MB status=200
Warning (when growth > threshold):
⚠️ [14:35:00] MEMORY GROWTH ALERT: +87.3 MB above baseline
(baseline=312.4 MB, current=399.7 MB)
Session summary (on shutdown):
======================================================================
LEAKYSPANNER SESSION SUMMARY
======================================================================
Peak RSS : 4821.3 MB
Final RSS : 4103.7 MB
Baseline RSS: 312.4 MB
Net growth : +3791.3 MB
ENDPOINT MEMORY DELTAS (sorted by avg):
POST /ocr/process calls= 412 avg=+8.73MB max=+41.20MB
GET /health calls=1204 avg=+0.00MB max= +0.01MB
======================================================================
Deep level — tracemalloc traces:
── top allocations ──
42.3KB × 18 /app/services/gemini.py:87
38.1KB × 412 /app/services/ocr.py:134
21.7KB × 1 /usr/lib/python3.11/ssl.py:1092
Reading the Logs
Once you have the log, look for:
- Endpoint with highest
avgdelta → that's your primary suspect - Steadily rising RSS snapshots that never drops → classic leak
- tracemalloc lines pointing to specific file:line → that's the smoking gun
- GC counts growing without dropping → reference cycles not being collected
Real-world Example (OCR App)
app = FastAPI()
leakyspanner(
app,
level="deep",
log_path="/var/log/myapp/leaks",
leak_threshold_mb=200.0, # alert at 200MB growth
interval_seconds=10.0, # sample every 10s in prod
)
@app.post("/ocr/process")
async def process(file_id: str):
# LeakySpanner watches this — you change nothing
results = await asyncio.gather(
call_gemini_1(file_id),
call_gemini_2(file_id),
call_qr_service(file_id),
fetch_from_db(file_id),
)
return results
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file leakyspanner-0.1.2.tar.gz.
File metadata
- Download URL: leakyspanner-0.1.2.tar.gz
- Upload date:
- Size: 17.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0cb81e4eeecf931fa0ad28f0c8f1e4ab50736d4daac72de10011ec303b7c8189
|
|
| MD5 |
71670809483eb18a3ae08095418fb453
|
|
| BLAKE2b-256 |
98a011ababb2043d41273592f91aee030fc840a73b1e56189025c46972b22fcb
|
File details
Details for the file leakyspanner-0.1.2-py3-none-any.whl.
File metadata
- Download URL: leakyspanner-0.1.2-py3-none-any.whl
- Upload date:
- Size: 12.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ef1205410d9f5bab73e03809da2e3d6d35b41e936adcf154896ee0e8053c4577
|
|
| MD5 |
14c80b2484cc7fbd6bffead9cd896680
|
|
| BLAKE2b-256 |
afea220ece6f06221bd104a8c03cc7908d1438de44b22de9a3d0d134929e0f32
|