Detect Intel integrated graphics, which nvidia-smi cannot see
HOST2 has UHD Graphics 730 and reported no GPU at all. An iGPU has no VRAM, no sensor of its own and no encode/decode split, so it is drawn from the per-engine busy figures Intel actually publishes and the fields it lacks are null rather than a confident zero.
This commit is contained in:
@@ -196,9 +196,114 @@ function vv_gpu_stats_all(): array {
|
||||
'power_w' => is_numeric($p[7]) ? round((float)$p[7], 1) : null,
|
||||
'enc_pct' => (int)$p[8],
|
||||
'dec_pct' => (int)$p[9],
|
||||
'vendor' => 'nvidia',
|
||||
];
|
||||
}
|
||||
return $gpus;
|
||||
// Integrated graphics after the discrete cards, numbered on from them. A box with both shows
|
||||
// both; a box with only an iGPU stops reporting that it has no GPU at all.
|
||||
return array_merge($gpus, vv_igpu_stats_all(count($gpus)));
|
||||
}
|
||||
|
||||
// Intel integrated graphics, which nvidia-smi cannot see and which therefore rendered as "no GPU
|
||||
// detected" on any box without a discrete card — HOST2 has UHD Graphics 730 and showed nothing.
|
||||
//
|
||||
// An iGPU is not a small discrete card and the difference is not cosmetic. It has no VRAM (it
|
||||
// shares system memory), no temperature sensor of its own (it is inside the CPU package, which
|
||||
// vv_cpu_temp() already reports), and no encode/decode split — Intel publishes per-engine busy
|
||||
// figures instead: Render/3D, Blitter, Video, VideoEnhance. Those fields are returned null rather
|
||||
// than zero, because 0 MB of VRAM and 0°C are claims, and "this part does not have one" is not.
|
||||
//
|
||||
// Utilisation comes from rc6, the idle residency percentage: busy is what is left of it. That is
|
||||
// the whole-GPU number; the engine breakdown is carried alongside because on a media server the
|
||||
// Video engine is the one worth watching — it is what a hardware transcode actually uses.
|
||||
//
|
||||
// Sampled, not read: intel_gpu_top measures over an interval, so this costs roughly the sample
|
||||
// window. Memoised per request and bounded by timeout, because the cache writer calls it once a
|
||||
// minute and nothing should be able to hang that.
|
||||
function vv_igpu_stats_all(int $startIndex = 0): array {
|
||||
static $cache = null;
|
||||
if ($cache !== null) return $cache;
|
||||
$cache = [];
|
||||
|
||||
foreach ((array)@glob('/sys/class/drm/card[0-9]*') as $card) {
|
||||
if (trim((string)@file_get_contents("$card/device/vendor")) !== '0x8086') continue;
|
||||
|
||||
$addr = basename((string)@readlink("$card/device"));
|
||||
if ($addr === '') continue;
|
||||
|
||||
// Marketing name where lspci publishes one: "Alder Lake-S GT1 [UHD Graphics 730]" is
|
||||
// better known as UHD Graphics 730, and the bracketed half is the half people recognise.
|
||||
$desc = trim((string)shell_exec('lspci -s ' . escapeshellarg($addr) . ' 2>/dev/null'));
|
||||
$name = 'Intel integrated graphics';
|
||||
if (preg_match('/\[([^\]]+)\]/', $desc, $m)) $name = 'Intel ' . trim($m[1]);
|
||||
elseif (preg_match('/controller:\s*(.+?)(?:\s*\(rev|$)/', $desc, $m)) $name = trim($m[1]);
|
||||
|
||||
$entry = [
|
||||
'available' => true,
|
||||
'vendor' => 'intel',
|
||||
'index' => $startIndex + count($cache),
|
||||
// Prefixed so a discrete card's processes can never be attributed to this one on a
|
||||
// box that has both.
|
||||
'uuid' => 'intel:' . $addr,
|
||||
'name' => $name,
|
||||
'memory_used' => null, // shared system memory — there is no separate pool
|
||||
'memory_total' => null,
|
||||
'utilization' => null,
|
||||
'temperature' => null, // no die sensor of its own; it is in the CPU package
|
||||
'power_w' => null,
|
||||
'package_w' => null,
|
||||
'enc_pct' => null, // Intel reports engines, not an encode/decode split
|
||||
'dec_pct' => null,
|
||||
'engines' => [],
|
||||
];
|
||||
|
||||
// The card still renders without this — name and "no sample" beats no card at all, which
|
||||
// is what the box showed before.
|
||||
$sample = vv_igpu_sample();
|
||||
if ($sample !== null) {
|
||||
$rc6 = $sample['rc6']['value'] ?? null;
|
||||
if (is_numeric($rc6)) $entry['utilization'] = max(0, min(100, (int)round(100 - (float)$rc6)));
|
||||
|
||||
$gpuW = $sample['power']['GPU'] ?? null;
|
||||
$pkgW = $sample['power']['Package'] ?? null;
|
||||
// Alder Lake reports 0.00 for the GPU rail. Reported as null rather than as a
|
||||
// confident zero watts, with package power carried separately and labelled as such.
|
||||
if (is_numeric($gpuW) && (float)$gpuW > 0) $entry['power_w'] = round((float)$gpuW, 1);
|
||||
if (is_numeric($pkgW)) $entry['package_w'] = round((float)$pkgW, 1);
|
||||
|
||||
foreach ((array)($sample['engines'] ?? []) as $engine => $vals) {
|
||||
if (!is_array($vals) || !isset($vals['busy'])) continue;
|
||||
$entry['engines'][$engine] = round((float)$vals['busy'], 1);
|
||||
}
|
||||
}
|
||||
|
||||
$cache[] = $entry;
|
||||
}
|
||||
return $cache;
|
||||
}
|
||||
|
||||
// One sample from intel_gpu_top, decoded, or null.
|
||||
//
|
||||
// -J streams an unterminated JSON array: a bare "[" and then one object per interval, forever.
|
||||
// Neither half is valid JSON on its own, so the first object is cut out by hand — from its opening
|
||||
// brace to the first closing brace in column 1 — and decoded alone.
|
||||
//
|
||||
// timeout, and a short window: this runs inside the once-a-minute cache write and a sampler that
|
||||
// never returns would take the whole payload with it.
|
||||
function vv_igpu_sample(): ?array {
|
||||
static $sample = null;
|
||||
static $tried = false;
|
||||
if ($tried) return $sample;
|
||||
$tried = true;
|
||||
|
||||
if (trim((string)shell_exec('command -v intel_gpu_top 2>/dev/null')) === '') return null;
|
||||
|
||||
$raw = shell_exec('timeout 4 intel_gpu_top -J -s 600 2>/dev/null | sed -n "/^{/,/^}/p" | sed "/^}/q"');
|
||||
if (!$raw) return null;
|
||||
|
||||
$decoded = json_decode(trim($raw), true);
|
||||
$sample = is_array($decoded) ? $decoded : null;
|
||||
return $sample;
|
||||
}
|
||||
|
||||
// Kept for the existing single-GPU consumers — same shape as before, always GPU 0.
|
||||
|
||||
Reference in New Issue
Block a user