Commit 7b273277 authored by liming6's avatar liming6
Browse files

feature 添加进程环境变量视图

parent 1e4cd019
......@@ -52,8 +52,8 @@ type ModelMsg struct {
}
type TickMsg time.Time
type ProcessAction int
type ViewMode int
type ProcessAction int // 进程动作
type ViewMode int // 视图
const (
PAKill ProcessAction = 9
......@@ -61,11 +61,11 @@ const (
PATerm ProcessAction = 15
PANone ProcessAction = 0
VMMain ViewMode = 0
VMTree ViewMode = 1
VMOneProcess ViewMode = 2
VMProcessEnv ViewMode = 3
VMHelp ViewMode = 4
VMMain ViewMode = 0 // 主视图
VMTree ViewMode = 1 // 进程树视图
VMOneProcess ViewMode = 2 // 进程详情视图
VMProcessEnv ViewMode = 3 // 进程环境变量视图
VMHelp ViewMode = 4 // 帮助视图
)
func (pa ProcessAction) String() string {
......@@ -97,6 +97,7 @@ func (pa ProcessAction) Signal() syscall.Signal {
// ActionMsg 动作消息
type ActionMsg struct {
VM ViewMode // 所在视图,默认为0,即主视图
VMOld ViewMode // 进入进程环境变量视图前的视图
SelectPids map[int32]bool // 选择的pid进程
Action *ProcessAction // 对选择的pid的动作
PidView *int32 // 进程视图指标的pid号,为null表示没有进入进程指标视图
......@@ -117,7 +118,8 @@ type ModelMain struct {
actionMsg *ActionMsg
dialog *Dialog // 对话框
pstree *ModelPsTree // 进程树视图
processDetail *ModelProcessDetail
processDetail *ModelProcessDetail // 进程详情视图
processEnv *ModelProcessEnv // 进程环境变量视图
}
func NewModelMain(width, height int) ModelMain {
......@@ -131,6 +133,7 @@ func NewModelMain(width, height int) ModelMain {
result.dialog = NewDialog(nil)
result.pstree = NewModelPsTree(width, height)
result.actionMsg = &ActionMsg{}
result.processEnv = nil
return result
}
......@@ -171,6 +174,9 @@ func (m *ModelMain) Init() tea.Cmd {
if c := m.pstree.Init(); c != nil {
cmds = append(cmds, c)
}
if c := m.processEnv.Init(); c != nil {
cmds = append(cmds, c)
}
m.modelMsg = &modelMsg
// 将调用初始化方法
cmds = append(cmds, tickCmd(), sendMsgCmd(&modelMsg))
......@@ -212,6 +218,9 @@ func (m *ModelMain) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
case "t":
cmd := m.handleKeyT()
return m, cmd
case "e":
cmd := m.handleKeyE()
return m, cmd
}
case TickMsg: // 定时事件
updateModelInfo(m.modelMsg, time.Time(msg))
......@@ -255,6 +264,24 @@ func (m *ModelMain) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil
}
func (m *ModelMain) handleKeyE() tea.Cmd {
// 检查光标是否在某个进程上,若有,且当前为Main或Tree视图,就进入进程环境变量模式
if m.actionMsg == nil || m.actionMsg.PointPid == nil {
return nil
}
if m.actionMsg.VM != VMMain && m.actionMsg.VM != VMTree {
return nil
}
if m.processEnv != nil {
return nil
}
m.processEnv = NewModelProcessEnv(m.width, m.height, int(*m.actionMsg.PointPid), m.modelMsg)
m.actionMsg.VMOld = m.actionMsg.VM
m.actionMsg.VM = VMProcessEnv
return nil
}
func (m *ModelMain) handleCtrlC() tea.Cmd {
if m.actionMsg.Action != nil || m.actionMsg.VM != VMTree {
return nil
......@@ -341,11 +368,26 @@ func (m *ModelMain) handleKeyT() tea.Cmd {
// handleKeyLR 处理左右键消息,左右键仅在对话框中有效
func (m *ModelMain) handleKeyLR(s string) tea.Cmd {
if m.processEnv != nil {
switch s {
case tea.KeyRight.String():
env, _ := m.processEnv.Update(tea.KeyRight)
m.processEnv = env.(*ModelProcessEnv)
return nil
case tea.KeyLeft.String():
env, _ := m.processEnv.Update(tea.KeyLeft)
m.processEnv = env.(*ModelProcessEnv)
return nil
default:
return nil
}
}
if m.actionMsg == nil || m.actionMsg.Action == nil {
return nil
}
if m.dialog != nil {
switch s {
case "right":
case tea.KeyRight.String():
switch *m.actionMsg.Action {
case PAKill:
*m.actionMsg.Action = PATerm
......@@ -356,7 +398,7 @@ func (m *ModelMain) handleKeyLR(s string) tea.Cmd {
default:
return nil
}
case "left":
case tea.KeyLeft.String():
switch *m.actionMsg.Action {
case PATerm:
*m.actionMsg.Action = PAKill
......@@ -373,6 +415,8 @@ func (m *ModelMain) handleKeyLR(s string) tea.Cmd {
dialog, _ := m.dialog.Update(m.actionMsg)
m.dialog = dialog.(*Dialog)
return nil
}
return nil
}
func (m *ModelMain) handleKeyK() tea.Cmd {
......@@ -454,6 +498,10 @@ func (m *ModelMain) handleKeyEsc() tea.Cmd {
m.actionMsg.VM = VMMain
m.processDetail = nil
return nil
case VMProcessEnv:
m.actionMsg.VM = m.actionMsg.VMOld
m.processEnv = nil
return nil
default:
return nil
}
......@@ -511,6 +559,8 @@ func (m *ModelMain) View() string {
return m.pstree.View()
case VMOneProcess:
return m.processDetail.View()
case VMProcessEnv:
return m.processEnv.View()
default:
return ""
}
......@@ -627,6 +677,13 @@ func (m *ModelMain) handleKeyUp() tea.Cmd {
pstree, _ := m.pstree.Update(m.actionMsg)
m.pstree = pstree.(*ModelPsTree)
return nil
case VMProcessEnv:
if m.processEnv == nil {
return nil
}
env, _ := m.processEnv.Update(tea.KeyUp)
m.processEnv = env.(*ModelProcessEnv)
return nil
default:
return nil
}
......@@ -708,8 +765,14 @@ func (m *ModelMain) handleKeyDown() tea.Cmd {
pstree, _ := m.pstree.Update(m.actionMsg)
m.pstree = pstree.(*ModelPsTree)
return nil
case VMProcessEnv:
if m.processEnv == nil {
return nil
}
env, _ := m.processEnv.Update(tea.KeyDown)
m.processEnv = env.(*ModelProcessEnv)
return nil
default:
return nil
}
}
......@@ -120,7 +120,7 @@ func (m *ModelProcessInfo) View() string {
if process.Info.ContInfo != nil {
sbInner.WriteString(FormatStr(process.Info.ContInfo.Name, 20, lipgloss.Left))
} else {
sbInner.WriteString(FormatStr(" - ", 20, lipgloss.Right))
sbInner.WriteString(FormatStr(" - ", 20, lipgloss.Left))
}
sbInner.WriteByte(' ')
w := m.width - lipgloss.Width(sbInner.String()) - 3
......@@ -129,7 +129,7 @@ func (m *ModelProcessInfo) View() string {
sbInner.WriteString(process.Info.Cmd)
sbInner.WriteString(strings.Repeat(" ", w-tw))
} else {
sbInner.WriteString(FormatStr(process.Info.Cmd, w-1, lipgloss.Left))
sbInner.WriteString(FormatStr(process.Info.Cmd, w-2, lipgloss.Left))
sbInner.WriteString("..")
}
sbInner.WriteByte(' ')
......
package tui
import (
"fmt"
"get-container/cmd/hytop/backend"
"slices"
"strings"
"github.com/acarl005/stripansi"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/x/ansi"
"github.com/shirou/gopsutil/v3/process"
)
type ModelProcessEnv struct {
w, h int // 屏幕宽、高
pid int // 显示的进程pid
modelMsg *ModelMsg // 模型信息
lineNum int // 光标所在的行
xOffset, yOffset int // 偏移量
title string // 界面标题
line string // 分割线
proc *process.Process // 进程信息
envs []string
styleW, styleH lipgloss.Style
envW, envH int // 环境变量字符串的宽高
envStr string
}
func NewModelProcessEnv(w, h, pid int, modelMsg *ModelMsg) *ModelProcessEnv {
var dev string
if modelMsg == nil || modelMsg.DCUPidInfo == nil {
dev = "CPU"
} else {
dev = "CPU"
for _, v := range modelMsg.DCUPidInfo {
for _, vv := range v {
if vv.Info.Pid == int32(pid) {
dev = "DCU"
break
}
}
if dev == "DCU" {
break
}
}
}
style := lipgloss.NewStyle().Foreground(lipgloss.Color("#00fffb7a"))
header := style.SetString(fmt.Sprintf("Environment of process %d (%s@%s: Compute): ", pid, backend.User, dev)).String()
proc, err := process.NewProcess(int32(pid))
style = style.UnsetForeground().Foreground(lipgloss.Color("#6cff4eff"))
result := &ModelProcessEnv{
w: w,
h: h,
pid: pid,
lineNum: -1,
line: style.Render(strings.Repeat("#", w)),
title: header,
modelMsg: modelMsg,
proc: nil,
envs: nil,
styleW: lipgloss.NewStyle().MaxWidth(w),
styleH: lipgloss.NewStyle().MaxHeight(h),
}
if err != nil {
return result
}
cmd, err := proc.Cmdline()
if err != nil {
return result
}
result.title += cmd
envs, err := proc.Environ()
if err != nil {
return result
}
slices.Sort(envs)
result.envs = envs
result.addEnvStyle()
s := strings.Join(result.envs, "\n")
result.envStr = lipgloss.NewStyle().Width(lipgloss.Width(s)).Render(s)
result.envW, result.envH = lipgloss.Size(result.envStr)
return result
}
func (pe *ModelProcessEnv) Init() tea.Cmd {
return nil
}
// Update 处理光标移动
func (pe *ModelProcessEnv) Update(imsg tea.Msg) (tea.Model, tea.Cmd) {
// 判断是否需要滚动
consider := false
if pe.envH > (pe.h - 2) {
consider = true
}
cW := false
if pe.envW > pe.w {
cW = true
}
switch msg := imsg.(type) {
case tea.KeyType:
switch msg.String() {
case tea.KeyUp.String():
if pe.envH == 0 {
return pe, nil
}
if pe.lineNum == -1 {
if consider {
pe.lineNum = pe.h - 2
pe.yOffset = pe.envH - pe.lineNum
} else {
pe.lineNum = pe.envH
}
return pe, nil
}
if consider {
if pe.lineNum > 1 {
pe.lineNum--
} else {
if pe.yOffset > 0 {
pe.yOffset--
}
}
return pe, nil
} else {
if pe.lineNum > 1 {
pe.lineNum--
}
return pe, nil
}
case tea.KeyDown.String():
if pe.envH == 0 {
return pe, nil
}
if pe.lineNum == -1 {
pe.lineNum = 1
return pe, nil
}
if consider {
if pe.lineNum < (pe.h - 2) {
pe.lineNum++
return pe, nil
} else {
if pe.yOffset+pe.lineNum < pe.envH {
pe.yOffset++
}
return pe, nil
}
} else {
if pe.lineNum < pe.envH {
pe.lineNum++
return pe, nil
}
}
case tea.KeyLeft.String():
if !cW {
return pe, nil
}
if pe.xOffset > 0 {
pe.xOffset--
return pe, nil
}
case tea.KeyRight.String():
if !cW {
return pe, nil
}
if pe.xOffset+pe.w < pe.envW {
pe.xOffset++
return pe, nil
}
}
}
return pe, nil
}
func (pe *ModelProcessEnv) View() string {
sb := strings.Builder{}
sb.WriteString(pe.title)
sb.WriteByte('\n')
sb.WriteString(pe.line)
sb.WriteByte('\n')
lines := strings.Split(pe.envStr, "\n")
l := len(lines[pe.yOffset:])
for k, line := range lines[pe.yOffset:] {
if k+1 == pe.lineNum {
sb.WriteString(pe.styleW.Render(ansi.TruncateLeft(HeightLightStyle.Render(stripansi.Strip(line)), pe.xOffset, "")))
} else {
sb.WriteString(pe.styleW.Render(ansi.TruncateLeft(line, pe.xOffset, "")))
}
if k+1 < l {
sb.WriteByte('\n')
}
}
return pe.styleH.Render(sb.String())
}
func (pe *ModelProcessEnv) addEnvStyle() {
if len(pe.envs) == 0 {
return
}
keyStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#8a8af7ff"))
equalStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#f237ffff"))
eq := equalStyle.Render("=")
target := make([]string, 0, len(pe.envs))
for _, v := range pe.envs {
if v == "" {
continue
}
fields := strings.SplitN(v, "=", 2)
if len(fields) != 2 {
continue
}
target = append(target, keyStyle.Render(fields[0])+eq+fields[1])
}
pe.envs = nil
pe.envs = target
}
......@@ -5,10 +5,13 @@ import (
"get-container/cmd/hytop/backend"
"get-container/cmd/hytop/tchart"
"math/rand/v2"
"strings"
"testing"
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/x/ansi"
)
func TestHeader(t *testing.T) {
......@@ -85,3 +88,24 @@ func TestTimeChart(t *testing.T) {
chart1.ResetPutPoint(points)
t.Logf("\n%s", chart1.View())
}
func TestFromatStr(t *testing.T) {
style := lipgloss.NewStyle().Width(20)
str := `hello
adfsafsfdsfsdfsfd
asd`
ss := style.Render(str)
for _, v := range strings.Split(ss, "\n") {
t.Logf("w: %d", lipgloss.Width(v))
t.Log(ansi.TruncateLeft(v,2,""))
}
}
func TestNewProcessEnv(t *testing.T) {
pe := NewModelProcessEnv(10, 50, 1, nil)
t.Log(pe.lineNum)
t.Log(pe.View())
pe.Update(tea.KeyRight)
t.Log(pe.View())
}
package types
// ProcessInfo 进程信息
type ProcessInfo struct {
Pid int32
User string
GPUMemUsed uint64
CPUUsed float32
MemUsed float32
RunTime string
Cmdline string
}
......@@ -91,6 +91,7 @@ require (
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/NimbleMarkets/ntcharts v0.3.1
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/x/ansi v0.10.1
......
......@@ -5,7 +5,6 @@ import (
"slices"
"strings"
"sync/atomic"
"syscall"
"testing"
"time"
......@@ -198,14 +197,23 @@ func TestChild(t *testing.T) {
}
func TestCHar(t *testing.T) {
p, err := process.NewProcess(286962)
p, err := process.NewProcess(1)
if err != nil {
t.Error(err)
}
cmd, err := p.Cmdline()
env, err := p.Environ()
if err != nil {
t.Error(err)
t.Errorf("error get env of process: %v", err)
}
slices.Sort(env)
for _, v := range env {
if v == "" {
continue
}
t.Log(v)
}
t.Logf("%s \n", cmd)
SendSignal([]int32{286962}, syscall.SIGKILL)
}
func TestSplitN(t *testing.T) {
t.Logf("%v",strings.SplitN("a=b=c","=",2))
}
\ No newline at end of file
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