Commit fbb93034 authored by liming6's avatar liming6
Browse files

feature 完成表格部分

parent 687d3e07
......@@ -156,3 +156,14 @@ func UpdateDCUInfo(full bool) {
d.PwrMode = v.PwrMode
}
}
func GetDCUInfo() map[int]DCUInfo {
result := make(map[int]DCUInfo)
MapIdDCU.Range(func(key, value any) bool {
id := key.(int)
val := value.(*DCUInfo)
result[id] = *val
return true
})
return result
}
......@@ -8,8 +8,22 @@ import (
func TestUpdateDCUInfo(t *testing.T) {
for i := 0; i < 10; i++ {
start := time.Now()
UpdateDCUInfo(true)
UpdateDCUInfo(false)
end := time.Now()
t.Logf("%d ms", end.Sub(start).Milliseconds())
}
}
func TestGetDCUInfo(t *testing.T) {
UpdateDCUInfo(true)
info := GetDCUInfo()
t.Logf("%+v", info)
for i := range 10 {
time.Sleep(time.Second)
start := time.Now()
UpdateDCUInfo(false)
info = GetDCUInfo()
tt := time.Since(start).Milliseconds()
t.Logf("%d|%d : %+v", i, tt, info)
}
}
package tui
import (
"get-container/gpu"
"fmt"
"get-container/cmd/dcutop/backend"
"maps"
"strconv"
"strings"
"github.com/charmbracelet/bubbles/progress"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
type ModelDCUInfo struct {
DCUNum int // dcu数量
width, height int // 终端的尺寸
DCUStaticInfo []gpu.SMIAllOutput
DCURunningInfo []gpu.DCURunningInfo
info map[int]backend.DCUInfo // dcu相关信息
pro progress.Model // 进度条
proWidth int // 进度条宽度
}
const (
TopLine = `╞═══════════════════════════════╪══════════════════════╪══════════════════════╪`
MiddleLine = `├───────────────────────────────┼──────────────────────┼──────────────────────┼`
BottomLine = `╞═══════════════════════════════╧══════════════════════╧══════════════════════╪`
OtherWidth = 8
StaticWidth = 87 // 固定表格的宽度
ProgressMaxWidth = 105 // 进度条最大宽度
ProgressMinWidth = 15 // 进度条最小宽度
)
func (m *ModelDCUInfo) Init() tea.Cmd {
if m.width < StaticWidth+ProgressMinWidth {
return tea.Quit
}
m.proWidth = ProgressMinWidth
if m.width > StaticWidth+ProgressMaxWidth {
m.proWidth = ProgressMaxWidth
} else {
m.proWidth = m.width - StaticWidth
}
m.pro = progress.New(progress.WithDefaultGradient(), progress.WithWidth(m.proWidth))
return nil
}
func (m *ModelDCUInfo) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func (m *ModelDCUInfo) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := inputMsg.(type) {
case *ModelMsg:
clear(m.info)
maps.Copy(m.info, msg.DCUInfo)
return m, nil
}
return m, nil
}
func (m *ModelDCUInfo) View() string {
return ""
lineWidth := 0
l := len(m.info)
strBuilder := strings.Builder{}
infos := make([]string, l)
for i := range l {
ii := m.info[i]
strBuilder.WriteString(myBorder.Left)
strBuilder.WriteByte(' ')
strBuilder.WriteString(FormatStr(strconv.Itoa(ii.Id), 4, lipgloss.Left))
strBuilder.WriteByte(' ')
strBuilder.WriteString(FormatStr(ii.Name, 6, lipgloss.Left))
strBuilder.WriteByte(' ')
strBuilder.WriteString(FormatStr(ii.PerformanceLevel, 17, lipgloss.Right))
strBuilder.WriteByte(' ')
strBuilder.WriteString(myBorder.Left)
strBuilder.WriteByte(' ')
strBuilder.WriteString(FormatStr(ii.BusId, 13, lipgloss.Left))
strBuilder.WriteByte(' ')
strBuilder.WriteString(FormatStr("Off", 6, lipgloss.Right))
strBuilder.WriteByte(' ')
strBuilder.WriteString(myBorder.Left)
strBuilder.WriteByte(' ')
if ii.Mig {
strBuilder.WriteString(FormatStr("On", 16, lipgloss.Left))
} else {
strBuilder.WriteString(FormatStr("Off", 16, lipgloss.Left))
}
strBuilder.WriteByte(' ')
if ii.Ecc {
strBuilder.WriteString(FormatStr("On", 3, lipgloss.Right))
} else {
strBuilder.WriteString(FormatStr("Off", 3, lipgloss.Right))
}
strBuilder.WriteByte(' ')
strBuilder.WriteString(myBorder.Left)
strBuilder.WriteString(" DCU: ")
strBuilder.WriteString(m.pro.ViewAs(float64(ii.DCUUTil) / 100))
strBuilder.WriteByte(' ')
strBuilder.WriteString(myBorder.Left)
strBuilder.WriteByte('\n')
strBuilder.WriteString(myBorder.Left)
strBuilder.WriteByte(' ')
strBuilder.WriteString(FormatStr(ii.Fan, 4, lipgloss.Left))
strBuilder.WriteByte(' ')
strBuilder.WriteString(FormatStr(fmt.Sprintf("%.1fC", ii.Temp), 6, lipgloss.Left))
strBuilder.WriteByte(' ')
// 18
strBuilder.WriteString(FormatStr(fmt.Sprintf("%.1fW / %.1fW", ii.PwrAvg, ii.PwrCap), 17, lipgloss.Right))
strBuilder.WriteByte(' ')
strBuilder.WriteString(myBorder.Left)
strBuilder.WriteByte(' ')
// 20
strBuilder.WriteString(FormatStr(fmt.Sprintf("%dMiB / %dMiB", ii.MemUsed, ii.MemTotal), 20, lipgloss.Right))
strBuilder.WriteByte(' ')
strBuilder.WriteString(myBorder.Left)
strBuilder.WriteByte(' ')
strBuilder.WriteString(FormatStr(fmt.Sprintf("%.1f%%", ii.DCUUTil), 6, lipgloss.Left))
strBuilder.WriteByte(' ')
strBuilder.WriteString(FormatStr(ii.PwrMode, 13, lipgloss.Right))
strBuilder.WriteByte(' ')
strBuilder.WriteString(myBorder.Left)
strBuilder.WriteString(" Mem: ")
strBuilder.WriteString(m.pro.ViewAs(float64(ii.MemUsedPerent) / 100))
strBuilder.WriteByte(' ')
strBuilder.WriteString(myBorder.Left)
infos[i] = strBuilder.String()
strBuilder.Reset()
}
if len(infos) > 0 {
lineWidth = lipgloss.Width(infos[0])
}
subLen := lineWidth - StaticWidth + OtherWidth - 1
if subLen <= 0 {
subLen = 0
}
top := TopLine + strings.Repeat("═", subLen) + "╕\n"
middle := "\n" + MiddleLine + strings.Repeat("─", subLen) + "┤\n"
bottom := "\n" + BottomLine + strings.Repeat("═", subLen) + "╡\n"
return top + strings.Join(infos, middle) + bottom
}
......@@ -23,12 +23,6 @@ func (mh *ModelHeader) Init() tea.Cmd {
func (mh *ModelHeader) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := inputMsg.(type) {
case ModelMsg:
mh.t = msg.t
mh.DCUTopVersion = msg.MyVersion
mh.DriverVersion = msg.Version.DriverVersion
mh.SMIVersion = msg.Version.SMIVersion
return mh, nil
case *ModelMsg:
mh.t = msg.t
mh.DCUTopVersion = msg.MyVersion
......@@ -42,8 +36,8 @@ func (mh *ModelHeader) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
const (
Title = `
├───────────────────────────────┬──────────────────────┬──────────────────────┤
│ DCU Name Performance Level│ Bus-Id DisP.A │ MIG M. ECC │
│ Fan Temp Pwr:Usage/Cap│ Memory-Usage │ DCU-Util PowerMode │
│ DCU Name Performance Level │ Bus-Id DisP.A │ MIG M. ECC │
│ Fan Temp Pwr:Usage/Cap │ Memory-Usage │ DCU-Util PowerMode │
`
)
......
package tui
import (
"get-container/cmd/dcutop/backend"
"get-container/gpu"
"time"
......@@ -18,8 +19,7 @@ type ModelMsg struct {
index uint64 // update次数
Version *gpu.HYVersionInfo // gpu版本相关信息
MyVersion string
DCUInfo []gpu.DCUInfo // DCU全量信息
DCURunningInfo []gpu.DCURunningInfo // DCU运行时信息
DCUInfo map[int]backend.DCUInfo // DCU全量信息
// DCUPidInfo []gpu.DCUPidInfo // 使用dcu的进程信息
}
......@@ -41,7 +41,7 @@ func NewModelMain(width, height int) ModelMain {
result.width = width
result.height = height
result.Header = &ModelHeader{}
result.DCUInfo = &ModelDCUInfo{width: width, height: height}
result.DCUInfo = &ModelDCUInfo{width: width, height: height, info: make(map[int]backend.DCUInfo)}
return result
}
......@@ -59,17 +59,21 @@ func sendMsgCmd(modelMsg *ModelMsg) tea.Cmd {
// Init 初始化信息
func (m *ModelMain) Init() tea.Cmd {
modelMsg := &ModelMsg{}
if err := initModelInfo(modelMsg); err != nil {
modelMsg := ModelMsg{}
if err := initModelInfo(&modelMsg); err != nil {
return tea.Quit
}
cmds := make([]tea.Cmd, 0)
if c := m.Header.Init(); c != nil {
cmds = append(cmds, c)
}
m.modelMsg = modelMsg
m.DCUInfo.DCUNum = len(modelMsg.DCUInfo)
if c := m.DCUInfo.Init(); c != nil {
cmds = append(cmds, c)
}
m.modelMsg = &modelMsg
// 将调用初始化方法
cmds = append(cmds, tickCmd(), sendMsgCmd(modelMsg))
cmds = append(cmds, tickCmd(), sendMsgCmd(&modelMsg))
return tea.Batch(cmds...)
}
......@@ -85,15 +89,23 @@ func (m *ModelMain) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
updateModelInfo(m.modelMsg, m.index, time.Time(msg))
cmds := make([]tea.Cmd, 0)
header, cmdHeader := m.Header.Update(m.modelMsg)
dcuInfo, dcuHeader := m.DCUInfo.Update(m.modelMsg)
m.Header = header.(*ModelHeader)
cmds = append(cmds, cmdHeader, tickCmd())
m.DCUInfo = dcuInfo.(*ModelDCUInfo)
cmds = append(cmds, cmdHeader, dcuHeader, tickCmd())
return m, tea.Batch(cmds...)
case ModelMsg: // 初始化
header, _ := m.Header.Update(m.modelMsg)
dcuInfo, _ := m.DCUInfo.Update(m.modelMsg)
m.Header = header.(*ModelHeader)
m.DCUInfo = dcuInfo.(*ModelDCUInfo)
return m, nil
}
return m, nil
}
func (m *ModelMain) View() string {
return m.Header.View()
return m.Header.View() + m.DCUInfo.View()
}
// type ModelDCUInfo struct{}
......@@ -125,6 +137,12 @@ func initModelInfo(model *ModelMsg) error {
return err
}
model.Version = ver
if model.index%20 == 0 {
backend.UpdateDCUInfo(true)
} else {
backend.UpdateDCUInfo(false)
}
model.DCUInfo = backend.GetDCUInfo()
return nil
}
......@@ -132,4 +150,10 @@ func initModelInfo(model *ModelMsg) error {
func updateModelInfo(modelMsg *ModelMsg, index uint64, t time.Time) {
modelMsg.index = index
modelMsg.t = t
if modelMsg.index%20 == 0 {
backend.UpdateDCUInfo(true)
} else {
backend.UpdateDCUInfo(false)
}
modelMsg.DCUInfo = backend.GetDCUInfo()
}
......@@ -2,15 +2,17 @@ package tui
import (
"fmt"
"strings"
"testing"
"github.com/charmbracelet/lipgloss"
)
const Border = `├───────────────────────────────┬──────────────────────┬──────────────────────┤
│ GPU Name Persistence-M│ Bus-Id Disp.A │ MIG M. Uncorr. ECC │
│ Fan Temp Perf Pwr:Usage/Cap│ Memory-Usage │ GPU-Util Compute M. │`
const S = `├───────────────────────────────┼──────────────────────┼──────────────────────┼
│ 2 BW200, manual │ 0000:5e:00.0 Off │ Off On │ DCU: ░░░░░░░░░░░░░░░░░░░░░░░░░ 0% │`
func TestLine(t *testing.T) {
t.Logf("%d", lipgloss.Width(S))
}
func TestHeader(t *testing.T) {
m := ModelHeader{}
......@@ -19,13 +21,6 @@ func TestHeader(t *testing.T) {
fmt.Println(m.View())
}
func TestBorder(t *testing.T) {
lines := strings.Split(Border, "\n")
for _, i := range lines {
t.Logf("%d", lipgloss.Width(i))
}
}
func TestAis(t *testing.T) {
for i := 10; i < 180; i++ {
str := genXAxis(i)
......@@ -35,3 +30,19 @@ func TestAis(t *testing.T) {
fmt.Println(str)
}
}
func TestFormatStr(t *testing.T) {
str := lipgloss.NewStyle().Foreground(lipgloss.Color("#2b95ffff")).SetString("hello world!").String()
t.Logf("|%s|", FormatStr(str, 5, lipgloss.Left))
t.Logf("|%s|", FormatStr(str, 5, lipgloss.Right))
t.Logf("|%s|", FormatStr(str, 20, lipgloss.Left))
t.Logf("|%s|", FormatStr(str, 20, lipgloss.Right))
}
func TestModel(t *testing.T) {
m := NewModelMain(200, 100)
m.Init()
m.DCUInfo.Update(m.modelMsg)
str := m.View()
t.Log(str)
}
......@@ -128,3 +128,26 @@ func StackPosition(up, down string, vPos, hPos lipgloss.Position) string {
return ""
}
// FormatStr 格式化字符串,输出的字符串可视长度为length,hPos表示是左对齐还是右对齐
// str必须为单行字符串,没有\n
func FormatStr(str string, length int, hPos lipgloss.Position) string {
realLen := lipgloss.Width(str)
if realLen < length {
switch hPos {
case lipgloss.Left:
return str + strings.Repeat(" ", length-realLen)
case lipgloss.Right:
return strings.Repeat(" ", length-realLen) + str
}
} else if realLen > length {
// 需要截断
switch hPos {
case lipgloss.Left: // 左对齐
return ansi.Truncate(str, length, "")
case lipgloss.Right: // 右对齐
return ansi.TruncateLeft(str, realLen-length, "")
}
}
return str
}
......@@ -5,14 +5,15 @@ go 1.24.2
require (
github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/lipgloss v1.1.0
github.com/lrstanley/bubblezone v0.0.0-20240914071701-b48c55a5e78e
github.com/moby/moby/api v1.52.0-beta.2
github.com/moby/moby/client v0.1.0-beta.2
github.com/shirou/gopsutil/v4 v4.25.9
)
require (
github.com/charmbracelet/bubbles v0.20.0 // indirect
github.com/lrstanley/bubblezone v0.0.0-20240914071701-b48c55a5e78e // indirect
github.com/charmbracelet/bubbles v0.20.0
github.com/charmbracelet/harmonica v0.2.0 // indirect
)
require (
......@@ -21,9 +22,9 @@ require (
github.com/NimbleMarkets/ntcharts v0.3.1
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/x/ansi v0.10.1 // indirect
github.com/charmbracelet/x/ansi v0.10.1
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/charmbracelet/x/term v0.2.1
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/distribution/reference v0.6.0 // indirect
......
......@@ -51,7 +51,7 @@ var (
RePCIeRelay = regexp.MustCompile(`(?mi)^PCIe\s*Replay\s*Count\s*:\s*([0-9]*)$`)
ReSerialNum = regexp.MustCompile(`(?mi)^Serial\s*Number\s*:\s*([0-9a-zA-Z]*)$`)
ReVoltage = regexp.MustCompile(`(?mi)^Voltage\s*\((.*)\)\s*:\s*([0-9.]*)$`)
RePCIBus = regexp.MustCompile(`(?mi)^PCI\s*Bus\s*:\s*([0-9a-zA-Z.:]*)$`)
RePCIBus = regexp.MustCompile(`(?mi)^PCI\s*Bus\s*:\s*([0-9a-zA-Z.:]*).*$`)
ReMECFWVersion = regexp.MustCompile(`(?mi)^MEC\s*Firmware\s*Version\s*:\s*([0-9.]*)$`)
ReMEC2FWVersion = regexp.MustCompile(`(?mi)^MEC2\s*Firmware\s*Version\s*:\s*([0-9.]*)$`)
ReRLCFWVersion = regexp.MustCompile(`(?mi)^RLC\s*Firmware\s*Version\s*:\s*([0-9.]*)$`)
......@@ -342,7 +342,7 @@ func parseSMIAllOutput(id int, str string) (*SMIAllOutput, error) {
result.Voltage = float32(p)
}
}
if s := regMatch(RePCIBus, str, 2); s != nil {
if s := regMatch(RePCIBus, str, 1); s != nil {
result.PCIBus = s[0]
}
if s := regMatch(ReMECFWVersion, str, 1); s != nil {
......
......@@ -291,5 +291,16 @@ func TestParseSMIAllOutput(t *testing.T) {
for _, i := range result {
t.Logf("%+v", *i)
}
}
func TestPCIBus(t *testing.T) {
f := RePCIBus.FindStringSubmatch("PCI Bus: 0000:9f:00.0 --> Socket id: 1")
for _, l := range f {
t.Logf("%s", l)
}
if s := regMatch(RePCIBus, "PCI Bus: 0000:9f:00.0 --> Socket id: 1", 1); s != nil {
t.Logf("%+v", s)
}
}
2025-11-12 17:04:45 (Press h for help or q for quit)
╒═════════════════════════════════════════════════════════════════════════════╕
│ hytop: 1.0.0 Driver Version: 6.3.16-V1.1.0 SMI Version: 1.20.0 │
├───────────────────────────────┬──────────────────────┬──────────────────────┤
│ DCU Name Performance Level │ Bus-Id DisP.A │ MIG M. ECC │
│ Fan Temp Pwr:Usage/Cap │ Memory-Usage │ DCU-Util PowerMode │
╞═════════════════════════════════════════════════════════════════════════════╪
│ 0 BW200, manual │ 0000:49:00.0 Off │ Off On │ DCU: ░░░░░░░░░░░░░░░░░░░░░░░░░ 0% │
│ N/A 32.0C 132.0W / 800.0W │ 2MiB / 65520MiB │ 0.0% Normal │ Mem: ░░░░░░░░░░░░░░░░░░░░░░░░░ 0% │
├───────────────────────────────┼──────────────────────┼──────────────────────┼
│ 1 BW200, manual │ 0000:54:00.0 Off │ Off On │ DCU: ░░░░░░░░░░░░░░░░░░░░░░░░░ 0% │
│ N/A 32.0C 126.0W / 800.0W │ 2MiB / 65520MiB │ 0.0% Normal │ Mem: ░░░░░░░░░░░░░░░░░░░░░░░░░ 0% │
├───────────────────────────────┼──────────────────────┼──────────────────────┼
│ 2 BW200, manual │ 0000:5e:00.0 Off │ Off On │ DCU: ░░░░░░░░░░░░░░░░░░░░░░░░░ 0% │
│ N/A 33.0C 132.0W / 800.0W │ 2MiB / 65520MiB │ 0.0% Normal │ Mem: ░░░░░░░░░░░░░░░░░░░░░░░░░ 0% │
├───────────────────────────────┼──────────────────────┼──────────────────────┼
│ 3 BW200, manual │ 0000:67:00.0 Off │ Off On │ DCU: ░░░░░░░░░░░░░░░░░░░░░░░░░ 0% │
│ N/A 34.0C 133.0W / 800.0W │ 2MiB / 65520MiB │ 0.0% Normal │ Mem: ░░░░░░░░░░░░░░░░░░░░░░░░░ 0% │
├───────────────────────────────┼──────────────────────┼──────────────────────┼
│ 4 BW200, manual │ 0000:9c:00.0 Off │ Off On │ DCU: ░░░░░░░░░░░░░░░░░░░░░░░░░ 0% │
│ N/A 34.0C 134.0W / 800.0W │ 2MiB / 65520MiB │ 0.0% Normal │ Mem: ░░░░░░░░░░░░░░░░░░░░░░░░░ 0% │
├───────────────────────────────┼──────────────────────┼──────────────────────┼
│ 5 BW200, manual │ 0000:bc:00.0 Off │ Off On │ DCU: ░░░░░░░░░░░░░░░░░░░░░░░░░ 0% │
│ N/A 34.0C 133.0W / 800.0W │ 2MiB / 65520MiB │ 0.0% Normal │ Mem: ░░░░░░░░░░░░░░░░░░░░░░░░░ 0% │
├───────────────────────────────┼──────────────────────┼──────────────────────┼
│ 6 BW200, manual │ 0000:cd:00.0 Off │ Off On │ DCU: ░░░░░░░░░░░░░░░░░░░░░░░░░ 0% │
│ N/A 32.0C 132.0W / 800.0W │ 2MiB / 65520MiB │ 0.0% Normal │ Mem: ░░░░░░░░░░░░░░░░░░░░░░░░░ 0% │
├───────────────────────────────┼──────────────────────┼──────────────────────┼
│ 7 BW200, manual │ 0000:dd:00.0 Off │ Off On │ DCU: ░░░░░░░░░░░░░░░░░░░░░░░░░ 0% │
│ N/A 31.0C 128.0W / 800.0W │ 2MiB / 65520MiB │ 0.0% Normal │ Mem: ░░░░░░░░░░░░░░░░░░░░░░░░░ 0% │
╘═══════════════════════════════╧══════════════════════╧══════════════════════╧
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment