diff --git a/cmd/client/main.go b/cmd/client/main.go index 80bf831..0e2ad46 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -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 }