memray tracks every Python memory allocation. pip install memray. CLI: python -m memray run -o out.bin script.py. Flamegraph: python -m memray flamegraph out.bin → HTML. Table: python -m memray table out.bin. Tree: python -m memray tree out.bin. Stats: python -m memray stats out.bin. Live: python -m memray run --live script.py. Attach: python -m memray attach <pid>. Programmatic: import memray; with memray.Tracker("out.bin"): .... Native: memray run --native script.py — tracks C extensions too. Filter: memray flamegraph --filter-allocations 100 out.bin — only show ≥100 byte allocs. Peak: stats show peak RSS and heap. No-native for speed. Async: works with asyncio; each coroutine tracked. memray run --follow-fork for subprocess tracking. pytest plugin: pip install pytest-memray; pytest --memray --most-allocations 5. @pytest.mark.limit_memory("50MB") decorator. memray.Tracker(destination=memray.FileDestination("out.bin"), native_traces=True). Reader API: from memray._memray import FileReader; reader = FileReader("out.bin"). reader.get_high_watermark_allocation_records(). reader.get_leaked_allocation_records(top_k=10). Size filter: FileReader(...).get_leaked_allocation_records(top_k=5). memray transform speedscope out.bin -o out.speedscope.json. Claude Code generates memray Tracker context managers, pytest memory budgets, and leak detection pipelines.
CLAUDE.md for memray
## memray Stack
- Version: memray >= 1.8 | pip install memray
- CLI: python -m memray run -o out.bin script.py
- Profile: python -m memray flamegraph out.bin → interactive HTML report
- In-process: with memray.Tracker("out.bin"): ... → captures allocations
- pytest: pytest --memray | @pytest.mark.limit_memory("50 MB")
- Reader: FileReader("out.bin").get_high_watermark_allocation_records()
memray Memory Profiling Pipeline
# app/mem_profile.py — memray Tracker, FileReader, leak detector, and pytest helpers
from __future__ import annotations
import io
import subprocess
import sys
from contextlib import contextmanager
from pathlib import Path
from typing import Any, Generator
# ─────────────────────────────────────────────────────────────────────────────
# 1. Tracker context managers
# ─────────────────────────────────────────────────────────────────────────────
@contextmanager
def profile_memory(
output: str | Path = "/tmp/memray_output.bin",
native_traces: bool = False,
follow_fork: bool = False,
) -> Generator[Path, None, None]:
"""
Context manager that records all memory allocations to a .bin file.
Yields the output path for use in reports.
Usage:
with profile_memory("/tmp/my_app.bin") as bin_path:
run_expensive_function()
generate_flamegraph(bin_path, "/tmp/flamegraph.html")
native_traces=True: also track allocations from C extensions (slower).
follow_fork=True: track child processes.
"""
try:
import memray
except ImportError as e:
raise ImportError("pip install memray") from e
out_path = Path(output)
out_path.unlink(missing_ok=True)
dest = memray.FileDestination(str(out_path), overwrite=True)
with memray.Tracker(destination=dest, native_traces=native_traces,
follow_fork=follow_fork):
yield out_path
@contextmanager
def profile_memory_string(
native_traces: bool = False,
) -> Generator[io.BytesIO, None, None]:
"""
Context manager that records allocations to an in-memory buffer.
Yields the BytesIO buffer (readable after the context exits).
Usage:
with profile_memory_string() as buf:
run_function()
buf.seek(0)
# pass buf to FileReader
"""
try:
import memray
except ImportError as e:
raise ImportError("pip install memray") from e
buf = io.BytesIO()
dest = memray.SocketDestination # fallback: use file if no socket
# Use file-based approach for compatibility
import tempfile, os
with tempfile.NamedTemporaryFile(suffix=".bin", delete=False) as tmp:
tmp_path = tmp.name
try:
dest_f = memray.FileDestination(tmp_path, overwrite=True)
with memray.Tracker(destination=dest_f, native_traces=native_traces):
yield buf
# Copy to buffer after context
buf.write(Path(tmp_path).read_bytes())
buf.seek(0)
finally:
Path(tmp_path).unlink(missing_ok=True)
# ─────────────────────────────────────────────────────────────────────────────
# 2. Report generators (shell out to memray CLI)
# ─────────────────────────────────────────────────────────────────────────────
def generate_flamegraph(
bin_path: str | Path,
output_html: str | Path | None = None,
leaks: bool = False,
show_memory_leaks: bool = False,
) -> Path:
"""
Generate an HTML flamegraph from a memray .bin file.
Returns path to the generated HTML file.
leaks=True: include --leaks flag to focus on unfreed allocations.
"""
bin_p = Path(bin_path)
out_p = Path(output_html) if output_html else bin_p.with_suffix(".html")
cmd = [sys.executable, "-m", "memray", "flamegraph", str(bin_p),
"-o", str(out_p), "--force"]
if leaks or show_memory_leaks:
cmd.append("--leaks")
subprocess.run(cmd, check=True, capture_output=True)
return out_p
def generate_table_report(bin_path: str | Path) -> str:
"""
Run `memray table` and return the text output.
"""
result = subprocess.run(
[sys.executable, "-m", "memray", "table", str(bin_path)],
capture_output=True, text=True, check=False,
)
return result.stdout or result.stderr
def get_stats(bin_path: str | Path) -> dict[str, Any]:
"""
Run `memray stats` and return a dict with key metrics.
Parses: total allocations, peak memory (bytes), number of leaked objects.
"""
result = subprocess.run(
[sys.executable, "-m", "memray", "stats", str(bin_path), "--json"],
capture_output=True, text=True, check=False,
)
if result.returncode == 0 and result.stdout.strip():
import json
try:
return json.loads(result.stdout)
except json.JSONDecodeError:
pass
# Fallback: return raw output
return {"raw": result.stdout + result.stderr}
# ─────────────────────────────────────────────────────────────────────────────
# 3. FileReader API (programmatic analysis)
# ─────────────────────────────────────────────────────────────────────────────
def analyze_allocations(
bin_path: str | Path,
top_k: int = 10,
leaks_only: bool = False,
) -> list[dict[str, Any]]:
"""
Read a memray .bin file and return the top allocation records.
Returns list of {size, n_allocations, stack_trace} dicts.
leaks_only=True: return only allocations not freed at end of run.
"""
try:
from memray._memray import FileReader
except ImportError as e:
raise ImportError("pip install memray") from e
reader = FileReader(str(bin_path))
records = (
reader.get_leaked_allocation_records(top_k=top_k)
if leaks_only
else reader.get_high_watermark_allocation_records(top_k=top_k)
)
results = []
for record in records:
results.append({
"size": record.size,
"n_allocations": record.n_allocations,
"stack": [str(frame) for frame in record.stack_trace()],
})
return results
def peak_memory_bytes(bin_path: str | Path) -> int:
"""Return peak heap memory from a memray bin file in bytes."""
try:
from memray._memray import FileReader
reader = FileReader(str(bin_path))
records = reader.get_high_watermark_allocation_records(top_k=1)
# Sum total size at high watermark
total = sum(r.size for r in
reader.get_high_watermark_allocation_records(top_k=10000))
return total
except Exception:
return -1
# ─────────────────────────────────────────────────────────────────────────────
# 4. Memory budget checker (for tests / CI)
# ─────────────────────────────────────────────────────────────────────────────
class MemoryBudget:
"""
Assert that a code block does not exceed a peak memory budget.
Usage:
with MemoryBudget(max_mb=50) as budget:
process_large_file("data.csv")
budget.assert_ok() # raises if peak > 50 MB
"""
def __init__(self, max_mb: float, tmp_dir: str | Path = "/tmp"):
self.max_mb = max_mb
self._bin = Path(tmp_dir) / "budget_check.bin"
self._peak_mb: float | None = None
self._ctx = None
def __enter__(self):
self._bin.unlink(missing_ok=True)
try:
import memray
dest = memray.FileDestination(str(self._bin), overwrite=True)
self._ctx = memray.Tracker(destination=dest).__enter__()
except ImportError:
pass
return self
def __exit__(self, *args):
if self._ctx is not None:
import memray
memray.Tracker.__exit__(self._ctx, *args) # type: ignore[arg-type]
@property
def peak_mb(self) -> float:
if self._peak_mb is None and self._bin.exists():
raw = peak_memory_bytes(self._bin)
self._peak_mb = raw / 1_048_576 if raw > 0 else 0.0
return self._peak_mb or 0.0
def assert_ok(self, msg: str = "") -> None:
peak = self.peak_mb
if peak > self.max_mb:
raise AssertionError(
f"Memory budget exceeded: {peak:.1f} MB > {self.max_mb:.1f} MB"
+ (f" — {msg}" if msg else "")
)
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
BIN = Path("/tmp/memray_demo.bin")
with profile_memory(BIN) as path:
# Allocate ~10 MB of data
big = [bytearray(1024) for _ in range(10_000)]
del big
print(f"Profiling bin: {path}")
print("\n=== Stats ===")
stats = get_stats(BIN)
print(stats)
print("\n=== Top allocations ===")
allocs = analyze_allocations(BIN, top_k=3)
for a in allocs:
print(f" {a['size']:,} bytes × {a['n_allocations']:,} allocs")
if a["stack"]:
print(f" → {a['stack'][0]}")
print("\n=== Flamegraph ===")
html = generate_flamegraph(BIN, "/tmp/memray_demo.html")
print(f" Flamegraph: {html}")
print("\n=== Memory budget ===")
with MemoryBudget(max_mb=200) as budget:
moderate = [bytearray(1024) for _ in range(50_000)]
del moderate
print(f" Peak: {budget.peak_mb:.1f} MB (budget: 200 MB)")
budget.assert_ok("moderate allocation")
print(" Budget OK")
For the tracemalloc stdlib alternative — tracemalloc (built in to Python 3.4+) records allocation snapshots and shows top memory consumers, but it only captures Python-level allocations at fixed points in time; memray continuously records every allocation including C extension calls, produces interactive flamegraph HTML, and integrates with pytest for per-test memory budgets with @pytest.mark.limit_memory(). For the memory_profiler (@profile decorator) alternative — memory_profiler instruments individual functions line-by-line and reports RSS growth per line, which is useful for finding which line in a function allocates; memray tracks all allocations across the entire process in a single pass and provides flamegraph visualization — use memory_profiler for per-line granularity, memray for full-call-graph memory profiling. The Claude Skills 360 bundle includes memray skill sets covering profile_memory() context manager, profile_memory_string() in-memory variant, generate_flamegraph()/generate_table_report()/get_stats() CLI wrappers, analyze_allocations()/peak_memory_bytes() FileReader API, MemoryBudget context manager for CI assertions, pytest —memray integration notes, and native_traces + follow_fork options. Start with the free tier to try memory profiling code generation.