import { readdirSync, readFileSync, realpathSync } from "node:fs"; import { basename, join } from "node:path"; import { Effect } from "effect"; import type { GpuInfo } from "../../models/types"; import { resolveBinary, runCommandAsyncEffect } from "../../../core/command"; type IntelPciGpu = { path: string; address: string; deviceId: string; classCode: string; driver: string | null; }; const PCI_DEVICES_DIR = "/sys/bus/pci/devices"; const DRM_DIR = "/sys/class/drm"; const readText = (path: string): string | null => { try { return readFileSync(path, "utf8").trim(); } catch { return null; } }; const readNumber = (path: string): number | null => { const text = readText(path); if (!text) return null; const value = Number(text); return Number.isFinite(value) ? value : null; }; const readDeviceDriver = (devicePath: string): string | null => { try { return basename(realpathSync(join(devicePath, "driver"))); } catch { return null; } }; const isIntelComputeGpu = (gpu: IntelPciGpu): boolean => { if (gpu.driver === "xe") return true; if (gpu.deviceId.toLowerCase() === "0xe223") return true; return gpu.classCode.toLowerCase().startsWith("0x03"); }; const discoverIntelPciGpus = (): IntelPciGpu[] => { try { return readdirSync(PCI_DEVICES_DIR, { withFileTypes: true }) .filter((entry) => entry.isSymbolicLink() || entry.isDirectory()) .map((entry) => { const path = join(PCI_DEVICES_DIR, entry.name); const vendor = readText(join(path, "vendor"))?.toLowerCase(); if (vendor !== "0x8086") return null; const gpu: IntelPciGpu = { path, address: entry.name, deviceId: readText(join(path, "device")) ?? "", classCode: readText(join(path, "class")) ?? "", driver: readDeviceDriver(path), }; return isIntelComputeGpu(gpu) ? gpu : null; }) .filter((entry): entry is IntelPciGpu => Boolean(entry)) .sort((a, b) => a.address.localeCompare(b.address)); } catch { return []; } }; const findDrmDevicePaths = (pciPath: string): string[] => { try { return readdirSync(DRM_DIR, { withFileTypes: true }) .filter((entry) => entry.name.startsWith("card")) .map((entry) => { const devicePath = join(DRM_DIR, entry.name, "device"); try { return realpathSync(devicePath) === realpathSync(pciPath) ? join(DRM_DIR, entry.name, "device") : null; } catch { return null; } }) .filter((entry): entry is string => Boolean(entry)); } catch { return []; } }; const readFirstNumber = (paths: string[]): number | null => { for (const path of paths) { const value = readNumber(path); if (value !== null) return value; } return null; }; const findHwmonPaths = (pciPath: string): string[] => { try { return readdirSync(join(pciPath, "hwmon"), { withFileTypes: true }) .filter((entry) => entry.name.startsWith("hwmon")) .map((entry) => join(pciPath, "hwmon", entry.name)); } catch { return []; } }; const readHwmonMetric = (hwmonPaths: string[], fileName: string): number | null => readFirstNumber(hwmonPaths.map((path) => join(path, fileName))); const readIntelName = (gpu: IntelPciGpu): Effect.Effect => { const lspci = resolveBinary("lspci"); if (lspci) { return runCommandAsyncEffect(lspci, ["-s", gpu.address.replace(/^0000:/, "")], { timeoutMs: 2_000, }).pipe( Effect.map((result) => { if (result.status === 0 && result.stdout) { const name = result.stdout.replace(/^[0-9a-f:.]+\s+/i, "").trim(); if (name) return name; } return gpu.deviceId.toLowerCase() === "0xe223" ? "Intel Arc Pro B70" : "Intel Arc GPU"; }), ); } return Effect.succeed( gpu.deviceId.toLowerCase() === "0xe223" ? "Intel Arc Pro B70" : "Intel Arc GPU", ); }; export const getGpuInfoFromIntelSysfs = (): Effect.Effect => Effect.sync(discoverIntelPciGpus).pipe( Effect.flatMap((gpus) => Effect.forEach(gpus, (gpu, index) => Effect.gen(function* () { const drmDevicePaths = findDrmDevicePaths(gpu.path); const memoryTotal = readFirstNumber(drmDevicePaths.map((path) => join(path, "mem_info_vram_total"))) ?? 0; const memoryUsed = readFirstNumber(drmDevicePaths.map((path) => join(path, "mem_info_vram_used"))) ?? 0; const memoryFree = Math.max(0, memoryTotal - memoryUsed); const hwmonPaths = findHwmonPaths(gpu.path); const temperature = Math.round((readHwmonMetric(hwmonPaths, "temp1_input") ?? 0) / 1000); const powerDraw = Number( ((readHwmonMetric(hwmonPaths, "power1_input") ?? 0) / 1_000_000).toFixed(1), ); const powerLimit = Number( ((readHwmonMetric(hwmonPaths, "power1_cap") ?? 0) / 1_000_000).toFixed(1), ); const toMb = (bytes: number): number => Math.max(0, Math.round(bytes / 1024 / 1024)); return { index, name: yield* readIntelName(gpu), memory_total_mb: toMb(memoryTotal), memory_used_mb: toMb(memoryUsed), memory_free_mb: toMb(memoryFree), utilization_pct: 0, temp_c: temperature, power_draw: powerDraw, power_limit: powerLimit, }; }), ), ), );