# Process Guide

Cross-platform DCC process monitoring, lifecycle management, and crash recovery.

## Overview

Provides:

- **Process Monitoring** — Live resource usage via `PyProcessMonitor` (CPU, memory, status)
- **DCC Launching** — Async spawn/terminate/kill via `PyDccLauncher`
- **Crash Recovery** — Restart policy with exponential/fixed backoff via `PyCrashRecoveryPolicy`
- **Background Watching** — Event polling via `PyProcessWatcher`

## PyProcessMonitor

Track and query process resource usage using `sysinfo`.

### Basic Usage

```python
import os
from dcc_mcp_core import PyProcessMonitor

monitor = PyProcessMonitor()

# Track current process
monitor.track(os.getpid(), "self")

# Refresh before querying
monitor.refresh()

# Query specific PID
info = monitor.query(os.getpid())
if info:
    print(f"Status: {info['status']}")
    print(f"CPU: {info['cpu_usage_percent']:.1f}%")
    print(f"Memory: {info['memory_bytes'] / 1024 / 1024:.1f} MB")
```

### Track/Untrack

```python
monitor = PyProcessMonitor()

# Track by PID
monitor.track(pid=1234, name="maya")

# Stop tracking
monitor.untrack(pid=1234)
```

### Query Methods

```python
monitor.refresh()

# Query single process
info = monitor.query(pid=1234)

# Query all tracked processes
all_info = monitor.list_all()
for info in all_info:
    print(f"{info['name']}: {info['cpu_usage_percent']}% CPU")

# Check if alive
if monitor.is_alive(pid=1234):
    print("Process is running")

# Count tracked
print(f"Tracking {monitor.tracked_count()} processes")
```

### Returned Dict Keys

| Key | Type | Description |
|-----|------|-------------|
| `pid` | `int` | Process ID |
| `name` | `str` | User-defined name |
| `status` | `str` | OS status string |
| `cpu_usage_percent` | `float` | CPU usage (0-100) |
| `memory_bytes` | `int` | Memory usage in bytes |
| `restart_count` | `int` | Restart count |

## PyDccLauncher

Launch and manage DCC processes asynchronously.

### Basic Launch

```python
from dcc_mcp_core import PyDccLauncher

launcher = PyDccLauncher()

# Launch a DCC
info = launcher.launch(
    name="maya",
    executable="/usr/autodesk/maya/bin/maya",
    args=["-prompt", "-batch"],
    launch_timeout_ms=30000,
)

print(f"Launched PID: {info['pid']}")
```

### Launch with Environment

```python
info = launcher.launch(
    name="nuke-mcp",
    executable="/opt/Nuke15.2/Nuke15.2",
    args=["--disable-nuke-frameserver", "project.nk"],
    launch_timeout_ms=60000,
    environment={
        "NUKE_DISABLE_FRAMESERVER": "1",
        "DCC_MCP_NUKE_PORT": "0",
    },
    working_directory="/projects/solar-system",
)
```

Environment values and the working directory apply only to the launched child.
The parent process environment is unchanged, so multiple isolated DCC sessions
can use different runtime policies safely.

### Process Lifecycle

```python
# Terminate gracefully
launcher.terminate("maya", timeout_ms=5000)

# Kill forcefully
launcher.kill("maya")

# Get PID by name
pid = launcher.pid_of("maya")
if pid:
    print(f"Maya running as PID {pid}")

# Check running count
print(f"Running: {launcher.running_count()} processes")

# Check restart count
print(f"Restart count: {launcher.restart_count('maya')}")
```

### Maya Example

```python
launcher = PyDccLauncher()

maya_info = launcher.launch(
    name="maya-2025",
    executable="/usr/autodesk/maya/bin/maya",
    args=["-prompt", "-batch"],
    launch_timeout_ms=60000,
)

print(f"Maya running as PID {maya_info['pid']}")

# ... do work ...

launcher.terminate("maya-2025")
```

## PyCrashRecoveryPolicy

Automatic restart policy with backoff strategies.

### Basic Policy

```python
from dcc_mcp_core import PyCrashRecoveryPolicy

policy = PyCrashRecoveryPolicy(max_restarts=3)
policy.use_exponential_backoff(initial_ms=1000, max_delay_ms=30000)

# Check if should restart
if policy.should_restart("crashed"):
    delay = policy.next_delay_ms("maya", attempt=0)
    print(f"Restarting in {delay}ms...")
```

### Fixed Backoff

```python
policy = PyCrashRecoveryPolicy(max_restarts=5)
policy.use_fixed_backoff(delay_ms=2000)

if policy.should_restart("unresponsive"):
    delay = policy.next_delay_ms("maya", attempt=0)
    print(f"Retrying in {delay}ms...")
```

### Exponential Backoff

```python
policy = PyCrashRecoveryPolicy(max_restarts=3)
policy.use_exponential_backoff(initial_ms=1000, max_delay_ms=30000)

# Attempt 0 -> 1000ms, Attempt 1 -> 2000ms, Attempt 2 -> 4000ms
for attempt in range(3):
    if policy.should_restart("crashed"):
        delay = policy.next_delay_ms("maya", attempt=attempt)
        print(f"Attempt {attempt}: waiting {delay}ms")
```

### Managing Policy State

```python
policy = PyCrashRecoveryPolicy(max_restarts=3)

# Check max_restarts limit
print(f"Max restarts: {policy.max_restarts}")

# Check restart eligibility
if policy.should_restart("crashed"):
    # Attempt restart
    pass
```

## PyProcessWatcher

Async background process watcher with event polling.

### Basic Watch

```python
import os
import time
from dcc_mcp_core import PyProcessWatcher

watcher = PyProcessWatcher(poll_interval_ms=200)
watcher.track(os.getpid(), "self")
watcher.start()

time.sleep(0.5)

# Poll for events
for event in watcher.poll_events():
    print(f"Event: {event['type']} - {event['name']}")

watcher.stop()
```

### Event Types

Event dicts contain: `type`, `pid`, `name`

| Event Type | Additional Fields |
|------------|-------------------|
| `heartbeat` | `new_status`, `cpu_usage_percent`, `memory_bytes` |
| `status_changed` | `old_status`, `new_status` |
| `exited` | — |

### Polling Pattern

```python
watcher = PyProcessWatcher(poll_interval_ms=500)
watcher.track(pid=1234, name="maya")
watcher.start()

try:
    while True:
        events = watcher.poll_events()
        for event in events:
            if event["type"] == "exited":
                print(f"{event['name']} exited")
            elif event["type"] == "heartbeat":
                print(f"CPU: {event['cpu_usage_percent']}%")
        time.sleep(0.1)
finally:
    watcher.stop()
```

### Start/Stop

```python
watcher = PyProcessWatcher()

watcher.track(pid=1234, name="maya")
watcher.start()

# ... do work ...

watcher.stop()

# Check status
print(f"Watcher running: {watcher.is_running()}")
print(f"Tracked: {watcher.tracked_count()}")
```

## Complete Example

### Auto-Restart DCC

```python
import time
from dcc_mcp_core import PyDccLauncher, PyProcessWatcher, PyCrashRecoveryPolicy

launcher = PyDccLauncher()
watcher = PyProcessWatcher(poll_interval_ms=500)
policy = PyCrashRecoveryPolicy(max_restarts=5)
policy.use_exponential_backoff(initial_ms=1000, max_delay_ms=30000)

# Launch Maya
info = launcher.launch(
    name="maya",
    executable="/usr/autodesk/maya/bin/maya",
    args=["-prompt"],
)
print(f"Launched Maya PID {info['pid']}")

watcher.track(info["pid"], "maya")
watcher.start()

attempt = 0
while True:
    events = watcher.poll_events()
    for event in events:
        if event["type"] == "exited":
            print("Maya exited")
            if policy.should_restart("crashed") and attempt < 5:
                delay = policy.next_delay_ms("maya", attempt=attempt)
                print(f"Restarting in {delay}ms...")
                time.sleep(delay / 1000)
                info = launcher.launch(
                    name="maya",
                    executable="/usr/autodesk/maya/bin/maya",
                    args=["-prompt"],
                )
                watcher.track(info["pid"], "maya")
                attempt += 1
            else:
                print("Max restarts exceeded")
                watcher.stop()
                exit(1)

    time.sleep(0.1)
```

## Best Practices

### 1. Always Refresh Before Query

```python
monitor.refresh()
info = monitor.query(pid=1234)  # Now has fresh data
```

### 2. Handle Missing Processes Gracefully

```python
info = monitor.query(pid=1234)
if info is None:
    print("Process not found")
else:
    print(f"CPU: {info['cpu_usage_percent']}%")
```

### 3. Use Appropriate Timeouts

```python
# Short timeout for quick operations
launcher.terminate("quick_proc", timeout_ms=2000)

# Longer timeout for DCC apps
launcher.terminate("maya", timeout_ms=10000)
```

### 4. Monitor Resource Usage

```python
def check_resources():
    monitor.refresh()
    for info in monitor.list_all():
        if info["cpu_usage_percent"] > 90:
            print(f"High CPU: {info['name']}")
        if info["memory_bytes"] > 10 * 1024 * 1024 * 1024:
            print(f"High memory: {info['name']}")
```
