Commit b87220dd authored by liming6's avatar liming6
Browse files

feature 添加单个进程视图

parent b670ea3c
...@@ -271,7 +271,7 @@ func (m *DCUInfoMap) GetQuitInfo() (map[int]*DCUQuickInfo, sync.Locker) { ...@@ -271,7 +271,7 @@ func (m *DCUInfoMap) GetQuitInfo() (map[int]*DCUQuickInfo, sync.Locker) {
type DCUProcessInfo struct { type DCUProcessInfo struct {
DCU int // 使用的dcu号 DCU int // 使用的dcu号
DCUMem string // 使用的dcu内存容量 DCUMem utils.MemorySize // 使用的dcu内存容量
SDMA int SDMA int
Info ProcessInfo // 通用进程信息 Info ProcessInfo // 通用进程信息
} }
...@@ -345,7 +345,7 @@ func (m *DCUInfoMap) GetDCUProcessInfo() map[int][]DCUProcessInfo { ...@@ -345,7 +345,7 @@ func (m *DCUInfoMap) GetDCUProcessInfo() map[int][]DCUProcessInfo {
item := DCUProcessInfo{DCU: i} item := DCUProcessInfo{DCU: i}
item.Info = pinfo[int32(v.Pid)] item.Info = pinfo[int32(v.Pid)]
mem.Num = v.VarmUsage mem.Num = v.VarmUsage
item.DCUMem = mem.HumanReadStr(1) item.DCUMem = mem
item.SDMA = int(v.SdmaUsage) item.SDMA = int(v.SdmaUsage)
l = append(l, item) l = append(l, item)
result[i] = l result[i] = l
...@@ -384,7 +384,7 @@ func (m *DCUInfoMap) GetDCUProcessInfo2() map[int][]DCUProcessInfo { ...@@ -384,7 +384,7 @@ func (m *DCUInfoMap) GetDCUProcessInfo2() map[int][]DCUProcessInfo {
} }
item := DCUProcessInfo{DCU: i} item := DCUProcessInfo{DCU: i}
item.Info = pinfo[int32(v.Pid)] item.Info = pinfo[int32(v.Pid)]
item.DCUMem = v.VRamUsed.HumanReadStr(1) item.DCUMem = v.VRamUsed
item.SDMA = v.SDMAUsed item.SDMA = v.SDMAUsed
l = append(l, item) l = append(l, item)
......
...@@ -102,6 +102,7 @@ type ModelMain struct { ...@@ -102,6 +102,7 @@ type ModelMain struct {
actionMsg *ActionMsg actionMsg *ActionMsg
dialog *Dialog // 对话框 dialog *Dialog // 对话框
pstree *ModelPsTree // 进程树视图 pstree *ModelPsTree // 进程树视图
processDetail *ModelProcessDetail
} }
func NewModelMain(width, height int) ModelMain { func NewModelMain(width, height int) ModelMain {
...@@ -174,7 +175,8 @@ func (m *ModelMain) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) { ...@@ -174,7 +175,8 @@ func (m *ModelMain) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
cmd := m.handleKeyDown() cmd := m.handleKeyDown()
return m, cmd return m, cmd
case "enter": case "enter":
return m, tea.Quit cmd := m.handleKeyEnter()
return m, cmd
case "h": case "h":
return m, tea.Quit return m, tea.Quit
case "left", "right": case "left", "right":
...@@ -205,6 +207,10 @@ func (m *ModelMain) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) { ...@@ -205,6 +207,10 @@ func (m *ModelMain) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
m.SysLoad = sysLoad.(*ModelSysLoad) m.SysLoad = sysLoad.(*ModelSysLoad)
m.ProcessInfo = pidinfo.(*ModelProcessInfo) m.ProcessInfo = pidinfo.(*ModelProcessInfo)
m.pstree = pstree.(*ModelPsTree) m.pstree = pstree.(*ModelPsTree)
if m.processDetail != nil {
detail, _ := m.processDetail.Update(m.modelMsg)
m.processDetail = detail.(*ModelProcessDetail)
}
return m, tickCmd() return m, tickCmd()
case ModelMsg: // 初始化 case ModelMsg: // 初始化
header, _ := m.Header.Update(m.modelMsg) header, _ := m.Header.Update(m.modelMsg)
...@@ -231,6 +237,18 @@ func (m *ModelMain) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) { ...@@ -231,6 +237,18 @@ func (m *ModelMain) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil return m, nil
} }
func (m *ModelMain) handleKeyEnter() tea.Cmd {
if m.actionMsg.VM != VMMain || m.actionMsg.PointPid == nil {
return nil
}
pid := *m.actionMsg.PointPid
m.actionMsg.PidView = &pid
m.processDetail = NewModelProcessDetail(m.width, m.height, pid)
m.processDetail.Update(m.modelMsg)
m.actionMsg.VM = VMOneProcess
return nil
}
func (m *ModelMain) handleKeyT() tea.Cmd { func (m *ModelMain) handleKeyT() tea.Cmd {
// 在主视图下,没有选择任何进程时,可以进入进程树视图,且 // 在主视图下,没有选择任何进程时,可以进入进程树视图,且
if m.actionMsg.VM == VMMain && len(m.actionMsg.SelectPids) == 0 { if m.actionMsg.VM == VMMain && len(m.actionMsg.SelectPids) == 0 {
...@@ -361,6 +379,10 @@ func (m *ModelMain) handleKeyEsc() tea.Cmd { ...@@ -361,6 +379,10 @@ func (m *ModelMain) handleKeyEsc() tea.Cmd {
pstree, _ := m.pstree.Update(m.actionMsg) pstree, _ := m.pstree.Update(m.actionMsg)
m.pstree = pstree.(*ModelPsTree) m.pstree = pstree.(*ModelPsTree)
return nil return nil
case VMOneProcess:
m.actionMsg.VM = VMMain
m.processDetail = nil
return nil
default: default:
return nil return nil
} }
...@@ -417,6 +439,8 @@ func (m *ModelMain) View() string { ...@@ -417,6 +439,8 @@ func (m *ModelMain) View() string {
return StackPosition(up, down, lipgloss.Center, lipgloss.Center) return StackPosition(up, down, lipgloss.Center, lipgloss.Center)
} }
return m.pstree.View() return m.pstree.View()
case VMOneProcess:
return m.processDetail.View()
default: default:
return "" return ""
} }
...@@ -470,8 +494,13 @@ func (m *ModelMain) handleKeyUp() tea.Cmd { ...@@ -470,8 +494,13 @@ func (m *ModelMain) handleKeyUp() tea.Cmd {
} }
switch m.actionMsg.VM { switch m.actionMsg.VM {
case VMMain: case VMMain:
processes := make([]backend.DCUProcessInfo, 0) processes := make([]backend.DCUProcessInfo, 0, 16)
for index := range 64 { keys := make([]int, 0, 16)
for k := range m.modelMsg.DCUPidInfo {
keys = append(keys, k)
}
slices.Sort(keys)
for _, index := range keys {
p, have := m.modelMsg.DCUPidInfo[index] p, have := m.modelMsg.DCUPidInfo[index]
if have && len(p) > 0 { if have && len(p) > 0 {
processes = append(processes, p...) processes = append(processes, p...)
...@@ -543,8 +572,13 @@ func (m *ModelMain) handleKeyDown() tea.Cmd { ...@@ -543,8 +572,13 @@ func (m *ModelMain) handleKeyDown() tea.Cmd {
} }
switch m.actionMsg.VM { switch m.actionMsg.VM {
case VMMain: case VMMain:
processes := make([]backend.DCUProcessInfo, 0) processes := make([]backend.DCUProcessInfo, 0, 16)
for index := range 64 { keys := make([]int, 0, 16)
for key := range m.modelMsg.DCUPidInfo {
keys = append(keys, key)
}
slices.Sort(keys)
for _, index := range keys {
p, have := m.modelMsg.DCUPidInfo[index] p, have := m.modelMsg.DCUPidInfo[index]
if have && len(p) > 0 { if have && len(p) > 0 {
processes = append(processes, p...) processes = append(processes, p...)
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"get-container/cmd/hytop/backend" "get-container/cmd/hytop/backend"
"maps" "maps"
"slices"
"strings" "strings"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
...@@ -88,11 +89,14 @@ func (m *ModelProcessInfo) View() string { ...@@ -88,11 +89,14 @@ func (m *ModelProcessInfo) View() string {
lines := make([]string, 0) lines := make([]string, 0)
sb := strings.Builder{} sb := strings.Builder{}
sbInner := strings.Builder{} sbInner := strings.Builder{}
for index := range 64 {
processes, have := m.Cache[index] keys := make([]int, 0, len(m.Cache))
if !have { for k := range m.Cache {
continue keys = append(keys, k)
} }
slices.Sort(keys)
for _, index := range keys {
processes := m.Cache[index]
for _, process := range processes { for _, process := range processes {
haveProcess = true haveProcess = true
sb.WriteString(myBorder.Left) sb.WriteString(myBorder.Left)
...@@ -103,7 +107,7 @@ func (m *ModelProcessInfo) View() string { ...@@ -103,7 +107,7 @@ func (m *ModelProcessInfo) View() string {
sbInner.WriteByte(' ') sbInner.WriteByte(' ')
sbInner.WriteString(FormatStr(process.Info.User, 8, lipgloss.Right)) sbInner.WriteString(FormatStr(process.Info.User, 8, lipgloss.Right))
sbInner.WriteByte(' ') sbInner.WriteByte(' ')
sbInner.WriteString(FormatStr(process.DCUMem, 8, lipgloss.Right)) sbInner.WriteString(FormatStr(process.DCUMem.HumanReadStr(1), 8, lipgloss.Right))
sbInner.WriteByte(' ') sbInner.WriteByte(' ')
sbInner.WriteString(FormatStr(fmt.Sprintf("%d", process.SDMA), 4, lipgloss.Right)) sbInner.WriteString(FormatStr(fmt.Sprintf("%d", process.SDMA), 4, lipgloss.Right))
sbInner.WriteByte(' ') sbInner.WriteByte(' ')
......
package tui package tui
import (
"fmt"
"get-container/cmd/hytop/backend"
"get-container/cmd/hytop/tchart"
"get-container/utils"
"math"
"strings"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
linkedlist "github.com/emirpasic/gods/v2/lists/doublylinkedlist"
)
const (
MPDHeader = " DCU PID USER DCU-MEM %CPU %MEM TIME Docker Command"
MPDProcess = "This process has finished running."
)
type ModelProcessDetail struct {
CPU, Mem, DCU, DCUMem *MyTimeChart
Pid int32
dcu int
Header, Cmd, docker string
width, height int // 界面的尺寸
modelMsg *ModelMsg //
maxVal [4]float64 // 分别代表CPU、Mem、DCU、DCUMem的最大值
maxValThreshold [4]float64
cpuPoints *linkedlist.List[tchart.TimePoint]
cpuPointsNum int
// axeYStr [4]string // 纵坐标字符串
h, w [2]int // h[0]代表上边两个表的高度、h[1]代表下边两个表的高度,w[0]表示左边两个表的宽度,w[1]表示右边两个表的宽度
lines [5]string // 5条横线
DCUMemTotal, MemTotal utils.MemorySize // 内存总量
DCUMemMax, MemMax utils.MemorySize // 内存使用最大值
DCUMemUsed utils.MemorySize
CPUPercent, MemPercent float64
t string
}
func NewModelProcessDetail(width, height int, pid int32) *ModelProcessDetail {
result := ModelProcessDetail{}
if (width-3)%2 == 0 {
result.w[0] = (width - 3) / 2
result.w[1] = result.w[0]
} else {
result.w[0] = (width - 3) / 2
result.w[1] = result.w[0] + 1
}
if (height-8)%2 == 0 {
result.h[0] = (height - 8) / 2
result.h[1] = result.h[0]
} else {
result.h[0] = (height - 8) / 2
result.h[1] = result.h[0] + 1
}
var lines [5]string
lines[0] = myBorder.TopLeft + strings.Repeat(myBorder.Top, width-2) + myBorder.TopRight
lines[1] = "╞" + strings.Repeat("═", width-2) + "╡"
lines[2] = "╞" + strings.Repeat("═", result.w[0]) + "╤" + strings.Repeat("═", result.w[1]) + "╡"
lines[3] = myBorder.MiddleLeft + genXAxis(result.w[0]) + myBorder.Middle + genXAxis(result.w[1]) + myBorder.MiddleRight
lines[4] = myBorder.BottomLeft + strings.Repeat("═", result.w[0]) + "╧" + strings.Repeat("═", result.w[1]) + myBorder.BottomRight
result.lines = lines
result.Pid = pid
result.cpuPoints = linkedlist.New[tchart.TimePoint]()
result.height = height
result.width = width
style := lipgloss.NewStyle()
uah := fmt.Sprintf("%s@%s ", style.Foreground(lipgloss.Color("#edff2cff")).Render(backend.User), style.Foreground(lipgloss.Color("#a3ff2bff")).Render(backend.HostName))
sb := strings.Builder{}
sb.WriteString(result.lines[0])
sb.WriteByte('\n')
sb.WriteString(myBorder.Left)
sb.WriteString(" Process:")
sb.WriteString(strings.Repeat(" ", width-2-lipgloss.Width(uah)-lipgloss.Width(" Process:")))
sb.WriteString(uah)
sb.WriteString(myBorder.Left)
sb.WriteByte('\n')
sb.WriteString(myBorder.Left)
sb.WriteString(MPDHeader)
sb.WriteString(strings.Repeat(" ", width-2-lipgloss.Width(MPDHeader)))
sb.WriteString(myBorder.Left)
sb.WriteByte('\n')
sb.WriteString(result.lines[1])
sb.WriteByte('\n')
result.Header = sb.String()
result.CPU = NewTimeChart(result.w[0], result.h[0], 0, 100, []lipgloss.Color{lipgloss.Color("#00fffbff")})
result.Mem = NewTimeChart(result.w[0], result.h[1], 0, 100, []lipgloss.Color{lipgloss.Color("#8800ffff")})
result.DCU = NewTimeChart(result.w[1], result.h[1], 0, 100, []lipgloss.Color{lipgloss.Color("#0dff00ff")})
result.DCUMem = NewTimeChart(result.w[1], result.h[0], 0, 100, []lipgloss.Color{lipgloss.Color("#ff1e00ff")})
result.cpuPointsNum = result.w[0]*2 + 1
for k := range result.maxVal {
result.maxVal[k] = 0
}
for k := range result.maxValThreshold {
result.maxValThreshold[k] = 100
}
return &result
}
func (m *ModelProcessDetail) Init() tea.Cmd {
return nil
}
func (m *ModelProcessDetail) Update(imsg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := imsg.(type) {
case *ModelMsg:
m.modelMsg = msg
m.handleModelMsg(msg)
}
return m, nil
}
func (m *ModelProcessDetail) handleModelMsg(msg *ModelMsg) {
find := false
var process *backend.DCUProcessInfo
for dcu, pids := range msg.DCUPidInfo {
for _, v := range pids {
if v.Info.Pid == m.Pid {
find = true
process = &v
if m.Cmd == "" {
m.Cmd = v.Info.Cmd
m.dcu = dcu
}
if v.Info.ContInfo != nil {
m.docker = v.Info.ContInfo.Name
}
break
}
}
if find {
break
}
}
var cpu, mem, dcu, dcumem float64
if process == nil {
m.Cmd = MPDProcess
m.CPUPercent = 0
m.MemPercent = 0
cpu = 0
mem = 0
} else {
cpu = process.Info.CPU
mem = float64(process.Info.Mem)
m.CPUPercent = cpu
m.MemPercent = mem
m.t = process.Info.Time
}
qinfo, lock := msg.DCUInfo.GetQuitInfo()
dcuInfo, have := qinfo[m.dcu]
if !have {
dcu = 0
dcumem = 0
} else {
dcumem = float64(dcuInfo.MemUsedPerent)
dcu = float64(dcuInfo.DCUUTil)
}
m.DCUMemMax = utils.MemorySize{Unit: utils.Byte, Num: dcuInfo.MemTotal}
m.DCUMemUsed = utils.MemorySize{Unit: utils.Byte, Num: dcuInfo.MemUsed}
lock.Unlock()
m.MemMax = utils.MemorySize{Unit: utils.Byte, Num: msg.systemInfo.MemTotal}
m.maxVal[0] = max(m.maxVal[0], cpu)
m.maxVal[1] = max(m.maxVal[1], mem)
m.maxVal[2] = max(m.maxVal[2], dcu)
m.maxVal[3] = max(m.maxVal[3], dcumem)
m.DCU.Update(MyTimeChartMsg{Reset: false, Points: []tchart.TimePoint{{Time: msg.t, Value: dcu}}})
m.DCUMem.Update(MyTimeChartMsg{Reset: false, Points: []tchart.TimePoint{{Time: msg.t, Value: dcumem}}})
m.Mem.Update(MyTimeChartMsg{Reset: false, Points: []tchart.TimePoint{{Time: msg.t, Value: mem}}})
m.cpuPoints.Add(tchart.TimePoint{Time: msg.t, Value: cpu})
if m.cpuPoints.Size() > m.cpuPointsNum {
m.cpuPoints.Remove(0)
}
if m.CPU.max < cpu {
// 需要更新
if int(math.Ceil(cpu))%50 == 0 {
m.maxValThreshold[0] = math.Ceil(cpu)
} else {
m.maxValThreshold[0] = float64(int(math.Ceil(cpu))/50)*50 + 50
}
m.CPU = NewTimeChart(m.w[0], m.h[0], 0, m.maxValThreshold[0], []lipgloss.Color{lipgloss.Color("#00fffbff")})
m.CPU.PutPoint(m.cpuPoints.Values())
} else {
m.CPU.Update(MyTimeChartMsg{Reset: false, Points: []tchart.TimePoint{{Time: msg.t, Value: cpu}}})
}
}
func (m *ModelProcessDetail) View() string {
sb := strings.Builder{}
sb.WriteString(m.Header)
sb.WriteString(myBorder.Left)
sb.WriteString(FormatStr(fmt.Sprintf("%d", m.dcu), 4, lipgloss.Right))
sb.WriteByte(' ')
sb.WriteString(FormatStr(fmt.Sprintf("%d", m.Pid), 7, lipgloss.Left))
sb.WriteByte(' ')
sb.WriteString(FormatStr(backend.User, 8, lipgloss.Left))
sb.WriteByte(' ')
sb.WriteString(FormatStr(m.DCUMemUsed.HumanReadStr(1), 9, lipgloss.Left))
sb.WriteByte(' ')
sb.WriteString(FormatStr(fmt.Sprintf("%.1f", m.CPUPercent), 5, lipgloss.Left))
sb.WriteByte(' ')
sb.WriteString(FormatStr(fmt.Sprintf("%.1f", m.MemPercent), 5, lipgloss.Left))
sb.WriteByte(' ')
sb.WriteString(FormatStr(m.t, 8, lipgloss.Left))
sb.WriteByte(' ')
if m.docker == "" {
sb.WriteString(FormatStr(" - ", 19, lipgloss.Left))
} else {
sb.WriteString(FormatStr(m.docker, 19, lipgloss.Left))
}
sb.WriteByte(' ')
sb.WriteString(FormatStr(m.Cmd, m.width-3-lipgloss.Width(MPDHeader)+lipgloss.Width("Command"), lipgloss.Left))
sb.WriteByte(' ')
sb.WriteString(myBorder.Left)
sb.WriteByte('\n')
sb.WriteString(m.lines[2])
style := lipgloss.NewStyle()
CPU := style.Border(lipgloss.NormalBorder(), false, true, false).Render(m.CPU.View())
DCUM := style.Border(lipgloss.NormalBorder(), false, true, false, false).Render(m.DCUMem.View())
Mem := style.Border(lipgloss.NormalBorder(), false, true, false).Render(m.Mem.View())
DCU := style.Border(lipgloss.NormalBorder(), false, true, false, false).Render(m.DCU.View())
sb.WriteString(lipgloss.JoinVertical(lipgloss.Left, lipgloss.JoinHorizontal(lipgloss.Top, CPU, DCUM), m.lines[3], lipgloss.JoinHorizontal(lipgloss.Top, Mem, DCU), m.lines[4]))
return sb.String()
}
package tui
type ModelProcessEnv struct {
}
...@@ -172,7 +172,7 @@ func (m *ModelPsTree) View() string { ...@@ -172,7 +172,7 @@ func (m *ModelPsTree) View() string {
} }
} }
downStr = strings.Join(lines, "\n") downStr = strings.Join(lines, "\n")
finalStr := lipgloss.JoinVertical(lipgloss.Left, m.header[0], downStr, strings.Repeat("\n", m.height-1-lipgloss.Height(downStr))) finalStr := lipgloss.JoinVertical(lipgloss.Left, m.header[0], downStr)
return ModelPsFullHeightStyle.Render(finalStr) return ModelPsFullHeightStyle.Render(finalStr)
} else { } else {
return m.header[0] + "\n There is no DCU process running" return m.header[0] + "\n There is no DCU process running"
......
...@@ -4,7 +4,6 @@ import ( ...@@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"get-container/cmd/hytop/tchart" "get-container/cmd/hytop/tchart"
"get-container/utils" "get-container/utils"
"image/color"
"strings" "strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
...@@ -12,8 +11,7 @@ import ( ...@@ -12,8 +11,7 @@ import (
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/emirpasic/gods/v2/maps/treemap" linkedlist "github.com/emirpasic/gods/v2/lists/doublylinkedlist"
"github.com/emirpasic/gods/v2/trees/binaryheap"
"github.com/lucasb-eyer/go-colorful" "github.com/lucasb-eyer/go-colorful"
"github.com/muesli/gamut" "github.com/muesli/gamut"
) )
...@@ -38,19 +36,21 @@ type ModelSysLoad struct { ...@@ -38,19 +36,21 @@ type ModelSysLoad struct {
SysCPU *MyTimeChart SysCPU *MyTimeChart
DCU *MyTimeChart DCU *MyTimeChart
DCUMem *MyTimeChart DCUMem *MyTimeChart
sysInfoLock sync.Mutex // sysinfo和keys的保护锁,防止并发写 dataMaplock sync.RWMutex // 保护下面两个map的锁,防止并发
sysInfo *treemap.Map[time.Time, SysLoadInfo] dcuMemData map[int]*linkedlist.List[tchart.TimePoint] // 记录dcu的内存使用信息点
keys *binaryheap.Heap[time.Time] dcuUsgData map[int]*linkedlist.List[tchart.TimePoint] // 记录dcu的使用率信息点
current *SysLoadInfo current *SysLoadInfo // 当前的全量信息
topLine, topL string topLine, topL string
bottomLine, bottomL string bottomLine, bottomL string
style lipgloss.Style style lipgloss.Style
width int // 组件总宽度 width int // 组件总宽度
colors []color.Color // 时序图的颜色表 colors []lipgloss.Color // 时序图的颜色表
colorsLow []color.Color // 低亮度模式的颜色表 colorsLow []lipgloss.Color // 低亮度模式的颜色表
actionMsg *ActionMsg // 动作消息 actionMsg *ActionMsg // 动作消息
modelMsg *ModelMsg // 模型周期性消息 modelMsg *ModelMsg // 模型周期性消息
DCUToShow atomic.Int32 // 要显示的DCU信息的索引,如果为-1,表示显示dcu的平均数据 DCUToShow atomic.Int32 // 要显示的DCU信息的索引,如果为-1,表示显示dcu的平均数据
dataNumToStore int // 维持DCU和DCUMem图表需要的最多数据点的数量
} }
type SysLoadInfo struct { type SysLoadInfo struct {
...@@ -69,28 +69,13 @@ type SysLoadInfo struct { ...@@ -69,28 +69,13 @@ type SysLoadInfo struct {
func NewModelSysLoad(width int) *ModelSysLoad { func NewModelSysLoad(width int) *ModelSysLoad {
result := ModelSysLoad{} result := ModelSysLoad{}
result.width = width result.width = width
result.sysInfo = treemap.NewWith[time.Time, SysLoadInfo](func(x, y time.Time) int { result.dcuMemData = make(map[int]*linkedlist.List[tchart.TimePoint])
if x.Before(y) { result.dcuUsgData = make(map[int]*linkedlist.List[tchart.TimePoint])
return -1 result.colors = ConvertColor(gamut.Blends(lipgloss.Color("#ff0000"), lipgloss.Color("#00ff00ff"), SysLoadHeight))
} result.colorsLow = ConvertColor(gamut.Blends(gamut.Darker(lipgloss.Color("#ff0000"), 0.8), gamut.Darker(lipgloss.Color("#00ff00ff"), 0.8), SysLoadHeight))
if x.After(y) {
return 1
}
return 0
})
result.keys = binaryheap.NewWith(func(a, b time.Time) int {
if a.After(b) {
return 1
}
if a.Before(b) {
return -1
}
return 0
})
result.colors = gamut.Blends(lipgloss.Color("#ff0000"), lipgloss.Color("#00ff00ff"), SysLoadHeight)
result.colorsLow = gamut.Blends(gamut.Darker(lipgloss.Color("#ff0000"), 0.8), gamut.Darker(lipgloss.Color("#00ff00ff"), 0.8), SysLoadHeight)
subLine := width - StaticWidth - 1 subLine := width - StaticWidth - 1
result.dataNumToStore = subLine*2 + 1
result.SysMem = NewTimeChart(SysLoadWidth, SysLoadHeight, 0, 100, result.colors) result.SysMem = NewTimeChart(SysLoadWidth, SysLoadHeight, 0, 100, result.colors)
result.SysCPU = NewTimeChart(SysLoadWidth, SysLoadHeight, 0, 100, result.colors) result.SysCPU = NewTimeChart(SysLoadWidth, SysLoadHeight, 0, 100, result.colors)
result.DCU = NewTimeChart(subLine, SysLoadHeight, 0, 100, result.colors) result.DCU = NewTimeChart(subLine, SysLoadHeight, 0, 100, result.colors)
...@@ -186,7 +171,7 @@ func (m *ModelSysLoad) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) { ...@@ -186,7 +171,7 @@ func (m *ModelSysLoad) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
// handleAction 处理ActionMsg // handleAction 处理ActionMsg
func (m *ModelSysLoad) handleAction() { func (m *ModelSysLoad) handleAction() {
if m.actionMsg == nil || m.actionMsg.TargetDCUIndex == nil { if m.actionMsg == nil || m.actionMsg.TargetDCUIndex == nil || m.actionMsg.VM != VMMain {
return return
} }
targetIndex := int32(*m.actionMsg.TargetDCUIndex) targetIndex := int32(*m.actionMsg.TargetDCUIndex)
...@@ -198,31 +183,11 @@ func (m *ModelSysLoad) handleAction() { ...@@ -198,31 +183,11 @@ func (m *ModelSysLoad) handleAction() {
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(1) wg.Add(1)
index := int(targetIndex) index := int(targetIndex)
i := m.sysInfo.Iterator() rl := m.dataMaplock.RLocker()
rl.Lock()
mem := make([]tchart.TimePoint, 0, m.sysInfo.Size()) mem := m.dcuMemData[index].Values()
usage := make([]tchart.TimePoint, 0, m.sysInfo.Size()) usage := m.dcuUsgData[index].Values()
for { rl.Unlock()
if !i.Next() {
break
}
key := i.Key()
var valM, valU float32
vU, haveU := i.Value().DCUUsage[index]
vM, haveM := i.Value().DCUMemUsage[index]
if haveU {
valU = vU
} else {
valU = 0
}
if haveM {
valM = vM
} else {
valM = 0
}
mem = append(mem, tchart.TimePoint{Time: key, Value: float64(valM)})
usage = append(usage, tchart.TimePoint{Time: key, Value: float64(valU)})
}
go func() { go func() {
defer wg.Done() defer wg.Done()
m3, _ := m.DCU.Update(NewTimeCharMsg(usage, true)) m3, _ := m.DCU.Update(NewTimeCharMsg(usage, true))
...@@ -294,7 +259,12 @@ func (m *ModelSysLoad) View() string { ...@@ -294,7 +259,12 @@ func (m *ModelSysLoad) View() string {
// updateInfo 向ModelSysLoad中添加数据 // updateInfo 向ModelSysLoad中添加数据
func (m *ModelSysLoad) updateInfo(t *ModelMsg) { func (m *ModelSysLoad) updateInfo(t *ModelMsg) {
sysInfo, _ := utils.GetSysInfo() var sysInfo *utils.SysInfo
if t.systemInfo != nil {
sysInfo = t.systemInfo
} else {
sysInfo, _ = utils.GetSysInfo()
}
s := SysLoadInfo{} s := SysLoadInfo{}
s.Load1 = sysInfo.LoadAverage1 s.Load1 = sysInfo.LoadAverage1
s.Load5 = sysInfo.LoadAverage5 s.Load5 = sysInfo.LoadAverage5
...@@ -312,11 +282,26 @@ func (m *ModelSysLoad) updateInfo(t *ModelMsg) { ...@@ -312,11 +282,26 @@ func (m *ModelSysLoad) updateInfo(t *ModelMsg) {
s.DCUMemUsageAvg, s.DCUUsageAvg = 0, 0 s.DCUMemUsageAvg, s.DCUUsageAvg = 0, 0
qinfo, lock := t.DCUInfo.GetQuitInfo() qinfo, lock := t.DCUInfo.GetQuitInfo()
m.dataMaplock.Lock()
for k, v := range qinfo { for k, v := range qinfo {
s.DCUMemUsageAvg += v.MemUsedPerent s.DCUMemUsageAvg += v.MemUsedPerent
s.DCUMemUsage[k] = v.MemUsedPerent s.DCUMemUsage[k] = v.MemUsedPerent
s.DCUUsageAvg += v.DCUUTil s.DCUUsageAvg += v.DCUUTil
s.DCUUsage[k] = v.DCUUTil s.DCUUsage[k] = v.DCUUTil
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)})
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)})
} }
l := len(qinfo) l := len(qinfo)
lock.Unlock() lock.Unlock()
...@@ -324,6 +309,32 @@ func (m *ModelSysLoad) updateInfo(t *ModelMsg) { ...@@ -324,6 +309,32 @@ func (m *ModelSysLoad) updateInfo(t *ModelMsg) {
s.DCUUsageAvg /= float32(l) s.DCUUsageAvg /= float32(l)
s.DCUMemUsage[-1] = s.DCUMemUsageAvg s.DCUMemUsage[-1] = s.DCUMemUsageAvg
s.DCUUsage[-1] = s.DCUUsageAvg s.DCUUsage[-1] = s.DCUUsageAvg
dcu, have := m.dcuUsgData[-1]
if !have {
dcu = linkedlist.New[tchart.TimePoint]()
m.dcuUsgData[-1] = dcu
}
dcu.Add(tchart.TimePoint{Time: t.t, Value: float64(s.DCUUsageAvg)})
mem, have := m.dcuMemData[-1]
if !have {
mem = linkedlist.New[tchart.TimePoint]()
m.dcuMemData[-1] = mem
}
mem.Add(tchart.TimePoint{Time: t.t, Value: float64(s.DCUMemUsageAvg)})
// 判断是否要丢弃多余的点
if dcu.Size() > m.dataNumToStore {
for _, v := range m.dcuMemData {
v.Remove(0)
}
for _, v := range m.dcuUsgData {
v.Remove(0)
}
}
m.dataMaplock.Unlock()
m.current = &s m.current = &s
needUpdate, index := false, -1 needUpdate, index := false, -1
...@@ -354,44 +365,14 @@ func (m *ModelSysLoad) updateInfo(t *ModelMsg) { ...@@ -354,44 +365,14 @@ func (m *ModelSysLoad) updateInfo(t *ModelMsg) {
m2, _ := m.SysCPU.Update(NewTimeCharMsg([]tchart.TimePoint{{Time: t.t, Value: s.CPUPercent}}, false)) m2, _ := m.SysCPU.Update(NewTimeCharMsg([]tchart.TimePoint{{Time: t.t, Value: s.CPUPercent}}, false))
m.SysCPU = m2.(*MyTimeChart) m.SysCPU = m2.(*MyTimeChart)
}() }()
// 存放数据
m.sysInfoLock.Lock()
m.sysInfo.Put(t.t, s)
m.keys.Push(t.t)
if m.keys.Size() > SysLoadCap {
delKey, have := m.keys.Pop()
if have {
m.sysInfo.Remove(delKey)
}
}
m.sysInfoLock.Unlock()
if needUpdate { if needUpdate {
// 准备全量数据 // 准备全量数据
i := m.sysInfo.Iterator() rl := m.dataMaplock.RLocker()
mem := make([]tchart.TimePoint, 0, m.sysInfo.Size()) rl.Lock()
usage := make([]tchart.TimePoint, 0, m.sysInfo.Size()) mem := m.dcuMemData[index].Values()
for { usage := m.dcuUsgData[index].Values()
if !i.Next() { rl.Unlock()
break
}
key := i.Key()
var valM, valU float32
vU, haveU := i.Value().DCUUsage[index]
vM, haveM := i.Value().DCUMemUsage[index]
if haveU {
valU = vU
} else {
valU = 0
}
if haveM {
valM = vM
} else {
valM = 0
}
mem = append(mem, tchart.TimePoint{Time: key, Value: float64(valM)})
usage = append(usage, tchart.TimePoint{Time: key, Value: float64(valU)})
}
go func() { go func() {
defer wg.Done() defer wg.Done()
m3, _ := m.DCU.Update(NewTimeCharMsg(usage, true)) m3, _ := m.DCU.Update(NewTimeCharMsg(usage, true))
......
package tui package tui
import ( import (
"image/color"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
...@@ -15,7 +14,6 @@ import ( ...@@ -15,7 +14,6 @@ import (
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
zone "github.com/lrstanley/bubblezone" zone "github.com/lrstanley/bubblezone"
"github.com/lucasb-eyer/go-colorful"
) )
const ( const (
...@@ -107,11 +105,11 @@ type MyTimeChart struct { ...@@ -107,11 +105,11 @@ type MyTimeChart struct {
width, height int // 图表的高度和宽度 width, height int // 图表的高度和宽度
max, min float64 // y轴的最值 max, min float64 // y轴的最值
lockPoints sync.RWMutex // 保护dataSet的并发读写 lockPoints sync.RWMutex // 保护dataSet的并发读写
color []color.Color color []lipgloss.Color
} }
// New 新建图表,其中dataSet的Key为数据集名称,value为数据集的颜色 // New 新建图表,其中dataSet的Key为数据集名称,value为数据集的颜色
func NewTimeChart(width, height int, vmin, vmax float64, color []color.Color) *MyTimeChart { func NewTimeChart(width, height int, vmin, vmax float64, color []lipgloss.Color) *MyTimeChart {
result := MyTimeChart{} result := MyTimeChart{}
result.max = vmax result.max = vmax
result.min = vmin result.min = vmin
...@@ -247,7 +245,7 @@ func (m *MyTimeChart) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) { ...@@ -247,7 +245,7 @@ func (m *MyTimeChart) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil return m, nil
} }
func (m *MyTimeChart) ViewWithColor(color []color.Color) string { func (m *MyTimeChart) ViewWithColor(color []lipgloss.Color) string {
str := m.zM.Scan(m.chart.View()) str := m.zM.Scan(m.chart.View())
sb := strings.Builder{} sb := strings.Builder{}
sb.WriteString(str[m.width+1:]) sb.WriteString(str[m.width+1:])
...@@ -334,10 +332,10 @@ func (m *MyTimeChart) ViewWithColor(color []color.Color) string { ...@@ -334,10 +332,10 @@ func (m *MyTimeChart) ViewWithColor(color []color.Color) string {
for k, v := range runes { for k, v := range runes {
resultLines[k] = string(v) resultLines[k] = string(v)
} }
style := lipgloss.NewStyle()
if len(color) == height { if len(color) == height {
for k, v := range resultLines { for k, v := range resultLines {
c, _ := colorful.MakeColor(color[k]) resultLines[k] = style.Foreground(color[k]).Render(v)
resultLines[k] = lipgloss.NewStyle().Foreground(lipgloss.Color(c.Hex())).Render(v)
} }
} }
return strings.Join(resultLines, "\n") return strings.Join(resultLines, "\n")
...@@ -431,10 +429,13 @@ func (m *MyTimeChart) View() string { ...@@ -431,10 +429,13 @@ func (m *MyTimeChart) View() string {
resultLines[k] = string(v) resultLines[k] = string(v)
} }
if len(m.color) == height { if len(m.color) == height {
style := lipgloss.NewStyle()
for k, v := range resultLines { for k, v := range resultLines {
c, _ := colorful.MakeColor(m.color[k]) resultLines[k] = style.Foreground(m.color[k]).Render(v)
resultLines[k] = lipgloss.NewStyle().Foreground(lipgloss.Color(c.Hex())).Render(v)
} }
} else if len(m.color) == 1 {
style := lipgloss.NewStyle().Foreground(m.color[0])
return style.Render(strings.Join(resultLines, "\n"))
} }
return strings.Join(resultLines, "\n") return strings.Join(resultLines, "\n")
} }
package tui package tui
import ( import (
"image/color"
"strings" "strings"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/x/ansi" "github.com/charmbracelet/x/ansi"
"github.com/lucasb-eyer/go-colorful"
) )
func absInt(a int) int { func absInt(a int) int {
...@@ -151,3 +153,15 @@ func FormatStr(str string, length int, hPos lipgloss.Position) string { ...@@ -151,3 +153,15 @@ func FormatStr(str string, length int, hPos lipgloss.Position) string {
} }
return str return str
} }
func ConvertColor(color []color.Color) []lipgloss.Color {
if len(color) == 0 {
return nil
}
result := make([]lipgloss.Color, len(color))
for k, v := range color {
c, _ := colorful.MakeColor(v)
result[k] = lipgloss.Color(c.Hex())
}
return result
}
...@@ -44,16 +44,6 @@ var ( ...@@ -44,16 +44,6 @@ var (
RightOnlyMW = []rune("⠈⠐⠠⢀⠘⠰⠨⢈⢐⢠⢘⢨⠸⢰⢸") RightOnlyMW = []rune("⠈⠐⠠⢀⠘⠰⠨⢈⢐⢠⢘⢨⠸⢰⢸")
) )
/*
├─────────────120s├──────────────────────────60s├───────────30s├──────────────┼──────────────────────360s├─────────────────────────300s├─────────────────────────240s├─────────────────────────180s├─────────────────────────120s├──────────────────────────60s├───────────30s├──────────────┤
│ │ ⡏⠉⡇⣿⢸⢹ │
│ │ ⡇ ⢣⠃⠇⠈⡆ │
│ │ ⡇ ⠸ ⡇ │
│ │ ⡇ ⢣ │
│ MEM: 93.0GiB (6.2%)⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⠤⠤⠤⠤⠤⠤⠤⠤⠄│ AVG DCU UTL: 0.0%⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⡇ ⢸⣀⣀⣀⣀⣀⡀│
*/
func Full() map[rune]rune { func Full() map[rune]rune {
result := make(map[rune]rune) result := make(map[rune]rune)
str := strings.ReplaceAll(strings.ReplaceAll(strings.Trim(MW, "\n"), "|", ""), "\n", "") str := strings.ReplaceAll(strings.ReplaceAll(strings.Trim(MW, "\n"), "|", ""), "\n", "")
......
...@@ -195,3 +195,9 @@ func TestChild(t *testing.T) { ...@@ -195,3 +195,9 @@ func TestChild(t *testing.T) {
t.Logf("%d ms \n", d.Milliseconds()) t.Logf("%d ms \n", d.Milliseconds())
t.Logf("childs num: %d", len(childs)) t.Logf("childs num: %d", len(childs))
} }
func TestCHar(t *testing.T) {
c1 := "⣿⣿⣿⣿⣿⣿⣿⣿"
c2 := "├"
t.Log(lipgloss.JoinHorizontal(lipgloss.Top, c2, c1))
}
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