Files
olivv-cs-ComfyUI-FunPack/core/sysinfo.py
T
Claude 3adebb731f Rename FunPack to FunUI (5.0.3 Classy Chopper): /fun endpoint, wink logo, load-time migration of old names
Old project/workflow names and browser settings migrate on load; /funpack redirects to /fun.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016acRss69eH1235eRdFTZHY
2026-10-25 13:11:03 +00:00

188 lines
6.1 KiB
Python

"""Host facts for the About section -- the machine ComfyUI runs on, not the
browser's.
That distinction is the whole point on a rental: the frontend already knows
the laptop it is displayed on, and nobody cares. What matters is the box doing
the sampling -- which GPU, how much VRAM, which torch, how much disk is left
for outputs.
Everything here is best-effort and individually guarded: a missing probe
returns None and the panel renders a dash. Nothing in this module may raise,
because About is the one screen that should still work when the rest of the
install is broken.
"""
from __future__ import annotations
import os
import platform
import shutil
import subprocess
import sys
from . import config
def _bytes_gb(n):
"""Bytes -> GB as a float, using the 1024 base the OS's own About panels use."""
try:
return round(float(n) / (1024 ** 3), 2)
except Exception:
return None
def _cpu_name():
"""A human CPU model string. platform.processor() is famously empty on Linux and
just 'arm' on Apple silicon, so ask the OS directly first."""
try:
if sys.platform == "darwin":
out = subprocess.run(["sysctl", "-n", "machdep.cpu.brand_string"],
capture_output=True, text=True, timeout=3)
if out.returncode == 0 and out.stdout.strip():
return out.stdout.strip()
elif sys.platform.startswith("linux"):
cpuinfo_path = "/proc/cpuinfo"
if os.path.exists(cpuinfo_path):
with open(cpuinfo_path, errors="replace") as f:
for line in f:
if line.lower().startswith("model name"):
return line.split(":", 1)[1].strip()
except Exception:
pass
return platform.processor() or platform.machine() or None
def _cpu():
info = {"name": _cpu_name(), "arch": platform.machine() or None,
"cores": None, "threads": os.cpu_count()}
try:
import psutil # a ComfyUI requirement, so normally present
info["cores"] = psutil.cpu_count(logical=False)
info["threads"] = psutil.cpu_count(logical=True) or info["threads"]
except Exception:
pass
return info
def _memory():
try:
import psutil
vm = psutil.virtual_memory()
return {"total_gb": _bytes_gb(vm.total), "available_gb": _bytes_gb(vm.available)}
except Exception:
pass
try: # stdlib fallback (Linux/macOS)
total = os.sysconf("SC_PAGE_SIZE") * os.sysconf("SC_PHYS_PAGES")
return {"total_gb": _bytes_gb(total), "available_gb": None}
except Exception:
return {"total_gb": None, "available_gb": None}
def _gpus():
"""Every visible CUDA/ROCm device. Empty list on a CPU-only box (the local Mac)."""
out = []
try:
import torch
if not torch.cuda.is_available():
return out
for i in range(torch.cuda.device_count()):
entry = {"name": None, "vram_gb": None, "capability": None}
try:
entry["name"] = torch.cuda.get_device_name(i)
except Exception:
pass
try:
props = torch.cuda.get_device_properties(i)
entry["vram_gb"] = _bytes_gb(props.total_memory)
entry["capability"] = f"sm_{props.major}{props.minor}"
except Exception:
pass
out.append(entry)
except Exception:
pass
return out
def _mps():
"""Apple-silicon GPU, which reports through MPS rather than CUDA."""
try:
import torch
if getattr(torch.backends, "mps", None) is not None and torch.backends.mps.is_available():
return True
except Exception:
pass
return False
def _disk():
"""Free space where FunUI lives -- the volume that fills up with renders."""
try:
usage = shutil.disk_usage(str(config.ROOT))
return {"total_gb": _bytes_gb(usage.total), "free_gb": _bytes_gb(usage.free)}
except Exception:
return {"total_gb": None, "free_gb": None}
def _os_name():
try:
if sys.platform == "darwin":
return f"macOS {platform.mac_ver()[0]}".strip()
if sys.platform.startswith("win"):
return f"Windows {platform.release()}"
# Prefer the distro's own pretty name over the kernel version.
osr_path = "/etc/os-release"
if os.path.exists(osr_path):
with open(osr_path, errors="replace") as f:
for line in f:
if line.startswith("PRETTY_NAME="):
return line.split("=", 1)[1].strip().strip('"')
return f"{platform.system()} {platform.release()}"
except Exception:
return None
def _torch():
info = {"version": None, "cuda": None, "attention": None}
try:
import torch
info["version"] = torch.__version__
info["cuda"] = getattr(torch.version, "cuda", None)
except Exception:
pass
# Which fast-attention backend is actually importable. This is the line that
# answers "is sage actually installed on this rental?" without digging
# through the launch args.
for mod, label in (("sageattention", "SageAttention"), ("flash_attn", "FlashAttention"),
("xformers", "xformers")):
try:
m = __import__(mod)
info["attention"] = f"{label} {getattr(m, '__version__', '')}".strip()
break
except Exception:
continue
return info
def _comfy_version():
try:
import comfyui_version
return comfyui_version.__version__
except Exception:
return None
def collect():
"""The whole About payload. Static enough that the frontend caches it per load."""
gpus = _gpus()
return {
"host": platform.node() or None,
"os": _os_name(),
"cpu": _cpu(),
"memory": _memory(),
"gpus": gpus,
"mps": _mps() if not gpus else False,
"disk": _disk(),
"python": platform.python_version(),
"torch": _torch(),
"comfyui": _comfy_version(),
}