Selkies
Developer Referenceresource_stats

ResourceMonitor

Samples the session's CPU and memory, and the GPU its pipeline encodes on, once a period off the event loop, and keeps the latest sample in the shapes the pages take.

system is always the last SystemUsage sample; gpu is the card's last reading, None while it cannot be read, and never polled again once the first probe finds no GPU, since a vendor tool may be spawned per query. on_tick runs every period with the time, for whatever a transport does on the same cadence; metrics, when given, takes the GPU utilization. A period is sampled only for someone: watched, when set, says whether a page has its stats open, and without one and without metrics the period passes unsampled and system and gpu read None rather than go stale. The GPU is dri_node's card when that narrows the list to one, else the gpu_idth of the unfiltered list.

Attributes

attributeperiod
= max(1.0, float(period))
attributegpu_id
= gpu_id
attributedri_node
= dri_node
attributemetrics
= metrics
attributesystemOptional[Dict[str, Any]]
= None
attributegpuOptional[Dict[str, Any]]
= None
attributeon_tickOptional[Callable[[float], Awaitable[None]]]
= None
attributewatchedOptional[Callable[[], bool]]
= None

Functions

constructor__init__(period=1.0, gpu_id=0, dri_node='', metrics=None) -> None
Source Code
def __init__(self, period: float = 1.0, gpu_id: int = 0, dri_node: str = "",
             metrics: Optional[Any] = None) -> None:
    self.period = max(1.0, float(period))
    self.gpu_id = gpu_id
    self.dri_node = dri_node
    self.metrics = metrics
    self.system: Optional[Dict[str, Any]] = None
    self.gpu: Optional[Dict[str, Any]] = None
    self.on_tick: Optional[Callable[[float], Awaitable[None]]] = None
    self.watched: Optional[Callable[[], bool]] = None
    self._usage = SystemUsage()
    self._probe_gpu = True
    self._gpu_seen = False
    self._stop: Optional[asyncio.Event] = None
    self._task: Optional[asyncio.Task] = None
paramperiodfloat
= 1.0
paramgpu_idint
= 0
paramdri_nodestr
= ''
parammetricsOptional[Any]
= None

Returns

None
func_gpu_sample() -> Optional[Dict[str, Any]]

One GPU reading, or None where there is nothing to read.

A card is listed for what it is even when it exposes no counters, so the pipeline can match it by vendor or PCI address, but a card nothing counts and that reports no memory is not a reading: published every tick it would leave a page showing a utilization that can never move and a memory total of nothing. None instead stops the probe and leaves those off the page. A card that is merely idle has a utilization, so it keeps reporting.

Source Code
def _gpu_sample(self) -> Optional[Dict[str, Any]]:
    """One GPU reading, or None where there is nothing to read.

    A card is listed for what it is even when it exposes no counters, so
    the pipeline can match it by vendor or PCI address, but a card nothing
    counts and that reports no memory is not a reading: published every
    tick it would leave a page showing a utilization that can never move
    and a memory total of nothing. None instead stops the probe and leaves
    those off the page. A card that is merely idle has a utilization, so it
    keeps reporting.
    """
    gpus = get_gpus(self.dri_node)
    idx = 0 if (self.dri_node and len(gpus) == 1) else self.gpu_id
    if not gpus or not 0 <= idx < len(gpus):
        return None
    gpu = gpus[idx]
    if gpu.load is None and gpu.memoryTotal <= 0:
        return None
    return {
        "gpu_percent": (gpu.load or 0.0) * 100,
        "memory_total": gpu.memoryTotal * 1024 * 1024,
        "memory_used": gpu.memoryUsed * 1024 * 1024,
    }

Returns

typing.Optional[typing.Dict[str, typing.Any]]
func_sample() -> Tuple[Dict[str, Any], Optional[Dict[str, Any]]]

One blocking sample of both, for a worker thread.

Source Code
def _sample(self) -> Tuple[Dict[str, Any], Optional[Dict[str, Any]]]:
    """One blocking sample of both, for a worker thread."""
    cpu, total, used = self._usage.sample()
    system = {"cpu_percent": cpu, "mem_total": total, "mem_used": used}
    gpu = None
    if self._probe_gpu:
        try:
            gpu = self._gpu_sample()
        except Exception as exc:
            logger.warning(f"GPU stats unavailable this tick: {exc}")
        if gpu is not None:
            self._gpu_seen = True
        elif not self._gpu_seen:
            self._probe_gpu = False
            logger.info(
                f"No GPU with ID {self.gpu_id} reports utilization or memory; "
                "GPU stats disabled.")
    return system, gpu

Returns

typing.Tuple[typing.Dict[str, typing.Any], typing.Optional[typing.Dict[str, typing.Any]]]
func_loop() -> None
Source Code
async def _loop(self) -> None:
    try:
        while self._stop is not None and not self._stop.is_set():
            if self.metrics is not None or self.watched is None or self.watched():
                self.system, self.gpu = await asyncio.to_thread(self._sample)
            else:
                self.system = self.gpu = None
            if self.metrics is not None and self.gpu is not None:
                self.metrics.set_gpu_utilization(self.gpu["gpu_percent"])
            if self.on_tick is not None:
                await self.on_tick(time.time())
            try:
                await asyncio.wait_for(self._stop.wait(), timeout=self.period)
            except asyncio.TimeoutError:
                pass
    except asyncio.CancelledError:
        pass
    except Exception as exc:
        logger.error(f"Resource monitor error: {exc}", exc_info=True)

Returns

None
funcstart() -> None

Starts sampling on the running loop; a second start is a no-op.

Source Code
def start(self) -> None:
    """Starts sampling on the running loop; a second start is a no-op."""
    if self._task is not None and not self._task.done():
        return
    self._stop = asyncio.Event()
    self._task = asyncio.create_task(self._loop())

Returns

None
funcstop() -> None

Ends the loop at once and waits for it.

Source Code
async def stop(self) -> None:
    """Ends the loop at once and waits for it."""
    if self._stop is not None:
        self._stop.set()
    if self._task is not None:
        await self._task
        self._task = None

Returns

None

On this page

Edit on GitHub