# Memray Python API Reference

Use the Python API for programmatic profiling and custom analysis scripts.

## Tracker — Capture Allocations

```python
import memray

# Basic file capture
with memray.Tracker("output.bin"):
    my_function()

# Full options
with memray.Tracker(
    "output.bin",
    native_traces=True,           # Capture C/C++ frames (default: False)
    trace_python_allocators=True,  # Track pymalloc (default: False)
    follow_fork=False,             # Track child processes (default: False)
    memory_interval_ms=10,         # Memory snapshot interval (default: 10)
    file_format=memray.FileFormat.ALL_ALLOCATIONS,  # or AGGREGATED_ALLOCATIONS
):
    my_function()
```

### Constraints
- **Only one Tracker per process** — cannot nest or run concurrently
- Tracker instances are **not reusable** — create a fresh one each time
- Constructor blocks until client connects when using `SocketDestination`

### Destinations

```python
# File output (default)
dest = memray.FileDestination(
    path="output.bin",
    overwrite=False,       # Default: False
    compress_on_exit=True, # Default: True
)
with memray.Tracker(destination=dest):
    ...

# Socket output (for live monitoring)
dest = memray.SocketDestination(
    server_port=12345,
    address="127.0.0.1",  # Default
)
with memray.Tracker(destination=dest):
    ...
```

### File Formats

| Format | Description | Supports |
|--------|-------------|----------|
| `FileFormat.ALL_ALLOCATIONS` | Records every allocation | Temporary allocations, stats reporter, full analysis |
| `FileFormat.AGGREGATED_ALLOCATIONS` | Summarizes by location | Smaller files, high watermark, leaks. Cannot detect temps or use stats |

### Object Lifetime Tracking

```python
with memray.Tracker("output.bin", track_object_lifetimes=True) as tracker:
    my_function()

# After the context manager exits:
surviving = tracker.get_surviving_objects()
# Returns list of objects still alive at tracking end
```

Raises `RuntimeError` if `track_object_lifetimes` was not enabled.

---

## FileReader — Analyze Capture Files

```python
from memray import FileReader

reader = FileReader("output.bin")
```

### Metadata

```python
metadata = reader.metadata
metadata.peak_memory        # Peak memory in bytes
metadata.total_allocations  # Total allocation count
metadata.main_thread_id     # Main thread ID
```

### High Watermark Records (Peak Memory)

```python
# All threads merged
records = list(reader.get_high_watermark_allocation_records(merge_threads=True))

# Per-thread
records = list(reader.get_high_watermark_allocation_records(merge_threads=False))
```

### Leaked Allocation Records

```python
records = list(reader.get_leaked_allocation_records(merge_threads=True))
```

### AllocationRecord Properties

```python
for record in records:
    record.size             # Allocation size in bytes
    record.n_allocations    # Number of sub-allocations in this group
    record.tid              # Thread ID

    # Stack trace: list of (function, file, line) tuples
    stack = record.stack_trace()        # Python frames only
    hybrid = record.hybrid_stack_trace()  # Python + native frames (if captured)

    if stack:
        func, file, line = stack[0]  # Most recent frame
```

---

## Common Analysis Patterns

### Ranked Allocations at Peak

```python
from memray import FileReader

def analyze_peak(bin_path, top_n=20, min_mb=0.5):
    reader = FileReader(bin_path)
    records = list(reader.get_high_watermark_allocation_records())
    records.sort(key=lambda r: r.size, reverse=True)

    total = sum(r.size for r in records)
    print(f"Total at peak: {total / 1024**2:.1f} MB")
    print(f"Allocations at peak: {len(records)}")

    for i, record in enumerate(records):
        size_mb = record.size / 1024**2
        if size_mb < min_mb or i >= top_n:
            break
        pct = (record.size / total) * 100
        stack = record.stack_trace()
        loc = f"{stack[0][0]} @ {stack[0][1]}:{stack[0][2]}" if stack else "unknown"
        print(f"  #{i+1:3d}  {size_mb:8.2f} MB  ({pct:4.1f}%)  {loc}")
```

### Compare Two Captures (A/B Testing)

```python
def compare_peaks(bin_a, bin_b):
    reader_a = FileReader(bin_a)
    reader_b = FileReader(bin_b)

    peak_a = reader_a.metadata.peak_memory / 1024**2
    peak_b = reader_b.metadata.peak_memory / 1024**2
    delta = peak_b - peak_a

    print(f"A: {peak_a:.1f} MB")
    print(f"B: {peak_b:.1f} MB")
    print(f"Delta: {delta:+.1f} MB ({delta/peak_a*100:+.1f}%)")
```

### Find Allocations by Module/Function

```python
def find_allocations_by(bin_path, pattern):
    """Find allocations where any frame matches pattern."""
    reader = FileReader(bin_path)
    records = reader.get_high_watermark_allocation_records()

    matches = []
    for record in records:
        for func, file, line in record.stack_trace():
            if pattern in func or pattern in (file or ""):
                matches.append(record)
                break

    matches.sort(key=lambda r: r.size, reverse=True)
    total = sum(r.size for r in matches)
    print(f"Found {len(matches)} allocations matching '{pattern}': {total/1024**2:.1f} MB")
    return matches
```

### Leak Analysis with Filtering

```python
def find_real_leaks(bin_path, min_kb=10, ignore_patterns=None):
    """Find leaked allocations, filtering out known false positives."""
    ignore_patterns = ignore_patterns or ["lru_cache", "re.compile", "logging"]
    reader = FileReader(bin_path)
    records = reader.get_leaked_allocation_records(merge_threads=True)

    for record in sorted(records, key=lambda r: r.size, reverse=True):
        if record.size < min_kb * 1024:
            continue

        stack = record.stack_trace()
        if not stack:
            continue

        # Filter known false positives
        skip = False
        for func, file, line in stack:
            for pattern in ignore_patterns:
                if pattern in func or pattern in (file or ""):
                    skip = True
                    break
            if skip:
                break

        if not skip:
            func, file, line = stack[0]
            print(f"  {record.size/1024:.1f} KB leaked at {func} @ {file}:{line}")
```

---

## Integration with memray CLI

The Python API and CLI are fully interoperable. Capture with the API, analyze with CLI:

```python
# Capture programmatically
with memray.Tracker("/tmp/capture.bin", native_traces=True):
    run_workload()
```

```bash
# Analyze with CLI reporters
memray flamegraph /tmp/capture.bin -o /tmp/flame.html
memray stats /tmp/capture.bin
```

Or capture with CLI, analyze programmatically:

```bash
memray run --native -o /tmp/capture.bin my_script.py
```

```python
# Custom analysis
from memray import FileReader
reader = FileReader("/tmp/capture.bin")
# ... analyze records
```
