Commit 10bfd381 authored by liming6's avatar liming6
Browse files

fix 添加空值判断,提高稳定性

parent 51496f87
......@@ -3,6 +3,8 @@ package backend
import (
"testing"
"time"
"github.com/samber/mo"
)
func TestDcoker(t *testing.T) {
......@@ -90,3 +92,9 @@ func BenchmarkUpdateSlowInfo(b *testing.B) {
}
}
}
func TestOpt(t *testing.T) {
o := mo.Option[int]{}
t.Log(o.IsNone())
}
\ No newline at end of file
......@@ -15,6 +15,7 @@ import (
"sync/atomic"
"time"
"github.com/samber/mo"
"github.com/shirou/gopsutil/v3/process"
)
......@@ -84,18 +85,18 @@ func Shutdown() {
type DCUQuickInfo struct {
lock sync.RWMutex
Id int //
Name string //
PerformanceLevel string //
Fan string //
Temp float32 //
PwrAvg float32 //
PwrCap float32 //
BusId string //
MemTotal uint64 //
MemUsed uint64 //
MemUsedPerent float32 //
DCUUTil float32 //
Id int //
Name mo.Option[string] //
PerformanceLevel mo.Option[string] //
Fan mo.Option[string] //
Temp mo.Option[float32] //
PwrAvg mo.Option[float32] //
PwrCap mo.Option[float32] //
BusId mo.Option[string] //
MemTotal mo.Option[uint64] //
MemUsed mo.Option[uint64] //
MemUsedPerent mo.Option[float32] //
DCUUTil mo.Option[float32] //
}
type DCUSlowInfo struct {
......@@ -122,46 +123,16 @@ func (m *DCUInfoMap) UpdateQuickInfo() error {
if err != nil {
return err
}
names, err := Rocmlib.GetDevName()
if err != nil {
return err
}
plevel, err := Rocmlib.GetPerfLevel()
if err != nil {
return err
}
fan, err := Rocmlib.GetFanSpeed()
if err != nil {
return err
}
temp, err := Rocmlib.GetTemp()
if err != nil {
return err
}
pwrAvg, err := Rocmlib.GetPowerAvg()
if err != nil {
return err
}
pwrCap, err := Rocmlib.GetPowerCap()
if err != nil {
return err
}
busid, err := Rocmlib.GetPCIBusId()
if err != nil {
return err
}
memTotal, err := Rocmlib.GetMemTotal()
if err != nil {
return err
}
memUsed, err := Rocmlib.GetMemUsed()
if err != nil {
return err
}
dcu, err := Rocmlib.GetBusyPercent()
if err != nil {
return err
}
names, _ := Rocmlib.GetDevName()
plevel, _ := Rocmlib.GetPerfLevel()
fan, _ := Rocmlib.GetFanSpeed()
temp, _ := Rocmlib.GetTemp()
pwrAvg, _ := Rocmlib.GetPowerAvg()
pwrCap, _ := Rocmlib.GetPowerCap()
busid, _ := Rocmlib.GetPCIBusId()
memTotal, _ := Rocmlib.GetMemTotal()
memUsed, _ := Rocmlib.GetMemUsed()
dcu, _ := Rocmlib.GetBusyPercent()
set := make(map[int]bool)
......@@ -176,25 +147,51 @@ func (m *DCUInfoMap) UpdateQuickInfo() error {
}
qinfo.lock.Lock()
qinfo.Id = i
qinfo.Name = names[i]
qinfo.PerformanceLevel = plevel[i]
if rpm, have := fan[i]; !have || rpm == 0 {
qinfo.Fan = "N/A"
} else {
qinfo.Fan = strconv.Itoa(int(rpm))
if names != nil {
qinfo.Name = mo.Some(names[i])
}
qinfo.Temp = float32(temp[i]) / 1000
qinfo.PwrAvg = float32(pwrAvg[i]) / 1000000
qinfo.PwrCap = float32(pwrCap[i]) / 1000000
qinfo.BusId = busid[i]
qinfo.MemTotal = memTotal[i]
qinfo.MemUsed = memUsed[i]
if qinfo.MemTotal == 0 {
qinfo.MemUsedPerent = 0
} else {
qinfo.MemUsedPerent = float32(qinfo.MemUsed) / float32(qinfo.MemTotal) * 100
if plevel != nil {
qinfo.PerformanceLevel = mo.Some(plevel[i])
}
if fan != nil {
if rpm, have := fan[i]; !have || rpm == 0 {
qinfo.Fan = mo.Some("N/A")
} else {
qinfo.Fan = mo.Some(strconv.Itoa(int(rpm)))
}
}
if temp != nil {
qinfo.Temp = mo.Some(float32(temp[i]) / 1000)
}
if pwrAvg != nil {
qinfo.PwrAvg = mo.Some(float32(pwrAvg[i]) / 1000000)
}
qinfo.DCUUTil = float32(dcu[i])
if pwrCap != nil {
qinfo.PwrCap = mo.Some(float32(pwrCap[i]) / 1000000)
}
if busid != nil {
qinfo.BusId = mo.Some(busid[i])
}
if memTotal != nil {
qinfo.MemTotal = mo.Some(memTotal[i])
}
if memUsed != nil {
qinfo.MemUsed = mo.Some(memUsed[i])
}
if qinfo.MemTotal.IsSome() {
if qinfo.MemTotal.MustGet() == 0 {
qinfo.MemUsedPerent = mo.Some(float32(0.0))
} else {
if qinfo.MemUsed.IsSome() {
qinfo.MemUsedPerent = mo.Some(float32(qinfo.MemUsed.MustGet()) / float32(qinfo.MemTotal.MustGet()) * 100)
}
}
}
if dcu != nil {
qinfo.DCUUTil = mo.Some(float32(dcu[i]))
}
qinfo.lock.Unlock()
set[i] = true
}
......@@ -215,7 +212,12 @@ func (m *DCUInfoMap) UpdateSlowInfo() error {
}
ecc, err := gpu.GetEccInfo()
if err != nil {
return err
ecc = make(map[int]bool)
for i := range num {
ecc[i] = false
}
err = nil
// return err
}
rinfo, err := gpu.GetRunningInfo()
if err != nil {
......
package main
import (
"fmt"
"get-container/cmd/hytop/backend"
"get-container/cmd/hytop/tui"
"log"
......@@ -8,9 +9,29 @@ import (
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/x/term"
"github.com/spf13/pflag"
)
var (
flagHelp = pflag.BoolP("help", "h", false, "show help info of hytop")
flagVersion = pflag.BoolP("version", "v", false, "show version info of hytop")
)
func main() {
pflag.Parse()
if *flagVersion {
fmt.Printf("hytop version: %s\n", tui.DCUTopVersion)
os.Exit(0)
}
if *flagHelp {
fmt.Println("hytop: a tool that allows viewing the operation status of DCU from the command line")
fmt.Println("Usage of hytop:")
fmt.Println(" hytop [option]")
fmt.Println("options: ")
pflag.PrintDefaults()
os.Exit(0)
}
w, h, err := term.GetSize(os.Stdout.Fd())
if err != nil {
log.Fatalf("error get terminal size: %v", err)
......
......@@ -2,14 +2,20 @@ package tui
const (
HelpStr = `
hytop 1.0.0 - (C) Ming Li, 2025-2025.
hytop 1.0.1 - (C) Ming Li, 2025-2026.
Released under the GNU GPLv3 License.
hytop is a tool for monitoring DCUs from the command line.
Button function introduction:
h: show help info
h : show help info
q : exit
Esc : go back or cancel all selected processes
Enter: enter the process view of the process where the cursor is located, or indicate confirmation
Space: select or unselect a process
up / down / left / right: move the cursor
(Press Esc to go back)
`
)
......@@ -113,9 +113,9 @@ func (m *ModelDCUInfo) View() string {
strCache.WriteByte(' ')
strCache.WriteString(FormatStr(strconv.Itoa(qinfo.Id), 4, lipgloss.Left))
strCache.WriteByte(' ')
strCache.WriteString(FormatStr(ReDCUName.FindString(qinfo.Name), 8, lipgloss.Left))
strCache.WriteString(FormatStr(ReDCUName.FindString(qinfo.Name.OrElse("Unknow")), 8, lipgloss.Left))
strCache.WriteByte(' ')
strCache.WriteString(FormatStr(qinfo.PerformanceLevel, 15, lipgloss.Right))
strCache.WriteString(FormatStr(qinfo.PerformanceLevel.OrElse("Unknow"), 15, lipgloss.Right))
strCache.WriteByte(' ')
if isLowStyle {
......@@ -128,7 +128,7 @@ func (m *ModelDCUInfo) View() string {
strBuilder.WriteString(borderStr)
strCache.WriteByte(' ')
strCache.WriteString(FormatStr(qinfo.BusId, 13, lipgloss.Left))
strCache.WriteString(FormatStr(qinfo.BusId.OrElse("Unknow"), 13, lipgloss.Left))
strCache.WriteByte(' ')
strCache.WriteString(FormatStr("Off", 6, lipgloss.Right))
strCache.WriteByte(' ')
......@@ -161,23 +161,32 @@ func (m *ModelDCUInfo) View() string {
if isLowStyle {
strBuilder.WriteString(LowLeightStyle.Render(" DCU: "))
strBuilder.WriteString(m.proLowLeight.ViewAs(float64(qinfo.DCUUTil / 100)))
strBuilder.WriteString(LowLeightStyle.Render(fmt.Sprintf(" %3d%% ", int(qinfo.DCUUTil))))
strBuilder.WriteString(m.proLowLeight.ViewAs(float64(qinfo.DCUUTil.OrElse(0.0) / 100)))
strBuilder.WriteString(LowLeightStyle.Render(fmt.Sprintf(" %3d%% ", int(qinfo.DCUUTil.OrElse(0.0)))))
} else {
strBuilder.WriteString(" DCU: ")
strBuilder.WriteString(m.pro.ViewAs(float64(qinfo.DCUUTil / 100)))
strBuilder.WriteString(fmt.Sprintf(" %3d%% ", int(qinfo.DCUUTil)))
strBuilder.WriteString(m.pro.ViewAs(float64(qinfo.DCUUTil.OrElse(0.0) / 100)))
strBuilder.WriteString(fmt.Sprintf(" %3d%% ", int(qinfo.DCUUTil.OrElse(0.0))))
}
strBuilder.WriteString(borderStr)
strBuilder.WriteByte('\n')
strBuilder.WriteString(borderStr)
strCache.WriteByte(' ')
strCache.WriteString(FormatStr(qinfo.Fan, 4, lipgloss.Left))
strCache.WriteString(FormatStr(qinfo.Fan.OrElse("N/A"), 4, lipgloss.Left))
strCache.WriteByte(' ')
strCache.WriteString(FormatStr(fmt.Sprintf("%.1fC", qinfo.Temp), 6, lipgloss.Left))
if qinfo.Temp.IsSome() {
temp := qinfo.Temp.MustGet()
if temp == 0 {
strCache.WriteString(FormatStr("N/A", 6, lipgloss.Left))
} else {
strCache.WriteString(FormatStr(fmt.Sprintf("%.1f°C", temp), 6, lipgloss.Left))
}
} else {
strCache.WriteString(FormatStr("N/A", 6, lipgloss.Left))
}
strCache.WriteByte(' ')
strCache.WriteString(FormatStr(fmt.Sprintf("%.1fW / %.1fW", qinfo.PwrAvg, qinfo.PwrCap), 17, lipgloss.Right))
strCache.WriteString(FormatStr(fmt.Sprintf("%.1fW / %.1fW", qinfo.PwrAvg.OrElse(0.0), qinfo.PwrCap.OrElse(0.0)), 17, lipgloss.Right))
strCache.WriteByte(' ')
if isLowStyle {
......@@ -191,13 +200,13 @@ func (m *ModelDCUInfo) View() string {
if isLowStyle {
strCache.WriteByte(' ')
strCache.WriteString(FormatStr(fmt.Sprintf("%dMiB / %dMiB", qinfo.MemUsed/uint64(utils.MiB), qinfo.MemTotal/uint64(utils.MiB)), 20, lipgloss.Right))
strCache.WriteString(FormatStr(fmt.Sprintf("%dMiB / %dMiB", qinfo.MemUsed.OrElse(0)/uint64(utils.MiB), qinfo.MemTotal.OrElse(0)/uint64(utils.MiB)), 20, lipgloss.Right))
strCache.WriteByte(' ')
strBuilder.WriteString(LowLeightStyle.Render(strCache.String()))
strCache.Reset()
} else {
strBuilder.WriteByte(' ')
strBuilder.WriteString(FormatStr(fmt.Sprintf("%dMiB / %dMiB", qinfo.MemUsed/uint64(utils.MiB), qinfo.MemTotal/uint64(utils.MiB)), 20, lipgloss.Right))
strBuilder.WriteString(FormatStr(fmt.Sprintf("%dMiB / %dMiB", qinfo.MemUsed.OrElse(0)/uint64(utils.MiB), qinfo.MemTotal.OrElse(0)/uint64(utils.MiB)), 20, lipgloss.Right))
strBuilder.WriteByte(' ')
}
......@@ -205,7 +214,7 @@ func (m *ModelDCUInfo) View() string {
if isLowStyle {
strCache.WriteByte(' ')
strCache.WriteString(FormatStr(fmt.Sprintf("%.1f%%", qinfo.DCUUTil), 6, lipgloss.Left))
strCache.WriteString(FormatStr(fmt.Sprintf("%.1f%%", qinfo.DCUUTil.OrElse(0)), 6, lipgloss.Left))
strCache.WriteByte(' ')
strCache.WriteString(FormatStr(sinfo.PwrMode.Load().(string), 13, lipgloss.Right))
strCache.WriteByte(' ')
......@@ -213,7 +222,7 @@ func (m *ModelDCUInfo) View() string {
strCache.Reset()
} else {
strBuilder.WriteByte(' ')
strBuilder.WriteString(FormatStr(fmt.Sprintf("%.1f%%", qinfo.DCUUTil), 6, lipgloss.Left))
strBuilder.WriteString(FormatStr(fmt.Sprintf("%.1f%%", qinfo.DCUUTil.OrElse(0)), 6, lipgloss.Left))
strBuilder.WriteByte(' ')
strBuilder.WriteString(FormatStr(sinfo.PwrMode.Load().(string), 13, lipgloss.Right))
strBuilder.WriteByte(' ')
......@@ -223,15 +232,15 @@ func (m *ModelDCUInfo) View() string {
if isLowStyle {
strCache.WriteString(" Mem: ")
strCache.WriteString(m.proLowLeight.ViewAs(float64(qinfo.MemUsed) / float64(qinfo.MemTotal)))
strCache.WriteString(LowLeightStyle.Render(fmt.Sprintf(" %3d%% ", int(100*float64(qinfo.MemUsed)/float64(qinfo.MemTotal)))))
strCache.WriteString(m.proLowLeight.ViewAs(float64(qinfo.MemUsed.OrElse(0)) / float64(qinfo.MemTotal.OrElse(1))))
strCache.WriteString(LowLeightStyle.Render(fmt.Sprintf(" %3d%% ", int(100*float64(qinfo.MemUsed.OrElse(0))/float64(qinfo.MemTotal.OrElse(1))))))
strBuilder.WriteString(LowLeightStyle.Render(strCache.String()))
strCache.Reset()
} else {
strBuilder.WriteString(" Mem: ")
strBuilder.WriteString(m.pro.ViewAs(float64(qinfo.MemUsed) / float64(qinfo.MemTotal)))
strBuilder.WriteString(m.pro.ViewAs(float64(qinfo.MemUsed.OrElse(0)) / float64(qinfo.MemTotal.OrElse(1))))
strBuilder.WriteByte(' ')
strBuilder.WriteString(fmt.Sprintf("%3d%%", int(100*float64(qinfo.MemUsed)/float64(qinfo.MemTotal))))
strBuilder.WriteString(fmt.Sprintf("%3d%%", int(100*float64(qinfo.MemUsed.OrElse(0))/float64(qinfo.MemTotal.OrElse(1)))))
strBuilder.WriteByte(' ')
}
strBuilder.WriteString(borderStr)
......
......@@ -13,7 +13,7 @@ import (
)
const (
DCUTopVersion = "1.0.0"
DCUTopVersion = "1.0.1"
)
var (
......@@ -65,7 +65,6 @@ const (
VMTree ViewMode = 1 // 进程树视图
VMOneProcess ViewMode = 2 // 进程详情视图
VMProcessEnv ViewMode = 3 // 进程环境变量视图
VMHelp ViewMode = 4 // 帮助视图
)
func (pa ProcessAction) String() string {
......@@ -105,6 +104,7 @@ type ActionMsg struct {
PidEnvView *int32 // 进程环境变量视图的pid号,为null表示不进入进程环境变量视图
PidTreePids []int32 // pid tree视图下,进程id的顺序列表
TargetDCUIndex int // PointPid使用的dcu index,为-1表示没有选择任何dcu
HelpView bool // 是否进入帮助界面
}
// ModelMain tui主模型类
......@@ -202,7 +202,8 @@ func (m *ModelMain) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
cmd := m.handleKeyEnter()
return m, cmd
case "h":
return m, tea.Quit
m.actionMsg.HelpView = true
return m, nil
case "left", "right":
cmd := m.handleKeyLR(msg.String())
return m, cmd
......@@ -462,6 +463,10 @@ func (m *ModelMain) handleKeyK() tea.Cmd {
// VMMain视图下,取消所有选择的进程、退出单个进程模式或对话框返回
// VMTree视图下,取消所有选择的进程、退出单个进程模式或对话框返回,返回到VMMain视图
func (m *ModelMain) handleKeyEsc() tea.Cmd {
if m.actionMsg.HelpView {
m.actionMsg.HelpView = false
return nil
}
switch m.actionMsg.VM {
case VMMain:
if m.actionMsg.Action != nil {
......@@ -542,6 +547,9 @@ func (m *ModelMain) handleKeySpace() tea.Cmd {
}
func (m *ModelMain) View() string {
if m.actionMsg != nil && m.actionMsg.HelpView {
return HelpStr
}
switch m.actionMsg.VM {
case VMMain:
if m.actionMsg.Action != nil {
......
......@@ -166,13 +166,13 @@ func (m *ModelProcessDetail) handleModelMsg(msg *ModelMsg) {
m.DCUPercent = 0
m.DCUMemPercent = 0
} else {
dcumem = float64(dcuInfo.MemUsedPerent)
dcu = float64(dcuInfo.DCUUTil)
dcumem = float64(dcuInfo.MemUsedPerent.OrElse(0))
dcu = float64(dcuInfo.DCUUTil.OrElse(0))
m.DCUPercent = dcu
m.DCUMemPercent = dcumem
}
m.DCUMemTotal = utils.MemorySize{Unit: utils.Byte, Num: dcuInfo.MemTotal}
m.DCUMemUsed = utils.MemorySize{Unit: utils.Byte, Num: dcuInfo.MemUsed}
m.DCUMemTotal = utils.MemorySize{Unit: utils.Byte, Num: dcuInfo.MemTotal.OrElse(0)}
m.DCUMemUsed = utils.MemorySize{Unit: utils.Byte, Num: dcuInfo.MemUsed.OrElse(0)}
lock.Unlock()
m.MemTotal = utils.MemorySize{Unit: utils.Byte, Num: msg.systemInfo.MemTotal}
......
......@@ -284,24 +284,24 @@ func (m *ModelSysLoad) updateInfo(t *ModelMsg) {
qinfo, lock := t.DCUInfo.GetQuitInfo()
m.dataMaplock.Lock()
for k, v := range qinfo {
s.DCUMemUsageAvg += v.MemUsedPerent
s.DCUMemUsage[k] = v.MemUsedPerent
s.DCUUsageAvg += v.DCUUTil
s.DCUUsage[k] = v.DCUUTil
s.DCUMemUsageAvg += v.MemUsedPerent.OrElse(0.0)
s.DCUMemUsage[k] = v.MemUsedPerent.OrElse(0.0)
s.DCUUsageAvg += v.DCUUTil.OrElse(0.0)
s.DCUUsage[k] = v.DCUUTil.OrElse(0.0)
l1, have := m.dcuMemData[k]
if !have {
l1 = linkedlist.New[tchart.TimePoint]()
m.dcuMemData[k] = l1
}
l1.Add(tchart.TimePoint{Time: t.t, Value: float64(v.MemUsedPerent)})
l1.Add(tchart.TimePoint{Time: t.t, Value: float64(v.MemUsedPerent.OrElse(0.0))})
l2, have := m.dcuUsgData[k]
if !have {
l2 = linkedlist.New[tchart.TimePoint]()
m.dcuUsgData[k] = l2
}
l2.Add(tchart.TimePoint{Time: t.t, Value: float64(v.DCUUTil)})
l2.Add(tchart.TimePoint{Time: t.t, Value: float64(v.DCUUTil.OrElse(0.0))})
}
l := len(qinfo)
lock.Unlock()
......
......@@ -52,6 +52,7 @@ require (
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.54.0 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/samber/mo v1.16.0 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect
......
......@@ -375,11 +375,11 @@ func parseSMIAllOutput(id int, str string) (*SMIAllOutput, error) {
// DCURunningInfo DCU运行状态信息
type DCURunningInfo struct {
Id int
Temp float32
AvgPower float32
Temp float32 // 为负数表示无法获取
AvgPower float32 // 为负数表示无法获取
PerformanceLevel string
MemPerc float32
HCUPerc float32
MemPerc float32 // 为负数表示无法获取
HCUPerc float32 // 为负数表示无法获取
Mode string // Normal / Low Power
}
......@@ -423,24 +423,32 @@ func parseRunningInfo(info string) (map[int]DCURunningInfo, error) {
item.Id = id
temp, err := strconv.ParseFloat(strings.TrimSuffix(strings.ToLower(fields[fieldMap[HeaderMap["Temp"]]]), "c"), 32)
if err != nil {
return nil, err
temp = -1.0
err = nil
// return nil, err
}
item.Temp = float32(temp)
avgPwr, err := strconv.ParseFloat(strings.TrimSuffix(strings.ToLower(fields[fieldMap[HeaderMap["AvgPower"]]]), "w"), 32)
if err != nil {
return nil, err
avgPwr = -1.0
err = nil
// return nil, err
}
item.AvgPower = float32(avgPwr)
item.PerformanceLevel = fields[3]
vram, err := strconv.ParseFloat(strings.TrimSuffix(fields[fieldMap[HeaderMap["MemPerc"]]], "%"), 32)
if err != nil {
return nil, err
vram = -1.0
err = nil
// return nil, err
}
item.MemPerc = float32(vram)
utl, err := strconv.ParseFloat(strings.TrimSuffix(fields[fieldMap[HeaderMap["HCUPerc"]]], "%"), 32)
if err != nil {
return nil, err
utl = -1.0
err = nil
// return nil, err
}
item.HCUPerc = float32(utl)
item.Mode = fields[fieldMap[HeaderMap["Mode"]]]
......
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