feat: nvidia-smi full field parsing, GPU TUI compact table, GPU summary at top, power fix
This commit is contained in:
+322
-121
@@ -134,15 +134,21 @@ type MemInfo struct {
|
||||
Percent float64 `json:"percent"`
|
||||
}
|
||||
type GPUInfo struct {
|
||||
Name string `json:"name"`
|
||||
VRAMTotalMB float64 `json:"vram_total_mb"`
|
||||
VRAMUsedMB float64 `json:"vram_used_mb"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
LoadPercent float64 `json:"load_percent"`
|
||||
CoreClock float64 `json:"core_clock_mhz"`
|
||||
MemClock float64 `json:"mem_clock_mhz"`
|
||||
Power float64 `json:"power_watts"`
|
||||
FanSpeed float64 `json:"fan_speed"`
|
||||
Name string `json:"name"`
|
||||
VRAMTotalMB float64 `json:"vram_total_mb"`
|
||||
VRAMUsedMB float64 `json:"vram_used_mb"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
LoadPercent float64 `json:"load_percent"`
|
||||
CoreClock float64 `json:"core_clock_mhz"`
|
||||
MemClock float64 `json:"mem_clock_mhz"`
|
||||
Power float64 `json:"power"`
|
||||
PowerLimit float64 `json:"power_limit"`
|
||||
FanSpeed float64 `json:"fan_speed"`
|
||||
PState string `json:"pstate"`
|
||||
Persistence string `json:"persistence_mode"`
|
||||
ComputeMode string `json:"compute_mode"`
|
||||
DriverVersion string `json:"driver_version"`
|
||||
CudaVersion string `json:"cuda_version"`
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════
|
||||
@@ -543,6 +549,10 @@ func (m model) viewMonitor() string {
|
||||
if tw < 60 {
|
||||
tw = 60
|
||||
}
|
||||
th := m.h
|
||||
if th < 20 {
|
||||
th = 20
|
||||
}
|
||||
|
||||
// ── 顶栏 ──
|
||||
wsDot := "○"
|
||||
@@ -602,7 +612,7 @@ func (m model) viewMonitor() string {
|
||||
full := lipgloss.JoinVertical(lipgloss.Left,
|
||||
header, sep, "", mainContent, "", sep, footer,
|
||||
)
|
||||
return full
|
||||
return lipgloss.Place(tw, th, lipgloss.Left, lipgloss.Top, full)
|
||||
}
|
||||
|
||||
// ── CPU 盒子 ──
|
||||
@@ -714,7 +724,7 @@ func (m model) viewMemBox(innerW int) string {
|
||||
// ── GPU 盒子 ──
|
||||
|
||||
func (m model) viewGPUBox(innerW int) string {
|
||||
titleLine := stTitle.Render("GPU")
|
||||
titleLine := stTitle.Render("GPU") + stDim.Render(fmt.Sprintf(" (%d张)", len(m.gpus)))
|
||||
|
||||
if len(m.gpus) == 0 {
|
||||
body := lipgloss.JoinVertical(lipgloss.Left,
|
||||
@@ -723,68 +733,88 @@ func (m model) viewGPUBox(innerW int) string {
|
||||
return stBox.Width(innerW + 4).Render(body)
|
||||
}
|
||||
|
||||
// 总功耗汇总
|
||||
totalP := 0.0
|
||||
totalFan := 0.0
|
||||
fanCnt := 0
|
||||
for _, g := range m.gpus {
|
||||
totalP += g.Power
|
||||
if g.FanSpeed > 0 {
|
||||
totalFan += g.FanSpeed
|
||||
fanCnt++
|
||||
}
|
||||
}
|
||||
avgFan := 0.0
|
||||
if fanCnt > 0 {
|
||||
avgFan = totalFan / float64(fanCnt)
|
||||
}
|
||||
summary := stDim.Render(" ") + stVal.Render(fmt.Sprintf("总功耗: %.0fW", totalP)) +
|
||||
stDim.Render(" │ ") + stVal.Render(fmt.Sprintf("均温: %.0f°C", m.avgTemp())) +
|
||||
stDim.Render(" │ ") + stVal.Render(fmt.Sprintf("风扇: %.0f%%", avgFan))
|
||||
|
||||
var blocks []string
|
||||
blocks = append(blocks, titleLine, "")
|
||||
blocks = append(blocks, titleLine, summary, "")
|
||||
|
||||
// 表头
|
||||
hdr := lipgloss.JoinHorizontal(lipgloss.Left,
|
||||
stDim.Render(fmt.Sprintf(" %-4s", "#")),
|
||||
stDim.Render(fmt.Sprintf("%-6s", "Temp")),
|
||||
stDim.Render(fmt.Sprintf("%-6s", "Load")),
|
||||
stDim.Render(fmt.Sprintf("%-8s", "Power")),
|
||||
stDim.Render(fmt.Sprintf("%-10s", "Clock")),
|
||||
stDim.Render(fmt.Sprintf("%-10s", "MemClk")),
|
||||
stDim.Render(fmt.Sprintf("%-10s", "VRAM")),
|
||||
stDim.Render(fmt.Sprintf("%-5s", "Fan")),
|
||||
)
|
||||
blocks = append(blocks, hdr, stDim.Render(" "+strings.Repeat("─", innerW-4)))
|
||||
|
||||
for i, g := range m.gpus {
|
||||
if i > 0 {
|
||||
blocks = append(blocks, stDim.Render(" "+strings.Repeat("·", innerW-4)))
|
||||
tmpC := tempStyle(&g.Temperature)
|
||||
loadC := pctStyle(g.LoadPercent)
|
||||
|
||||
vramStr := "—"
|
||||
if g.VRAMTotalMB > 0 {
|
||||
vramStr = fmt.Sprintf("%.0f/%.0fM", g.VRAMUsedMB, g.VRAMTotalMB)
|
||||
}
|
||||
blocks = append(blocks, m.viewSingleGPU(g, innerW))
|
||||
|
||||
fanStr := "—"
|
||||
if g.FanSpeed > 0 {
|
||||
fanStr = fmt.Sprintf("%.0f%%", g.FanSpeed)
|
||||
}
|
||||
|
||||
line := lipgloss.JoinHorizontal(lipgloss.Left,
|
||||
stVal.Render(fmt.Sprintf(" %-4d", i)),
|
||||
tmpC.Render(fmt.Sprintf("%-6.0f°C", g.Temperature)),
|
||||
loadC.Render(fmt.Sprintf("%-6.0f%%", g.LoadPercent)),
|
||||
stVal.Render(fmt.Sprintf("%-8.0fW", g.Power)),
|
||||
stVal.Render(fmt.Sprintf("%-10.0f", g.CoreClock)),
|
||||
stVal.Render(fmt.Sprintf("%-10.0f", g.MemClock)),
|
||||
stVal.Render(fmt.Sprintf("%-10s", vramStr)),
|
||||
stVal.Render(fmt.Sprintf("%-5s", fanStr)),
|
||||
)
|
||||
blocks = append(blocks, line)
|
||||
}
|
||||
|
||||
// 总功耗
|
||||
totalP = 0.0
|
||||
for _, g := range m.gpus {
|
||||
totalP += g.Power
|
||||
}
|
||||
blocks = append(blocks, stDim.Render(" ")+stVal.Render(fmt.Sprintf("总功耗: %.0fW", totalP)), "")
|
||||
|
||||
body := lipgloss.JoinVertical(lipgloss.Left, blocks...)
|
||||
return stBox.Width(innerW + 4).Render(body)
|
||||
}
|
||||
|
||||
func (m model) viewSingleGPU(g GPUInfo, w int) string {
|
||||
// 名称
|
||||
nm := g.Name
|
||||
if len(nm) > w-2 && w > 5 {
|
||||
nm = nm[:w-5] + "…"
|
||||
func (m model) avgTemp() float64 {
|
||||
if len(m.gpus) == 0 {
|
||||
return 0
|
||||
}
|
||||
nameLine := stBold.Render(" " + nm)
|
||||
|
||||
// 统计行
|
||||
loadC := pctStyle(g.LoadPercent)
|
||||
tmpC := tempStyle(&g.Temperature)
|
||||
statsLine := lipgloss.JoinHorizontal(lipgloss.Left,
|
||||
stLabel.Render(" 负载 "), loadC.Render(fmt.Sprintf("%.0f%%", g.LoadPercent)),
|
||||
" ",
|
||||
stLabel.Render("温度 "), tmpC.Render(fmt.Sprintf("%.0f°C", g.Temperature)),
|
||||
" ",
|
||||
stLabel.Render("功耗 "), stVal.Render(fmt.Sprintf("%.0fW", g.Power)),
|
||||
)
|
||||
|
||||
// 频率行
|
||||
clockLine := lipgloss.JoinHorizontal(lipgloss.Left,
|
||||
stLabel.Render(" 核心 "), stVal.Render(fmt.Sprintf("%.0f MHz", g.CoreClock)),
|
||||
" ",
|
||||
stLabel.Render("显存 "), stVal.Render(fmt.Sprintf("%.0f MHz", g.MemClock)),
|
||||
)
|
||||
|
||||
// VRAM
|
||||
vramLine := ""
|
||||
if g.VRAMTotalMB > 0 {
|
||||
vpct := g.VRAMUsedMB / g.VRAMTotalMB * 100
|
||||
barW := w - 20
|
||||
if barW < 6 {
|
||||
barW = 6
|
||||
}
|
||||
vbar := renderBar(vpct, barW)
|
||||
usedGB := fmt.Sprintf("%.1f", g.VRAMUsedMB/1024)
|
||||
totalGB := fmt.Sprintf("%.1f", g.VRAMTotalMB/1024)
|
||||
vramLine = lipgloss.JoinHorizontal(lipgloss.Left,
|
||||
stLabel.Render(" 显存 "),
|
||||
pctStyle(vpct).Render(usedGB),
|
||||
stVal.Render("/"+totalGB+" GB "),
|
||||
vbar,
|
||||
)
|
||||
total := 0.0
|
||||
for _, g := range m.gpus {
|
||||
total += g.Temperature
|
||||
}
|
||||
|
||||
return lipgloss.JoinVertical(lipgloss.Left,
|
||||
nameLine, statsLine, clockLine, vramLine,
|
||||
)
|
||||
return total / float64(len(m.gpus))
|
||||
}
|
||||
|
||||
// ── History 盒子 ──
|
||||
@@ -1014,27 +1044,72 @@ func collectCPU(perCore []float64) CPUInfo {
|
||||
}
|
||||
|
||||
func getCPUTemp() *float64 {
|
||||
if runtime.GOOS != "windows" {
|
||||
return nil
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
out, err := exec.CommandContext(ctx, "powershell", "-NoProfile", "-Command",
|
||||
"(Get-CimInstance MSAcpi_ThermalZoneTemperature -Namespace 'root/wmi' -EA SilentlyContinue | Select -First 1).CurrentTemperature",
|
||||
).Output()
|
||||
if err != nil {
|
||||
return nil
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
out, err := exec.CommandContext(ctx, "powershell", "-NoProfile", "-Command",
|
||||
"(Get-CimInstance MSAcpi_ThermalZoneTemperature -Namespace 'root/wmi' -EA SilentlyContinue | Select -First 1).CurrentTemperature",
|
||||
).Output()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
raw := strings.TrimSpace(string(out))
|
||||
val, err := strconv.ParseFloat(raw, 64)
|
||||
if err != nil || val < 1000 {
|
||||
return nil
|
||||
}
|
||||
c := (val - 2732) / 10.0
|
||||
if c < 0 || c > 150 {
|
||||
return nil
|
||||
}
|
||||
return &c
|
||||
case "linux":
|
||||
out, err := exec.CommandContext(ctx, "cat", "/sys/class/thermal/thermal_zone0/temp").Output()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
raw := strings.TrimSpace(string(out))
|
||||
val, err := strconv.ParseFloat(raw, 64)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
c := val / 1000.0
|
||||
if c < 0 || c > 150 {
|
||||
return nil
|
||||
}
|
||||
return &c
|
||||
case "darwin":
|
||||
out, err := exec.CommandContext(ctx, "sudo", "powermetrics", "--samplers", "smc", "-i", "1", "-n", "1", "--format", "plist").Output()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
s := string(out)
|
||||
idx := strings.Index(s, "<key>CPU die temperature</key>")
|
||||
if idx < 0 {
|
||||
return nil
|
||||
}
|
||||
s = s[idx:]
|
||||
idx = strings.Index(s, "<real>")
|
||||
if idx < 0 {
|
||||
return nil
|
||||
}
|
||||
s = s[idx+6:]
|
||||
end := strings.Index(s, "</real>")
|
||||
if end < 0 {
|
||||
return nil
|
||||
}
|
||||
val, err := strconv.ParseFloat(strings.TrimSpace(s[:end]), 64)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if val < 0 || val > 150 {
|
||||
return nil
|
||||
}
|
||||
return &val
|
||||
}
|
||||
raw := strings.TrimSpace(string(out))
|
||||
val, err := strconv.ParseFloat(raw, 64)
|
||||
if err != nil || val < 1000 {
|
||||
return nil
|
||||
}
|
||||
c := (val - 2732) / 10.0
|
||||
if c < 0 || c > 150 {
|
||||
return nil
|
||||
}
|
||||
return &c
|
||||
return nil
|
||||
}
|
||||
|
||||
func collectMemory() MemInfo {
|
||||
@@ -1050,34 +1125,76 @@ func collectMemory() MemInfo {
|
||||
}
|
||||
|
||||
func collectMotherboard() BoardInfo {
|
||||
if runtime.GOOS != "windows" {
|
||||
return BoardInfo{}
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
out, err := exec.CommandContext(ctx, "wmic", "baseboard", "get", "manufacturer,product", "/format:list").Output()
|
||||
if err != nil {
|
||||
return BoardInfo{}
|
||||
}
|
||||
var b BoardInfo
|
||||
for _, line := range strings.Split(string(out), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "Manufacturer=") {
|
||||
b.Manufacturer = strings.TrimPrefix(line, "Manufacturer=")
|
||||
} else if strings.HasPrefix(line, "Product=") {
|
||||
b.Product = strings.TrimPrefix(line, "Product=")
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
out, err := exec.CommandContext(ctx, "wmic", "baseboard", "get", "manufacturer,product", "/format:list").Output()
|
||||
if err != nil {
|
||||
return BoardInfo{}
|
||||
}
|
||||
var b BoardInfo
|
||||
for _, line := range strings.Split(string(out), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "Manufacturer=") {
|
||||
b.Manufacturer = strings.TrimPrefix(line, "Manufacturer=")
|
||||
} else if strings.HasPrefix(line, "Product=") {
|
||||
b.Product = strings.TrimPrefix(line, "Product=")
|
||||
}
|
||||
}
|
||||
return b
|
||||
case "linux":
|
||||
man, _ := os.ReadFile("/sys/class/dmi/id/board_vendor")
|
||||
prod, _ := os.ReadFile("/sys/class/dmi/id/board_name")
|
||||
return BoardInfo{
|
||||
Manufacturer: strings.TrimSpace(string(man)),
|
||||
Product: strings.TrimSpace(string(prod)),
|
||||
}
|
||||
case "darwin":
|
||||
out, err := exec.CommandContext(ctx, "system_profiler", "SPHardwareDataType").Output()
|
||||
if err != nil {
|
||||
return BoardInfo{}
|
||||
}
|
||||
var b BoardInfo
|
||||
for _, line := range strings.Split(string(out), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "Model Name:") {
|
||||
b.Product = strings.TrimSpace(strings.TrimPrefix(line, "Model Name:"))
|
||||
} else if strings.HasPrefix(line, "Model Identifier:") {
|
||||
if b.Manufacturer == "" {
|
||||
b.Manufacturer = "Apple"
|
||||
}
|
||||
}
|
||||
}
|
||||
if b.Manufacturer == "" {
|
||||
b.Manufacturer = "Apple"
|
||||
}
|
||||
return b
|
||||
}
|
||||
return b
|
||||
return BoardInfo{}
|
||||
}
|
||||
|
||||
func collectGPUs() []GPUInfo {
|
||||
if runtime.GOOS == "windows" {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
gpus := tryNvidiaSMI()
|
||||
if len(gpus) > 0 {
|
||||
return gpus
|
||||
}
|
||||
return tryWMIGPU()
|
||||
case "linux":
|
||||
gpus := tryNvidiaSMI()
|
||||
if len(gpus) > 0 {
|
||||
return gpus
|
||||
}
|
||||
return tryLinuxGPU()
|
||||
case "darwin":
|
||||
gpus := tryNvidiaSMI()
|
||||
if len(gpus) > 0 {
|
||||
return gpus
|
||||
}
|
||||
return tryMacGPU()
|
||||
}
|
||||
return tryNvidiaSMI()
|
||||
}
|
||||
@@ -1086,7 +1203,7 @@ func tryNvidiaSMI() []GPUInfo {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
out, err := exec.CommandContext(ctx, "nvidia-smi",
|
||||
"--query-gpu=index,name,memory.total,memory.used,memory.free,temperature.gpu,utilization.gpu,clocks.current.graphics,clocks.current.memory,power.draw,fan.speed",
|
||||
"--query-gpu=index,name,memory.total,memory.used,memory.free,temperature.gpu,utilization.gpu,clocks.current.graphics,clocks.current.memory,power.draw,power.limit,fan.speed,pstate,persistence_mode,compute_mode,driver_version,cuda_version",
|
||||
"--format=csv,noheader,nounits",
|
||||
).Output()
|
||||
if err != nil {
|
||||
@@ -1100,38 +1217,52 @@ func tryNvidiaSMI() []GPUInfo {
|
||||
}
|
||||
for i := range parts {
|
||||
parts[i] = strings.TrimSpace(parts[i])
|
||||
parts[i] = strings.Trim(parts[i], "[]")
|
||||
}
|
||||
fanStr := strings.TrimSpace(parts[10])
|
||||
fanStr = strings.Trim(fanStr, "[]")
|
||||
var fan float64
|
||||
if fanStr == "N/A" || fanStr == "" {
|
||||
fan = 0
|
||||
} else {
|
||||
fan = parseFloat(fanStr)
|
||||
}
|
||||
powerStr := strings.TrimSpace(parts[9])
|
||||
powerStr = strings.Trim(powerStr, "[]")
|
||||
var power float64
|
||||
if powerStr == "N/A" || powerStr == "" {
|
||||
power = 0
|
||||
} else {
|
||||
power = parseFloat(powerStr)
|
||||
}
|
||||
|
||||
fan := parseFloatSafe(parts[10])
|
||||
power := parseFloatSafe(parts[9])
|
||||
powerLimit := parseFloatSafe(parts[10])
|
||||
|
||||
gpus = append(gpus, GPUInfo{
|
||||
Name: parts[1],
|
||||
VRAMTotalMB: parseFloat(parts[2]),
|
||||
VRAMUsedMB: parseFloat(parts[3]),
|
||||
Temperature: parseFloat(parts[5]),
|
||||
LoadPercent: parseFloat(parts[6]),
|
||||
CoreClock: parseFloat(parts[7]),
|
||||
MemClock: parseFloat(parts[8]),
|
||||
Power: power,
|
||||
FanSpeed: fan,
|
||||
Name: parts[1],
|
||||
VRAMTotalMB: parseFloat(parts[2]),
|
||||
VRAMUsedMB: parseFloat(parts[3]),
|
||||
Temperature: parseFloat(parts[5]),
|
||||
LoadPercent: parseFloat(parts[6]),
|
||||
CoreClock: parseFloat(parts[7]),
|
||||
MemClock: parseFloat(parts[8]),
|
||||
Power: power,
|
||||
PowerLimit: powerLimit,
|
||||
FanSpeed: fan,
|
||||
PState: cleanStr(parts[12]),
|
||||
Persistence: cleanStr(parts[13]),
|
||||
ComputeMode: cleanStr(parts[14]),
|
||||
DriverVersion: cleanStr(parts[15]),
|
||||
CudaVersion: cleanStr(parts[16]),
|
||||
})
|
||||
}
|
||||
return gpus
|
||||
}
|
||||
|
||||
func parseFloatSafe(s string) float64 {
|
||||
s = strings.Trim(s, "[] ")
|
||||
s = strings.TrimSuffix(s, "W")
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "N/A" || s == "" {
|
||||
return 0
|
||||
}
|
||||
return parseFloat(s)
|
||||
}
|
||||
|
||||
func cleanStr(s string) string {
|
||||
s = strings.Trim(s, "[] ")
|
||||
if s == "N/A" || s == "[N/A]" {
|
||||
return ""
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func tryWMIGPU() []GPUInfo {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
@@ -1164,6 +1295,76 @@ func tryWMIGPU() []GPUInfo {
|
||||
return gpus
|
||||
}
|
||||
|
||||
func tryLinuxGPU() []GPUInfo {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
out, err := exec.CommandContext(ctx, "lspci", "-vnn").Output()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var gpus []GPUInfo
|
||||
lines := strings.Split(string(out), "\n")
|
||||
for i, line := range lines {
|
||||
if strings.Contains(line, "VGA") || strings.Contains(line, "3D") || strings.Contains(line, "Display") {
|
||||
name := line
|
||||
if idx := strings.Index(name, ": "); idx >= 0 {
|
||||
name = name[idx+2:]
|
||||
}
|
||||
if idx := strings.Index(name, " ["); idx >= 0 {
|
||||
name = name[:idx]
|
||||
}
|
||||
name = strings.TrimSpace(name)
|
||||
var vram float64
|
||||
for j := i + 1; j < len(lines) && j < i+10; j++ {
|
||||
if strings.Contains(lines[j], "prefetchable") && strings.Contains(lines[j], "Memory") {
|
||||
if idx := strings.Index(lines[j], "size="); idx >= 0 {
|
||||
sz := lines[j][idx+5:]
|
||||
if end := strings.Index(sz, "M"); end > 0 {
|
||||
vram, _ = strconv.ParseFloat(sz[:end], 64)
|
||||
} else if end := strings.Index(sz, "G"); end > 0 {
|
||||
v, _ := strconv.ParseFloat(sz[:end], 64)
|
||||
vram = v * 1024
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
gpus = append(gpus, GPUInfo{Name: name, VRAMTotalMB: vram})
|
||||
}
|
||||
}
|
||||
return gpus
|
||||
}
|
||||
|
||||
func tryMacGPU() []GPUInfo {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
out, err := exec.CommandContext(ctx, "system_profiler", "SPDisplaysDataType", "-json").Output()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var data struct {
|
||||
SPDisplaysDataType []struct {
|
||||
sppci_model string `json:"_name"`
|
||||
sppci_memory string `json:"sppci_memory"`
|
||||
SPDisplaysVendorID string `json:"spdisplays_vendor-id"`
|
||||
} `json:"SPDisplaysDataType"`
|
||||
}
|
||||
if json.Unmarshal(out, &data) != nil {
|
||||
return nil
|
||||
}
|
||||
var gpus []GPUInfo
|
||||
for _, d := range data.SPDisplaysDataType {
|
||||
g := GPUInfo{Name: d.sppci_model}
|
||||
if vram, err := strconv.ParseFloat(d.sppci_memory, 64); err == nil {
|
||||
g.VRAMTotalMB = vram / (1024 * 1024)
|
||||
if g.VRAMTotalMB > 1024*10 {
|
||||
g.VRAMTotalMB = vram / (1024 * 1024)
|
||||
}
|
||||
}
|
||||
gpus = append(gpus, g)
|
||||
}
|
||||
return gpus
|
||||
}
|
||||
|
||||
func parseFloat(s string) float64 {
|
||||
v, _ := strconv.ParseFloat(strings.TrimSpace(s), 64)
|
||||
return v
|
||||
|
||||
@@ -344,13 +344,20 @@ Vue.component('gpu-section',{
|
||||
<div class="gpu-pop-grid">
|
||||
<div class="gd-item"><span class="gd-l">负载</span><span class="gd-v" :style="{color:pColor(gg.load_percent||0)}">{{ (gg.load_percent||0).toFixed(1) }}%</span></div>
|
||||
<div class="gd-item"><span class="gd-l">温度</span><span class="gd-v" :style="{color:tmpColor(gg)}">{{ tmpStr(gg) }}</span></div>
|
||||
<div class="gd-item"><span class="gd-l">功耗</span><span class="gd-v">{{ Math.round(gg.power||0) }} W</span></div>
|
||||
<div class="gd-item"><span class="gd-l">功耗</span><span class="gd-v">{{ Math.round(gg.power||0) }}W / {{ Math.round(gg.power_limit||0) }}W</span></div>
|
||||
<div class="gd-item"><span class="gd-l">核心频率</span><span class="gd-v">{{ Math.round(gg.core_clock_mhz||0) }} MHz</span></div>
|
||||
<div class="gd-item"><span class="gd-l">显存频率</span><span class="gd-v">{{ Math.round(gg.mem_clock_mhz||0) }} MHz</span></div>
|
||||
<div class="gd-item"><span class="gd-l">风扇</span><span class="gd-v">{{ fanStr(gg) }}</span></div>
|
||||
<div class="gd-item"><span class="gd-l">性能状态</span><span class="gd-v">{{ gg.pstate||'N/A' }}</span></div>
|
||||
<div class="gd-item"><span class="gd-l">持久模式</span><span class="gd-v">{{ gg.persistence_mode||'N/A' }}</span></div>
|
||||
<div class="gd-item"><span class="gd-l">计算模式</span><span class="gd-v">{{ gg.compute_mode||'N/A' }}</span></div>
|
||||
</div>
|
||||
<div v-if="vramTotal(gg)>0" style="margin-top:2px"><div class="info-row" style="margin-bottom:1px"><span class="label">显存</span><span class="value">{{ vramUsed(gg) }} / {{ vramTotal(gg) }} GB</span></div>
|
||||
<a-progress :percent="Math.round(vramPct(gg))" :stroke-color="pColor(vramPct(gg))" :stroke-width="4" size="small" :format="fmtPct" /></div>
|
||||
<div v-if="gg.driver_version" style="margin-top:2px;font-size:8px;color:var(--dim);display:flex;gap:8px">
|
||||
<span>驱动: {{ gg.driver_version }}</span>
|
||||
<span v-if="gg.cuda_version">CUDA: {{ gg.cuda_version }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div></template>
|
||||
<div @click.stop="toggle(i)">
|
||||
|
||||
Reference in New Issue
Block a user