Skip to main content

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 tracemalloc to 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:

  1. Endpoint with highest avg delta → that's your primary suspect
  2. Steadily rising RSS snapshots that never drops → classic leak
  3. tracemalloc lines pointing to specific file:line → that's the smoking gun
  4. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

leakyspanner-0.1.2.tar.gz (17.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

leakyspanner-0.1.2-py3-none-any.whl (12.0 kB view details)

Uploaded Python 3

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

Hashes for leakyspanner-0.1.2.tar.gz
Algorithm Hash digest
SHA256 0cb81e4eeecf931fa0ad28f0c8f1e4ab50736d4daac72de10011ec303b7c8189
MD5 71670809483eb18a3ae08095418fb453
BLAKE2b-256 98a011ababb2043d41273592f91aee030fc840a73b1e56189025c46972b22fcb

See more details on using hashes here.

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

Hashes for leakyspanner-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 ef1205410d9f5bab73e03809da2e3d6d35b41e936adcf154896ee0e8053c4577
MD5 14c80b2484cc7fbd6bffead9cd896680
BLAKE2b-256 afea220ece6f06221bd104a8c03cc7908d1438de44b22de9a3d0d134929e0f32

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page