mirror of
https://github.com/henrygd/beszel.git
synced 2026-08-18 08:17:47 +02:00
feat(agent): monitor Intel Arc (xe) GPUs via nvtop (#2223)
intel_gpu_top does not support the xe driver, so skip it for xe devices
and let the existing nvtop last-resort collector handle them. nvtop leaves
device_name unset on xe, so name the GPU from its PCI device id ("Intel GPU
(<id>)"). Adds nvtop to the Intel agent image (gputop already ships with
igt-gpu-tools).
This commit is contained in:
@@ -126,6 +126,7 @@ type gpuCapabilities struct {
|
|||||||
hasAmdSysfs bool
|
hasAmdSysfs bool
|
||||||
hasTegrastats bool
|
hasTegrastats bool
|
||||||
hasIntelGpuTop bool
|
hasIntelGpuTop bool
|
||||||
|
hasXe bool
|
||||||
hasIntelSysfs bool
|
hasIntelSysfs bool
|
||||||
hasNvtop bool
|
hasNvtop bool
|
||||||
hasMacmon bool
|
hasMacmon bool
|
||||||
@@ -449,7 +450,8 @@ func (gm *GPUManager) storeSnapshot(id string, gpu *system.GPUData, cacheKey uin
|
|||||||
// It only reports capability presence and does not apply policy decisions.
|
// It only reports capability presence and does not apply policy decisions.
|
||||||
func (gm *GPUManager) discoverGpuCapabilities() gpuCapabilities {
|
func (gm *GPUManager) discoverGpuCapabilities() gpuCapabilities {
|
||||||
caps := gpuCapabilities{
|
caps := gpuCapabilities{
|
||||||
hasAmdSysfs: gm.hasAmdSysfs(),
|
hasAmdSysfs: gm.hasAmdSysfs(),
|
||||||
|
hasXe: gm.hasXe(),
|
||||||
hasIntelSysfs: gm.hasIntelSysfs(),
|
hasIntelSysfs: gm.hasIntelSysfs(),
|
||||||
}
|
}
|
||||||
if _, err := exec.LookPath(nvidiaSmiCmd); err == nil {
|
if _, err := exec.LookPath(nvidiaSmiCmd); err == nil {
|
||||||
@@ -719,7 +721,7 @@ func (gm *GPUManager) resolveLegacyCollectorPriority(caps gpuCapabilities) []col
|
|||||||
priorities = append(priorities, collectorSourceAmdSysfs)
|
priorities = append(priorities, collectorSourceAmdSysfs)
|
||||||
}
|
}
|
||||||
|
|
||||||
if caps.hasIntelGpuTop {
|
if caps.hasIntelGpuTop && !caps.hasXe {
|
||||||
priorities = append(priorities, collectorSourceIntelGpuTop)
|
priorities = append(priorities, collectorSourceIntelGpuTop)
|
||||||
}
|
}
|
||||||
if caps.hasIntelSysfs {
|
if caps.hasIntelSysfs {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -48,9 +49,14 @@ func (gm *GPUManager) updateNvtopSnapshots(snapshots []nvtopSnapshot) bool {
|
|||||||
|
|
||||||
valid := false
|
valid := false
|
||||||
usedIDs := make(map[string]struct{}, len(snapshots))
|
usedIDs := make(map[string]struct{}, len(snapshots))
|
||||||
|
var xeName string
|
||||||
for i, sample := range snapshots {
|
for i, sample := range snapshots {
|
||||||
|
// nvtop leaves device_name unset on xe devices.
|
||||||
if sample.DeviceName == "" {
|
if sample.DeviceName == "" {
|
||||||
continue
|
if xeName == "" {
|
||||||
|
xeName = xeGpuName()
|
||||||
|
}
|
||||||
|
sample.DeviceName = xeName
|
||||||
}
|
}
|
||||||
indexID := "n" + strconv.Itoa(i)
|
indexID := "n" + strconv.Itoa(i)
|
||||||
id := indexID
|
id := indexID
|
||||||
@@ -158,3 +164,38 @@ func (gm *GPUManager) startNvtopCollector(interval string, onFailure func()) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// xeDevicePath returns the sysfs device path of the first xe GPU, or "".
|
||||||
|
func xeDevicePath() string {
|
||||||
|
cards, err := filepath.Glob("/sys/class/drm/card*")
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
for _, card := range cards {
|
||||||
|
if strings.Contains(filepath.Base(card), "-") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if uevent, err := utils.ReadStringFileLimited(filepath.Join(card, "device", "uevent"), 4096); err == nil && strings.Contains(uevent, "DRIVER=xe") {
|
||||||
|
return filepath.Join(card, "device")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gm *GPUManager) hasXe() bool {
|
||||||
|
return xeDevicePath() != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// xeGpuName names an xe GPU from its PCI device id; nvtop leaves device_name unset on xe.
|
||||||
|
func xeGpuName() string {
|
||||||
|
devicePath := xeDevicePath()
|
||||||
|
if devicePath == "" {
|
||||||
|
return "GPU"
|
||||||
|
}
|
||||||
|
id, err := utils.ReadStringFileLimited(filepath.Join(devicePath, "device"), 64)
|
||||||
|
if err != nil {
|
||||||
|
return "GPU"
|
||||||
|
}
|
||||||
|
id = strings.ToLower(strings.TrimSpace(strings.TrimPrefix(id, "0x")))
|
||||||
|
return "Intel GPU (" + id + ")"
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ FROM alpine:3.23
|
|||||||
|
|
||||||
COPY --from=builder /agent /agent
|
COPY --from=builder /agent /agent
|
||||||
|
|
||||||
RUN apk add --no-cache -X https://dl-cdn.alpinelinux.org/alpine/edge/testing igt-gpu-tools smartmontools
|
RUN apk add --no-cache -X https://dl-cdn.alpinelinux.org/alpine/edge/testing igt-gpu-tools nvtop smartmontools
|
||||||
|
|
||||||
# Ensure data persistence across container recreations
|
# Ensure data persistence across container recreations
|
||||||
VOLUME ["/var/lib/beszel-agent"]
|
VOLUME ["/var/lib/beszel-agent"]
|
||||||
|
|||||||
Reference in New Issue
Block a user