feat: comprehensive 30-field nvidia-smi query, TUI 2-row layout, card width 600px

This commit is contained in:
ngfchl
2026-06-20 15:28:47 +08:00
parent 8f1325800c
commit d39713a196
+69 -77
View File
@@ -549,7 +549,8 @@ func (m model) viewMonitor() string {
if tw < 60 {
tw = 60
}
innerW := tw - 4
gap := 2
halfW := (tw - gap) / 2
// ── 顶栏 ──
wsDot := "○"
@@ -569,23 +570,28 @@ func (m model) viewMonitor() string {
)
sep := stDim.Render(strings.Repeat("─", tw))
// ── 内容:全部垂直排列 ──
cpuBox := m.viewCPUBox(innerW)
memBox := m.viewMemBox(innerW)
histBox := m.viewHistBox(innerW)
gpuBox := m.viewGPUBox(innerW)
// ── 第一行: CPU + Memory ──
cpuBox := m.viewCPUBox(halfW - 4)
memBox := m.viewMemBox(halfW - 4)
row1 := lipgloss.JoinHorizontal(lipgloss.Top,
cpuBox, strings.Repeat(" ", gap), memBox,
)
// ── 第二行: GPU + History ──
gpuBox := m.viewGPUBox(halfW - 4)
histBox := m.viewHistBox(halfW - 4)
row2 := lipgloss.JoinHorizontal(lipgloss.Top,
gpuBox, strings.Repeat(" ", gap), histBox,
)
full := lipgloss.JoinVertical(lipgloss.Left,
header, sep, "",
cpuBox, "",
memBox, "",
histBox, "",
gpuBox, "",
row1, "",
row2, "",
sep,
stDim.Render(" q 退出 · r 重新配置 · 连接: "+m.cfg.Server),
)
// 截断到终端高度,防止滚动
lines := strings.Split(full, "\n")
if len(lines) > m.h {
lines = lines[:m.h]
@@ -599,40 +605,38 @@ func (m model) viewCPUBox(innerW int) string {
c := m.cpu
pct := c.UsageTotal
// 标题
modelName := c.Model
maxNameW := innerW - 10
maxNameW := innerW - 8
if len(modelName) > maxNameW && maxNameW > 3 {
modelName = modelName[:maxNameW-1] + "…"
}
titleLine := stTitle.Render("CPU") + " " + stFg.Render(modelName)
// 百分比进度条
// 标题 + 核心/频率/温度 一行
coresStr := fmt.Sprintf("%dC/%dT", c.CoresPhysical, c.CoresLogical)
freqStr := fmt.Sprintf("%.0fMHz", c.FreqMHz)
tempStr := tempText(c.Temperature)
titleLine := lipgloss.JoinHorizontal(lipgloss.Left,
stTitle.Render("CPU"),
" ", stFg.Render(modelName),
" ", stLabel.Render(coresStr),
" ", stVal.Render(freqStr),
" ", tempStyle(c.Temperature).Render(tempStr),
)
// 进度条 + 百分比 一行
barW := innerW - 8
if barW < 10 {
barW = 10
}
bar := renderBar(pct, barW)
pctStr := pctStyle(pct).Bold(true).Render(fmt.Sprintf("%5.1f%%", pct))
pctStr := pctStyle(pct).Bold(true).Render(fmt.Sprintf("%.1f%%", pct))
barLine := lipgloss.JoinHorizontal(lipgloss.Center, bar, " ", pctStr)
// 信息行
coresStr := fmt.Sprintf("%dC/%dT", c.CoresPhysical, c.CoresLogical)
freqStr := fmt.Sprintf("%.0f MHz", c.FreqMHz)
tempStr := tempText(c.Temperature)
infoLine := lipgloss.JoinHorizontal(lipgloss.Left,
stLabel.Render("核心 "), stVal.Render(coresStr),
" ",
stLabel.Render("频率 "), stVal.Render(freqStr),
" ",
stLabel.Render("温度 "), tempStyle(c.Temperature).Render(tempStr),
)
// 核心热力图
coreLine := m.viewCoreHeatmap(innerW)
body := lipgloss.JoinVertical(lipgloss.Left,
titleLine, "", barLine, infoLine, "", coreLine,
titleLine, barLine, coreLine,
)
return stBox.Width(innerW + 4).Render(body)
}
@@ -671,10 +675,16 @@ func (m model) viewCoreHeatmap(innerW int) string {
func (m model) viewMemBox(innerW int) string {
pct := m.mem.Percent
titleLine := stTitle.Render("MEMORY")
usedGB := fmtBytes(m.mem.UsedBytes)
totalGB := fmtBytes(m.mem.TotalBytes)
// 百分比
pctStr := pctStyle(pct).Bold(true).Render(fmt.Sprintf("%.1f%%", pct))
// 标题 + 详情 一行
titleLine := lipgloss.JoinHorizontal(lipgloss.Left,
stTitle.Render("MEMORY"),
" ", stLabel.Render("已用 "), stVal.Render(usedGB),
" / ", stVal.Render(totalGB),
" ", pctStyle(pct).Bold(true).Render(fmt.Sprintf("%.1f%%", pct)),
)
// 进度条
barW := innerW - 8
@@ -682,19 +692,10 @@ func (m model) viewMemBox(innerW int) string {
barW = 10
}
bar := renderBar(pct, barW)
barLine := lipgloss.JoinHorizontal(lipgloss.Center, bar, " ", pctStr)
// 详情
usedGB := fmtBytes(m.mem.UsedBytes)
totalGB := fmtBytes(m.mem.TotalBytes)
detailLine := lipgloss.JoinHorizontal(lipgloss.Left,
stLabel.Render("已用 "), stVal.Render(usedGB),
" ",
stLabel.Render("总计 "), stVal.Render(totalGB),
)
barLine := lipgloss.JoinHorizontal(lipgloss.Center, bar)
body := lipgloss.JoinVertical(lipgloss.Left,
titleLine, "", pctStr, barLine, "", detailLine,
titleLine, barLine,
)
return stBox.Width(innerW + 4).Render(body)
}
@@ -1194,7 +1195,15 @@ 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,power.limit,fan.speed,pstate,driver_version,cuda_version",
"--query-gpu=index,name,uuid,pci.bus_id,driver_version,vbios_version,"+
"temperature.gpu,temperature.memory,fan.speed,"+
"power.draw,power.limit,power.default_limit,power.min_limit,power.max_limit,"+
"clocks.current.graphics,clocks.current.sm,clocks.current.memory,"+
"clocks.max.graphics,clocks.max.memory,"+
"utilization.gpu,utilization.memory,utilization.encoder,utilization.decoder,"+
"memory.total,memory.used,memory.free,"+
"ecc.errors.corrected.volatile.total,ecc.errors.uncorrected.volatile.total,"+
"compute_mode,pstate",
"--format=csv,noheader,nounits",
).Output()
if err != nil {
@@ -1202,12 +1211,8 @@ func tryNvidiaSMI() []GPUInfo {
}
var gpus []GPUInfo
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, " ") {
continue
}
parts := strings.Split(line, ",")
if len(parts) < 9 {
if len(parts) < 24 {
continue
}
for i := range parts {
@@ -1215,34 +1220,21 @@ func tryNvidiaSMI() []GPUInfo {
parts[i] = strings.Trim(parts[i], "[]")
}
g := 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]),
}
if len(parts) > 9 {
g.Power = parseFloatSafe(parts[9])
}
if len(parts) > 10 {
g.PowerLimit = parseFloatSafe(parts[10])
}
if len(parts) > 11 {
g.FanSpeed = parseFloatSafe(parts[11])
}
if len(parts) > 12 {
g.PState = cleanStr(parts[12])
}
if len(parts) > 13 {
g.DriverVersion = cleanStr(parts[13])
}
if len(parts) > 14 {
g.CudaVersion = cleanStr(parts[14])
}
gpus = append(gpus, g)
gpus = append(gpus, GPUInfo{
Name: cleanStr(parts[1]),
VRAMTotalMB: parseFloat(parts[23]),
VRAMUsedMB: parseFloat(parts[24]),
Temperature: parseFloat(parts[6]),
LoadPercent: parseFloat(parts[19]),
CoreClock: parseFloat(parts[14]),
MemClock: parseFloat(parts[16]),
Power: parseFloatSafe(parts[9]),
PowerLimit: parseFloatSafe(parts[10]),
FanSpeed: parseFloatSafe(parts[8]),
PState: cleanStr(parts[29]),
ComputeMode: cleanStr(parts[28]),
DriverVersion: cleanStr(parts[4]),
})
}
return gpus
}