feat: comprehensive 30-field nvidia-smi query, TUI 2-row layout, card width 600px
This commit is contained in:
+69
-77
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user