Commit 72702c5d authored by liming6's avatar liming6
Browse files

feature hytop添加功耗监控功能

parent b7bee696
package main
import (
"log"
"os"
"os/exec"
)
/**
这个工具是对docker命令的一个包装,用于记录docker创建、删除容器的动作,并记录执行的用户
*/
// runWithoutAction 没有任何额外动作,仅执行命令
func runWithoutAction(args []string) error {
var cmd *exec.Cmd
if args == nil || len(args) == 0 {
cmd = exec.Command("docker")
}
cmd = exec.Command("docker", args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
func main() {
args := os.Args[1:]
if len(args) == 0 {
err := runWithoutAction(args)
if err != nil {
log.Fatal(err)
}
os.Exit(0)
}
switch args[0] {
case "run":
case "create":
case "rm":
default:
err := runWithoutAction(args)
if err != nil {
log.Fatal(err)
}
os.Exit(0)
}
}
/*
用户
时间
当前文件夹
*/
...@@ -16,6 +16,14 @@ const ( ...@@ -16,6 +16,14 @@ const (
DCUTopVersion = "1.0.1" DCUTopVersion = "1.0.1"
) )
var (
KeyUp = tea.KeyUp.String()
KeyDown = tea.KeyDown.String()
KeyLeft = tea.KeyLeft.String()
KeyRight = tea.KeyRight.String()
KeySpace = tea.KeySpace.String()
)
var ( var (
HeightLightStyle = lipgloss.NewStyle().Background(lipgloss.Color("#00fffb7a")) // 高亮风格 HeightLightStyle = lipgloss.NewStyle().Background(lipgloss.Color("#00fffb7a")) // 高亮风格
SelectedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#ffb81fe3")) // 被选中的风格 SelectedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#ffb81fe3")) // 被选中的风格
...@@ -65,6 +73,7 @@ const ( ...@@ -65,6 +73,7 @@ const (
VMTree ViewMode = 1 // 进程树视图 VMTree ViewMode = 1 // 进程树视图
VMOneProcess ViewMode = 2 // 进程详情视图 VMOneProcess ViewMode = 2 // 进程详情视图
VMProcessEnv ViewMode = 3 // 进程环境变量视图 VMProcessEnv ViewMode = 3 // 进程环境变量视图
VMPower ViewMode = 4 // 功耗视图
) )
func (pa ProcessAction) String() string { func (pa ProcessAction) String() string {
...@@ -120,6 +129,7 @@ type ModelMain struct { ...@@ -120,6 +129,7 @@ type ModelMain struct {
pstree *ModelPsTree // 进程树视图 pstree *ModelPsTree // 进程树视图
processDetail *ModelProcessDetail // 进程详情视图 processDetail *ModelProcessDetail // 进程详情视图
processEnv *ModelProcessEnv // 进程环境变量视图 processEnv *ModelProcessEnv // 进程环境变量视图
powerDetail *ModelPowerDetail // 功耗视图
} }
func NewModelMain(width, height int) ModelMain { func NewModelMain(width, height int) ModelMain {
...@@ -138,6 +148,7 @@ func NewModelMain(width, height int) ModelMain { ...@@ -138,6 +148,7 @@ func NewModelMain(width, height int) ModelMain {
VM: VMMain, VM: VMMain,
} }
result.processEnv = nil result.processEnv = nil
result.powerDetail = nil
return result return result
} }
...@@ -197,6 +208,9 @@ func (m *ModelMain) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) { ...@@ -197,6 +208,9 @@ func (m *ModelMain) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := inputMsg.(type) { switch msg := inputMsg.(type) {
case tea.KeyMsg: // 键盘事件 case tea.KeyMsg: // 键盘事件
switch msg.String() { switch msg.String() {
case "a":
cmd := m.handleKeyA(msg)
return m, cmd
case "q": case "q":
return m, tea.Quit return m, tea.Quit
case "ctrl+c": case "ctrl+c":
...@@ -214,7 +228,7 @@ func (m *ModelMain) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) { ...@@ -214,7 +228,7 @@ func (m *ModelMain) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
case "h": case "h":
m.actionMsg.HelpView = true m.actionMsg.HelpView = true
return m, nil return m, nil
case "left", "right": case KeyLeft, KeyRight:
cmd := m.handleKeyLR(msg.String()) cmd := m.handleKeyLR(msg.String())
return m, cmd return m, cmd
case "k": case "k":
...@@ -232,6 +246,9 @@ func (m *ModelMain) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) { ...@@ -232,6 +246,9 @@ func (m *ModelMain) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
case "e": case "e":
cmd := m.handleKeyE() cmd := m.handleKeyE()
return m, cmd return m, cmd
case "p":
cmd := m.handleKeyP(msg)
return m, cmd
} }
case TickMsg: // 定时事件 case TickMsg: // 定时事件
updateModelInfo(m.modelMsg, time.Time(msg)) updateModelInfo(m.modelMsg, time.Time(msg))
...@@ -249,6 +266,10 @@ func (m *ModelMain) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) { ...@@ -249,6 +266,10 @@ func (m *ModelMain) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
detail, _ := m.processDetail.Update(m.modelMsg) detail, _ := m.processDetail.Update(m.modelMsg)
m.processDetail = detail.(*ModelProcessDetail) m.processDetail = detail.(*ModelProcessDetail)
} }
if m.powerDetail != nil {
power, _ := m.powerDetail.Update(m.modelMsg)
m.powerDetail = power.(*ModelPowerDetail)
}
return m, tickCmd() return m, tickCmd()
case ModelMsg: // 初始化 case ModelMsg: // 初始化
header, _ := m.Header.Update(m.modelMsg) header, _ := m.Header.Update(m.modelMsg)
...@@ -275,6 +296,36 @@ func (m *ModelMain) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) { ...@@ -275,6 +296,36 @@ func (m *ModelMain) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil return m, nil
} }
func (m *ModelMain) handleKeyA(k tea.KeyMsg) tea.Cmd {
switch m.actionMsg.VM {
case VMPower:
power, cmd := m.powerDetail.Update(k)
m.powerDetail = power.(*ModelPowerDetail)
return cmd
default:
return nil
}
}
func (m *ModelMain) handleKeyP(k tea.KeyMsg) tea.Cmd {
switch m.actionMsg.VM {
case VMMain:
m.actionMsg.VMOld = VMMain
m.actionMsg.VM = VMPower
m.powerDetail = NewModelPower(m.height, m.width)
m.powerDetail.Init()
model, cmd := m.powerDetail.Update(m.modelMsg)
m.powerDetail = model.(*ModelPowerDetail)
return cmd
case VMPower:
power, cmd := m.powerDetail.Update(k)
m.powerDetail = power.(*ModelPowerDetail)
return cmd
default:
return nil
}
}
func (m *ModelMain) handleKeyE() tea.Cmd { func (m *ModelMain) handleKeyE() tea.Cmd {
// 检查光标是否在某个进程上,若有,且当前为Main或Tree视图,就进入进程环境变量模式 // 检查光标是否在某个进程上,若有,且当前为Main或Tree视图,就进入进程环境变量模式
if m.actionMsg == nil || m.actionMsg.PointPid == nil { if m.actionMsg == nil || m.actionMsg.PointPid == nil {
...@@ -381,11 +432,11 @@ func (m *ModelMain) handleKeyT() tea.Cmd { ...@@ -381,11 +432,11 @@ func (m *ModelMain) handleKeyT() tea.Cmd {
func (m *ModelMain) handleKeyLR(s string) tea.Cmd { func (m *ModelMain) handleKeyLR(s string) tea.Cmd {
if m.processEnv != nil { if m.processEnv != nil {
switch s { switch s {
case tea.KeyRight.String(): case KeyRight:
env, _ := m.processEnv.Update(tea.KeyRight) env, _ := m.processEnv.Update(tea.KeyRight)
m.processEnv = env.(*ModelProcessEnv) m.processEnv = env.(*ModelProcessEnv)
return nil return nil
case tea.KeyLeft.String(): case KeyLeft:
env, _ := m.processEnv.Update(tea.KeyLeft) env, _ := m.processEnv.Update(tea.KeyLeft)
m.processEnv = env.(*ModelProcessEnv) m.processEnv = env.(*ModelProcessEnv)
return nil return nil
...@@ -398,7 +449,7 @@ func (m *ModelMain) handleKeyLR(s string) tea.Cmd { ...@@ -398,7 +449,7 @@ func (m *ModelMain) handleKeyLR(s string) tea.Cmd {
} }
if m.dialog != nil { if m.dialog != nil {
switch s { switch s {
case tea.KeyRight.String(): case KeyRight:
switch *m.actionMsg.Action { switch *m.actionMsg.Action {
case PAKill: case PAKill:
*m.actionMsg.Action = PATerm *m.actionMsg.Action = PATerm
...@@ -409,7 +460,7 @@ func (m *ModelMain) handleKeyLR(s string) tea.Cmd { ...@@ -409,7 +460,7 @@ func (m *ModelMain) handleKeyLR(s string) tea.Cmd {
default: default:
return nil return nil
} }
case tea.KeyLeft.String(): case KeyLeft:
switch *m.actionMsg.Action { switch *m.actionMsg.Action {
case PATerm: case PATerm:
*m.actionMsg.Action = PAKill *m.actionMsg.Action = PAKill
...@@ -517,6 +568,10 @@ func (m *ModelMain) handleKeyEsc() tea.Cmd { ...@@ -517,6 +568,10 @@ func (m *ModelMain) handleKeyEsc() tea.Cmd {
m.actionMsg.VM = m.actionMsg.VMOld m.actionMsg.VM = m.actionMsg.VMOld
m.processEnv = nil m.processEnv = nil
return nil return nil
case VMPower:
m.actionMsg.VM = m.actionMsg.VMOld
m.powerDetail = nil
return nil
default: default:
return nil return nil
} }
...@@ -524,33 +579,43 @@ func (m *ModelMain) handleKeyEsc() tea.Cmd { ...@@ -524,33 +579,43 @@ func (m *ModelMain) handleKeyEsc() tea.Cmd {
// handleKeySpace 处理空格键动作,空格键仅用于选择或取消进程 // handleKeySpace 处理空格键动作,空格键仅用于选择或取消进程
func (m *ModelMain) handleKeySpace() tea.Cmd { func (m *ModelMain) handleKeySpace() tea.Cmd {
if len(m.modelMsg.DCUPidInfo) == 0 {
return nil
}
if m.actionMsg.PointPid == nil || m.actionMsg.Action != nil {
return nil
}
if m.actionMsg.VM != VMMain && m.actionMsg.VM != VMTree {
return nil
}
if m.actionMsg.SelectPids == nil {
m.actionMsg.SelectPids = make(map[int32]bool)
}
_, have := m.actionMsg.SelectPids[*m.actionMsg.PointPid]
if have {
delete(m.actionMsg.SelectPids, *m.actionMsg.PointPid)
} else {
m.actionMsg.SelectPids[*m.actionMsg.PointPid] = true
}
switch m.actionMsg.VM { switch m.actionMsg.VM {
case VMMain: case VMMain, VMTree:
m1, cmd1 := m.ProcessInfo.Update(m.actionMsg) if len(m.modelMsg.DCUPidInfo) == 0 {
m.ProcessInfo = m1.(*ModelProcessInfo) return nil
return cmd1 }
case VMTree: if m.actionMsg.PointPid == nil || m.actionMsg.Action != nil {
m1, cmd1 := m.pstree.Update(m.actionMsg) return nil
m.pstree = m1.(*ModelPsTree) }
return cmd1 if m.actionMsg.VM != VMMain && m.actionMsg.VM != VMTree {
return nil
}
if m.actionMsg.SelectPids == nil {
m.actionMsg.SelectPids = make(map[int32]bool)
}
_, have := m.actionMsg.SelectPids[*m.actionMsg.PointPid]
if have {
delete(m.actionMsg.SelectPids, *m.actionMsg.PointPid)
} else {
m.actionMsg.SelectPids[*m.actionMsg.PointPid] = true
}
switch m.actionMsg.VM {
case VMMain:
m1, cmd1 := m.ProcessInfo.Update(m.actionMsg)
m.ProcessInfo = m1.(*ModelProcessInfo)
return cmd1
case VMTree:
m1, cmd1 := m.pstree.Update(m.actionMsg)
m.pstree = m1.(*ModelPsTree)
return cmd1
default:
return nil
}
case VMPower:
power, _ := m.powerDetail.Update(tea.KeySpace)
m.powerDetail = power.(*ModelPowerDetail)
return nil
default: default:
return nil return nil
} }
...@@ -579,6 +644,8 @@ func (m *ModelMain) View() string { ...@@ -579,6 +644,8 @@ func (m *ModelMain) View() string {
return m.processDetail.View() return m.processDetail.View()
case VMProcessEnv: case VMProcessEnv:
return m.processEnv.View() return m.processEnv.View()
case VMPower:
return m.powerDetail.View()
default: default:
return "" return ""
} }
...@@ -624,14 +691,14 @@ func updateModelInfo(modelMsg *ModelMsg, t time.Time) error { ...@@ -624,14 +691,14 @@ func updateModelInfo(modelMsg *ModelMsg, t time.Time) error {
} }
func (m *ModelMain) handleKeyUp() tea.Cmd { func (m *ModelMain) handleKeyUp() tea.Cmd {
if len(m.modelMsg.DCUPidInfo) == 0 {
return nil
}
if m.actionMsg.Action != nil {
return nil
}
switch m.actionMsg.VM { switch m.actionMsg.VM {
case VMMain: case VMMain:
if len(m.modelMsg.DCUPidInfo) == 0 {
return nil
}
if m.actionMsg.Action != nil {
return nil
}
processes := make([]backend.DCUProcessInfo, 0, 16) processes := make([]backend.DCUProcessInfo, 0, 16)
keys := make([]int, 0, 16) keys := make([]int, 0, 16)
for k := range m.modelMsg.DCUPidInfo { for k := range m.modelMsg.DCUPidInfo {
...@@ -677,6 +744,12 @@ func (m *ModelMain) handleKeyUp() tea.Cmd { ...@@ -677,6 +744,12 @@ func (m *ModelMain) handleKeyUp() tea.Cmd {
m.SysLoad = m3.(*ModelSysLoad) m.SysLoad = m3.(*ModelSysLoad)
return tea.Batch(cmd1, cmd2, cmd3) return tea.Batch(cmd1, cmd2, cmd3)
case VMTree: case VMTree:
if len(m.modelMsg.DCUPidInfo) == 0 {
return nil
}
if m.actionMsg.Action != nil {
return nil
}
if len(m.actionMsg.PidTreePids) == 0 { if len(m.actionMsg.PidTreePids) == 0 {
return nil return nil
} }
...@@ -701,6 +774,10 @@ func (m *ModelMain) handleKeyUp() tea.Cmd { ...@@ -701,6 +774,10 @@ func (m *ModelMain) handleKeyUp() tea.Cmd {
env, _ := m.processEnv.Update(tea.KeyUp) env, _ := m.processEnv.Update(tea.KeyUp)
m.processEnv = env.(*ModelProcessEnv) m.processEnv = env.(*ModelProcessEnv)
return nil return nil
case VMPower:
power, _ := m.powerDetail.Update(tea.KeyUp)
m.powerDetail = power.(*ModelPowerDetail)
return nil
default: default:
return nil return nil
} }
...@@ -708,14 +785,14 @@ func (m *ModelMain) handleKeyUp() tea.Cmd { ...@@ -708,14 +785,14 @@ func (m *ModelMain) handleKeyUp() tea.Cmd {
} }
func (m *ModelMain) handleKeyDown() tea.Cmd { func (m *ModelMain) handleKeyDown() tea.Cmd {
if len(m.modelMsg.DCUPidInfo) == 0 {
return nil
}
if m.actionMsg.Action != nil {
return nil
}
switch m.actionMsg.VM { switch m.actionMsg.VM {
case VMMain: case VMMain:
if len(m.modelMsg.DCUPidInfo) == 0 {
return nil
}
if m.actionMsg.Action != nil {
return nil
}
processes := make([]backend.DCUProcessInfo, 0, 16) processes := make([]backend.DCUProcessInfo, 0, 16)
keys := make([]int, 0, 16) keys := make([]int, 0, 16)
for key := range m.modelMsg.DCUPidInfo { for key := range m.modelMsg.DCUPidInfo {
...@@ -764,6 +841,12 @@ func (m *ModelMain) handleKeyDown() tea.Cmd { ...@@ -764,6 +841,12 @@ func (m *ModelMain) handleKeyDown() tea.Cmd {
m.SysLoad = m3.(*ModelSysLoad) m.SysLoad = m3.(*ModelSysLoad)
return tea.Batch(cmd1, cmd2, cmd3) return tea.Batch(cmd1, cmd2, cmd3)
case VMTree: case VMTree:
if len(m.modelMsg.DCUPidInfo) == 0 {
return nil
}
if m.actionMsg.Action != nil {
return nil
}
if len(m.actionMsg.PidTreePids) == 0 { if len(m.actionMsg.PidTreePids) == 0 {
return nil return nil
} }
...@@ -788,6 +871,10 @@ func (m *ModelMain) handleKeyDown() tea.Cmd { ...@@ -788,6 +871,10 @@ func (m *ModelMain) handleKeyDown() tea.Cmd {
env, _ := m.processEnv.Update(tea.KeyDown) env, _ := m.processEnv.Update(tea.KeyDown)
m.processEnv = env.(*ModelProcessEnv) m.processEnv = env.(*ModelProcessEnv)
return nil return nil
case VMPower:
power, _ := m.powerDetail.Update(tea.KeyDown)
m.powerDetail = power.(*ModelPowerDetail)
return nil
default: default:
return nil return nil
} }
......
package tui
import (
"errors"
"fmt"
"get-container/cmd/hytop/tchart"
"slices"
"strings"
"sync"
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
linkedlist "github.com/emirpasic/gods/v2/lists/doublylinkedlist"
)
/*
所有dcu的平均功耗表常态维护
一旦选中了dcu,选中dcu的平均功耗表常态维护
ModelPowerDetail.Charts中key为非负数的值,如果存在就更新数据
*/
// ModelPowerDetail 展示DCU功率消耗的Model
type ModelPowerDetail struct {
PowerCap map[int]float32 // dcu功耗墙,即最大功耗,单位为瓦
DataCache map[int]*linkedlist.List[tchart.TimePoint] // 功率数据,key为dcu index,value为dcu功率
DCUDataCache map[int]*[4]float32 // 记录dcu与功耗相关的其他信息,key为dcu index,value为dcu的温度、使用率、内存使用率和功率
height, width int // 屏幕高度、宽度
Charts map[int]*ChartAndArea // 图表,key为dcu index,key为-1表示平均值图表,-2表示选中的dcu的平均值图表
Pids map[int]int // 记录哪些dcu在被使用
selectedDCU map[int]bool // 记录哪些dcu被选中
cursor *int // 光标指向的dcu index
dcuIndex []int // dcu index
DisplaySummary bool // 显示概要信息
DisplayAvg bool // 显示功率平均值
lock sync.RWMutex // 保护DataCache和Charts的锁
ScreenSplit map[int][][]ScreenArea // 记录屏幕尺寸
}
type ChartAndArea struct {
chart *MyTimeChart
area ScreenArea
h, l int
}
func NewModelPower(height, width int) *ModelPowerDetail {
result := ModelPowerDetail{
PowerCap: make(map[int]float32),
DataCache: make(map[int]*linkedlist.List[tchart.TimePoint]),
DCUDataCache: make(map[int]*[4]float32),
height: height,
width: width,
Pids: make(map[int]int),
Charts: make(map[int]*ChartAndArea),
selectedDCU: make(map[int]bool),
dcuIndex: make([]int, 0, 16),
cursor: nil,
ScreenSplit: make(map[int][][]ScreenArea),
DisplaySummary: false,
DisplayAvg: false,
lock: sync.RWMutex{},
}
return &result
}
const (
ModelPowerTitle = "DCU Pids Temp(°C) HCU(%) Mem(%) PwrCap(W) AvgPwr(W)\n"
)
var (
MPStyle = lipgloss.NewStyle().Border(lipgloss.NormalBorder(), false, false, false, true)
)
func (m *ModelPowerDetail) Init() tea.Cmd {
for i := 1; i < 10; i++ {
m.ScreenSplit[i] = m.SplitScreen(i)
}
return nil
}
func (m *ModelPowerDetail) Update(imsg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := imsg.(type) {
case *ModelMsg:
// 更新数据
var powerTotal float32 = 0
threshold := msg.t.Add(time.Second * (time.Duration(m.width * -2)))
m.dcuIndex = m.dcuIndex[:0]
var pwrCap float32 = 0
info, lock := msg.DCUInfo.GetQuitInfo()
extAvg := m.DisplayAvg && len(m.selectedDCU) > 0
var selectedTotal float32 = 0
m.lock.Lock()
for k, v := range info {
tmp := v.PwrCap.OrElse(0)
m.PowerCap[k] = tmp
if tmp > 0 {
pwrCap = tmp
}
l, have := m.DataCache[k]
if !have {
l = linkedlist.New[tchart.TimePoint]()
}
tmp = v.PwrAvg.OrElse(0)
tp := tchart.TimePoint{Time: msg.t, Value: float64(tmp)}
l.Append(tp)
chart, have := m.Charts[k]
if have {
chart.chart.PutPoint([]tchart.TimePoint{tp})
}
powerTotal += tmp
if extAvg && m.selectedDCU[k] {
selectedTotal += tmp
}
if !have {
m.DataCache[k] = l
}
// 剔除多余的点
for {
point, have := l.Get(0)
if !have {
break
}
if point.Time.Before(threshold) {
l.Remove(0)
} else {
break
}
}
d, have := m.DCUDataCache[k]
if !have {
d := &[4]float32{v.Temp.OrElse(0), v.DCUUTil.OrElse(0), v.MemUsedPerent.OrElse(0), v.PwrAvg.OrElse(0)}
m.DCUDataCache[k] = d
} else {
d[0] = v.Temp.OrElse(0)
d[1] = v.DCUUTil.OrElse(0)
d[2] = v.MemUsedPerent.OrElse(0)
d[3] = v.PwrAvg.OrElse(0)
}
m.dcuIndex = append(m.dcuIndex, k)
}
lock.Unlock()
slices.Sort(m.dcuIndex)
powerTotal /= float32(len(m.dcuIndex))
selectedTotal /= float32(len(m.selectedDCU))
l, have := m.DataCache[-1]
if !have {
l = linkedlist.New[tchart.TimePoint]()
m.DataCache[-1] = l
}
l.Append(tchart.TimePoint{Time: msg.t, Value: float64(powerTotal)})
for {
point, have := l.Get(0)
if !have {
break
}
if point.Time.Before(threshold) {
l.Remove(0)
} else {
break
}
}
l, have = m.DataCache[-2]
if !have {
l = linkedlist.New[tchart.TimePoint]()
m.DataCache[-2] = l
}
for {
point, have := l.Get(0)
if !have {
break
}
if point.Time.Before(threshold) {
l.Remove(0)
} else {
break
}
}
chart, have := m.Charts[-1]
if !have {
c := NewTimeChart(m.width, m.height-1, 0, float64(pwrCap), []lipgloss.Color{lipgloss.Color("#ff0000"), lipgloss.Color("#00ff00ff")})
chart = &ChartAndArea{chart: c, area: m.ScreenSplit[1][0][0], h: 0, l: 0}
m.Charts[-1] = chart
}
chart.chart.PutPoint([]tchart.TimePoint{{Time: msg.t, Value: float64(powerTotal)}})
chart, have = m.Charts[-2]
if !have {
c := NewTimeChart(m.width, m.height-1, 0, float64(pwrCap), []lipgloss.Color{lipgloss.Color("#ff0000"), lipgloss.Color("#00ff00ff")})
chart = &ChartAndArea{chart: c, area: m.ScreenSplit[1][0][0], h: 0, l: 0}
m.Charts[-2] = chart
}
chart.chart.PutPoint([]tchart.TimePoint{{Time: msg.t, Value: float64(selectedTotal)}})
m.lock.Unlock()
m.PowerCap[-1] = pwrCap
for k := range m.Pids {
m.Pids[k] = 0
}
for k, v := range msg.DCUPidInfo {
m.Pids[k] = len(v)
}
case tea.KeyType:
m.HandleKey(msg.String())
case tea.KeyMsg:
m.HandleKey(msg.String())
}
return m, nil
}
// HandleKey 处理按键事件
func (m *ModelPowerDetail) HandleKey(key string) {
switch key {
case "p": // 显示概要信息
m.DisplaySummary = !m.DisplaySummary
case "a": // 显示平均功率
m.DisplayAvg = !m.DisplayAvg
case KeySpace: // 选中/取消dcu
if !m.DisplaySummary {
break
}
if m.cursor == nil {
break
}
a, b := m.selectedDCU[*m.cursor]
selected := a && b
if selected {
// 已经选中了,现在取消
delete(m.selectedDCU, *m.cursor)
m.UpdateCharts(*m.cursor, false)
} else {
// 判断
if len(m.selectedDCU) >= 8 {
break
}
m.selectedDCU[*m.cursor] = true
m.UpdateCharts(*m.cursor, true)
}
case KeyUp:
if !m.DisplaySummary {
break
}
if m.cursor == nil {
index := m.dcuIndex[len(m.dcuIndex)-1]
m.cursor = &index
} else {
index := slices.Index(m.dcuIndex, *m.cursor)
if index == -1 {
*m.cursor = m.dcuIndex[len(m.dcuIndex)-1]
} else {
if index > 0 {
*m.cursor = m.dcuIndex[index-1]
}
}
}
case KeyDown:
if !m.DisplaySummary {
break
}
if m.cursor == nil {
index := m.dcuIndex[0]
m.cursor = &index
} else {
index := slices.Index(m.dcuIndex, *m.cursor)
if index == -1 {
*m.cursor = m.dcuIndex[0]
} else {
if index < (len(m.dcuIndex) - 1) {
*m.cursor = m.dcuIndex[index+1]
}
}
}
}
}
// SummaryInfo 生成概要信息
func (m *ModelPowerDetail) SummaryInfo() string {
sb := strings.Builder{}
sb.WriteString(ModelPowerTitle)
tmp := len(m.dcuIndex) - 1
for k, v := range m.dcuIndex {
d := m.DCUDataCache[v]
a, b := m.selectedDCU[v]
selected := a && b
cursorOn := (m.cursor != nil && *m.cursor == v)
if !selected && !cursorOn {
fmt.Fprintf(&sb, "%3d %4d %8.2f %6.2f %6.2f %9.2f %9.2f", v, m.Pids[v], d[0], d[1], d[2], m.PowerCap[v], d[3])
} else {
var style lipgloss.Style
if selected && cursorOn {
style = HeightSelectedStyle
} else if selected {
style = SelectedStyle
} else {
style = HeightLightStyle
}
s := fmt.Sprintf("%3d %4d %8.2f %6.2f %6.2f %9.2f %9.2f", v, m.Pids[v], d[0], d[1], d[2], m.PowerCap[v], d[3])
sb.WriteString(style.Render(s))
}
if k < tmp {
sb.WriteByte('\n')
}
}
style := lipgloss.NewStyle().Border(lipgloss.NormalBorder())
return style.Render(sb.String())
}
// UpdateCharts 解析数据,生成图表结构信息
func (m *ModelPowerDetail) UpdateCharts(index int, isAdd bool) {
dcuNum := len(m.selectedDCU)
dcus := make([]int, 0, dcuNum)
for k, v := range m.selectedDCU {
if v {
dcus = append(dcus, k)
}
}
dcuNum = len(dcus)
slices.Sort(dcus)
m.lock.Lock()
if isAdd {
// 添加dcu
for k, v := range dcus {
area, h, l, err := m.getScreenArea(dcuNum, k)
if err != nil {
panic(fmt.Sprintf("%v", err))
}
chart, have := m.Charts[v]
if !have {
// 需要新建图表
c := createChart(area, float64(m.PowerCap[v]), 0)
chart = &ChartAndArea{
chart: c,
area: area,
h: h,
l: l,
}
m.Charts[v] = chart
chart.chart.PutPoint(m.DataCache[v].Values())
continue
}
// 判断图表尺寸是否一致
if chart.area.Equal(&area) {
// 一致,仅修改位置即可
chart.h = h
chart.l = l
} else {
// 尺寸不一致,需要重新生成图表
chart.chart = createChart(area, float64(m.PowerCap[v]), 0)
chart.h = h
chart.l = l
chart.area = area
// 新图表添加数据点
chart.chart.PutPoint(m.DataCache[v].Values())
}
}
} else {
// 删除dcu
delete(m.Charts, index)
for k, v := range dcus {
area, h, l, err := m.getScreenArea(dcuNum, k)
if err != nil {
panic(fmt.Sprintf("%v", err))
}
chart := m.Charts[v]
if chart.area.Equal(&area) {
// 尺寸一致,无需重新生成,仅修改位置即可
chart.h = h
chart.l = l
continue
}
// 尺寸不一致,需要重新生成图表
chart.chart = createChart(area, float64(m.PowerCap[v]), 0)
chart.h = h
chart.l = l
chart.area = area
// 新图表添加数据点
chart.chart.PutPoint(m.DataCache[v].Values())
}
}
// 更新选中dcu的平均值图表
if dcuNum != 0 {
l := linkedlist.New[tchart.TimePoint]()
var points []tchart.TimePoint
for _, v := range dcus {
p := m.DataCache[v].Values()
if points == nil {
points = p
continue
}
for k := range p {
v := p[k].Value + points[k].Value
points[k].Value = v
}
}
for k := range points {
points[k].Value /= float64(dcuNum)
}
l.Append(points...)
m.DataCache[-2] = l
chart, have := m.Charts[-2]
if !have {
c := NewTimeChart(m.width, m.height-1, 0, float64(m.PowerCap[-1]), []lipgloss.Color{lipgloss.Color("#ff0000"), lipgloss.Color("#00ff00ff")})
chart = &ChartAndArea{chart: c, area: m.ScreenSplit[1][0][0], h: 0, l: 0}
m.Charts[-2] = chart
}
chart.chart.ResetPutPoint(points)
}
m.lock.Unlock()
}
// RenderCharts 渲染图表
func (m *ModelPowerDetail) RenderCharts() string {
dcuSelected := make([]int, 0, len(m.selectedDCU))
for k, v := range m.selectedDCU {
if v {
dcuSelected = append(dcuSelected, k)
}
}
slices.Sort(dcuSelected)
selectNum := len(dcuSelected)
// 显示全部平均功率
if selectNum == 0 || (selectNum == len(m.dcuIndex) && m.DisplayAvg) {
x := genXAxis(m.width)
chart := m.Charts[-1].chart.View()
return chart + "\n" + x
} else if m.DisplayAvg {
// 显示选中dcu的平均功率图表
x := genXAxis(m.width)
chart := m.Charts[-2].chart.View()
return chart + "\n" + x
} else {
// 显示选中dcu的图表
areas := m.ScreenSplit[selectNum]
pa, err := parseAreas(areas)
if err != nil {
panic(err.Error())
}
strs := make([]string, 0, selectNum)
for _, v := range dcuSelected {
chart := m.Charts[v].chart
str := chart.View()
str = StackPosition(fmt.Sprintf("DCU: %d", v), str, lipgloss.Top, lipgloss.Left)
str += "\n"
str += genXAxis(chart.width)
strs = append(strs, MPStyle.Render(str))
}
lines := make(map[int]string)
for k, v := range strs {
po := pa[k]
l, have := lines[po[0]]
if !have {
l = v
lines[po[0]] = l
} else {
lines[po[0]] = lipgloss.JoinHorizontal(lipgloss.Top, l, v)
}
}
return lipgloss.JoinVertical(lipgloss.Left, lines[0], lines[1], lines[2])
}
}
func (m *ModelPowerDetail) View() string {
if m.DisplaySummary {
summary := m.SummaryInfo()
charts := m.RenderCharts()
return StackPosition(summary, charts, lipgloss.Center, lipgloss.Center)
} else {
return m.RenderCharts()
}
}
// getScreenArea 获取指定索引的屏幕区域尺寸、所在的行、列
func (m *ModelPowerDetail) getScreenArea(num, index int) (ScreenArea, int, int, error) {
if num > 9 || num <= 0 {
return ScreenArea{}, 0, 0, errors.New("num out of range")
}
if index < 0 || index >= num {
return ScreenArea{}, 0, 0, errors.New("index out of range")
}
if m.ScreenSplit == nil {
return ScreenArea{}, 0, 0, errors.New("Model PowerDetail not init")
}
target := m.ScreenSplit[num]
switch num {
case 1:
return target[0][0], 0, 0, nil
case 2:
switch index {
case 0:
return target[0][0], 0, 0, nil
case 1:
return target[1][0], 1, 0, nil
}
case 3, 4:
h := index / 2
l := index % 2
return target[h][l], h, l, nil
case 5, 6, 8, 9:
h := index / 3
l := index % 3
return target[h][l], h, l, nil
case 7:
switch index {
case 0, 1, 2:
return target[0][index], 0, index, nil
case 3, 4:
return target[1][index-3], 1, index - 3, nil
case 5, 6:
return target[2][index-5], 2, index - 5, nil
}
}
return ScreenArea{}, 0, 0, errors.New("Model PowerDetail unknown error")
}
// SplitScreen 分割屏幕,得到每个区域的尺寸,返回结果为二维切片,第一维是行,第二维是列
func (m *ModelPowerDetail) SplitScreen(num int) [][]ScreenArea {
if num <= 0 {
return nil
}
result := make([][]ScreenArea, 0, 3)
result = append(result, make([]ScreenArea, 0, 3))
switch num {
case 1:
result[0] = append(result[0], NewScreenArea(m.width, m.height))
case 2:
result = append(result, make([]ScreenArea, 0, 1))
result[0] = append(result[0], NewScreenArea(m.width, m.height/2))
result[1] = append(result[1], NewScreenArea(m.width, m.height/2))
if m.height%2 != 0 {
result[0][0].h++
}
case 3:
result = append(result, make([]ScreenArea, 0, 1))
result[0] = append(result[0], NewScreenArea(m.width/2, m.height/2), NewScreenArea(m.width/2, m.height/2))
result[1] = append(result[1], NewScreenArea(m.width, m.height/2))
if m.width%2 != 0 {
result[0][0].w++
}
if m.height%2 != 0 {
result[0][0].h++
result[0][1].h++
}
case 4:
result = append(result, make([]ScreenArea, 0, 1))
result[0] = append(result[0], NewScreenArea(m.width/2, m.height/2), NewScreenArea(m.width/2, m.height/2))
result[1] = append(result[1], NewScreenArea(m.width/2, m.height/2), NewScreenArea(m.width/2, m.height/2))
if m.width%2 != 0 {
result[0][0].w++
result[1][0].w++
}
if m.height%2 != 0 {
result[0][0].h++
result[0][1].h++
}
case 5: // 两行,3,2
result = append(result, make([]ScreenArea, 0, 2))
result[0] = append(result[0], NewScreenArea(m.width/3, m.height/2), NewScreenArea(m.width/3, m.height/2), NewScreenArea(m.width/3, m.height/2))
result[1] = append(result[1], NewScreenArea(m.width/2, m.height/2), NewScreenArea(m.width/2, m.height/2))
if m.width%2 != 0 {
result[1][0].w++
}
for l := 0; l < m.width%3; l++ {
result[0][l].w++
}
if m.height%2 != 0 {
for l := range result[0] {
result[0][l].h++
}
}
case 6: // 两行,3,3
result = append(result, make([]ScreenArea, 0, 3))
result[0] = append(result[0], NewScreenArea(m.width/3, m.height/2), NewScreenArea(m.width/3, m.height/2), NewScreenArea(m.width/3, m.height/2))
result[1] = append(result[1], NewScreenArea(m.width/3, m.height/2), NewScreenArea(m.width/3, m.height/2), NewScreenArea(m.width/3, m.height/2))
for h := range result {
for l := 0; l < m.width%3; l++ {
result[h][l].w++
}
}
if m.height%2 != 0 {
for l := range result[0] {
result[0][l].h++
}
}
case 7: // 三行,3,2,2
result = append(result, make([]ScreenArea, 0, 2), make([]ScreenArea, 0, 2))
result[0] = append(result[0], NewScreenArea(m.width/3, m.height/3), NewScreenArea(m.width/3, m.height/3), NewScreenArea(m.width/3, m.height/3))
result[1] = append(result[1], NewScreenArea(m.width/2, m.height/3), NewScreenArea(m.width/2, m.height/3))
result[2] = append(result[2], NewScreenArea(m.width/2, m.height/3), NewScreenArea(m.width/2, m.height/3))
for h := 0; h < m.height%3; h++ {
for l := range result[h] {
result[h][l].h++
}
}
for l := 0; l < m.width%3; l++ {
result[0][l].w++
}
if m.width%2 != 0 {
result[1][0].w++
result[1][1].w++
result[2][0].w++
result[2][1].w++
}
case 8: // 三行,3,3,2
result = append(result, make([]ScreenArea, 0, 3), make([]ScreenArea, 0, 2))
result[0] = append(result[0], NewScreenArea(m.width/3, m.height/3), NewScreenArea(m.width/3, m.height/3), NewScreenArea(m.width/3, m.height/3))
result[1] = append(result[1], NewScreenArea(m.width/3, m.height/3), NewScreenArea(m.width/3, m.height/3), NewScreenArea(m.width/3, m.height/3))
result[2] = append(result[2], NewScreenArea(m.width/2, m.height/3), NewScreenArea(m.width/2, m.height/3))
for h := 0; h < m.height%3; h++ {
for l := range result[h] {
result[h][l].h++
}
}
for h := range result[:1] {
for l := 0; l < m.width%3; l++ {
result[h][l].w++
}
}
if m.width%2 != 0 {
result[2][0].w++
}
case 9:
result = append(result, make([]ScreenArea, 0, 3), make([]ScreenArea, 0, 3))
result[0] = append(result[0], NewScreenArea(m.width/3, m.height/3), NewScreenArea(m.width/3, m.height/3), NewScreenArea(m.width/3, m.height/3))
result[1] = append(result[1], NewScreenArea(m.width/3, m.height/3), NewScreenArea(m.width/3, m.height/3), NewScreenArea(m.width/3, m.height/3))
result[2] = append(result[2], NewScreenArea(m.width/3, m.height/3), NewScreenArea(m.width/3, m.height/3), NewScreenArea(m.width/3, m.height/3))
for h := 0; h < m.height%3; h++ {
for l := range result[h] {
result[h][l].h++
}
}
for h := range result {
for l := 0; l < m.width%3; l++ {
result[h][l].w++
}
}
default:
return nil
}
return result
}
type ScreenArea struct {
w, h int
}
func (sa *ScreenArea) Equal(other *ScreenArea) bool {
return sa.h == other.h && sa.w == other.w
}
func NewScreenArea(w, h int) ScreenArea {
return ScreenArea{
w: w,
h: h,
}
}
func createChart(area ScreenArea, max, min float64) *MyTimeChart {
return NewTimeChart(area.w-1, area.h-1, min, max, []lipgloss.Color{lipgloss.Color("#ff0000"), lipgloss.Color("#00ff00ff")})
}
func parseAreas(areas [][]ScreenArea) (map[int][2]int, error) {
if areas == nil {
return nil, errors.New("error args is null")
}
index := 0
result := make(map[int][2]int)
for kout, vout := range areas {
for kin := range vout {
result[index] = [2]int{kout, kin}
index++
}
}
return result, nil
}
...@@ -109,7 +109,7 @@ type MyTimeChart struct { ...@@ -109,7 +109,7 @@ type MyTimeChart struct {
color []lipgloss.Color color []lipgloss.Color
} }
// New 新建图表,其中dataSet的Key为数据集名称,value为数据集的颜色 // New 新建图表,color的长度如果为1,则图表的颜色为color[0],如果color的长度为heigh,则图表的颜色随高度变化
func NewTimeChart(width, height int, vmin, vmax float64, color []lipgloss.Color) *MyTimeChart { func NewTimeChart(width, height int, vmin, vmax float64, color []lipgloss.Color) *MyTimeChart {
result := MyTimeChart{} result := MyTimeChart{}
result.max = vmax result.max = vmax
...@@ -122,10 +122,11 @@ func NewTimeChart(width, height int, vmin, vmax float64, color []lipgloss.Color) ...@@ -122,10 +122,11 @@ func NewTimeChart(width, height int, vmin, vmax float64, color []lipgloss.Color)
result.lockPoints.Lock() result.lockPoints.Lock()
result.points = linkedlist.New[tchart.TimePoint]() result.points = linkedlist.New[tchart.TimePoint]()
now := time.Now() now := time.Now()
// 用最小值填充空白点
t := result.width*2 + 1 t := result.width*2 + 1
tmpPoints := make([]tchart.TimePoint, 0, t) tmpPoints := make([]tchart.TimePoint, 0, t)
for i := range t { for i := range t {
tmpPoints = append(tmpPoints, tchart.TimePoint{Time: now.Add(time.Duration(-i) * time.Second)}) tmpPoints = append(tmpPoints, tchart.TimePoint{Time: now.Add(time.Duration(-i) * time.Second), Value: result.min})
} }
result.lockPoints.Unlock() result.lockPoints.Unlock()
s := tchart.New(width, height+1, s := tchart.New(width, height+1,
...@@ -136,10 +137,15 @@ func NewTimeChart(width, height int, vmin, vmax float64, color []lipgloss.Color) ...@@ -136,10 +137,15 @@ func NewTimeChart(width, height int, vmin, vmax float64, color []lipgloss.Color)
tchart.WithTimeSeries(tmpPoints), tchart.WithTimeSeries(tmpPoints),
) )
result.chart = &s result.chart = &s
result.color = color if len(color) == 2 {
result.color = GenGradientColor(color[0], color[1], height)
} else {
result.color = color
}
return &result return &result
} }
// SortPoints 对不是自动填充的点进行排序
func (m *MyTimeChart) SortPoints() { func (m *MyTimeChart) SortPoints() {
m.points.Sort(func(x, y tchart.TimePoint) int { m.points.Sort(func(x, y tchart.TimePoint) int {
if x.Time.After(y.Time) { if x.Time.After(y.Time) {
...@@ -152,17 +158,22 @@ func (m *MyTimeChart) SortPoints() { ...@@ -152,17 +158,22 @@ func (m *MyTimeChart) SortPoints() {
}) })
} }
// RemoveUselessPoint 删除无用的点(即超出x轴范围的点)
func (m *MyTimeChart) RemoveUselessPoint() { func (m *MyTimeChart) RemoveUselessPoint() {
m.SortPoints() m.SortPoints()
// th为时间阈值,在此之前的点需要删除
th := time.Now().Add(time.Duration(m.width*-2) * time.Second) th := time.Now().Add(time.Duration(m.width*-2) * time.Second)
for { for {
// 如果没有点,则直接退出
t, b := m.points.Get(0) t, b := m.points.Get(0)
if !b { if !b {
break break
} }
if t.Time.Before(th) { if t.Time.Before(th) {
// 如果第一个点在阈值之前,就删除
m.points.Remove(0) m.points.Remove(0)
} else { } else {
// 否则就退出
break break
} }
} }
...@@ -190,6 +201,7 @@ func (m *MyTimeChart) PutPoint(points []tchart.TimePoint) { ...@@ -190,6 +201,7 @@ func (m *MyTimeChart) PutPoint(points []tchart.TimePoint) {
} }
} }
} }
tmpPoint = append(tmpPoint, points...) tmpPoint = append(tmpPoint, points...)
sort.Slice(tmpPoint, func(i, j int) bool { sort.Slice(tmpPoint, func(i, j int) bool {
return tmpPoint[i].Time.Before(tmpPoint[j].Time) return tmpPoint[i].Time.Before(tmpPoint[j].Time)
...@@ -455,14 +467,17 @@ func (m *MyTimeChart) View() string { ...@@ -455,14 +467,17 @@ func (m *MyTimeChart) View() string {
for k, v := range runes { for k, v := range runes {
resultLines[k] = string(v) resultLines[k] = string(v)
} }
if len(m.color) == height { switch len(m.color) {
case 1:
style := lipgloss.NewStyle().Foreground(m.color[0])
return style.Render(strings.Join(resultLines, "\n"))
case height:
style := lipgloss.NewStyle() style := lipgloss.NewStyle()
for k, v := range resultLines { for k, v := range resultLines {
resultLines[k] = style.Foreground(m.color[k]).Render(v) resultLines[k] = style.Foreground(m.color[k]).Render(v)
} }
} else if len(m.color) == 1 { return strings.Join(resultLines, "\n")
style := lipgloss.NewStyle().Foreground(m.color[0]) default:
return style.Render(strings.Join(resultLines, "\n")) return strings.Join(resultLines, "\n")
} }
return strings.Join(resultLines, "\n")
} }
...@@ -97,15 +97,23 @@ asd` ...@@ -97,15 +97,23 @@ asd`
ss := style.Render(str) ss := style.Render(str)
for _, v := range strings.Split(ss, "\n") { for _, v := range strings.Split(ss, "\n") {
t.Logf("w: %d", lipgloss.Width(v)) t.Logf("w: %d", lipgloss.Width(v))
t.Log(ansi.TruncateLeft(v,2,"")) t.Log(ansi.TruncateLeft(v, 2, ""))
} }
} }
func TestNewProcessEnv(t *testing.T) { func TestNewProcessEnv(t *testing.T) {
pe := NewModelProcessEnv(10, 50, 1, nil) pe := NewModelProcessEnv(10, 50, 1, nil)
t.Log(pe.lineNum) t.Log(pe.lineNum)
t.Log(pe.View()) t.Log(pe.View())
pe.Update(tea.KeyRight) pe.Update(tea.KeyRight)
t.Log(pe.View()) t.Log(pe.View())
} }
func TestModelPower(t *testing.T) {
a := `hello world
time is comd`
b := "ok"
s := StackPosition(b, a, lipgloss.Top, lipgloss.Left)
t.Log("\n" + s)
}
...@@ -232,3 +232,8 @@ func TestReg(t *testing.T) { ...@@ -232,3 +232,8 @@ func TestReg(t *testing.T) {
} }
} }
func TestSpf(t *testing.T) {
s := []int{1, 2, 3, 4}
t.Log(strings.ReplaceAll(fmt.Sprintf("%v", s), " ", ","))
}
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