Commit 70a649e8 authored by liming6's avatar liming6
Browse files

feature 添加组件的低亮度模式

parent 3e5adb86
...@@ -6,7 +6,4 @@ ...@@ -6,7 +6,4 @@
优化抓取数据逻辑,避免卡顿 优化抓取数据逻辑,避免卡顿
- 减少无效数据的收集 - 减少无效数据的收集
- 多goroutine收集数据,收集数据的goroutine不阻塞tui相关goroutine的运行 - 多goroutine收集数据,收集数据的goroutine不阻塞tui相关goroutine的运行
- 优化数据结构,降低时间复杂度
尝试使用.so文件抓取系统信息,而非使用二进制文件的输出,以提高采集效率
集成docker相关功能,同时注意错误隔断,避免无用错误影响主逻辑(docker容器重启会影响获取docker容器相关信息,会返回err)
...@@ -16,12 +16,16 @@ import ( ...@@ -16,12 +16,16 @@ import (
) )
type ModelDCUInfo struct { type ModelDCUInfo struct {
pro progress.Model // 进度条 width, height int // 屏幕尺寸
proLowLeight progress.Model // 低亮度进度条 pro progress.Model // 进度条
proWidth int // 进度条宽度 proLowLeight progress.Model // 低亮度进度条
modMsg *ModelMsg proWidth int // 进度条宽度
width, height int modMsg *ModelMsg // 缓存的模型信息
actionMsg *ActionMsg actionMsg *ActionMsg // 缓存的动作信息
darkModule bool // 是否为黑暗模式
top, middle, bottom string // 缓存的边框
topL, middleL, bottomL string // 缓存的低亮度边框
subLin int // 边框更新标志
} }
const ( const (
...@@ -39,7 +43,7 @@ var ( ...@@ -39,7 +43,7 @@ var (
) )
func NewModelDCUInfo(w, h int) *ModelDCUInfo { func NewModelDCUInfo(w, h int) *ModelDCUInfo {
return &ModelDCUInfo{width: w, height: h} return &ModelDCUInfo{width: w, height: h, darkModule: false}
} }
func (m *ModelDCUInfo) Init() tea.Cmd { func (m *ModelDCUInfo) Init() tea.Cmd {
...@@ -68,6 +72,11 @@ func (m *ModelDCUInfo) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) { ...@@ -68,6 +72,11 @@ func (m *ModelDCUInfo) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil return m, nil
case *ActionMsg: case *ActionMsg:
m.actionMsg = msg m.actionMsg = msg
if msg.Action != nil {
m.darkModule = true
} else {
m.darkModule = false
}
return m, nil return m, nil
} }
return m, nil return m, nil
...@@ -80,14 +89,16 @@ func (m *ModelDCUInfo) View() string { ...@@ -80,14 +89,16 @@ func (m *ModelDCUInfo) View() string {
lineWidth := 0 lineWidth := 0
qmap, qlock := m.modMsg.DCUInfo.GetQuitInfo() qmap, qlock := m.modMsg.DCUInfo.GetQuitInfo()
smap, slock := m.modMsg.DCUInfo.GetSlowInfo() smap, slock := m.modMsg.DCUInfo.GetSlowInfo()
defer slock.Unlock()
defer qlock.Unlock()
strBuilder := strings.Builder{} strBuilder := strings.Builder{}
strCache := strings.Builder{} strCache := strings.Builder{}
var targetDCU int = -1 var targetDCU int = -1
if m.actionMsg != nil && m.actionMsg.TargetDCUIndex != nil { if m.actionMsg != nil && m.actionMsg.TargetDCUIndex != nil {
targetDCU = *m.actionMsg.TargetDCUIndex targetDCU = *m.actionMsg.TargetDCUIndex
} }
var borderStr string = myBorder.Left
if m.darkModule {
borderStr = LowLeightStyle.Render(borderStr)
}
infos := make([]string, 0) infos := make([]string, 0)
for i := range 64 { for i := range 64 {
qinfo, haveq := qmap[i] qinfo, haveq := qmap[i]
...@@ -95,8 +106,8 @@ func (m *ModelDCUInfo) View() string { ...@@ -95,8 +106,8 @@ func (m *ModelDCUInfo) View() string {
if !(haveq || haves) { if !(haveq || haves) {
continue continue
} }
isLowStyle := (targetDCU != qinfo.Id && targetDCU != -1) isLowStyle := (targetDCU != qinfo.Id && targetDCU != -1) || m.darkModule
strBuilder.WriteString(myBorder.Left) strBuilder.WriteString(borderStr)
strCache.WriteByte(' ') strCache.WriteByte(' ')
strCache.WriteString(FormatStr(strconv.Itoa(qinfo.Id), 4, lipgloss.Left)) strCache.WriteString(FormatStr(strconv.Itoa(qinfo.Id), 4, lipgloss.Left))
...@@ -113,7 +124,7 @@ func (m *ModelDCUInfo) View() string { ...@@ -113,7 +124,7 @@ func (m *ModelDCUInfo) View() string {
strBuilder.WriteString(strCache.String()) strBuilder.WriteString(strCache.String())
} }
strCache.Reset() strCache.Reset()
strBuilder.WriteString(myBorder.Left) strBuilder.WriteString(borderStr)
strCache.WriteByte(' ') strCache.WriteByte(' ')
strCache.WriteString(FormatStr(qinfo.BusId, 13, lipgloss.Left)) strCache.WriteString(FormatStr(qinfo.BusId, 13, lipgloss.Left))
...@@ -130,7 +141,7 @@ func (m *ModelDCUInfo) View() string { ...@@ -130,7 +141,7 @@ func (m *ModelDCUInfo) View() string {
strCache.WriteByte(' ') strCache.WriteByte(' ')
strCache.WriteString(FormatStr("Off", 16, lipgloss.Left)) strCache.WriteString(FormatStr("Off", 16, lipgloss.Left))
strBuilder.WriteString(myBorder.Left) strBuilder.WriteString(borderStr)
strCache.WriteByte(' ') strCache.WriteByte(' ')
if sinfo.Ecc.Load() { if sinfo.Ecc.Load() {
strCache.WriteString(FormatStr("On", 3, lipgloss.Right)) strCache.WriteString(FormatStr("On", 3, lipgloss.Right))
...@@ -145,7 +156,7 @@ func (m *ModelDCUInfo) View() string { ...@@ -145,7 +156,7 @@ func (m *ModelDCUInfo) View() string {
strBuilder.WriteString(strCache.String()) strBuilder.WriteString(strCache.String())
} }
strCache.Reset() strCache.Reset()
strBuilder.WriteString(myBorder.Left) strBuilder.WriteString(borderStr)
if isLowStyle { if isLowStyle {
strBuilder.WriteString(LowLeightStyle.Render(" DCU: ")) strBuilder.WriteString(LowLeightStyle.Render(" DCU: "))
...@@ -156,9 +167,9 @@ func (m *ModelDCUInfo) View() string { ...@@ -156,9 +167,9 @@ func (m *ModelDCUInfo) View() string {
strBuilder.WriteString(m.pro.ViewAs(float64(qinfo.DCUUTil / 100))) strBuilder.WriteString(m.pro.ViewAs(float64(qinfo.DCUUTil / 100)))
strBuilder.WriteString(fmt.Sprintf(" %3d%% ", int(qinfo.DCUUTil))) strBuilder.WriteString(fmt.Sprintf(" %3d%% ", int(qinfo.DCUUTil)))
} }
strBuilder.WriteString(myBorder.Left) strBuilder.WriteString(borderStr)
strBuilder.WriteByte('\n') strBuilder.WriteByte('\n')
strBuilder.WriteString(myBorder.Left) strBuilder.WriteString(borderStr)
strCache.WriteByte(' ') strCache.WriteByte(' ')
strCache.WriteString(FormatStr(qinfo.Fan, 4, lipgloss.Left)) strCache.WriteString(FormatStr(qinfo.Fan, 4, lipgloss.Left))
...@@ -175,7 +186,7 @@ func (m *ModelDCUInfo) View() string { ...@@ -175,7 +186,7 @@ func (m *ModelDCUInfo) View() string {
strBuilder.WriteString(strCache.String()) strBuilder.WriteString(strCache.String())
} }
strCache.Reset() strCache.Reset()
strBuilder.WriteString(myBorder.Left) strBuilder.WriteString(borderStr)
if isLowStyle { if isLowStyle {
strCache.WriteByte(' ') strCache.WriteByte(' ')
...@@ -189,7 +200,7 @@ func (m *ModelDCUInfo) View() string { ...@@ -189,7 +200,7 @@ func (m *ModelDCUInfo) View() string {
strBuilder.WriteByte(' ') strBuilder.WriteByte(' ')
} }
strBuilder.WriteString(myBorder.Left) strBuilder.WriteString(borderStr)
if isLowStyle { if isLowStyle {
strCache.WriteByte(' ') strCache.WriteByte(' ')
...@@ -207,7 +218,7 @@ func (m *ModelDCUInfo) View() string { ...@@ -207,7 +218,7 @@ func (m *ModelDCUInfo) View() string {
strBuilder.WriteByte(' ') strBuilder.WriteByte(' ')
} }
strBuilder.WriteString(myBorder.Left) strBuilder.WriteString(borderStr)
if isLowStyle { if isLowStyle {
strCache.WriteString(" Mem: ") strCache.WriteString(" Mem: ")
...@@ -222,19 +233,31 @@ func (m *ModelDCUInfo) View() string { ...@@ -222,19 +233,31 @@ func (m *ModelDCUInfo) View() string {
strBuilder.WriteString(fmt.Sprintf("%3d%%", int(100*float64(qinfo.MemUsed)/float64(qinfo.MemTotal)))) strBuilder.WriteString(fmt.Sprintf("%3d%%", int(100*float64(qinfo.MemUsed)/float64(qinfo.MemTotal))))
strBuilder.WriteByte(' ') strBuilder.WriteByte(' ')
} }
strBuilder.WriteString(myBorder.Left) strBuilder.WriteString(borderStr)
strBuilder.WriteByte('\n')
infos = append(infos, strBuilder.String()) infos = append(infos, strBuilder.String())
strBuilder.Reset() strBuilder.Reset()
} }
slock.Unlock()
qlock.Unlock()
if len(infos) > 0 { if len(infos) > 0 {
lineWidth = lipgloss.Width(infos[0]) lineWidth = lipgloss.Width(infos[0])
} }
subLen := lineWidth - StaticWidth - 1 subLen := max(lineWidth-StaticWidth-1, 0)
if subLen <= 0 { if subLen != m.subLin {
subLen = 0 top := TopLine + strings.Repeat("═", subLen) + "╕"
middle := MiddleLine + strings.Repeat("─", subLen) + "┤"
bottom := BottomLine + strings.Repeat("═", subLen) + "╡"
m.topL = LowLeightStyle.Render(top) + "\n"
m.middleL = LowLeightStyle.Render(middle) + "\n"
m.bottomL = LowLeightStyle.Render(bottom) + "\n"
m.top = top + "\n"
m.middle = middle + "\n"
m.bottom = bottom + "\n"
}
if m.darkModule {
return m.topL + strings.Join(infos, m.middleL) + m.bottomL
} else {
return m.top + strings.Join(infos, m.middle) + m.bottom
} }
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
} }
...@@ -10,28 +10,50 @@ import ( ...@@ -10,28 +10,50 @@ import (
) )
type ModelHeader struct { type ModelHeader struct {
t time.Time
DCUTopVersion string DCUTopVersion string
SMIVersion string SMIVersion string
DriverVersion string DriverVersion string
darkMode bool
strBuilder strings.Builder
style lipgloss.Style
static string
updateStatic bool
} }
func NewModelHeader() *ModelHeader { func NewModelHeader() *ModelHeader {
return &ModelHeader{} return &ModelHeader{
darkMode: false,
style: lipgloss.NewStyle().Padding(0, 1),
updateStatic: true,
}
} }
func (mh *ModelHeader) Init() tea.Cmd { func (mh *ModelHeader) Init() tea.Cmd {
mh.t = time.Now()
return nil return nil
} }
func (mh *ModelHeader) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) { func (mh *ModelHeader) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := inputMsg.(type) { switch msg := inputMsg.(type) {
case *ModelMsg: case *ModelMsg:
mh.t = msg.t if mh.DCUTopVersion != msg.MyVersion || mh.DriverVersion != msg.Version.DriverVersion || mh.SMIVersion != msg.Version.SMIVersion {
mh.DCUTopVersion = msg.MyVersion mh.DCUTopVersion = msg.MyVersion
mh.DriverVersion = msg.Version.DriverVersion mh.DriverVersion = msg.Version.DriverVersion
mh.SMIVersion = msg.Version.SMIVersion mh.SMIVersion = msg.Version.SMIVersion
hyv := FormatStr(fmt.Sprintf(" hytop: %s", mh.DCUTopVersion), 18, lipgloss.Left)
drv := FormatStr(fmt.Sprintf("Driver Version: %s", mh.DriverVersion), 35, lipgloss.Left)
smiv := FormatStr(fmt.Sprintf("SMI Version: %s", mh.SMIVersion), 24, lipgloss.Left)
mh.static = HeaderBorderStyle.Render(hyv+drv+smiv) + Title
mh.updateStatic = true
} else {
mh.updateStatic = false
}
return mh, nil
case *ActionMsg:
if msg.Action != nil {
mh.darkMode = true
} else {
mh.darkMode = false
}
return mh, nil return mh, nil
} }
return mh, nil return mh, nil
...@@ -41,22 +63,38 @@ const ( ...@@ -41,22 +63,38 @@ const (
Title = ` Title = `
├───────────────────────────────┬──────────────────────┬──────────────────────┤ ├───────────────────────────────┬──────────────────────┬──────────────────────┤
│ DCU Name Performance Level │ Bus-Id DisP.A │ MIG M. ECC │ │ DCU Name Performance Level │ Bus-Id DisP.A │ MIG M. ECC │
│ Fan Temp Pwr:Usage/Cap │ Memory-Usage │ DCU-Util PowerMode │ │ Fan Temp Pwr:Usage/Cap │ Memory-Usage │ DCU-Util PowerMode │`
`
) )
var ( var (
KeyStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#842cffff")).Italic(true) KeyStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#842cffff")).Italic(true)
KeyLowLeightStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#6a32b992")).Italic(true)
KeyH, KeyQ = KeyStyle.SetString("h").String(), KeyStyle.SetString("q").String() KeyH, KeyQ = KeyStyle.SetString("h").String(), KeyStyle.SetString("q").String()
KeyHL, KeyQL = KeyLowLeightStyle.SetString("h").String(), KeyLowLeightStyle.SetString("q").String()
Space = strings.Repeat(" ", 28) Space = strings.Repeat(" ", 28)
HeaderBorderStyle = lipgloss.NewStyle().Border(myBorder, true, true, false, true) HeaderBorderStyle = lipgloss.NewStyle().Border(myBorder, true, true, false, true)
) )
func (mh *ModelHeader) View() string { func (mh *ModelHeader) View() string {
header := fmt.Sprintf("%s%s(Press %s for help or %s for quit)\n", mh.t.Format("2006-01-02 15:04:05"), Space, KeyH, KeyQ) mh.strBuilder.Reset()
style := lipgloss.NewStyle().Padding(0, 1) if mh.darkMode {
hyv := style.Width(18).Render(fmt.Sprintf("hytop: %s", mh.DCUTopVersion)) mh.strBuilder.WriteString(time.Now().Format("2006-01-02 15:04:05"))
drv := style.Width(35).Render(fmt.Sprintf("Driver Version: %s", mh.DriverVersion)) mh.strBuilder.WriteString(Space)
smiv := style.Width(24).Render(fmt.Sprintf("SMI Version: %s", mh.SMIVersion)) mh.strBuilder.WriteString("(Press ")
return header + HeaderBorderStyle.Render(hyv+drv+smiv) + Title header := LowLeightStyle.Render(mh.strBuilder.String())
mh.strBuilder.Reset()
mh.strBuilder.WriteString(header)
mh.strBuilder.WriteString(KeyHL)
mh.strBuilder.WriteString(LowLeightStyle.Render(" for help or "))
mh.strBuilder.WriteString(KeyQL)
mh.strBuilder.WriteString(LowLeightStyle.Render(" for quit)"))
mh.strBuilder.WriteByte('\n')
header = mh.strBuilder.String()
return header + LowLeightStyle.SetString(mh.static).String() + "\n"
} else {
mh.strBuilder.WriteString(fmt.Sprintf("%s%s(Press %s for help or %s for quit)\n", time.Now().Format("2006-01-02 15:04:05"), Space, KeyH, KeyQ))
mh.strBuilder.WriteString(mh.static)
mh.strBuilder.WriteByte('\n')
return mh.strBuilder.String()
}
} }
...@@ -4,7 +4,6 @@ import ( ...@@ -4,7 +4,6 @@ import (
"get-container/cmd/hytop/backend" "get-container/cmd/hytop/backend"
"get-container/gpu" "get-container/gpu"
"get-container/utils" "get-container/utils"
"syscall"
"time" "time"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
...@@ -35,16 +34,24 @@ type ModelMsg struct { ...@@ -35,16 +34,24 @@ type ModelMsg struct {
} }
type TickMsg time.Time type TickMsg time.Time
type ProcessAction int
const (
PAKill ProcessAction = 9
PAInt ProcessAction = 2
PATerm ProcessAction = 15
PANone ProcessAction = 0
)
// ActionMsg 动作消息 // ActionMsg 动作消息
type ActionMsg struct { type ActionMsg struct {
SelectPids map[int32]bool // 选择的pid进程 SelectPids map[int32]bool // 选择的pid进程
Action *syscall.Signal // 对选择的pid的动作 Action *ProcessAction // 对选择的pid的动作
PidView *int32 // 进程视图指标的pid号,为null表示没有进入进程指标视图 PidView *int32 // 进程视图指标的pid号,为null表示没有进入进程指标视图
PointPid *int32 // 指针指向的pid,为null表示没有选择进程 PointPid *int32 // 指针指向的pid,为null表示没有选择进程
PidEnvView *int32 // 进程环境变量视图的pid号,为null表示不进入进程环境变量视图 PidEnvView *int32 // 进程环境变量视图的pid号,为null表示不进入进程环境变量视图
PidTreeView *int32 // 进入pstree视图的pid号,为null表示不进入pstree视图 PidTreeView *int32 // 进入pstree视图的pid号,为null表示不进入pstree视图
TargetDCUIndex *int // PointPid使用的dcu index TargetDCUIndex *int // PointPid使用的dcu index
} }
// ModelMain tui主模型类 // ModelMain tui主模型类
...@@ -124,7 +131,8 @@ func (m *ModelMain) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) { ...@@ -124,7 +131,8 @@ func (m *ModelMain) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
case "h": case "h":
return m, tea.Quit return m, tea.Quit
case "k": case "k":
return m, tea.Quit cmd := m.handleKeyK()
return m, cmd
case "esc": case "esc":
cmd := m.handleKeyEsc() cmd := m.handleKeyEsc()
return m, cmd return m, cmd
...@@ -168,7 +176,57 @@ func (m *ModelMain) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) { ...@@ -168,7 +176,57 @@ func (m *ModelMain) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil return m, nil
} }
func (m *ModelMain) handleKeyK() tea.Cmd {
if m.actionMsg == nil {
return nil
}
if m.actionMsg.PointPid == nil && m.actionMsg.SelectPids == nil {
return nil
}
var pa ProcessAction = PANone
if m.actionMsg.Action == nil {
m.actionMsg.Action = &pa
}
header, _ := m.Header.Update(m.actionMsg)
dcuInfo, _ := m.DCUInfo.Update(m.actionMsg)
sysLoad, _ := m.SysLoad.Update(m.actionMsg)
pidinfo, _ := m.ProcessInfo.Update(m.actionMsg)
m.Header = header.(*ModelHeader)
m.DCUInfo = dcuInfo.(*ModelDCUInfo)
m.SysLoad = sysLoad.(*ModelSysLoad)
m.ProcessInfo = pidinfo.(*ModelProcessInfo)
return nil
}
func (m *ModelMain) handleKeyEsc() tea.Cmd { func (m *ModelMain) handleKeyEsc() tea.Cmd {
if m.actionMsg == nil {
return nil
}
if m.actionMsg.Action != nil {
m.actionMsg.Action = nil
header, _ := m.Header.Update(m.actionMsg)
dcuInfo, _ := m.DCUInfo.Update(m.actionMsg)
sysLoad, _ := m.SysLoad.Update(m.actionMsg)
pidinfo, _ := m.ProcessInfo.Update(m.actionMsg)
m.Header = header.(*ModelHeader)
m.DCUInfo = dcuInfo.(*ModelDCUInfo)
m.SysLoad = sysLoad.(*ModelSysLoad)
m.ProcessInfo = pidinfo.(*ModelProcessInfo)
return nil
}
if m.actionMsg.PointPid != nil || m.actionMsg.SelectPids != nil || m.actionMsg.TargetDCUIndex != nil {
m.actionMsg.PointPid = nil
m.actionMsg.SelectPids = nil
m.actionMsg.TargetDCUIndex = nil
header, _ := m.Header.Update(m.actionMsg)
dcuInfo, _ := m.DCUInfo.Update(m.actionMsg)
sysLoad, _ := m.SysLoad.Update(m.actionMsg)
pidinfo, _ := m.ProcessInfo.Update(m.actionMsg)
m.Header = header.(*ModelHeader)
m.DCUInfo = dcuInfo.(*ModelDCUInfo)
m.SysLoad = sysLoad.(*ModelSysLoad)
m.ProcessInfo = pidinfo.(*ModelProcessInfo)
}
return nil return nil
} }
...@@ -256,6 +314,9 @@ func (m *ModelMain) handleKeyUp() tea.Cmd { ...@@ -256,6 +314,9 @@ func (m *ModelMain) handleKeyUp() tea.Cmd {
if m.modelMsg.DCUPidInfo == nil { if m.modelMsg.DCUPidInfo == nil {
return nil return nil
} }
if m.actionMsg.Action != nil {
return nil
}
processes := make([]backend.DCUProcessInfo, 0) processes := make([]backend.DCUProcessInfo, 0)
for index := range 64 { for index := range 64 {
p, have := m.modelMsg.DCUPidInfo[index] p, have := m.modelMsg.DCUPidInfo[index]
...@@ -289,11 +350,14 @@ func (m *ModelMain) handleKeyUp() tea.Cmd { ...@@ -289,11 +350,14 @@ func (m *ModelMain) handleKeyUp() tea.Cmd {
m.actionMsg.PointPid = &pid m.actionMsg.PointPid = &pid
idx := processes[index].DCU idx := processes[index].DCU
m.actionMsg.TargetDCUIndex = &idx m.actionMsg.TargetDCUIndex = &idx
m1, cmd1 := m.ProcessInfo.Update(m.actionMsg) m1, cmd1 := m.ProcessInfo.Update(m.actionMsg)
m2, cmd2 := m.DCUInfo.Update(m.actionMsg) m2, cmd2 := m.DCUInfo.Update(m.actionMsg)
m3, cmd3 := m.SysLoad.Update(m.actionMsg)
m.ProcessInfo = m1.(*ModelProcessInfo) m.ProcessInfo = m1.(*ModelProcessInfo)
m.DCUInfo = m2.(*ModelDCUInfo) m.DCUInfo = m2.(*ModelDCUInfo)
return tea.Batch(cmd1, cmd2) m.SysLoad = m3.(*ModelSysLoad)
return tea.Batch(cmd1, cmd2, cmd3)
} }
func (m *ModelMain) handleKeyDown() tea.Cmd { func (m *ModelMain) handleKeyDown() tea.Cmd {
...@@ -303,6 +367,9 @@ func (m *ModelMain) handleKeyDown() tea.Cmd { ...@@ -303,6 +367,9 @@ func (m *ModelMain) handleKeyDown() tea.Cmd {
if m.modelMsg.DCUPidInfo == nil { if m.modelMsg.DCUPidInfo == nil {
return nil return nil
} }
if m.actionMsg.Action != nil {
return nil
}
processes := make([]backend.DCUProcessInfo, 0) processes := make([]backend.DCUProcessInfo, 0)
for index := range 64 { for index := range 64 {
p, have := m.modelMsg.DCUPidInfo[index] p, have := m.modelMsg.DCUPidInfo[index]
...@@ -341,7 +408,9 @@ func (m *ModelMain) handleKeyDown() tea.Cmd { ...@@ -341,7 +408,9 @@ func (m *ModelMain) handleKeyDown() tea.Cmd {
m.actionMsg.TargetDCUIndex = &idx m.actionMsg.TargetDCUIndex = &idx
m1, cmd1 := m.ProcessInfo.Update(m.actionMsg) m1, cmd1 := m.ProcessInfo.Update(m.actionMsg)
m2, cmd2 := m.DCUInfo.Update(m.actionMsg) m2, cmd2 := m.DCUInfo.Update(m.actionMsg)
m3, cmd3 := m.SysLoad.Update(m.actionMsg)
m.ProcessInfo = m1.(*ModelProcessInfo) m.ProcessInfo = m1.(*ModelProcessInfo)
m.DCUInfo = m2.(*ModelDCUInfo) m.DCUInfo = m2.(*ModelDCUInfo)
return tea.Batch(cmd1, cmd2) m.SysLoad = m3.(*ModelSysLoad)
return tea.Batch(cmd1, cmd2, cmd3)
} }
...@@ -11,7 +11,7 @@ import ( ...@@ -11,7 +11,7 @@ import (
) )
type ModelProcessInfo struct { type ModelProcessInfo struct {
Title string Title [2]string
DoubleMiddleLine string DoubleMiddleLine string
MiddleLine string MiddleLine string
BottomLine string BottomLine string
...@@ -52,7 +52,22 @@ func (m *ModelProcessInfo) Init() tea.Cmd { ...@@ -52,7 +52,22 @@ func (m *ModelProcessInfo) Init() tea.Cmd {
sb.WriteString(strings.Repeat(" ", m.width-2-lipgloss.Width(ModelProcessInfoTitle))) sb.WriteString(strings.Repeat(" ", m.width-2-lipgloss.Width(ModelProcessInfoTitle)))
sb.WriteString(myBorder.Right) sb.WriteString(myBorder.Right)
sb.WriteByte('\n') sb.WriteByte('\n')
m.Title = sb.String() m.Title[0] = sb.String()
sb.Reset()
sb.WriteString(myBorder.Left)
sb.WriteString(Processes)
uah = fmt.Sprintf("%s@%s ", backend.User, backend.HostName)
space = strings.Repeat(" ", m.width-lipgloss.Width(Processes)-lipgloss.Width(uah)-2)
sb.WriteString(space)
sb.WriteString(uah)
sb.WriteString(myBorder.Right)
sb.WriteByte('\n')
sb.WriteString(myBorder.Left)
sb.WriteString(ModelProcessInfoTitle)
sb.WriteString(strings.Repeat(" ", m.width-2-lipgloss.Width(ModelProcessInfoTitle)))
sb.WriteString(myBorder.Right)
m.Title[1] = LowLeightStyle.Render(sb.String()) + "\n"
m.DoubleMiddleLine = "╞" + strings.Repeat("═", m.width-2) + "╡\n" m.DoubleMiddleLine = "╞" + strings.Repeat("═", m.width-2) + "╡\n"
m.MiddleLine = myBorder.MiddleLeft + strings.Repeat("─", m.width-2) + myBorder.MiddleRight + "\n" m.MiddleLine = myBorder.MiddleLeft + strings.Repeat("─", m.width-2) + myBorder.MiddleRight + "\n"
m.BottomLine = myBorder.BottomLeft + strings.Repeat("═", m.width-2) + myBorder.BottomRight m.BottomLine = myBorder.BottomLeft + strings.Repeat("═", m.width-2) + myBorder.BottomRight
...@@ -61,6 +76,10 @@ func (m *ModelProcessInfo) Init() tea.Cmd { ...@@ -61,6 +76,10 @@ func (m *ModelProcessInfo) Init() tea.Cmd {
} }
func (m *ModelProcessInfo) View() string { func (m *ModelProcessInfo) View() string {
darkMode := false
if m.actionMsg != nil && m.actionMsg.Action != nil {
darkMode = true
}
haveProcess := false haveProcess := false
var heightLightPid int32 = 0 var heightLightPid int32 = 0
if m.actionMsg != nil && m.actionMsg.PointPid != nil { if m.actionMsg != nil && m.actionMsg.PointPid != nil {
...@@ -117,7 +136,10 @@ func (m *ModelProcessInfo) View() string { ...@@ -117,7 +136,10 @@ func (m *ModelProcessInfo) View() string {
selected = true selected = true
} }
} }
if heightLightPid == process.Info.Pid { if darkMode {
selected = false
}
if heightLightPid == process.Info.Pid && !darkMode {
if selected { if selected {
sb.WriteString(HeightSelectedStyle.Render(sbInner.String())) sb.WriteString(HeightSelectedStyle.Render(sbInner.String()))
} else { } else {
...@@ -130,7 +152,6 @@ func (m *ModelProcessInfo) View() string { ...@@ -130,7 +152,6 @@ func (m *ModelProcessInfo) View() string {
sb.WriteString(sbInner.String()) sb.WriteString(sbInner.String())
} }
} }
sbInner.Reset() sbInner.Reset()
sb.WriteString(myBorder.Right) sb.WriteString(myBorder.Right)
sb.WriteByte('\n') sb.WriteByte('\n')
...@@ -140,17 +161,31 @@ func (m *ModelProcessInfo) View() string { ...@@ -140,17 +161,31 @@ func (m *ModelProcessInfo) View() string {
lines = append(lines, m.MiddleLine) lines = append(lines, m.MiddleLine)
} }
sb.WriteString(m.Title) if darkMode {
sb.WriteString(m.DoubleMiddleLine) sb.WriteString(m.Title[1])
if !haveProcess { sb.WriteString(m.DoubleMiddleLine)
sb.WriteString(m.NoProceseLine) if !haveProcess {
sb.WriteString(m.NoProceseLine)
} else {
for _, v := range lines[:len(lines)-1] {
sb.WriteString(v)
}
}
sb.WriteString(m.BottomLine)
return LowLeightStyle.Render(sb.String())
} else { } else {
for _, v := range lines[:len(lines)-1] { sb.WriteString(m.Title[0])
sb.WriteString(v) sb.WriteString(m.DoubleMiddleLine)
if !haveProcess {
sb.WriteString(m.NoProceseLine)
} else {
for _, v := range lines[:len(lines)-1] {
sb.WriteString(v)
}
} }
sb.WriteString(m.BottomLine)
return sb.String()
} }
sb.WriteString(m.BottomLine)
return sb.String()
} }
func (m *ModelProcessInfo) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) { func (m *ModelProcessInfo) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
......
...@@ -7,11 +7,12 @@ import ( ...@@ -7,11 +7,12 @@ import (
"image/color" "image/color"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/emirpasic/gods/v2/maps/linkedhashmap" "github.com/emirpasic/gods/v2/maps/treemap"
"github.com/emirpasic/gods/v2/trees/binaryheap" "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"
...@@ -23,22 +24,33 @@ const ( ...@@ -23,22 +24,33 @@ const (
SysLoadCap = 600 // 记录 SysLoadCap = 600 // 记录
) )
var (
SysLoadBorder = LowLeightStyle.Render(`│
│`)
)
// ModelSysLoad 系统负载组件 // ModelSysLoad 系统负载组件
type ModelSysLoad struct { type ModelSysLoad struct {
SysMem *MyTimeChart SysMem *MyTimeChart
SysCPU *MyTimeChart SysCPU *MyTimeChart
DCU *MyTimeChart DCU *MyTimeChart
DCUMem *MyTimeChart DCUMem *MyTimeChart
SysInfo *linkedhashmap.Map[time.Time, SysLoadInfo] sysInfoLock sync.Mutex // sysinfo和keys的保护锁,防止并发写
Keys *binaryheap.Heap[time.Time] sysInfo *treemap.Map[time.Time, SysLoadInfo]
current *SysLoadInfo keys *binaryheap.Heap[time.Time]
topLine string current *SysLoadInfo
bottomLine string topLine, topL string
style lipgloss.Style bottomLine, bottomL string
width int // 组件总宽度 style lipgloss.Style
colors []color.Color // 时序图的颜色表 width int // 组件总宽度
actionMsg *ActionMsg // 动作消息 colors []color.Color // 时序图的颜色表
modelMsg *ModelMsg // 模型周期性消息 colorsLow []color.Color // 低亮度模式的颜色表
actionMsg *ActionMsg // 动作消息
modelMsg *ModelMsg // 模型周期性消息
DCUToShow atomic.Int32 // 要显示的DCU信息的索引,如果为-1,表示显示dcu的平均数据
} }
type SysLoadInfo struct { type SysLoadInfo struct {
...@@ -57,8 +69,16 @@ type SysLoadInfo struct { ...@@ -57,8 +69,16 @@ 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 = linkedhashmap.New[time.Time, SysLoadInfo]() result.sysInfo = treemap.NewWith[time.Time, SysLoadInfo](func(x, y time.Time) int {
result.Keys = binaryheap.NewWith(func(a, b time.Time) int { if x.Before(y) {
return -1
}
if x.After(y) {
return 1
}
return 0
})
result.keys = binaryheap.NewWith(func(a, b time.Time) int {
if a.After(b) { if a.After(b) {
return 1 return 1
} }
...@@ -68,6 +88,8 @@ func NewModelSysLoad(width int) *ModelSysLoad { ...@@ -68,6 +88,8 @@ func NewModelSysLoad(width int) *ModelSysLoad {
return 0 return 0
}) })
result.colors = gamut.Blends(lipgloss.Color("#ff0000"), lipgloss.Color("#00ff00ff"), SysLoadHeight) 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.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)
...@@ -75,8 +97,12 @@ func NewModelSysLoad(width int) *ModelSysLoad { ...@@ -75,8 +97,12 @@ func NewModelSysLoad(width int) *ModelSysLoad {
result.DCUMem = NewTimeChart(subLine, SysLoadHeight, 0, 100, result.colors) result.DCUMem = NewTimeChart(subLine, SysLoadHeight, 0, 100, result.colors)
result.topLine = myBorder.MiddleLeft + genXAxis(SysLoadWidth) + myBorder.Middle + genXAxis(subLine) + myBorder.MiddleRight 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.bottomLine = "╞" + strings.Repeat(myBorder.Bottom, SysLoadWidth) + "╧" + strings.Repeat(myBorder.Bottom, subLine) + "╡"
result.style = lipgloss.NewStyle()
result.topL = LowLeightStyle.Render("├" + genXAxisNoStyle(SysLoadWidth) + "┼" + genXAxisNoStyle(subLine) + "┤")
result.bottomL = LowLeightStyle.Render(result.bottomLine)
result.style = lipgloss.NewStyle()
result.DCUToShow.Store(-1)
return &result return &result
} }
...@@ -152,38 +178,122 @@ func (m *ModelSysLoad) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) { ...@@ -152,38 +178,122 @@ func (m *ModelSysLoad) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil return m, nil
case *ActionMsg: case *ActionMsg:
m.actionMsg = msg m.actionMsg = msg
m.handleAction()
return m, nil return m, nil
} }
return m, nil return m, nil
} }
// handleAction 处理ActionMsg
func (m *ModelSysLoad) handleAction() {
if m.actionMsg == nil || m.actionMsg.TargetDCUIndex == nil {
return
}
targetIndex := int32(*m.actionMsg.TargetDCUIndex)
oldIndex := m.DCUToShow.Swap(targetIndex)
if oldIndex == targetIndex {
// 没有变
return
}
wg := sync.WaitGroup{}
wg.Add(1)
index := int(targetIndex)
i := m.sysInfo.Iterator()
mem := make([]tchart.TimePoint, 0, m.sysInfo.Size())
usage := make([]tchart.TimePoint, 0, m.sysInfo.Size())
for {
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() {
defer wg.Done()
m3, _ := m.DCU.Update(NewTimeCharMsg(usage, true))
m.DCU = m3.(*MyTimeChart)
}()
m4, _ := m.DCUMem.Update(NewTimeCharMsg(mem, true))
m.DCUMem = m4.(*MyTimeChart)
wg.Wait()
}
func (m *ModelSysLoad) View() string { func (m *ModelSysLoad) View() string {
darkMode := false
if m.actionMsg != nil && m.actionMsg.Action != nil {
darkMode = true
}
memUsed := utils.MemorySize{Num: m.current.MemUsed, Unit: utils.Byte} 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) 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) mem := fmt.Sprintf(" MEM: %s (%.1f%%)", memUsed.HumanReadStr(1), m.current.MemUsedPercent)
dcuMem := fmt.Sprintf(" AVG DCU MEM: %.1f%%", m.current.DCUMemUsageAvg) if darkMode {
dcu := fmt.Sprintf(" AVG DCU UTL: %.1f%%", m.current.DCUUsageAvg) load = LowLeightStyle.Render(load)
mem = LowLeightStyle.Render(mem)
load = StackPosition(load, m.SysCPU.View(), lipgloss.Top, lipgloss.Left) }
mem = StackPosition(mem, m.SysMem.View(), lipgloss.Bottom, lipgloss.Left) var dcuMem, dcu string
s := m.DCUMem.View() if m.actionMsg == nil || m.actionMsg.TargetDCUIndex == nil {
s = lipgloss.NewStyle().Foreground(lipgloss.Color("rgba(158, 143, 28, 0.86)")).Render(s) dcuMem = fmt.Sprintf(" DCU AVG MEM: %.1f%%", m.current.DCUMemUsageAvg)
dcuMem = StackPosition(dcuMem, s, lipgloss.Top, lipgloss.Left) dcu = fmt.Sprintf(" DCU AVG UTL: %.1f%%", m.current.DCUUsageAvg)
dcu = StackPosition(dcu, m.DCU.View(), lipgloss.Bottom, lipgloss.Left) } else {
index := int(*m.actionMsg.TargetDCUIndex)
load = m.style.Border(myBorder, false, true, false).Render(load) if index == -1 {
mem = m.style.Border(myBorder, false, true, false).Render(mem) dcuMem = fmt.Sprintf(" DCU AVG MEM: %.1f%%", m.current.DCUMemUsageAvg)
dcuMem = m.style.Border(myBorder, false, true, false, false).Render(dcuMem) dcu = fmt.Sprintf(" DCU AVG UTL: %.1f%%", m.current.DCUUsageAvg)
dcu = m.style.Border(myBorder, false, true, false, false).Render(dcu) } else {
dcuMem = fmt.Sprintf(" DCU %2d MEM: %.1f%%", index, m.current.DCUMemUsage[index])
dcu = fmt.Sprintf(" DCU %2d UTL: %.1f%%", index, m.current.DCUUsage[index])
}
}
if darkMode {
dcuMem = LowLeightStyle.Render(dcuMem)
dcu = LowLeightStyle.Render(dcu)
}
if darkMode {
load = StackPosition(load, m.SysCPU.ViewWithColor(m.colorsLow), lipgloss.Top, lipgloss.Left)
mem = StackPosition(mem, m.SysMem.ViewWithColor(m.colorsLow), lipgloss.Bottom, lipgloss.Left)
dcuMem = StackPosition(dcuMem, m.DCUMem.ViewWithColor(m.colorsLow), lipgloss.Top, lipgloss.Left)
dcu = StackPosition(dcu, m.DCU.ViewWithColor(m.colorsLow), lipgloss.Bottom, lipgloss.Left)
load = lipgloss.JoinHorizontal(lipgloss.Top, SysLoadBorder, load, SysLoadBorder)
mem = lipgloss.JoinHorizontal(lipgloss.Top, SysLoadBorder, mem, SysLoadBorder)
dcuMem = lipgloss.JoinHorizontal(lipgloss.Top, dcuMem, SysLoadBorder)
dcu = lipgloss.JoinHorizontal(lipgloss.Top, dcu, SysLoadBorder)
} else {
load = StackPosition(load, m.SysCPU.View(), lipgloss.Top, lipgloss.Left)
mem = StackPosition(mem, m.SysMem.View(), lipgloss.Bottom, lipgloss.Left)
dcuMem = StackPosition(dcuMem, m.DCUMem.View(), 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) up := lipgloss.JoinHorizontal(lipgloss.Top, load, dcuMem)
down := lipgloss.JoinHorizontal(lipgloss.Top, mem, dcu) down := lipgloss.JoinHorizontal(lipgloss.Top, mem, dcu)
if darkMode {
return lipgloss.JoinVertical(lipgloss.Left, up, m.topL, down, m.bottomL) + "\n"
}
return lipgloss.JoinVertical(lipgloss.Left, up, m.topLine, down, m.bottomLine) + "\n" return lipgloss.JoinVertical(lipgloss.Left, up, m.topLine, down, m.bottomLine) + "\n"
} }
// updateInfo // updateInfo 向ModelSysLoad中添加数据
func (m *ModelSysLoad) updateInfo(t *ModelMsg) { func (m *ModelSysLoad) updateInfo(t *ModelMsg) {
sysInfo, _ := utils.GetSysInfo() sysInfo, _ := utils.GetSysInfo()
s := SysLoadInfo{} s := SysLoadInfo{}
...@@ -213,37 +323,111 @@ func (m *ModelSysLoad) updateInfo(t *ModelMsg) { ...@@ -213,37 +323,111 @@ func (m *ModelSysLoad) updateInfo(t *ModelMsg) {
lock.Unlock() lock.Unlock()
s.DCUMemUsageAvg /= float32(l) s.DCUMemUsageAvg /= float32(l)
s.DCUUsageAvg /= float32(l) s.DCUUsageAvg /= float32(l)
s.DCUMemUsage[-1] = s.DCUMemUsageAvg
s.DCUUsage[-1] = s.DCUUsageAvg
m.current = &s m.current = &s
needUpdate, index := false, -1
if m.actionMsg != nil && m.actionMsg.TargetDCUIndex != nil {
index = *m.actionMsg.TargetDCUIndex
// 需要进入特定DCU的图表
oldIndex := m.DCUToShow.Swap(int32(index))
if oldIndex != int32(*m.actionMsg.TargetDCUIndex) {
// 需要更新
needUpdate = true
}
} else {
oldIndex := m.DCUToShow.Swap(-1)
if oldIndex != -1 {
// 需要更新
needUpdate = true
}
}
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(4) wg.Add(4)
go func() { go func() {
defer wg.Done() defer wg.Done()
m1, _ := m.SysMem.Update(MyTimeChartMsg{[]tchart.TimePoint{{Time: t.t, Value: s.MemUsedPercent}}}) m1, _ := m.SysMem.Update(NewTimeCharMsg([]tchart.TimePoint{{Time: t.t, Value: s.MemUsedPercent}}, false))
m.SysMem = m1.(*MyTimeChart) m.SysMem = m1.(*MyTimeChart)
}() }()
go func() { go func() {
defer wg.Done() defer wg.Done()
m2, _ := m.SysCPU.Update(MyTimeChartMsg{[]tchart.TimePoint{{Time: t.t, Value: s.CPUPercent}}}) m2, _ := m.SysCPU.Update(NewTimeCharMsg([]tchart.TimePoint{{Time: t.t, Value: s.CPUPercent}}, false))
m.SysCPU = m2.(*MyTimeChart) m.SysCPU = m2.(*MyTimeChart)
}() }()
go func() {
defer wg.Done()
m3, _ := m.DCU.Update(MyTimeChartMsg{[]tchart.TimePoint{{Time: t.t, Value: float64(s.DCUUsageAvg)}}})
m.DCU = m3.(*MyTimeChart)
}()
go func() {
defer wg.Done()
m4, _ := m.DCUMem.Update(MyTimeChartMsg{[]tchart.TimePoint{{Time: t.t, Value: float64(s.DCUMemUsageAvg)}}})
m.DCUMem = m4.(*MyTimeChart)
}()
// 存放数据 // 存放数据
m.SysInfo.Put(t.t, s) m.sysInfoLock.Lock()
m.Keys.Push(t.t) m.sysInfo.Put(t.t, s)
if m.Keys.Size() > SysLoadCap { m.keys.Push(t.t)
delKey, have := m.Keys.Pop() if m.keys.Size() > SysLoadCap {
delKey, have := m.keys.Pop()
if have { if have {
m.SysInfo.Remove(delKey) m.sysInfo.Remove(delKey)
}
}
m.sysInfoLock.Unlock()
if needUpdate {
// 准备全量数据
i := m.sysInfo.Iterator()
mem := make([]tchart.TimePoint, 0, m.sysInfo.Size())
usage := make([]tchart.TimePoint, 0, m.sysInfo.Size())
for {
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() {
defer wg.Done()
m3, _ := m.DCU.Update(NewTimeCharMsg(usage, true))
m.DCU = m3.(*MyTimeChart)
}()
go func() {
defer wg.Done()
m4, _ := m.DCUMem.Update(NewTimeCharMsg(mem, true))
m.DCUMem = m4.(*MyTimeChart)
}()
} else {
// 无需更新,仅追加数据
var valM, valU float32
vU, haveU := s.DCUUsage[index]
vM, haveM := s.DCUMemUsage[index]
if haveU {
valU = vU
} else {
valU = 0
}
if haveM {
valM = vM
} else {
valM = 0
} }
go func() {
defer wg.Done()
m3, _ := m.DCU.Update(NewTimeCharMsg([]tchart.TimePoint{{Time: t.t, Value: float64(valU)}}, false))
m.DCU = m3.(*MyTimeChart)
}()
go func() {
defer wg.Done()
m4, _ := m.DCUMem.Update(NewTimeCharMsg([]tchart.TimePoint{{Time: t.t, Value: float64(valM)}}, false))
m.DCUMem = m4.(*MyTimeChart)
}()
} }
wg.Wait() wg.Wait()
} }
......
...@@ -57,9 +57,46 @@ func genXAxis(l int) string { ...@@ -57,9 +57,46 @@ func genXAxis(l int) string {
return result return result
} }
func genXAxisNoStyle(l int) string {
t60 := l / 30
t30 := l >= 18
var result string
if t30 {
result = A + strings.Repeat("─", 14)
result = "30s" + result
} else {
return strings.Repeat("─", l)
}
// 长度不超过33
if l < 33 {
return strings.Repeat("─", l-18) + result
}
for i := 1; i <= t60+1; i++ {
timeStr := strconv.Itoa(i*60) + "s"
timeStrLen := len(timeStr)
targetLen := timeStrLen + i*30
if l < targetLen {
// 不渲染标记,仅增加轴长度
result = strings.Repeat("─", l-lipgloss.Width(result)) + result
break
}
// 渲染标记
result = timeStr + A + strings.Repeat("─", targetLen-lipgloss.Width(result)-timeStrLen-1) + result
}
return result
}
// MyTimeChartMsg 时间流表消息,用于插入数据 // MyTimeChartMsg 时间流表消息,用于插入数据
type MyTimeChartMsg struct { type MyTimeChartMsg struct {
Points []tchart.TimePoint Points []tchart.TimePoint // 待添加的数据点
Reset bool // 添加数据点前是否清除原有数据点
}
func NewTimeCharMsg(point []tchart.TimePoint, reset bool) MyTimeChartMsg {
return MyTimeChartMsg{
Points: point,
Reset: reset,
}
} }
// MyTimeChart 特化的时间流表,时间区域就是宽度的2倍,单位是秒 // MyTimeChart 特化的时间流表,时间区域就是宽度的2倍,单位是秒
...@@ -103,7 +140,9 @@ func NewTimeChart(width, height int, vmin, vmax float64, color []color.Color) *M ...@@ -103,7 +140,9 @@ func NewTimeChart(width, height int, vmin, vmax float64, color []color.Color) *M
return &result return &result
} }
// PutPoint 添加若干数据点
func (m *MyTimeChart) PutPoint(points []tchart.TimePoint) { func (m *MyTimeChart) PutPoint(points []tchart.TimePoint) {
m.lockPoints.Lock()
ops := append(m.points, points...) ops := append(m.points, points...)
// 排序 // 排序
sort.Slice(ops, func(i, j int) bool { sort.Slice(ops, func(i, j int) bool {
...@@ -117,7 +156,7 @@ func (m *MyTimeChart) PutPoint(points []tchart.TimePoint) { ...@@ -117,7 +156,7 @@ func (m *MyTimeChart) PutPoint(points []tchart.TimePoint) {
break break
} }
} }
nps := append(make([]tchart.TimePoint, 0), ops[targetIndex:]...) nps := append(make([]tchart.TimePoint, 0, targetIndex+1), ops[targetIndex:]...)
m.points = nps m.points = nps
s := tchart.New(m.width, m.height+1, s := tchart.New(m.width, m.height+1,
tchart.WithLineStyle(runes.ThinLineStyle), tchart.WithLineStyle(runes.ThinLineStyle),
...@@ -126,6 +165,62 @@ func (m *MyTimeChart) PutPoint(points []tchart.TimePoint) { ...@@ -126,6 +165,62 @@ func (m *MyTimeChart) PutPoint(points []tchart.TimePoint) {
tchart.WithXYSteps(0, 0), tchart.WithXYSteps(0, 0),
tchart.WithTimeSeries(m.points), tchart.WithTimeSeries(m.points),
) )
m.lockPoints.Unlock()
// 插入数据
m.chart = nil
m.chart = &s
m.chart.DrawXYAxisAndLabel()
m.chart.DrawBrailleAll()
}
// ResetPutPoint 清除原有的数据,然后插入数据
func (m *MyTimeChart) ResetPutPoint(points []tchart.TimePoint) {
// 清除原有的点
m.lockPoints.Lock()
m.points = nil
// 排序输入数据
sort.Slice(points, func(i, j int) bool {
return points[i].Time.Before(points[j].Time)
})
// 补充数据 或 剔除数据
now := time.Now()
threshold := now.Add(time.Duration(m.width*-2) * time.Second)
first := points[0].Time
if first.Before(threshold) {
// 需要剔除数据
index := 0
for k, v := range points {
if v.Time.Before(threshold) {
continue
} else {
index = k
break
}
}
m.points = append(make([]tchart.TimePoint, 0, len(points[index:])), points[index:]...)
} else if first.After(threshold) {
// 需要补充数据
m.points = make([]tchart.TimePoint, 0, len(points)+2*m.width+1)
max := m.width * 2
for i := 1; i < max; i++ {
ta := now.Add(time.Second * time.Duration(-i))
if !ta.Before(threshold) {
m.points = append(m.points, tchart.TimePoint{Time: ta, Value: m.min})
} else {
break
}
}
m.points = append(m.points, points...)
}
s := tchart.New(m.width, m.height+1,
tchart.WithLineStyle(runes.ThinLineStyle),
tchart.WithZoneManager(m.zM),
tchart.WithYRange(m.min, m.max),
tchart.WithXYSteps(0, 0),
tchart.WithTimeSeries(m.points),
)
m.lockPoints.Unlock()
// 插入数据 // 插入数据
m.chart = nil m.chart = nil
m.chart = &s m.chart = &s
...@@ -142,12 +237,112 @@ func (m *MyTimeChart) Init() tea.Cmd { ...@@ -142,12 +237,112 @@ func (m *MyTimeChart) Init() tea.Cmd {
func (m *MyTimeChart) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) { func (m *MyTimeChart) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := inputMsg.(type) { switch msg := inputMsg.(type) {
case MyTimeChartMsg: case MyTimeChartMsg:
m.PutPoint(msg.Points) if msg.Reset {
m.ResetPutPoint(msg.Points)
} else {
m.PutPoint(msg.Points)
}
return m, nil return m, nil
} }
return m, nil return m, nil
} }
func (m *MyTimeChart) ViewWithColor(color []color.Color) string {
str := m.zM.Scan(m.chart.View())
sb := strings.Builder{}
sb.WriteString(str[m.width+1:])
str = sb.String()
// return str
lines := strings.Split(strings.Trim(str, "\n"), "\n")
runes := make([][]rune, len(lines))
for k, v := range lines {
runes[k] = []rune(v)
}
width := lipgloss.Width(str)
height := lipgloss.Height(str)
for col := range width {
firstLine := -1
var charStat utils.CharType = utils.CharEmpty
for line := range height {
c := runes[line][col]
charType := utils.GetCharType(c)
if charType != utils.CharEmpty && firstLine == -1 {
firstLine = line
}
if firstLine == -1 {
continue
}
if firstLine == line {
// 遇到了一列的第一个非空字符
if t, have := utils.MM[c]; have {
runes[line][col] = t
}
charStat = utils.CharTypeOr(charStat, utils.GetCharType(runes[line][col]))
} else {
// 第一个非空字符下面的字符
switch charType {
case utils.CharEmpty:
switch charStat {
case utils.CharLeft:
runes[line][col] = utils.LeftFullMW
case utils.CharRight:
runes[line][col] = utils.RightFullMW
case utils.CharFull:
runes[line][col] = utils.FullMW
}
if t, have := utils.MM[runes[line][col]]; have {
runes[line][col] = t
}
case utils.CharLeft:
switch utils.CharTypeOr(charStat, utils.CharLeft) {
case utils.CharLeft:
runes[line][col] = utils.LeftFullMW
case utils.CharRight:
runes[line][col] = utils.RightFullMW
case utils.CharFull:
runes[line][col] = utils.FullMW
}
charStat = utils.CharTypeOr(charStat, utils.CharLeft)
if t, have := utils.MM[runes[line][col]]; have {
runes[line][col] = t
}
case utils.CharRight:
switch utils.CharTypeOr(charStat, utils.CharRight) {
case utils.CharLeft:
runes[line][col] = utils.LeftFullMW
case utils.CharRight:
runes[line][col] = utils.RightFullMW
case utils.CharFull:
runes[line][col] = utils.FullMW
}
charStat = utils.CharTypeOr(charStat, utils.CharRight)
if t, have := utils.MM[runes[line][col]]; have {
runes[line][col] = t
}
case utils.CharFull:
charStat = utils.CharFull
if t, have := utils.MM[runes[line][col]]; have {
runes[line][col] = t
}
}
}
}
}
resultLines := make([]string, height)
for k, v := range runes {
resultLines[k] = string(v)
}
if len(color) == height {
for k, v := range resultLines {
c, _ := colorful.MakeColor(color[k])
resultLines[k] = lipgloss.NewStyle().Foreground(lipgloss.Color(c.Hex())).Render(v)
}
}
return strings.Join(resultLines, "\n")
}
func (m *MyTimeChart) View() string { func (m *MyTimeChart) View() string {
str := m.zM.Scan(m.chart.View()) str := m.zM.Scan(m.chart.View())
sb := strings.Builder{} sb := strings.Builder{}
......
...@@ -5,6 +5,9 @@ import ( ...@@ -5,6 +5,9 @@ import (
"strings" "strings"
"sync/atomic" "sync/atomic"
"testing" "testing"
"time"
"github.com/emirpasic/gods/v2/maps/treemap"
) )
func TestRegexp(t *testing.T) { func TestRegexp(t *testing.T) {
...@@ -111,4 +114,24 @@ func TestChartype(t *testing.T) { ...@@ -111,4 +114,24 @@ func TestChartype(t *testing.T) {
t.Log(r) t.Log(r)
t.Log(CharTypeOr(GetCharType(' '), GetCharType(' '))) t.Log(CharTypeOr(GetCharType(' '), GetCharType(' ')))
t.Log(GetCharType('⢠')) t.Log(GetCharType('⢠'))
s := treemap.NewWith[time.Time, int](func(x, y time.Time) int {
if x.Before(y) {
return -1
}
if x.After(y) {
return 1
}
return 0
})
s.Put(time.Now(), 3)
s.Put(time.Now().Add(time.Second), 2)
s.Put(time.Now().Add(-2*time.Second), 9)
i := s.Iterator()
for {
if !i.Next() {
break
}
t.Logf("%v: %d", i.Key(), i.Value())
}
} }
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