claude-mpm 3.9.7__py3-none-any.whl → 3.9.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/base_agent.json +1 -1
- claude_mpm/agents/templates/ticketing.json +1 -1
- claude_mpm/cli/__init__.py +3 -1
- claude_mpm/cli/commands/__init__.py +3 -1
- claude_mpm/cli/commands/cleanup.py +21 -1
- claude_mpm/cli/commands/mcp.py +821 -0
- claude_mpm/cli/parser.py +148 -1
- claude_mpm/config/memory_guardian_config.py +325 -0
- claude_mpm/constants.py +13 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +76 -19
- claude_mpm/models/state_models.py +433 -0
- claude_mpm/services/__init__.py +28 -0
- claude_mpm/services/communication/__init__.py +2 -2
- claude_mpm/services/communication/socketio.py +18 -16
- claude_mpm/services/infrastructure/__init__.py +4 -1
- claude_mpm/services/infrastructure/logging.py +3 -3
- claude_mpm/services/infrastructure/memory_guardian.py +770 -0
- claude_mpm/services/mcp_gateway/__init__.py +138 -0
- claude_mpm/services/mcp_gateway/config/__init__.py +17 -0
- claude_mpm/services/mcp_gateway/config/config_loader.py +232 -0
- claude_mpm/services/mcp_gateway/config/config_schema.py +234 -0
- claude_mpm/services/mcp_gateway/config/configuration.py +371 -0
- claude_mpm/services/mcp_gateway/core/__init__.py +51 -0
- claude_mpm/services/mcp_gateway/core/base.py +315 -0
- claude_mpm/services/mcp_gateway/core/exceptions.py +239 -0
- claude_mpm/services/mcp_gateway/core/interfaces.py +476 -0
- claude_mpm/services/mcp_gateway/main.py +326 -0
- claude_mpm/services/mcp_gateway/registry/__init__.py +12 -0
- claude_mpm/services/mcp_gateway/registry/service_registry.py +397 -0
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +477 -0
- claude_mpm/services/mcp_gateway/server/__init__.py +15 -0
- claude_mpm/services/mcp_gateway/server/mcp_server.py +430 -0
- claude_mpm/services/mcp_gateway/server/mcp_server_simple.py +444 -0
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +373 -0
- claude_mpm/services/mcp_gateway/tools/__init__.py +22 -0
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +497 -0
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +729 -0
- claude_mpm/services/mcp_gateway/tools/hello_world.py +551 -0
- claude_mpm/utils/file_utils.py +293 -0
- claude_mpm/utils/platform_memory.py +524 -0
- claude_mpm/utils/subprocess_utils.py +305 -0
- {claude_mpm-3.9.7.dist-info → claude_mpm-3.9.9.dist-info}/METADATA +4 -1
- {claude_mpm-3.9.7.dist-info → claude_mpm-3.9.9.dist-info}/RECORD +49 -26
- claude_mpm/agents/templates/.claude-mpm/memories/README.md +0 -36
- claude_mpm/agents/templates/.claude-mpm/memories/engineer_agent.md +0 -39
- claude_mpm/agents/templates/.claude-mpm/memories/qa_agent.md +0 -38
- claude_mpm/agents/templates/.claude-mpm/memories/research_agent.md +0 -39
- claude_mpm/agents/templates/.claude-mpm/memories/version_control_agent.md +0 -38
- /claude_mpm/agents/templates/{research_memory_efficient.json → backup/research_memory_efficient.json} +0 -0
- {claude_mpm-3.9.7.dist-info → claude_mpm-3.9.9.dist-info}/WHEEL +0 -0
- {claude_mpm-3.9.7.dist-info → claude_mpm-3.9.9.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.9.7.dist-info → claude_mpm-3.9.9.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.9.7.dist-info → claude_mpm-3.9.9.dist-info}/top_level.txt +0 -0
| @@ -0,0 +1,524 @@ | |
| 1 | 
            +
            """Platform-specific memory monitoring utilities.
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            This module provides fallback methods for monitoring process memory
         | 
| 4 | 
            +
            when psutil is not available, using platform-specific approaches.
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            Design Principles:
         | 
| 7 | 
            +
            - Graceful degradation when psutil is unavailable
         | 
| 8 | 
            +
            - Platform-specific optimizations
         | 
| 9 | 
            +
            - Consistent interface across platforms
         | 
| 10 | 
            +
            - Error handling and logging
         | 
| 11 | 
            +
            """
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            import os
         | 
| 14 | 
            +
            import platform
         | 
| 15 | 
            +
            import subprocess
         | 
| 16 | 
            +
            import re
         | 
| 17 | 
            +
            import logging
         | 
| 18 | 
            +
            from typing import Optional, Dict, Any, Tuple
         | 
| 19 | 
            +
            from pathlib import Path
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            logger = logging.getLogger(__name__)
         | 
| 22 | 
            +
             | 
| 23 | 
            +
             | 
| 24 | 
            +
            class MemoryInfo:
         | 
| 25 | 
            +
                """Container for memory information."""
         | 
| 26 | 
            +
                
         | 
| 27 | 
            +
                def __init__(self, rss: int, vms: int, percent: float = 0.0):
         | 
| 28 | 
            +
                    """Initialize memory info.
         | 
| 29 | 
            +
                    
         | 
| 30 | 
            +
                    Args:
         | 
| 31 | 
            +
                        rss: Resident Set Size in bytes
         | 
| 32 | 
            +
                        vms: Virtual Memory Size in bytes  
         | 
| 33 | 
            +
                        percent: Memory usage as percentage of total
         | 
| 34 | 
            +
                    """
         | 
| 35 | 
            +
                    self.rss = rss
         | 
| 36 | 
            +
                    self.vms = vms
         | 
| 37 | 
            +
                    self.percent = percent
         | 
| 38 | 
            +
                
         | 
| 39 | 
            +
                @property
         | 
| 40 | 
            +
                def rss_mb(self) -> float:
         | 
| 41 | 
            +
                    """Get RSS in megabytes."""
         | 
| 42 | 
            +
                    return self.rss / (1024 * 1024)
         | 
| 43 | 
            +
                
         | 
| 44 | 
            +
                @property
         | 
| 45 | 
            +
                def vms_mb(self) -> float:
         | 
| 46 | 
            +
                    """Get VMS in megabytes."""
         | 
| 47 | 
            +
                    return self.vms / (1024 * 1024)
         | 
| 48 | 
            +
                
         | 
| 49 | 
            +
                def to_dict(self) -> Dict[str, Any]:
         | 
| 50 | 
            +
                    """Convert to dictionary."""
         | 
| 51 | 
            +
                    return {
         | 
| 52 | 
            +
                        'rss_bytes': self.rss,
         | 
| 53 | 
            +
                        'vms_bytes': self.vms,
         | 
| 54 | 
            +
                        'rss_mb': self.rss_mb,
         | 
| 55 | 
            +
                        'vms_mb': self.vms_mb,
         | 
| 56 | 
            +
                        'percent': self.percent
         | 
| 57 | 
            +
                    }
         | 
| 58 | 
            +
             | 
| 59 | 
            +
             | 
| 60 | 
            +
            def get_memory_info_psutil(pid: int) -> Optional[MemoryInfo]:
         | 
| 61 | 
            +
                """Get memory info using psutil (preferred method).
         | 
| 62 | 
            +
                
         | 
| 63 | 
            +
                Args:
         | 
| 64 | 
            +
                    pid: Process ID to monitor
         | 
| 65 | 
            +
                    
         | 
| 66 | 
            +
                Returns:
         | 
| 67 | 
            +
                    MemoryInfo object or None if psutil not available or process not found
         | 
| 68 | 
            +
                """
         | 
| 69 | 
            +
                try:
         | 
| 70 | 
            +
                    import psutil
         | 
| 71 | 
            +
                    process = psutil.Process(pid)
         | 
| 72 | 
            +
                    mem_info = process.memory_info()
         | 
| 73 | 
            +
                    mem_percent = process.memory_percent()
         | 
| 74 | 
            +
                    return MemoryInfo(
         | 
| 75 | 
            +
                        rss=mem_info.rss,
         | 
| 76 | 
            +
                        vms=mem_info.vms,
         | 
| 77 | 
            +
                        percent=mem_percent
         | 
| 78 | 
            +
                    )
         | 
| 79 | 
            +
                except ImportError:
         | 
| 80 | 
            +
                    logger.debug("psutil not available")
         | 
| 81 | 
            +
                    return None
         | 
| 82 | 
            +
                except psutil.NoSuchProcess:
         | 
| 83 | 
            +
                    logger.warning(f"Process {pid} not found")
         | 
| 84 | 
            +
                    return None
         | 
| 85 | 
            +
                except Exception as e:
         | 
| 86 | 
            +
                    logger.error(f"Error getting memory info with psutil: {e}")
         | 
| 87 | 
            +
                    return None
         | 
| 88 | 
            +
             | 
| 89 | 
            +
             | 
| 90 | 
            +
            def get_memory_info_macos(pid: int) -> Optional[MemoryInfo]:
         | 
| 91 | 
            +
                """Get memory info on macOS using ps command.
         | 
| 92 | 
            +
                
         | 
| 93 | 
            +
                Args:
         | 
| 94 | 
            +
                    pid: Process ID to monitor
         | 
| 95 | 
            +
                    
         | 
| 96 | 
            +
                Returns:
         | 
| 97 | 
            +
                    MemoryInfo object or None if unable to get info
         | 
| 98 | 
            +
                """
         | 
| 99 | 
            +
                try:
         | 
| 100 | 
            +
                    # Use ps to get memory info
         | 
| 101 | 
            +
                    # RSS is in KB, VSZ is in KB
         | 
| 102 | 
            +
                    result = subprocess.run(
         | 
| 103 | 
            +
                        ['ps', '-o', 'rss=,vsz=', '-p', str(pid)],
         | 
| 104 | 
            +
                        capture_output=True,
         | 
| 105 | 
            +
                        text=True,
         | 
| 106 | 
            +
                        timeout=5
         | 
| 107 | 
            +
                    )
         | 
| 108 | 
            +
                    
         | 
| 109 | 
            +
                    if result.returncode != 0:
         | 
| 110 | 
            +
                        logger.debug(f"ps command failed for pid {pid}")
         | 
| 111 | 
            +
                        return None
         | 
| 112 | 
            +
                    
         | 
| 113 | 
            +
                    output = result.stdout.strip()
         | 
| 114 | 
            +
                    if not output:
         | 
| 115 | 
            +
                        logger.debug(f"No output from ps for pid {pid}")
         | 
| 116 | 
            +
                        return None
         | 
| 117 | 
            +
                    
         | 
| 118 | 
            +
                    parts = output.split()
         | 
| 119 | 
            +
                    if len(parts) >= 2:
         | 
| 120 | 
            +
                        rss_kb = int(parts[0])
         | 
| 121 | 
            +
                        vsz_kb = int(parts[1])
         | 
| 122 | 
            +
                        
         | 
| 123 | 
            +
                        # Convert KB to bytes
         | 
| 124 | 
            +
                        rss_bytes = rss_kb * 1024
         | 
| 125 | 
            +
                        vms_bytes = vsz_kb * 1024
         | 
| 126 | 
            +
                        
         | 
| 127 | 
            +
                        # Try to get total memory for percentage
         | 
| 128 | 
            +
                        percent = 0.0
         | 
| 129 | 
            +
                        try:
         | 
| 130 | 
            +
                            total_result = subprocess.run(
         | 
| 131 | 
            +
                                ['sysctl', '-n', 'hw.memsize'],
         | 
| 132 | 
            +
                                capture_output=True,
         | 
| 133 | 
            +
                                text=True,
         | 
| 134 | 
            +
                                timeout=5
         | 
| 135 | 
            +
                            )
         | 
| 136 | 
            +
                            if total_result.returncode == 0:
         | 
| 137 | 
            +
                                total_bytes = int(total_result.stdout.strip())
         | 
| 138 | 
            +
                                percent = (rss_bytes / total_bytes) * 100
         | 
| 139 | 
            +
                        except Exception:
         | 
| 140 | 
            +
                            pass
         | 
| 141 | 
            +
                        
         | 
| 142 | 
            +
                        return MemoryInfo(rss=rss_bytes, vms=vms_bytes, percent=percent)
         | 
| 143 | 
            +
                    
         | 
| 144 | 
            +
                except subprocess.TimeoutExpired:
         | 
| 145 | 
            +
                    logger.warning(f"Timeout getting memory info for pid {pid}")
         | 
| 146 | 
            +
                except ValueError as e:
         | 
| 147 | 
            +
                    logger.error(f"Error parsing ps output: {e}")
         | 
| 148 | 
            +
                except Exception as e:
         | 
| 149 | 
            +
                    logger.error(f"Error getting macOS memory info: {e}")
         | 
| 150 | 
            +
                
         | 
| 151 | 
            +
                return None
         | 
| 152 | 
            +
             | 
| 153 | 
            +
             | 
| 154 | 
            +
            def get_memory_info_linux(pid: int) -> Optional[MemoryInfo]:
         | 
| 155 | 
            +
                """Get memory info on Linux using /proc filesystem.
         | 
| 156 | 
            +
                
         | 
| 157 | 
            +
                Args:
         | 
| 158 | 
            +
                    pid: Process ID to monitor
         | 
| 159 | 
            +
                    
         | 
| 160 | 
            +
                Returns:
         | 
| 161 | 
            +
                    MemoryInfo object or None if unable to get info
         | 
| 162 | 
            +
                """
         | 
| 163 | 
            +
                try:
         | 
| 164 | 
            +
                    status_path = Path(f'/proc/{pid}/status')
         | 
| 165 | 
            +
                    if not status_path.exists():
         | 
| 166 | 
            +
                        logger.debug(f"Process {pid} not found in /proc")
         | 
| 167 | 
            +
                        return None
         | 
| 168 | 
            +
                    
         | 
| 169 | 
            +
                    rss_bytes = 0
         | 
| 170 | 
            +
                    vms_bytes = 0
         | 
| 171 | 
            +
                    
         | 
| 172 | 
            +
                    with open(status_path, 'r') as f:
         | 
| 173 | 
            +
                        for line in f:
         | 
| 174 | 
            +
                            if line.startswith('VmRSS:'):
         | 
| 175 | 
            +
                                # VmRSS is in KB
         | 
| 176 | 
            +
                                rss_kb = int(line.split()[1])
         | 
| 177 | 
            +
                                rss_bytes = rss_kb * 1024
         | 
| 178 | 
            +
                            elif line.startswith('VmSize:'):
         | 
| 179 | 
            +
                                # VmSize is in KB
         | 
| 180 | 
            +
                                vms_kb = int(line.split()[1])
         | 
| 181 | 
            +
                                vms_bytes = vms_kb * 1024
         | 
| 182 | 
            +
                    
         | 
| 183 | 
            +
                    if rss_bytes == 0 and vms_bytes == 0:
         | 
| 184 | 
            +
                        logger.debug(f"No memory info found for pid {pid}")
         | 
| 185 | 
            +
                        return None
         | 
| 186 | 
            +
                    
         | 
| 187 | 
            +
                    # Try to get total memory for percentage
         | 
| 188 | 
            +
                    percent = 0.0
         | 
| 189 | 
            +
                    try:
         | 
| 190 | 
            +
                        meminfo_path = Path('/proc/meminfo')
         | 
| 191 | 
            +
                        if meminfo_path.exists():
         | 
| 192 | 
            +
                            with open(meminfo_path, 'r') as f:
         | 
| 193 | 
            +
                                for line in f:
         | 
| 194 | 
            +
                                    if line.startswith('MemTotal:'):
         | 
| 195 | 
            +
                                        total_kb = int(line.split()[1])
         | 
| 196 | 
            +
                                        total_bytes = total_kb * 1024
         | 
| 197 | 
            +
                                        percent = (rss_bytes / total_bytes) * 100
         | 
| 198 | 
            +
                                        break
         | 
| 199 | 
            +
                    except Exception:
         | 
| 200 | 
            +
                        pass
         | 
| 201 | 
            +
                    
         | 
| 202 | 
            +
                    return MemoryInfo(rss=rss_bytes, vms=vms_bytes, percent=percent)
         | 
| 203 | 
            +
                    
         | 
| 204 | 
            +
                except ValueError as e:
         | 
| 205 | 
            +
                    logger.error(f"Error parsing /proc data: {e}")
         | 
| 206 | 
            +
                except Exception as e:
         | 
| 207 | 
            +
                    logger.error(f"Error getting Linux memory info: {e}")
         | 
| 208 | 
            +
                
         | 
| 209 | 
            +
                return None
         | 
| 210 | 
            +
             | 
| 211 | 
            +
             | 
| 212 | 
            +
            def get_memory_info_windows(pid: int) -> Optional[MemoryInfo]:
         | 
| 213 | 
            +
                """Get memory info on Windows using wmic or tasklist.
         | 
| 214 | 
            +
                
         | 
| 215 | 
            +
                Args:
         | 
| 216 | 
            +
                    pid: Process ID to monitor
         | 
| 217 | 
            +
                    
         | 
| 218 | 
            +
                Returns:
         | 
| 219 | 
            +
                    MemoryInfo object or None if unable to get info
         | 
| 220 | 
            +
                """
         | 
| 221 | 
            +
                try:
         | 
| 222 | 
            +
                    # Try wmic first (more detailed)
         | 
| 223 | 
            +
                    result = subprocess.run(
         | 
| 224 | 
            +
                        ['wmic', 'process', 'where', f'ProcessId={pid}', 'get', 
         | 
| 225 | 
            +
                         'WorkingSetSize,VirtualSize', '/format:list'],
         | 
| 226 | 
            +
                        capture_output=True,
         | 
| 227 | 
            +
                        text=True,
         | 
| 228 | 
            +
                        timeout=5,
         | 
| 229 | 
            +
                        shell=True
         | 
| 230 | 
            +
                    )
         | 
| 231 | 
            +
                    
         | 
| 232 | 
            +
                    if result.returncode == 0:
         | 
| 233 | 
            +
                        output = result.stdout
         | 
| 234 | 
            +
                        rss_bytes = 0
         | 
| 235 | 
            +
                        vms_bytes = 0
         | 
| 236 | 
            +
                        
         | 
| 237 | 
            +
                        for line in output.split('\n'):
         | 
| 238 | 
            +
                            line = line.strip()
         | 
| 239 | 
            +
                            if line.startswith('WorkingSetSize='):
         | 
| 240 | 
            +
                                try:
         | 
| 241 | 
            +
                                    rss_bytes = int(line.split('=')[1])
         | 
| 242 | 
            +
                                except (IndexError, ValueError):
         | 
| 243 | 
            +
                                    pass
         | 
| 244 | 
            +
                            elif line.startswith('VirtualSize='):
         | 
| 245 | 
            +
                                try:
         | 
| 246 | 
            +
                                    vms_bytes = int(line.split('=')[1])
         | 
| 247 | 
            +
                                except (IndexError, ValueError):
         | 
| 248 | 
            +
                                    pass
         | 
| 249 | 
            +
                        
         | 
| 250 | 
            +
                        if rss_bytes > 0 or vms_bytes > 0:
         | 
| 251 | 
            +
                            return MemoryInfo(rss=rss_bytes, vms=vms_bytes)
         | 
| 252 | 
            +
                    
         | 
| 253 | 
            +
                    # Fallback to tasklist
         | 
| 254 | 
            +
                    result = subprocess.run(
         | 
| 255 | 
            +
                        ['tasklist', '/FI', f'PID eq {pid}', '/FO', 'CSV'],
         | 
| 256 | 
            +
                        capture_output=True,
         | 
| 257 | 
            +
                        text=True,
         | 
| 258 | 
            +
                        timeout=5,
         | 
| 259 | 
            +
                        shell=True
         | 
| 260 | 
            +
                    )
         | 
| 261 | 
            +
                    
         | 
| 262 | 
            +
                    if result.returncode == 0:
         | 
| 263 | 
            +
                        lines = result.stdout.strip().split('\n')
         | 
| 264 | 
            +
                        if len(lines) > 1:
         | 
| 265 | 
            +
                            # Parse CSV output
         | 
| 266 | 
            +
                            # Format: "Image Name","PID","Session Name","Session#","Mem Usage"
         | 
| 267 | 
            +
                            data = lines[1].split('","')
         | 
| 268 | 
            +
                            if len(data) >= 5:
         | 
| 269 | 
            +
                                mem_usage = data[4].rstrip('"')
         | 
| 270 | 
            +
                                # Remove 'K' suffix and convert to bytes
         | 
| 271 | 
            +
                                mem_usage = mem_usage.replace(',', '').replace('K', '').strip()
         | 
| 272 | 
            +
                                if mem_usage.isdigit():
         | 
| 273 | 
            +
                                    rss_bytes = int(mem_usage) * 1024
         | 
| 274 | 
            +
                                    return MemoryInfo(rss=rss_bytes, vms=rss_bytes)  # tasklist only gives working set
         | 
| 275 | 
            +
                    
         | 
| 276 | 
            +
                except subprocess.TimeoutExpired:
         | 
| 277 | 
            +
                    logger.warning(f"Timeout getting memory info for pid {pid}")
         | 
| 278 | 
            +
                except Exception as e:
         | 
| 279 | 
            +
                    logger.error(f"Error getting Windows memory info: {e}")
         | 
| 280 | 
            +
                
         | 
| 281 | 
            +
                return None
         | 
| 282 | 
            +
             | 
| 283 | 
            +
             | 
| 284 | 
            +
            def get_memory_info_resource(pid: int) -> Optional[MemoryInfo]:
         | 
| 285 | 
            +
                """Get memory info using resource module (very limited).
         | 
| 286 | 
            +
                
         | 
| 287 | 
            +
                This only works for the current process and its children,
         | 
| 288 | 
            +
                not for arbitrary PIDs.
         | 
| 289 | 
            +
                
         | 
| 290 | 
            +
                Args:
         | 
| 291 | 
            +
                    pid: Process ID to monitor (must be current process or child)
         | 
| 292 | 
            +
                    
         | 
| 293 | 
            +
                Returns:
         | 
| 294 | 
            +
                    MemoryInfo object or None if unable to get info
         | 
| 295 | 
            +
                """
         | 
| 296 | 
            +
                try:
         | 
| 297 | 
            +
                    import resource
         | 
| 298 | 
            +
                    
         | 
| 299 | 
            +
                    # This only works if pid is the current process
         | 
| 300 | 
            +
                    if pid != os.getpid():
         | 
| 301 | 
            +
                        logger.debug("resource module only works for current process")
         | 
| 302 | 
            +
                        return None
         | 
| 303 | 
            +
                    
         | 
| 304 | 
            +
                    usage = resource.getrusage(resource.RUSAGE_SELF)
         | 
| 305 | 
            +
                    
         | 
| 306 | 
            +
                    # Convert to bytes (ru_maxrss is in KB on Linux, bytes on macOS)
         | 
| 307 | 
            +
                    if platform.system() == 'Darwin':
         | 
| 308 | 
            +
                        rss_bytes = usage.ru_maxrss
         | 
| 309 | 
            +
                    else:
         | 
| 310 | 
            +
                        rss_bytes = usage.ru_maxrss * 1024
         | 
| 311 | 
            +
                    
         | 
| 312 | 
            +
                    # resource module doesn't provide VMS
         | 
| 313 | 
            +
                    return MemoryInfo(rss=rss_bytes, vms=rss_bytes)
         | 
| 314 | 
            +
                    
         | 
| 315 | 
            +
                except Exception as e:
         | 
| 316 | 
            +
                    logger.error(f"Error getting memory info with resource module: {e}")
         | 
| 317 | 
            +
                
         | 
| 318 | 
            +
                return None
         | 
| 319 | 
            +
             | 
| 320 | 
            +
             | 
| 321 | 
            +
            def get_process_memory(pid: int, method: Optional[str] = None) -> Optional[MemoryInfo]:
         | 
| 322 | 
            +
                """Get memory information for a process using the best available method.
         | 
| 323 | 
            +
                
         | 
| 324 | 
            +
                Args:
         | 
| 325 | 
            +
                    pid: Process ID to monitor
         | 
| 326 | 
            +
                    method: Specific method to use, or None for auto-detection
         | 
| 327 | 
            +
                    
         | 
| 328 | 
            +
                Returns:
         | 
| 329 | 
            +
                    MemoryInfo object or None if unable to get info
         | 
| 330 | 
            +
                """
         | 
| 331 | 
            +
                # If specific method requested, try it
         | 
| 332 | 
            +
                if method:
         | 
| 333 | 
            +
                    if method == 'psutil':
         | 
| 334 | 
            +
                        return get_memory_info_psutil(pid)
         | 
| 335 | 
            +
                    elif method == 'macos':
         | 
| 336 | 
            +
                        return get_memory_info_macos(pid)
         | 
| 337 | 
            +
                    elif method == 'linux':
         | 
| 338 | 
            +
                        return get_memory_info_linux(pid)
         | 
| 339 | 
            +
                    elif method == 'windows':
         | 
| 340 | 
            +
                        return get_memory_info_windows(pid)
         | 
| 341 | 
            +
                    elif method == 'resource':
         | 
| 342 | 
            +
                        return get_memory_info_resource(pid)
         | 
| 343 | 
            +
                
         | 
| 344 | 
            +
                # Auto-detect best method
         | 
| 345 | 
            +
                # Try psutil first (most reliable and cross-platform)
         | 
| 346 | 
            +
                info = get_memory_info_psutil(pid)
         | 
| 347 | 
            +
                if info:
         | 
| 348 | 
            +
                    return info
         | 
| 349 | 
            +
                
         | 
| 350 | 
            +
                # Fall back to platform-specific methods
         | 
| 351 | 
            +
                system = platform.system()
         | 
| 352 | 
            +
                if system == 'Darwin':
         | 
| 353 | 
            +
                    info = get_memory_info_macos(pid)
         | 
| 354 | 
            +
                elif system == 'Linux':
         | 
| 355 | 
            +
                    info = get_memory_info_linux(pid)
         | 
| 356 | 
            +
                elif system == 'Windows':
         | 
| 357 | 
            +
                    info = get_memory_info_windows(pid)
         | 
| 358 | 
            +
                
         | 
| 359 | 
            +
                # Last resort: resource module (only for current process)
         | 
| 360 | 
            +
                if not info and pid == os.getpid():
         | 
| 361 | 
            +
                    info = get_memory_info_resource(pid)
         | 
| 362 | 
            +
                
         | 
| 363 | 
            +
                if not info:
         | 
| 364 | 
            +
                    logger.warning(f"Unable to get memory info for pid {pid} on {system}")
         | 
| 365 | 
            +
                
         | 
| 366 | 
            +
                return info
         | 
| 367 | 
            +
             | 
| 368 | 
            +
             | 
| 369 | 
            +
            def get_system_memory() -> Tuple[int, int]:
         | 
| 370 | 
            +
                """Get total and available system memory in bytes.
         | 
| 371 | 
            +
                
         | 
| 372 | 
            +
                Returns:
         | 
| 373 | 
            +
                    Tuple of (total_bytes, available_bytes)
         | 
| 374 | 
            +
                """
         | 
| 375 | 
            +
                try:
         | 
| 376 | 
            +
                    import psutil
         | 
| 377 | 
            +
                    mem = psutil.virtual_memory()
         | 
| 378 | 
            +
                    return mem.total, mem.available
         | 
| 379 | 
            +
                except ImportError:
         | 
| 380 | 
            +
                    pass
         | 
| 381 | 
            +
                
         | 
| 382 | 
            +
                # Fallback methods
         | 
| 383 | 
            +
                system = platform.system()
         | 
| 384 | 
            +
                
         | 
| 385 | 
            +
                if system == 'Darwin':
         | 
| 386 | 
            +
                    try:
         | 
| 387 | 
            +
                        # Get total memory
         | 
| 388 | 
            +
                        result = subprocess.run(
         | 
| 389 | 
            +
                            ['sysctl', '-n', 'hw.memsize'],
         | 
| 390 | 
            +
                            capture_output=True,
         | 
| 391 | 
            +
                            text=True,
         | 
| 392 | 
            +
                            timeout=5
         | 
| 393 | 
            +
                        )
         | 
| 394 | 
            +
                        total = int(result.stdout.strip()) if result.returncode == 0 else 0
         | 
| 395 | 
            +
                        
         | 
| 396 | 
            +
                        # Get free pages and page size for available memory
         | 
| 397 | 
            +
                        vm_stat = subprocess.run(
         | 
| 398 | 
            +
                            ['vm_stat'],
         | 
| 399 | 
            +
                            capture_output=True,
         | 
| 400 | 
            +
                            text=True,
         | 
| 401 | 
            +
                            timeout=5
         | 
| 402 | 
            +
                        )
         | 
| 403 | 
            +
                        if vm_stat.returncode == 0:
         | 
| 404 | 
            +
                            free_pages = 0
         | 
| 405 | 
            +
                            for line in vm_stat.stdout.split('\n'):
         | 
| 406 | 
            +
                                if 'Pages free:' in line:
         | 
| 407 | 
            +
                                    free_pages = int(line.split(':')[1].strip().rstrip('.'))
         | 
| 408 | 
            +
                                    break
         | 
| 409 | 
            +
                            # Page size is typically 4096 bytes on macOS
         | 
| 410 | 
            +
                            available = free_pages * 4096
         | 
| 411 | 
            +
                        else:
         | 
| 412 | 
            +
                            available = 0
         | 
| 413 | 
            +
                        
         | 
| 414 | 
            +
                        return total, available
         | 
| 415 | 
            +
                    except Exception as e:
         | 
| 416 | 
            +
                        logger.error(f"Error getting macOS system memory: {e}")
         | 
| 417 | 
            +
                
         | 
| 418 | 
            +
                elif system == 'Linux':
         | 
| 419 | 
            +
                    try:
         | 
| 420 | 
            +
                        total = 0
         | 
| 421 | 
            +
                        available = 0
         | 
| 422 | 
            +
                        with open('/proc/meminfo', 'r') as f:
         | 
| 423 | 
            +
                            for line in f:
         | 
| 424 | 
            +
                                if line.startswith('MemTotal:'):
         | 
| 425 | 
            +
                                    total = int(line.split()[1]) * 1024  # Convert KB to bytes
         | 
| 426 | 
            +
                                elif line.startswith('MemAvailable:'):
         | 
| 427 | 
            +
                                    available = int(line.split()[1]) * 1024
         | 
| 428 | 
            +
                        return total, available
         | 
| 429 | 
            +
                    except Exception as e:
         | 
| 430 | 
            +
                        logger.error(f"Error getting Linux system memory: {e}")
         | 
| 431 | 
            +
                
         | 
| 432 | 
            +
                elif system == 'Windows':
         | 
| 433 | 
            +
                    try:
         | 
| 434 | 
            +
                        result = subprocess.run(
         | 
| 435 | 
            +
                            ['wmic', 'OS', 'get', 'TotalVisibleMemorySize,FreePhysicalMemory', '/format:list'],
         | 
| 436 | 
            +
                            capture_output=True,
         | 
| 437 | 
            +
                            text=True,
         | 
| 438 | 
            +
                            timeout=5,
         | 
| 439 | 
            +
                            shell=True
         | 
| 440 | 
            +
                        )
         | 
| 441 | 
            +
                        if result.returncode == 0:
         | 
| 442 | 
            +
                            total = 0
         | 
| 443 | 
            +
                            free = 0
         | 
| 444 | 
            +
                            for line in result.stdout.split('\n'):
         | 
| 445 | 
            +
                                line = line.strip()
         | 
| 446 | 
            +
                                if line.startswith('TotalVisibleMemorySize='):
         | 
| 447 | 
            +
                                    total = int(line.split('=')[1]) * 1024  # Convert KB to bytes
         | 
| 448 | 
            +
                                elif line.startswith('FreePhysicalMemory='):
         | 
| 449 | 
            +
                                    free = int(line.split('=')[1]) * 1024
         | 
| 450 | 
            +
                            return total, free
         | 
| 451 | 
            +
                    except Exception as e:
         | 
| 452 | 
            +
                        logger.error(f"Error getting Windows system memory: {e}")
         | 
| 453 | 
            +
                
         | 
| 454 | 
            +
                # Unable to determine
         | 
| 455 | 
            +
                return 0, 0
         | 
| 456 | 
            +
             | 
| 457 | 
            +
             | 
| 458 | 
            +
            def check_memory_pressure() -> str:
         | 
| 459 | 
            +
                """Check system memory pressure status.
         | 
| 460 | 
            +
                
         | 
| 461 | 
            +
                Returns:
         | 
| 462 | 
            +
                    One of: 'normal', 'warning', 'critical', 'unknown'
         | 
| 463 | 
            +
                """
         | 
| 464 | 
            +
                try:
         | 
| 465 | 
            +
                    import psutil
         | 
| 466 | 
            +
                    mem = psutil.virtual_memory()
         | 
| 467 | 
            +
                    percent_used = mem.percent
         | 
| 468 | 
            +
                    
         | 
| 469 | 
            +
                    if percent_used > 90:
         | 
| 470 | 
            +
                        return 'critical'
         | 
| 471 | 
            +
                    elif percent_used > 75:
         | 
| 472 | 
            +
                        return 'warning'
         | 
| 473 | 
            +
                    else:
         | 
| 474 | 
            +
                        return 'normal'
         | 
| 475 | 
            +
                except ImportError:
         | 
| 476 | 
            +
                    pass
         | 
| 477 | 
            +
                
         | 
| 478 | 
            +
                # Platform-specific checks
         | 
| 479 | 
            +
                system = platform.system()
         | 
| 480 | 
            +
                
         | 
| 481 | 
            +
                if system == 'Darwin':
         | 
| 482 | 
            +
                    try:
         | 
| 483 | 
            +
                        # Check macOS memory pressure
         | 
| 484 | 
            +
                        result = subprocess.run(
         | 
| 485 | 
            +
                            ['memory_pressure'],
         | 
| 486 | 
            +
                            capture_output=True,
         | 
| 487 | 
            +
                            text=True,
         | 
| 488 | 
            +
                            timeout=5
         | 
| 489 | 
            +
                        )
         | 
| 490 | 
            +
                        if result.returncode == 0:
         | 
| 491 | 
            +
                            output = result.stdout.lower()
         | 
| 492 | 
            +
                            if 'critical' in output:
         | 
| 493 | 
            +
                                return 'critical'
         | 
| 494 | 
            +
                            elif 'warning' in output:
         | 
| 495 | 
            +
                                return 'warning'
         | 
| 496 | 
            +
                            else:
         | 
| 497 | 
            +
                                return 'normal'
         | 
| 498 | 
            +
                    except Exception:
         | 
| 499 | 
            +
                        pass
         | 
| 500 | 
            +
                
         | 
| 501 | 
            +
                elif system == 'Linux':
         | 
| 502 | 
            +
                    try:
         | 
| 503 | 
            +
                        # Check if we're close to OOM
         | 
| 504 | 
            +
                        with open('/proc/meminfo', 'r') as f:
         | 
| 505 | 
            +
                            total = 0
         | 
| 506 | 
            +
                            available = 0
         | 
| 507 | 
            +
                            for line in f:
         | 
| 508 | 
            +
                                if line.startswith('MemTotal:'):
         | 
| 509 | 
            +
                                    total = int(line.split()[1])
         | 
| 510 | 
            +
                                elif line.startswith('MemAvailable:'):
         | 
| 511 | 
            +
                                    available = int(line.split()[1])
         | 
| 512 | 
            +
                            
         | 
| 513 | 
            +
                            if total > 0:
         | 
| 514 | 
            +
                                percent_used = ((total - available) / total) * 100
         | 
| 515 | 
            +
                                if percent_used > 90:
         | 
| 516 | 
            +
                                    return 'critical'
         | 
| 517 | 
            +
                                elif percent_used > 75:
         | 
| 518 | 
            +
                                    return 'warning'
         | 
| 519 | 
            +
                                else:
         | 
| 520 | 
            +
                                    return 'normal'
         | 
| 521 | 
            +
                    except Exception:
         | 
| 522 | 
            +
                        pass
         | 
| 523 | 
            +
                
         | 
| 524 | 
            +
                return 'unknown'
         |