diff --git a/audit/internal/platform/sat_gpu_packs.go b/audit/internal/platform/sat_gpu_packs.go index cf9e64f..ef0acae 100644 --- a/audit/internal/platform/sat_gpu_packs.go +++ b/audit/internal/platform/sat_gpu_packs.go @@ -14,6 +14,7 @@ type NvidiaGPU struct { Index int `json:"index"` Name string `json:"name"` MemoryMB int `json:"memory_mb"` + Serial string `json:"serial,omitempty"` } type NvidiaGPUStatus struct { @@ -226,7 +227,7 @@ func amdStressJobs(seconds int, cfgFile string) []satJob { // ListNvidiaGPUs returns GPUs visible to nvidia-smi. func (s *System) ListNvidiaGPUs() ([]NvidiaGPU, error) { out, err := exec.Command("nvidia-smi", - "--query-gpu=index,name,memory.total", + "--query-gpu=index,name,memory.total,serial", "--format=csv,noheader,nounits").Output() if err != nil { return nil, fmt.Errorf("nvidia-smi: %w", err) @@ -237,8 +238,8 @@ func (s *System) ListNvidiaGPUs() ([]NvidiaGPU, error) { if line == "" { continue } - parts := strings.SplitN(line, ", ", 3) - if len(parts) != 3 { + parts := strings.SplitN(line, ", ", 4) + if len(parts) < 3 { continue } idx, err := strconv.Atoi(strings.TrimSpace(parts[0])) @@ -246,10 +247,18 @@ func (s *System) ListNvidiaGPUs() ([]NvidiaGPU, error) { continue } memMB, _ := strconv.Atoi(strings.TrimSpace(parts[2])) + serial := "" + if len(parts) == 4 { + serial = strings.TrimSpace(parts[3]) + if strings.EqualFold(serial, "N/A") || strings.EqualFold(serial, "[N/A]") { + serial = "" + } + } gpus = append(gpus, NvidiaGPU{ Index: idx, Name: strings.TrimSpace(parts[1]), MemoryMB: memMB, + Serial: serial, }) } sort.Slice(gpus, func(i, j int) bool { diff --git a/audit/internal/webui/gpu_picker.go b/audit/internal/webui/gpu_picker.go new file mode 100644 index 0000000..7f382ce --- /dev/null +++ b/audit/internal/webui/gpu_picker.go @@ -0,0 +1,71 @@ +package webui + +// Shared NVIDIA GPU selection picker. +// +// Every page that lets the operator pick GPUs (Load, Burn, Benchmark) renders +// the same row markup: "GPU N — · MiB · sn: ". That +// markup lives here once. Pages keep their own selection-note text, multi-GPU +// mode toggles and CSS-class prefixes, but call beeGpuPicker.render({...}) from +// their *RenderGPUList wrapper instead of hand-building each