# Monitoring System
The monitoring system provides real-time monitoring of Stata processes during execution. This feature is designed to prevent resource exhaustion and improve system stability.
## Overview
The monitoring system is **disabled by default** to maintain 100% backward compatibility. When enabled, it monitors Stata subprocess execution and can automatically terminate processes that exceed configured resource limits.
### Current Features
- **RAM Monitoring**: Track memory usage and terminate processes exceeding RAM limits
- **Cross-Platform**: Works on macOS, Linux, and Windows using `psutil`
- **Non-Invasive**: Minimal overhead when disabled, daemon thread when enabled
- **Extensible**: Abstract base class for adding new monitor types (e.g., timeout monitoring)
## Architecture
### MonitorBase
Abstract base class for all monitor implementations:
```python
class MonitorBase(ABC):
@abstractmethod
def start(self, process: Any) -> None:
"""Start monitoring the given process."""
pass
@abstractmethod
def stop(self) -> None:
"""Stop monitoring."""
pass
```
### RAMMonitor
Monitors RAM usage of Stata processes:
- **Check Interval**: 0.5 seconds
- **Metric**: RSS (Resident Set Size) in MB
- **Action**: Kills process when limit exceeded
- **Error**: Raises `RAMLimitExceededError` with details
## Configuration
### Enable Monitoring
Monitoring is controlled by two configuration options:
#### Option 1: Configuration File
Edit `~/.statamcp/config.toml`:
```toml
[MONITOR]
IS_MONITOR = true
MAX_RAM_MB = 8192 # 8 GB limit
```
#### Option 2: Environment Variables
```bash
export STATA_MCP__IS_MONITOR=true
export STATA_MCP__RAM_LIMIT=8192
```
### Configuration Priority
1. Environment variables (highest)
2. Config file (`~/.statamcp/config.toml`)
3. Default values (lowest)
### RAM Limit Values
- `-1` or `None`: No limit (default)
- `0`: Not recommended (will kill immediately)
- Positive value: RAM limit in MB
Examples:
```bash
# No limit (default)
export STATA_MCP__RAM_LIMIT=-1
# 4 GB limit
export STATA_MCP__RAM_LIMIT=4096
# 8 GB limit
export STATA_MCP__RAM_LIMIT=8192
# 16 GB limit
export STATA_MCP__RAM_LIMIT=16384
```
## Usage
### Basic Setup
1. **Enable monitoring in config**:
```bash
export STATA_MCP__IS_MONITOR=true
export STATA_MCP__RAM_LIMIT=8192
```
2. **Run Stata-MCP normally**:
```bash
stata-mcp
# or
stata-mcp --agent
```
3. **Monitoring is automatic** when enabled:
- No code changes needed
- Works with all MCP tools
- Integrates seamlessly with existing workflows
### Programmatic Usage
If you're using Stata-MCP as a library:
```python
from stata_mcp.monitor import RAMMonitor
from stata_mcp.core.stata import StataDo
# Create monitor with 8GB limit
monitor = RAMMonitor(max_ram_mb=8192)
# Pass to StataDo
stata = StataDo(
dofile_path="analysis.do",
monitors=[monitor] # Optional: list of monitors
)
# Execution is automatically monitored
result = stata.execute()
```
## Behavior
### When RAM Limit is Exceeded
1. **Detection**: Monitor detects RAM usage > limit
2. **Logging**: Warning logged with details
3. **Termination**: Process is killed immediately
4. **Error**: `RAMLimitExceededError` is raised
### Example Output
```
WARNING: RAM limit exceeded: 8256MB > 8192MB. Killing Stata process (PID: 12345)
ERROR: RAM limit exceeded: Used 8256MB, Limit 8192MB
```
### Normal Completion
If process finishes before exceeding limit:
- Monitor stops gracefully
- No errors raised
- Normal execution flow continues
## Performance Considerations
### Overhead
- **Disabled**: Zero overhead (default)
- **Enabled**: Minimal overhead from daemon thread
- One RAM check every 0.5 seconds
- Uses `psutil` for cross-platform compatibility
### Recommendations
For **production environments**:
```toml
[MONITOR]
IS_MONITOR = true
MAX_RAM_MB = 16384 # Set reasonable limit for your hardware
```
For **development environments**:
```toml
[MONITOR]
IS_MONITOR = false # Disable to avoid accidental termination
```
For **high-performance computing**:
```toml
[MONITOR]
IS_MONITOR = true
MAX_RAM_MB = 65536 # 64 GB for large datasets
```
## Error Handling
### RAMLimitExceededError
Raised when RAM limit is exceeded:
```python
from stata_mcp.core.types import RAMLimitExceededError
try:
result = stata.execute()
except RAMLimitExceededError as e:
print(f"RAM exceeded: {e.ram_used_mb:.0f}MB > {e.ram_limit_mb}MB")
# Handle error: save work, notify user, etc.
```
Error attributes:
- `ram_used_mb`: Actual RAM used when limit exceeded
- `ram_limit_mb`: Configured RAM limit
## Extensibility
### Creating Custom Monitors
You can create custom monitors by extending `MonitorBase`:
```python
from stata_mcp.monitor.base import MonitorBase
import time
import threading
class TimeoutMonitor(MonitorBase):
"""Monitor and timeout long-running processes."""
def __init__(self, timeout_seconds: int):
self.timeout = timeout_seconds
self._start_time = None
self._monitor_thread = None
self._stop_event = threading.Event()
def start(self, process):
"""Start timeout monitoring."""
self._start_time = time.time()
self.process = process
def monitor_loop():
while not self._stop_event.is_set():
if time.time() - self._start_time > self.timeout:
self.process.kill()
break
self._stop_event.wait(1)
self._monitor_thread = threading.Thread(
target=monitor_loop,
daemon=True
)
self._monitor_thread.start()
def stop(self):
"""Stop monitoring."""
self._stop_event.set()
if self._monitor_thread:
self._monitor_thread.join(timeout=1.0)
```
### Using Multiple Monitors
You can use multiple monitors simultaneously:
```python
from stata_mcp.monitor import RAMMonitor, TimeoutMonitor
# Create multiple monitors
ram_monitor = RAMMonitor(max_ram_mb=8192)
timeout_monitor = TimeoutMonitor(timeout_seconds=3600)
# Pass to StataDo
stata = StataDo(
dofile_path="analysis.do",
monitors=[ram_monitor, timeout_monitor]
)
```
## Troubleshooting
### Monitor Not Working
1. **Check if monitoring is enabled**:
```bash
echo $STATA_MCP__IS_MONITOR
```
2. **Verify config file syntax**:
```bash
cat ~/.statamcp/config.toml
```
3. **Check for environment variable conflicts**:
```bash
env | grep STATA_MCP
```
### Process Killed Unexpectedly
1. **Check RAM limit is reasonable**:
```bash
echo $STATA_MCP__RAM_LIMIT
```
2. **Review logs for termination reason**:
```bash
tail -f ~/.statamcp/stata_mcp_debug.log
```
3. **Increase limit if needed**:
```bash
export STATA_MCP__RAM_LIMIT=16384
```
### psutil Issues
If you encounter `psutil` errors:
1. **Ensure psutil is installed**:
```bash
uv pip install psutil>=6.0.0
```
2. **Check psutil version**:
```bash
python3 -c "import psutil; print(psutil.__version__)"
```
3. **Verify process access permissions** (Linux/macOS):
```bash
# Monitor should have access to child processes
# No special action needed for child processes
```
## Best Practices
### 1. Start with No Limit
For initial testing:
```bash
export STATA_MCP__IS_MONITOR=true
export STATA_MCP__RAM_LIMIT=-1 # No limit initially
```
### 2. Monitor Typical Usage
Run typical workloads and observe RAM usage in logs.
### 3. Set Reasonable Limit
Set limit 20-50% above typical usage:
```bash
# If typical usage is 6GB
export STATA_MCP__RAM_LIMIT=8192 # 8GB limit
```
### 4. Test Edge Cases
Test with large datasets to ensure limit is appropriate.
### 5. Document Limits
Document RAM limits in project documentation for team members.
## Security Considerations
- Monitor runs in daemon thread (terminated when main thread exits)
- No privilege escalation required
- Uses standard `psutil` library for cross-platform compatibility
- Monitor only has access to child processes it creates
## Future Enhancements
Potential future monitor types:
- **Timeout Monitor**: Limit execution time
- **CPU Monitor**: Track CPU usage percentage
- **Disk I/O Monitor**: Monitor disk read/write operations
- **Network Monitor**: Track network activity (if applicable)
## Notes
- This feature was developed with assistance from Claude Code and GLM-4.7
- Monitoring is **opt-in** via configuration
- When disabled, behavior is 100% identical to previous versions
- Monitor system designed for extensibility to support future use cases