mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-23 08:00:55 +00:00
222 lines
6.0 KiB
Go
222 lines
6.0 KiB
Go
package gpu
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/1Panel-dev/1Panel/agent/global"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
|
)
|
|
|
|
const (
|
|
amdSMICommand = "amd-smi"
|
|
amdSMIDefaultPath = "/opt/rocm/bin/amd-smi"
|
|
)
|
|
|
|
type amdSMI struct {
|
|
command string
|
|
}
|
|
|
|
func findAMDSMI() (string, bool) {
|
|
for _, command := range []string{amdSMICommand, amdSMIDefaultPath} {
|
|
if cmd.Which(command) {
|
|
return command, true
|
|
}
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
func (a amdSMI) LoadInfo(ctx context.Context) (*Info, error) {
|
|
var (
|
|
staticData string
|
|
metricData string
|
|
processData string
|
|
staticErr error
|
|
metricErr error
|
|
processErr error
|
|
wg sync.WaitGroup
|
|
)
|
|
|
|
wg.Add(3)
|
|
go func() {
|
|
defer wg.Done()
|
|
staticData, staticErr = runAMDSMI(ctx, a.command, "static", "--asic", "--bus", "--driver", "--limit", "--json")
|
|
}()
|
|
go func() {
|
|
defer wg.Done()
|
|
metricData, metricErr = runAMDSMI(ctx, a.command, "metric", "--usage", "--power", "--temperature", "--mem-usage", "--fan", "--perf-level", "--json")
|
|
}()
|
|
go func() {
|
|
defer wg.Done()
|
|
processData, processErr = runAMDSMI(ctx, a.command, "process", "--general", "--json")
|
|
}()
|
|
wg.Wait()
|
|
|
|
if staticErr != nil {
|
|
return nil, fmt.Errorf("calling %s static failed: %w", a.command, staticErr)
|
|
}
|
|
info, err := parseAMDStatic(staticData)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing %s static output failed: %w", a.command, err)
|
|
}
|
|
|
|
if metricErr != nil {
|
|
global.LOG.Warnf("calling %s metric failed, metrics will be omitted: %v", a.command, metricErr)
|
|
} else if err := applyAMDMetrics(info, metricData); err != nil {
|
|
global.LOG.Warnf("parsing %s metric output failed, metrics will be omitted: %v", a.command, err)
|
|
}
|
|
if processErr != nil {
|
|
global.LOG.Warnf("calling %s process failed, process information will be omitted: %v", a.command, processErr)
|
|
} else if err := applyAMDProcesses(info, processData); err != nil {
|
|
global.LOG.Warnf("parsing %s process output failed, process information will be omitted: %v", a.command, err)
|
|
}
|
|
return info, nil
|
|
}
|
|
|
|
func runAMDSMI(ctx context.Context, command string, args ...string) (string, error) {
|
|
cmdMgr := cmd.NewCommandMgr(cmd.WithContext(ctx), cmd.WithTimeout(10*time.Second))
|
|
return cmdMgr.RunWithStdout(command, args...)
|
|
}
|
|
|
|
func parseAMDStatic(data string) (*Info, error) {
|
|
rows, err := decodeAMDRows(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
info := &Info{Type: "amd"}
|
|
for _, row := range rows {
|
|
index, ok := amdGPUIndex(row)
|
|
if !ok {
|
|
continue
|
|
}
|
|
device := Device{
|
|
Type: "amd",
|
|
Index: index,
|
|
ProductName: amdStringAt(row, "asic.market_name", "market_name", "gpu_name"),
|
|
PersistenceMode: "N/A",
|
|
BusID: amdStringAt(row, "bus.bdf", "bdf"),
|
|
DisplayActive: "N/A",
|
|
ECC: "N/A",
|
|
FanSpeed: "N/A",
|
|
Temperature: "N/A",
|
|
PerformanceState: "N/A",
|
|
PowerDraw: "N/A",
|
|
MaxPowerLimit: amdMetricAt(row, "W",
|
|
"limit.ppt0.max_power_limit",
|
|
"limit.max_power_limit",
|
|
"limit.max_power",
|
|
),
|
|
MemUsed: "N/A",
|
|
MemTotal: "N/A",
|
|
GPUUtil: "N/A",
|
|
ComputeMode: "N/A",
|
|
MigMode: "N/A",
|
|
}
|
|
if device.ProductName == "" {
|
|
device.ProductName = "AMD GPU"
|
|
}
|
|
if device.MaxPowerLimit == "" {
|
|
device.MaxPowerLimit = "N/A"
|
|
}
|
|
if info.DriverVersion == "" {
|
|
info.DriverVersion = amdStringAt(row, "driver.version", "driver_version", "amdgpu_version")
|
|
}
|
|
info.Devices = append(info.Devices, device)
|
|
}
|
|
sort.Slice(info.Devices, func(i, j int) bool {
|
|
return info.Devices[i].Index < info.Devices[j].Index
|
|
})
|
|
return info, nil
|
|
}
|
|
|
|
func applyAMDMetrics(info *Info, data string) error {
|
|
rows, err := decodeAMDRows(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
devices := amdDevicesByIndex(info)
|
|
for _, row := range rows {
|
|
index, ok := amdGPUIndex(row)
|
|
if !ok {
|
|
continue
|
|
}
|
|
device, ok := devices[index]
|
|
if !ok {
|
|
continue
|
|
}
|
|
setAMDMetric(&device.GPUUtil, row, "%", "usage.gfx_activity", "usage.gfx", "gfx_activity", "gfx_usage")
|
|
setAMDMetric(&device.Temperature, row, "°C", "temperature.hotspot", "temperature.edge", "hotspot_temperature", "gpu_temperature", "gpu_temp")
|
|
setAMDMetric(&device.PowerDraw, row, "W", "power.socket_power", "socket_power", "power_usage")
|
|
setAMDMetric(&device.MemUsed, row, "MB", "mem_usage.used_vram", "vram.used", "used_vram", "vram_used")
|
|
setAMDMetric(&device.MemTotal, row, "MB", "mem_usage.total_vram", "vram.total", "total_vram", "vram_total")
|
|
setAMDMetric(&device.FanSpeed, row, "%", "fan.speed", "fan_speed")
|
|
if value := amdStringAt(row, "perf_level", "performance_level"); value != "" {
|
|
device.PerformanceState = value
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func applyAMDProcesses(info *Info, data string) error {
|
|
rows, err := decodeAMDRows(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
devices := amdDevicesByIndex(info)
|
|
for _, row := range rows {
|
|
index, ok := amdGPUIndex(row)
|
|
if !ok {
|
|
continue
|
|
}
|
|
device, ok := devices[index]
|
|
if !ok {
|
|
continue
|
|
}
|
|
processList, _ := amdValueAt(row, "process_list")
|
|
items := amdObjectList(processList)
|
|
if len(items) == 0 {
|
|
if _, ok := amdValueAt(row, "process_info"); ok {
|
|
items = amdSMIRows{row}
|
|
}
|
|
}
|
|
for _, processRow := range items {
|
|
pid := amdStringAt(processRow, "process_info.pid", "pid")
|
|
if pid == "" || strings.EqualFold(pid, "N/A") {
|
|
continue
|
|
}
|
|
device.Processes = append(device.Processes, Process{
|
|
PID: pid,
|
|
Type: "C",
|
|
ProcessName: amdStringAt(processRow, "process_info.name", "name"),
|
|
UsedMemory: amdMetricAt(processRow, "B",
|
|
"process_info.mem_usage",
|
|
"process_info.mem",
|
|
"process_info.memory_usage.vram_mem",
|
|
"process_info.vram_mem",
|
|
"mem_usage",
|
|
),
|
|
})
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func amdDevicesByIndex(info *Info) map[uint]*Device {
|
|
devices := make(map[uint]*Device, len(info.Devices))
|
|
for index := range info.Devices {
|
|
devices[info.Devices[index].Index] = &info.Devices[index]
|
|
}
|
|
return devices
|
|
}
|
|
|
|
func setAMDMetric(target *string, row amdSMIRow, defaultUnit string, paths ...string) {
|
|
if value := amdMetricAt(row, defaultUnit, paths...); value != "" {
|
|
*target = value
|
|
}
|
|
}
|