package tui import ( "fmt" "get-container/cmd/hytop/backend" "get-container/cmd/hytop/tchart" "get-container/utils" "image/color" "maps" "strings" "sync" "time" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/emirpasic/gods/v2/maps/linkedhashmap" "github.com/emirpasic/gods/v2/trees/binaryheap" "github.com/lucasb-eyer/go-colorful" "github.com/muesli/gamut" ) const ( SysLoadHeight = 5 // 固定图表高度 SysLoadWidth = 77 // 固定图表宽度,不包含左右的边框 SysLoadCap = 600 // 记录 ) // ModelSysLoad 系统负载组件 type ModelSysLoad struct { SysMem *MyTimeChart SysCPU *MyTimeChart DCU *MyTimeChart DCUMem *MyTimeChart Cache map[int]backend.DCUInfo SysInfo *linkedhashmap.Map[time.Time, SysLoadInfo] Keys *binaryheap.Heap[time.Time] current *SysLoadInfo topLine string bottomLine string style lipgloss.Style width int // 组件总宽度 colors []color.Color } type SysLoadInfo struct { T time.Time DCUUsage map[int]float32 DCUMemUsage map[int]float32 Load1, Load5, Load15 float64 MemTotal, SwapTotal uint64 MemUsed, SwapUsed uint64 MemUsedPercent, SwapUsedPercent float64 CPUPercent float64 DCUUsageAvg float32 DCUMemUsageAvg float32 } func NewModelSysLoad(width int) *ModelSysLoad { result := ModelSysLoad{} result.width = width result.Cache = make(map[int]backend.DCUInfo) result.SysInfo = linkedhashmap.New[time.Time, SysLoadInfo]() result.Keys = binaryheap.NewWith(func(a, b time.Time) int { if a.After(b) { return 1 } if a.Before(b) { return -1 } return 0 }) subLine := width - StaticWidth - 1 result.SysMem = NewTimeChart(SysLoadWidth, SysLoadHeight, 0, 100, map[string]lipgloss.Color{"default": lipgloss.Color("#20ff2cff")}) result.SysCPU = NewTimeChart(SysLoadWidth, SysLoadHeight, 0, 100, map[string]lipgloss.Color{"default": lipgloss.Color("#ff3030ff")}) result.DCU = NewTimeChart(subLine, SysLoadHeight, 0, 100, map[string]lipgloss.Color{"default": lipgloss.Color("#27ffdbff")}) result.DCUMem = NewTimeChart(subLine, SysLoadHeight, 0, 100, map[string]lipgloss.Color{"default": lipgloss.Color("#20b1ffff")}) result.topLine = myBorder.MiddleLeft + genXAxis(SysLoadWidth) + myBorder.Middle + genXAxis(subLine) + myBorder.MiddleRight result.bottomLine = "╞" + strings.Repeat(myBorder.Bottom, SysLoadWidth) + "╧" + strings.Repeat(myBorder.Bottom, subLine) + "╡" result.style = lipgloss.NewStyle() result.colors = gamut.Blends(lipgloss.Color("#ff0000"), lipgloss.Color("#00ff00ff"), SysLoadHeight) return &result } func (m *ModelSysLoad) Init() tea.Cmd { result := make([]tea.Cmd, 0) if c := m.DCU.Init(); c != nil { result = append(result, c) } if c := m.DCUMem.Init(); c != nil { result = append(result, c) } if c := m.SysMem.Init(); c != nil { result = append(result, c) } if c := m.SysCPU.Init(); c != nil { result = append(result, c) } if len(result) > 0 { return tea.Batch(result...) } sysInfo, _ := utils.GetSysInfo() s := SysLoadInfo{} s.Load1 = sysInfo.LoadAverage1 s.Load5 = sysInfo.LoadAverage5 s.Load15 = sysInfo.LoadAverage15 s.MemTotal = sysInfo.MemTotal s.MemUsed = sysInfo.MemUsage s.SwapTotal = sysInfo.SwapTotal s.SwapUsed = sysInfo.SwapUsage s.MemUsedPercent = sysInfo.MemUsagePercent s.SwapUsedPercent = sysInfo.SwapUsagePercent s.T = time.Now() s.CPUPercent = sysInfo.CPUPercent s.DCUUsageAvg = 0 s.DCUMemUsageAvg = 0 m.current = &s return nil } func (m *ModelSysLoad) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) { switch msg := inputMsg.(type) { case *ModelMsg: clear(m.Cache) maps.Copy(m.Cache, msg.DCUInfo) m.updateInfo(msg.t) return m, nil } return m, nil } func (m *ModelSysLoad) View() string { memUsed := utils.MemorySize{Num: m.current.MemUsed, Unit: utils.Byte} load := fmt.Sprintf(" Load Average: %.2f %.2f %.2f\n CPU: %.1f%%", m.current.Load1, m.current.Load5, m.current.Load15, m.current.CPUPercent) mem := fmt.Sprintf(" MEM: %s (%.1f%%)", memUsed.HumanReadStr(1), m.current.MemUsedPercent) dcuMem := fmt.Sprintf(" AVG DCU MEM: %.1f%%", m.current.DCUMemUsageAvg) dcu := fmt.Sprintf(" AVG DCU UTL: %.1f%%", m.current.DCUUsageAvg) load = StackPosition(load, m.SysCPU.View(), lipgloss.Top, lipgloss.Left) mem = StackPosition(mem, m.SysMem.View(), lipgloss.Bottom, lipgloss.Left) s := m.DCUMem.View() s = lipgloss.NewStyle().Foreground(lipgloss.Color("rgba(158, 143, 28, 0.86)")).Render(s) dcuMem = StackPosition(dcuMem, s, lipgloss.Top, lipgloss.Left) dcu = StackPosition(dcu, m.DCU.View(), lipgloss.Bottom, lipgloss.Left) load = m.style.Border(myBorder, false, true, false).Render(load) mem = m.style.Border(myBorder, false, true, false).Render(mem) dcuMem = m.style.Border(myBorder, false, true, false, false).Render(dcuMem) dcu = m.style.Border(myBorder, false, true, false, false).Render(dcu) up := lipgloss.JoinHorizontal(lipgloss.Top, load, dcuMem) down := lipgloss.JoinHorizontal(lipgloss.Top, mem, dcu) return lipgloss.JoinVertical(lipgloss.Left, up, m.topLine, down, m.bottomLine) + "\n" } // updateInfo func (m *ModelSysLoad) updateInfo(t time.Time) { sysInfo, _ := utils.GetSysInfo() s := SysLoadInfo{} s.Load1 = sysInfo.LoadAverage1 s.Load5 = sysInfo.LoadAverage5 s.Load15 = sysInfo.LoadAverage15 s.MemTotal = sysInfo.MemTotal s.MemUsed = sysInfo.MemUsage s.SwapTotal = sysInfo.SwapTotal s.SwapUsed = sysInfo.SwapUsage s.MemUsedPercent = sysInfo.MemUsagePercent s.SwapUsedPercent = sysInfo.SwapUsagePercent s.T = t s.CPUPercent = sysInfo.CPUPercent s.DCUUsage = make(map[int]float32) s.DCUMemUsage = make(map[int]float32) s.DCUMemUsageAvg, s.DCUUsageAvg = 0, 0 for k, v := range m.Cache { s.DCUMemUsageAvg += v.MemUsedPerent s.DCUMemUsage[k] = v.MemUsedPerent s.DCUUsageAvg += v.DCUUTil s.DCUUsage[k] = v.DCUUTil } l := len(m.Cache) s.DCUMemUsageAvg /= float32(l) s.DCUUsageAvg /= float32(l) m.current = &s wg := sync.WaitGroup{} wg.Add(4) go func() { defer wg.Done() m1, _ := m.SysMem.Update(MyTimeChartMsg{Points: map[string][]tchart.TimePoint{"default": {{Time: t, Value: s.MemUsedPercent}}}}) m.SysMem = m1.(*MyTimeChart) }() go func() { defer wg.Done() m2, _ := m.SysCPU.Update(MyTimeChartMsg{Points: map[string][]tchart.TimePoint{"default": {{Time: t, Value: s.CPUPercent}}}}) m.SysCPU = m2.(*MyTimeChart) }() go func() { defer wg.Done() m3, _ := m.DCU.Update(MyTimeChartMsg{Points: map[string][]tchart.TimePoint{"default": {{Time: t, Value: float64(s.DCUUsageAvg)}}}}) m.DCU = m3.(*MyTimeChart) }() go func() { defer wg.Done() m4, _ := m.DCUMem.Update(MyTimeChartMsg{Points: map[string][]tchart.TimePoint{"default": {{Time: t, Value: float64(s.DCUMemUsageAvg)}}}}) m.DCUMem = m4.(*MyTimeChart) }() // 存放数据 m.SysInfo.Put(t, s) m.Keys.Push(t) if m.Keys.Size() > SysLoadCap { delKey, have := m.Keys.Pop() if have { m.SysInfo.Remove(delKey) } } wg.Wait() } func (m *ModelSysLoad) RanderColor(str string) string { lines := strings.Split(strings.Trim(str, "\n"), "\n") result := "" for k, line := range lines { c, _ := colorful.MakeColor(m.colors[k]) result += lipgloss.NewStyle().Foreground(lipgloss.Color(c.Hex())).Render(line) + "\n" } return result }