Ctrl K
ComunidadGuías…
backend/app/services/resource_manager.pypython · @kibot
20/9/2026, 17:22:33
"""Gestor de recursos para controlar transcodificaciones concurrentes en NetFlux.

Proporciona:
- Semaforo para limitar transcodificaciones simultaneas
- Monitoreo de uso de CPU y memoria
- Cleanup de procesos zombie
- Logging de uso de recursos
"""
import os
import psutil
import threading
import logging
import signal
import time
from typing import Dict, Optional
from contextlib import contextmanager
from datetime import datetime, timedelta

logger = logging.getLogger(__name__)


class TranscodeResourceManager:
    """Gestor avanzado de recursos para transcodificacion."""
    def __init__(self, max_concurrent: int = 2, max_memory_mb: int = 2048):
        self.max_concurrent = max_concurrent
        self.max_memory_mb = max_memory_mb
        self._active_transcodes: Dict[str, Dict] = {}
        self._lock = threading.Lock()
        self._semaphore = threading.Semaphore(max_concurrent)
        self._processes: list = []

    def acquire(self, transcode_id: str) -> bool:
        acquired = self._semaphore.acquire(blocking=False)
        if acquired:
            with self._lock:
                self._active_transcodes[transcode_id] = {
                    "started_at": datetime.utcnow(),
                    "pid": os.getpid(),
                    "memory_mb": self._get_current_memory_mb(),
                }
            logger.info(f"Transcode {transcode_id} iniciado. Activos: {len(self._active_transcodes)}")
        return acquired

    def release(self, transcode_id: str) -> None:
        with self._lock:
            self._active_transcodes.pop(transcode_id, None)
        self._semaphore.release()
        logger.info(f"Transcode {transcode_id} liberado. Activos: {len(self._active_transcodes)}")

    def _get_current_memory_mb(self) -> float:
        try:
            process = psutil.Process(os.getpid())
            return process.memory_info().rss / 1024 / 1024
        except Exception:
            return 0.0

    def get_active_count(self) -> int:
        with self._lock:
            return len(self._active_transcodes)

    def get_resource_usage(self) -> Dict:
        cpu_percent = psutil.cpu_percent(interval=0.1)
        mem = psutil.virtual_memory()
        with self._lock:
            active = len(self._active_transcodes)
        return {
            "active_transcodes": active,
            "max_concurrent": self.max_concurrent,
            "cpu_percent": cpu_percent,
            "memory_percent": mem.percent,
            "memory_used_mb": mem.used / 1024 / 1024,
            "memory_total_mb": mem.total / 1024 / 1024,
            "transcodes": dict(self._active_transcodes),
        }

    def cleanup_zombies(self) -> int:
        cleaned = 0
        for proc in self._processes[:]:
            try:
                if proc.poll() is not None:
                    self._processes.remove(proc)
                    cleaned += 1
            except Exception:
                pass
        if cleaned > 0:
            logger.info(f"Limpiados {cleaned} procesos zombie")
        return cleaned

    @contextmanager
    def transcode_slot(self, transcode_id: str = "unknown"):
        if not self.acquire(transcode_id):
            raise RuntimeError(
                f"Maximo de transcodificaciones concurrentes ({self.max_concurrent}) alcanzado."
            )
        try:
            yield self
        finally:
            self.release(transcode_id)

    def wait_for_slot(self, transcode_id: str, timeout: int = 60) -> bool:
        start = time.time()
        while time.time() - start < timeout:
            if self.acquire(transcode_id):
                return True
            time.sleep(1)
        return False

resource_manager = TranscodeResourceManager(
    max_concurrent=int(os.getenv("TRANSCODE_MAX_CONCURRENT", "2")),
    max_memory_mb=int(os.getenv("TRANSCODE_MAX_MEMORY_MB", "2048")),
)

Comentarios de código