Source code for xpcsviewer.xpcs_file.memory

"""Memory monitoring utilities for XpcsFile.

This module provides memory monitoring and status tracking for XPCS data operations.
"""

from __future__ import annotations

import time

import numpy as np
import psutil

# Cached psutil result with TTL to avoid repeated syscalls.
# Each psutil.virtual_memory() is a syscall (~0.1-0.5ms). With 30+ calls
# per operation cycle, caching saves 3-15ms of pure overhead.
_VMEM_CACHE_TTL = 2.0  # seconds
_vmem_cache_result: psutil._common.svmem | None = None
_vmem_cache_timestamp: float = 0.0


def _get_virtual_memory() -> psutil._common.svmem:
    """Return psutil.virtual_memory(), cached with a 2-second TTL."""
    global _vmem_cache_result, _vmem_cache_timestamp
    now = time.monotonic()
    if _vmem_cache_result is None or (now - _vmem_cache_timestamp) >= _VMEM_CACHE_TTL:
        _vmem_cache_result = psutil.virtual_memory()
        _vmem_cache_timestamp = now
    return _vmem_cache_result


[docs] class MemoryStatus: """Container for memory status information."""
[docs] def __init__(self, percent_used: float): self.percent_used = percent_used
[docs] class MemoryMonitor: """Simple memory monitoring utilities using psutil."""
[docs] def get_memory_info(self) -> tuple[float, float, float]: """Get current memory usage information. Returns ------- tuple[float, float, float] (used_mb, available_mb, pressure_ratio) """ memory = _get_virtual_memory() used_mb = (memory.total - memory.available) / 1024 / 1024 available_mb = memory.available / 1024 / 1024 pressure_ratio = memory.percent / 100.0 return used_mb, available_mb, pressure_ratio
[docs] def get_memory_status(self) -> MemoryStatus: """Get memory status object. Returns ------- MemoryStatus Object containing percent_used attribute """ memory = _get_virtual_memory() return MemoryStatus(memory.percent / 100.0)
[docs] @staticmethod def get_memory_usage() -> tuple[float, float]: """Get current memory usage in MB (static method for backward compatibility).""" monitor = get_cached_memory_monitor() used_mb, available_mb, _ = monitor.get_memory_info() return used_mb, available_mb
[docs] @staticmethod def get_memory_pressure() -> float: """Calculate memory pressure as a percentage (0-1) (static method for backward compatibility).""" monitor = get_cached_memory_monitor() status = monitor.get_memory_status() return status.percent_used
[docs] @staticmethod def is_memory_pressure_high(threshold: float = 0.85) -> bool: """Check if memory pressure is above threshold (static method for backward compatibility).""" memory = _get_virtual_memory() return (memory.percent / 100.0) > threshold
[docs] @staticmethod def estimate_array_memory(shape: tuple, dtype: np.dtype) -> float: """Estimate memory usage of a numpy array in MB. Parameters ---------- shape : tuple Array shape dtype : np.dtype Array data type Returns ------- float Estimated memory usage in MB """ elements = np.prod(shape) bytes_per_element = np.dtype(dtype).itemsize total_bytes = elements * bytes_per_element return total_bytes / (1024 * 1024)
# Global memory monitor instance _memory_monitor = None
[docs] def get_cached_memory_monitor() -> MemoryMonitor: """Get or create a cached memory monitor instance. Returns ------- MemoryMonitor Singleton memory monitor instance """ global _memory_monitor if _memory_monitor is None: _memory_monitor = MemoryMonitor() return _memory_monitor