SPB Git forge

spb/llm-api

Public
0commits 0branches 0releases
0 Bsize
maindefault branch
—last push
8.2 KB · 255 lines python
Raw Blame History
1"""Apple Silicon hardware detection and telemetry (macOS, graceful degradation elsewhere)."""23from __future__ import annotations45import asyncio6import platform7import re8import shutil9import subprocess10import time11from dataclasses import asdict, dataclass, field12from pathlib import Path1314import psutil1516GB = 1024**3171819def _run(cmd: list[str], timeout: float = 5.0) -> str:20    try:21        return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout).stdout22    except Exception:23        return ""242526def _sysctl(key: str) -> str:27    return _run(["sysctl", "-n", key]).strip()282930@dataclass31class Hardware:32    chip: str33    memory_gb: float34    memory_bytes: int35    cpu_cores: int36    performance_cores: int | None37    efficiency_cores: int | None38    gpu_cores: int | None39    os: str40    os_version: str41    hostname: str42    apple_silicon: bool43    disk_total_gb: float44    python: str4546    def to_dict(self) -> dict:47        return asdict(self)484950_HW_CACHE: Hardware | None = None515253def detect_hardware(models_dir: Path | None = None) -> Hardware:54    global _HW_CACHE55    if _HW_CACHE is not None:56        return _HW_CACHE57    is_mac = platform.system() == "Darwin"58    chip = _sysctl("machdep.cpu.brand_string") if is_mac else platform.processor() or "unknown"59    mem_bytes = int(_sysctl("hw.memsize") or psutil.virtual_memory().total) if is_mac else psutil.virtual_memory().total60    ncpu = int(_sysctl("hw.ncpu") or psutil.cpu_count() or 0) if is_mac else (psutil.cpu_count() or 0)61    perf = eff = None62    if is_mac:63        p0 = _sysctl("hw.perflevel0.physicalcpu")64        p1 = _sysctl("hw.perflevel1.physicalcpu")65        perf = int(p0) if p0.isdigit() else None66        eff = int(p1) if p1.isdigit() else None67    gpu_cores = None68    if is_mac:69        out = _run(["ioreg", "-r", "-c", "IOAccelerator", "-d", "1"])70        m = re.search(r'"gpu-core-count"\s*=\s*(\d+)', out)71        if m:72            gpu_cores = int(m.group(1))73    disk_path = models_dir if (models_dir and models_dir.exists()) else Path.home()74    du = shutil.disk_usage(disk_path)75    os_version = ""76    if is_mac:77        os_version = _run(["sw_vers", "-productVersion"]).strip()78    _HW_CACHE = Hardware(79        chip=chip or "unknown",80        memory_gb=round(mem_bytes / GB, 1),81        memory_bytes=mem_bytes,82        cpu_cores=ncpu,83        performance_cores=perf,84        efficiency_cores=eff,85        gpu_cores=gpu_cores,86        os="macOS" if is_mac else platform.system(),87        os_version=os_version or platform.release(),88        hostname=platform.node(),89        apple_silicon=is_mac and platform.machine() == "arm64",90        disk_total_gb=round(du.total / GB, 1),91        python=platform.python_version(),92    )93    return _HW_CACHE949596@dataclass97class Telemetry:98    ts: float99    mem_total_gb: float100    mem_used_gb: float101    mem_available_gb: float102    mem_wired_gb: float | None103    mem_compressed_gb: float | None104    mem_pressure_percent: int | None  # 0 = none … 100 = critical (derived from free %)105    mem_pressure_level: str  # normal | warning | critical106    swap_used_gb: float107    swap_total_gb: float108    cpu_percent: float109    cpu_per_core: list[float] = field(default_factory=list)110    load_avg: list[float] = field(default_factory=list)111    gpu_percent: float | None = None112    gpu_renderer_percent: float | None = None113    gpu_memory_gb: float | None = None114    thermal_state: str = "nominal"115    thermal_cpu_speed_limit: int | None = None116    disk_total_gb: float = 0117    disk_used_gb: float = 0118    disk_free_gb: float = 0119    uptime_seconds: float = 0120    process_rss_gb: float = 0121122    def to_dict(self) -> dict:123        return asdict(self)124125126def _read_gpu() -> tuple[float | None, float | None, float | None]:127    out = _run(["ioreg", "-r", "-c", "IOAccelerator", "-d", "1"], timeout=3)128    if not out:129        return None, None, None130    dev = re.search(r'"Device Utilization %"=(\d+)', out)131    rend = re.search(r'"Renderer Utilization %"=(\d+)', out)132    mem = re.search(r'"In use system memory"=(\d+)', out)133    return (134        float(dev.group(1)) if dev else None,135        float(rend.group(1)) if rend else None,136        round(int(mem.group(1)) / GB, 2) if mem else None,137    )138139140def _read_thermal() -> tuple[str, int | None]:141    out = _run(["pmset", "-g", "therm"], timeout=3)142    if not out:143        return "unknown", None144    m = re.search(r"CPU_Speed_Limit\s*=\s*(\d+)", out)145    limit = int(m.group(1)) if m else None146    if limit is not None and limit < 100:147        state = "throttled" if limit < 80 else "warm"148    elif "No thermal warning" in out or limit == 100:149        state = "nominal"150    else:151        state = "nominal"152    return state, limit153154155def _read_vm_stat() -> dict[str, int]:156    out = _run(["vm_stat"], timeout=3)157    vals: dict[str, int] = {}158    if not out:159        return vals160    m = re.search(r"page size of (\d+) bytes", out)161    page = int(m.group(1)) if m else 16384162    for line in out.splitlines()[1:]:163        if ":" not in line:164            continue165        k, v = line.split(":", 1)166        v = v.strip().rstrip(".")167        if v.isdigit():168            vals[k.strip()] = int(v) * page169    vals["_page"] = page170    return vals171172173def sample_telemetry(models_dir: Path | None = None) -> Telemetry:174    vm = psutil.virtual_memory()175    sw = psutil.swap_memory()176    is_mac = platform.system() == "Darwin"177    wired = compressed = None178    if is_mac:179        vs = _read_vm_stat()180        if vs:181            wired = round(vs.get("Pages wired down", 0) / GB, 2)182            compressed = round(vs.get("Pages occupied by compressor", 0) / GB, 2)183    gpu, gpu_r, gpu_mem = _read_gpu() if is_mac else (None, None, None)184    therm, limit = _read_thermal() if is_mac else ("unknown", None)185    disk_path = models_dir if (models_dir and models_dir.exists()) else Path.home()186    du = shutil.disk_usage(disk_path)187    avail_pct = vm.available / vm.total * 100188    if avail_pct > 20 and sw.used < 1 * GB:189        level = "normal"190    elif avail_pct > 8 and sw.used < 4 * GB:191        level = "warning"192    else:193        level = "critical"194    pressure = int(max(0.0, min(100.0, 100 - avail_pct)))195    proc = psutil.Process()196    try:197        load = list(psutil.getloadavg())198    except Exception:199        load = []200    return Telemetry(201        ts=time.time(),202        mem_total_gb=round(vm.total / GB, 2),203        mem_used_gb=round((vm.total - vm.available) / GB, 2),204        mem_available_gb=round(vm.available / GB, 2),205        mem_wired_gb=wired,206        mem_compressed_gb=compressed,207        mem_pressure_percent=pressure,208        mem_pressure_level=level,209        swap_used_gb=round(sw.used / GB, 2),210        swap_total_gb=round(sw.total / GB, 2),211        cpu_percent=psutil.cpu_percent(interval=None),212        cpu_per_core=psutil.cpu_percent(interval=None, percpu=True),213        load_avg=[round(x, 2) for x in load],214        gpu_percent=gpu,215        gpu_renderer_percent=gpu_r,216        gpu_memory_gb=gpu_mem,217        thermal_state=therm,218        thermal_cpu_speed_limit=limit,219        disk_total_gb=round(du.total / GB, 1),220        disk_used_gb=round(du.used / GB, 1),221        disk_free_gb=round(du.free / GB, 1),222        uptime_seconds=round(time.time() - psutil.boot_time(), 0),223        process_rss_gb=round(proc.memory_info().rss / GB, 3),224    )225226227async def sample_telemetry_async(models_dir: Path | None = None) -> Telemetry:228    return await asyncio.to_thread(sample_telemetry, models_dir)229230231def process_tree_rss_bytes(pid: int) -> int:232    """RSS of a process and its children (worker memory)."""233    try:234        p = psutil.Process(pid)235        total = p.memory_info().rss236        for c in p.children(recursive=True):237            try:238                total += c.memory_info().rss239            except psutil.Error:240                pass241        return total242    except psutil.Error:243        return 0244245246def process_footprint_bytes(pid: int) -> int:247    """macOS 'physical footprint' (closest to Activity Monitor 'Memory'). Falls back to RSS."""248    if platform.system() == "Darwin":249        out = _run(["footprint", "-p", str(pid)], timeout=3) if shutil.which("footprint") else ""250        m = re.search(r"phys_footprint:\s*([\d.]+)\s*([KMG]B)", out)251        if m:252            mult = {"KB": 1024, "MB": 1024**2, "GB": 1024**3}[m.group(2)]253            return int(float(m.group(1)) * mult)254    return process_tree_rss_bytes(pid)255