feat: node-agent detects NVIDIA GPUs and Blackmagic DeckLink cards, reports in heartbeat

This commit is contained in:
Zac Gaetano 2026-05-20 14:18:07 -04:00
parent 86d2960b60
commit a941f609f0

View file

@ -1,5 +1,6 @@
import express from 'express';
import os from 'os';
import fs from 'fs';
const MAM_API_URL = (process.env.MAM_API_URL || 'http://localhost:3000').replace(/\/$/, '');
const NODE_TOKEN = process.env.NODE_TOKEN || '';
@ -49,12 +50,48 @@ function getIp() {
return null;
}
// ── Hardware detection ────────────────────────────────────────────────────
// GPU_COUNT env var overrides filesystem detection (useful when /dev not mapped into container)
// BMD_COUNT env var similarly overrides Blackmagic DeckLink detection
function detectHardware() {
const capabilities = { gpus: [], blackmagic: [] };
const gpuOverride = parseInt(process.env.GPU_COUNT || '-1', 10);
if (gpuOverride >= 0) {
for (let i = 0; i < gpuOverride; i++) {
capabilities.gpus.push({ device: `/dev/nvidia${i}`, type: 'nvidia', index: i });
}
} else {
for (let i = 0; i < 16; i++) {
try {
fs.accessSync(`/dev/nvidia${i}`, fs.constants.F_OK);
capabilities.gpus.push({ device: `/dev/nvidia${i}`, type: 'nvidia', index: i });
} catch (_) { break; }
}
}
const bmdOverride = parseInt(process.env.BMD_COUNT || '-1', 10);
if (bmdOverride >= 0) {
for (let i = 0; i < bmdOverride; i++) {
capabilities.blackmagic.push({ device: `/dev/blackmagic/dv${i}`, index: i });
}
} else {
try {
const bmdEntries = fs.readdirSync('/dev/blackmagic');
capabilities.blackmagic = bmdEntries.map(d => ({ device: `/dev/blackmagic/${d}` }));
} catch (_) {}
}
return capabilities;
}
// ── Heartbeat ─────────────────────────────────────────────────────────────
async function heartbeat() {
const cpu_usage = await sampleCpu();
const totalMem = os.totalmem();
const freeMem = os.freemem();
const ip_address = getIp();
const cpu_usage = await sampleCpu();
const totalMem = os.totalmem();
const freeMem = os.freemem();
const ip_address = getIp();
const capabilities = detectHardware();
const payload = {
hostname: os.hostname(),
@ -65,6 +102,7 @@ async function heartbeat() {
cpu_usage,
mem_used_mb: Math.round((totalMem - freeMem) / 1048576),
mem_total_mb: Math.round(totalMem / 1048576),
capabilities,
};
const headers = { 'Content-Type': 'application/json' };
@ -78,9 +116,11 @@ async function heartbeat() {
signal: AbortSignal.timeout(8000),
});
if (res.ok) {
const gpuStr = capabilities.gpus.length ? ` gpu=${capabilities.gpus.length}` : '';
const bmdStr = capabilities.blackmagic.length ? ` bmd=${capabilities.blackmagic.length}` : '';
process.stdout.write(
`[hb] ${payload.hostname} cpu=${cpu_usage}% ` +
`mem=${payload.mem_used_mb}/${payload.mem_total_mb}MB\n`
`mem=${payload.mem_used_mb}/${payload.mem_total_mb}MB${gpuStr}${bmdStr}\n`
);
} else {
const txt = await res.text().catch(() => '');