You need to sign in or sign up before continuing.
Commit 7622e62a authored by liming6's avatar liming6
Browse files

feature 配置为全屏模式,提出部分优化项

parent 63d7bbfc
# Todo
待办清单:
- [x] 解析/etc/passwd文件,解析Linux系统用户和家目录
- [ ] 解析docker容器相关信息,尝试找出启动容器的用户
- [ ] 使用包装命令,记录容器和系统用户的对应关系
- [ ] 添加一种docker top命令查找进程的容器归属的方法
- [ ] 调研一种golang tui库
package main
import (
"get-container/cmd/dcutop/tui"
"log"
"os"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/x/term"
)
func main() {
w, h, err := term.GetSize(os.Stdout.Fd())
if err != nil {
log.Fatalf("error get terminal size: %v", err)
}
model := tui.NewModelMain(w, h)
if _, err := tea.NewProgram(&model).Run(); err != nil {
log.Fatalf("error create program; %v", err)
}
os.Exit(0)
}
// type TickMsg time.Time
// type model struct {
// c *tui.MyTimeChart
// }
// func (m *model) Init() tea.Cmd {
// m.c = tui.New(100, 20, 0, 100, map[string]lipgloss.Color{"default": lipgloss.Color("#ff2222ff"), "other": lipgloss.Color("#0037ffff")})
// return tea.Batch(m.c.Init(), tea.Tick(time.Second, func(t time.Time) tea.Msg {
// return TickMsg(t)
// }))
// }
// func (m *model) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
// switch msg := inputMsg.(type) {
// case TickMsg:
// randPoint1 := rand.Float64() * 100.0
// randPoint2 := rand.Float64() * 100.0
// now := time.Now()
// timePoint1 := tchart.TimePoint{Time: now, Value: randPoint1}
// timePoint2 := tchart.TimePoint{Time: now, Value: randPoint2}
// tmsg := tui.MyTimeChartMsg{Points: map[string][]tchart.TimePoint{"default": {timePoint1}, "other": {timePoint2}}}
// mm, cmd := m.c.Update(tmsg)
// m.c = mm.(*tui.MyTimeChart)
// return m, tea.Batch(cmd, tea.Tick(time.Second, func(t time.Time) tea.Msg {
// return TickMsg(t)
// }))
// case tea.KeyMsg:
// switch msg.String() {
// case "q", "ctrl+c":
// return m, tea.Quit
// }
// }
// return m, nil
// }
// func (m *model) View() string {
// sty := lipgloss.NewStyle().Border(lipgloss.NormalBorder())
// st := sty.SetString(m.c.View()).String()
// return st + "\n"
// }
// func main() {
// _, err := tea.NewProgram(&model{}).Run()
// if err != nil {
// log.Fatal(err)
// }
// }
# Todo
待办清单:
解决非root用户执行出错的问题
优化抓取数据逻辑,避免卡顿
- 减少无效数据的收集
- 多goroutine收集数据,收集数据的goroutine不阻塞tui相关goroutine的运行
尝试使用.so文件抓取系统信息,而非使用二进制文件的输出,以提高采集效率
提高采集频率
集成docker相关功能,同时注意错误隔断,避免无用错误影响主逻辑(docker容器重启会影响获取docker容器相关信息,会返回err)
package backend
import (
"fmt"
"get-container/docker"
"get-container/gpu"
"get-container/utils"
"maps"
"math"
"os"
"strconv"
"strings"
"sync"
"time"
......@@ -254,7 +257,7 @@ func getProcessInfo(pids []int32) map[int32]ProcessInfo {
item.Mem, _ = p.MemoryPercent()
t, err := p.Times()
if err == nil {
item.Time = (time.Duration((t.System+t.User)*1000.0) * time.Millisecond).String()
item.Time = durationStr(time.Duration((t.System + t.User)) * time.Second)
}
item.Cmd, _ = p.Cmdline()
a, b := dockerPids[item.Pid]
......@@ -301,3 +304,11 @@ func GetDCUProcessInfo() map[int][]DCUProcessInfo {
}
return result
}
// durationStr 将时间段格式化为 小时:分钟:秒s的格式
func durationStr(d time.Duration) string {
h := int(math.Floor(d.Hours()))
m := int(d.Minutes()) % 60
s := int(math.Floor(d.Seconds())) % 60
return strings.Replace(fmt.Sprintf("%d:%2d:%2d", h, m, s), " ", "0", -1)
}
File added
package main
import (
"get-container/cmd/hytop/tui"
"log"
"os"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/x/term"
)
func main() {
w, h, err := term.GetSize(os.Stdout.Fd())
if err != nil {
log.Fatalf("error get terminal size: %v", err)
}
model := tui.NewModelMain(w, h)
if _, err := tea.NewProgram(&model, tea.WithAltScreen()).Run(); err != nil {
log.Fatalf("error create program; %v", err)
}
os.Exit(0)
}
......@@ -2,14 +2,16 @@ package tui
import (
"fmt"
"get-container/cmd/dcutop/backend"
"get-container/cmd/hytop/backend"
"maps"
"regexp"
"strconv"
"strings"
"github.com/charmbracelet/bubbles/progress"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/muesli/termenv"
)
type ModelDCUInfo struct {
......@@ -30,6 +32,10 @@ const (
ProgressMinWidth = 15 // 进度条最小宽度
)
var (
ReDCUName = regexp.MustCompile(`(?i)^[A-Z0-9-_]*`)
)
func (m *ModelDCUInfo) Init() tea.Cmd {
if m.width < StaticWidth+ProgressMinWidth {
return tea.Quit
......@@ -38,7 +44,7 @@ func (m *ModelDCUInfo) Init() tea.Cmd {
if m.width > StaticWidth+ProgressMinWidth+OtherWidth {
m.proWidth = m.width - OtherWidth - StaticWidth
}
m.pro = progress.New(progress.WithDefaultGradient(), progress.WithWidth(m.proWidth))
m.pro = progress.New(progress.WithColorProfile(termenv.TrueColor), progress.WithGradient("#0000ffff", "#ff0000ff"), progress.WithWidth(m.proWidth))
return nil
}
......@@ -63,9 +69,9 @@ func (m *ModelDCUInfo) View() string {
strBuilder.WriteByte(' ')
strBuilder.WriteString(FormatStr(strconv.Itoa(ii.Id), 4, lipgloss.Left))
strBuilder.WriteByte(' ')
strBuilder.WriteString(FormatStr(ii.Name, 6, lipgloss.Left))
strBuilder.WriteString(FormatStr(ReDCUName.FindString(ii.Name), 8, lipgloss.Left))
strBuilder.WriteByte(' ')
strBuilder.WriteString(FormatStr(ii.PerformanceLevel, 17, lipgloss.Right))
strBuilder.WriteString(FormatStr(ii.PerformanceLevel, 15, lipgloss.Right))
strBuilder.WriteByte(' ')
strBuilder.WriteString(myBorder.Left)
strBuilder.WriteByte(' ')
......
......@@ -45,14 +45,15 @@ var (
KeyStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#842cffff")).Italic(true)
KeyH, KeyQ = KeyStyle.SetString("h").String(), KeyStyle.SetString("q").String()
Space = strings.Repeat(" ", 28)
HeaderBorderStyle = lipgloss.NewStyle().Border(myBorder, true, true, false, true)
)
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)
style := lipgloss.NewStyle().Padding(0, 1)
borderStyle := lipgloss.NewStyle().Border(myBorder, true, true, false, true)
hyv := style.Width(18).Render(fmt.Sprintf("hytop: %s", mh.DCUTopVersion))
drv := style.Width(35).Render(fmt.Sprintf("Driver Version: %s", mh.DriverVersion))
smiv := style.Width(24).Render(fmt.Sprintf("SMI Version: %s", mh.SMIVersion))
return header + borderStyle.Render(hyv+drv+smiv) + Title
return header + HeaderBorderStyle.Render(hyv+drv+smiv) + Title
}
package tui
import (
"get-container/cmd/dcutop/backend"
"get-container/cmd/hytop/backend"
"get-container/gpu"
"get-container/utils"
"time"
......@@ -122,7 +122,7 @@ func (m *ModelMain) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
}
func (m *ModelMain) View() string {
return m.Header.View() + m.DCUInfo.View() + m.SysLoad.View() + m.ProcessInfo.Header + m.ProcessInfo.View() + "\n"
return m.Header.View() + m.DCUInfo.View() + m.SysLoad.View() + m.ProcessInfo.View() + "\n"
}
var myBorder = lipgloss.Border{
......
......@@ -2,7 +2,7 @@ package tui
import (
"fmt"
"get-container/cmd/dcutop/backend"
"get-container/cmd/hytop/backend"
"maps"
"strings"
......@@ -11,7 +11,6 @@ import (
)
type ModelProcessInfo struct {
Header string
Title string
DoubleMiddleLine string
MiddleLine string
......@@ -46,9 +45,6 @@ func (m *ModelProcessInfo) Init() tea.Cmd {
sb.WriteString(uah)
sb.WriteString(myBorder.Right)
sb.WriteByte('\n')
m.Header = sb.String()
sb.Reset()
sb.WriteString(myBorder.Left)
sb.WriteString(ModelProcessInfoTitle)
sb.WriteString(strings.Repeat(" ", m.width-2-lipgloss.Width(ModelProcessInfoTitle)))
......@@ -107,14 +103,16 @@ func (m *ModelProcessInfo) View() string {
}
lines = append(lines, m.MiddleLine)
}
if !haveProcess {
return m.Title + m.DoubleMiddleLine + m.NoProceseLine + m.BottomLine
}
sb.WriteString(m.Title)
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()
}
......
......@@ -2,9 +2,10 @@ package tui
import (
"fmt"
"get-container/cmd/dcutop/backend"
"get-container/cmd/dcutop/tchart"
"get-container/cmd/hytop/backend"
"get-container/cmd/hytop/tchart"
"get-container/utils"
"image/color"
"maps"
"strings"
"sync"
......@@ -14,6 +15,8 @@ import (
"github.com/charmbracelet/lipgloss"
"github.com/emirpasic/gods/v2/maps/linkedhashmap"
"github.com/emirpasic/gods/v2/trees/binaryheap"
"github.com/lucasb-eyer/go-colorful"
"github.com/muesli/gamut"
)
const (
......@@ -36,6 +39,7 @@ type ModelSysLoad struct {
bottomLine string
style lipgloss.Style
width int // 组件总宽度
colors []color.Color
}
type SysLoadInfo struct {
......@@ -66,14 +70,15 @@ func NewModelSysLoad(width int) *ModelSysLoad {
return 0
})
subLine := width - StaticWidth - 1
result.SysMem = New(SysLoadWidth, SysLoadHeight, 0, 100, map[string]lipgloss.Color{"default": lipgloss.Color("#0400ffff")})
result.SysCPU = New(SysLoadWidth, SysLoadHeight, 0, 100, map[string]lipgloss.Color{"default": lipgloss.Color("#8400ffff")})
result.DCU = New(subLine, SysLoadHeight, 0, 100, map[string]lipgloss.Color{"default": lipgloss.Color("#00ff00ff")})
result.DCUMem = New(subLine, SysLoadHeight, 0, 100, map[string]lipgloss.Color{"default": lipgloss.Color("#eeff00ff")})
result.SysMem = NewTimeChart(SysLoadWidth, SysLoadHeight, 0, 100, map[string]lipgloss.Color{"default": lipgloss.Color("#20ff2cff")})
result.SysCPU = NewTimeChart(SysLoadWidth, SysLoadHeight, 0, 100, map[string]lipgloss.Color{"default": lipgloss.Color("#ff3030ff")})
result.DCU = NewTimeChart(subLine, SysLoadHeight, 0, 100, map[string]lipgloss.Color{"default": lipgloss.Color("#27ffdbff")})
result.DCUMem = NewTimeChart(subLine, SysLoadHeight, 0, 100, map[string]lipgloss.Color{"default": lipgloss.Color("#20b1ffff")})
result.topLine = myBorder.MiddleLeft + genXAxis(SysLoadWidth) + myBorder.Middle + genXAxis(subLine) + myBorder.MiddleRight
result.bottomLine = "╞" + strings.Repeat(myBorder.Bottom, SysLoadWidth) + "╧" + strings.Repeat(myBorder.Bottom, subLine) + "╡"
result.style = lipgloss.NewStyle()
result.colors = gamut.Blends(lipgloss.Color("#ff0000"), lipgloss.Color("#00ff00ff"), SysLoadHeight)
return &result
}
......@@ -127,14 +132,16 @@ func (m *ModelSysLoad) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
func (m *ModelSysLoad) View() string {
memUsed := utils.MemorySize{Num: m.current.MemUsed, Unit: utils.Byte}
load := fmt.Sprintf(" Load Average: %.2f %.2f %.2f", m.current.Load1, m.current.Load5, m.current.Load15)
load := fmt.Sprintf(" Load Average: %.2f %.2f %.2f\n CPU: %.1f%%", m.current.Load1, m.current.Load5, m.current.Load15, m.current.CPUPercent)
mem := fmt.Sprintf(" MEM: %s (%.1f%%)", memUsed.HumanReadStr(1), m.current.MemUsedPercent)
dcuMem := fmt.Sprintf(" AVG DCU MEM: %.1f%%", m.current.DCUMemUsageAvg)
dcu := fmt.Sprintf(" AVG DCU UTL: %.1f%%", m.current.DCUUsageAvg)
load = StackPosition(load, m.SysCPU.View(), lipgloss.Top, lipgloss.Left)
mem = StackPosition(mem, m.SysMem.View(), lipgloss.Bottom, lipgloss.Left)
dcuMem = StackPosition(dcuMem, m.DCUMem.View(), lipgloss.Top, lipgloss.Left)
s := m.DCUMem.View()
s = lipgloss.NewStyle().Foreground(lipgloss.Color("rgba(158, 143, 28, 0.86)")).Render(s)
dcuMem = StackPosition(dcuMem, s, lipgloss.Top, lipgloss.Left)
dcu = StackPosition(dcu, m.DCU.View(), lipgloss.Bottom, lipgloss.Left)
load = m.style.Border(myBorder, false, true, false).Render(load)
......@@ -144,7 +151,7 @@ func (m *ModelSysLoad) View() string {
up := lipgloss.JoinHorizontal(lipgloss.Top, load, dcuMem)
down := lipgloss.JoinHorizontal(lipgloss.Top, mem, dcu)
return lipgloss.JoinVertical(lipgloss.Left, up, m.topLine, down, m.bottomLine)
return lipgloss.JoinVertical(lipgloss.Left, up, m.topLine, down, m.bottomLine) + "\n"
}
// updateInfo
......@@ -208,3 +215,13 @@ func (m *ModelSysLoad) updateInfo(t time.Time) {
}
wg.Wait()
}
func (m *ModelSysLoad) RanderColor(str string) string {
lines := strings.Split(strings.Trim(str, "\n"), "\n")
result := ""
for k, line := range lines {
c, _ := colorful.MakeColor(m.colors[k])
result += lipgloss.NewStyle().Foreground(lipgloss.Color(c.Hex())).Render(line) + "\n"
}
return result
}
......@@ -7,7 +7,7 @@ import (
"sync"
"time"
"get-container/cmd/dcutop/tchart"
"get-container/cmd/hytop/tchart"
"github.com/NimbleMarkets/ntcharts/canvas/runes"
tea "github.com/charmbracelet/bubbletea"
......@@ -20,7 +20,7 @@ const (
)
var (
axisFStyle = lipgloss.NewStyle().Inline(true).Foreground(lipgloss.Color("#707070ff"))
axisFStyle = lipgloss.NewStyle().Inline(true).Foreground(lipgloss.Color("#4d4d4dff"))
)
// genXAxis 生成X轴,参数l是x轴的长度
......@@ -72,7 +72,7 @@ type MyTimeChart struct {
}
// New 新建图表,其中dataSet的Key为数据集名称,value为数据集的颜色
func New(width, height int, vmin, vmax float64, dataSet map[string]lipgloss.Color) *MyTimeChart {
func NewTimeChart(width, height int, vmin, vmax float64, dataSet map[string]lipgloss.Color) *MyTimeChart {
result := MyTimeChart{}
result.max = vmax
result.min = vmin
......
......@@ -2,13 +2,11 @@ package tui
import (
"fmt"
"get-container/cmd/dcutop/tchart"
"strings"
"get-container/cmd/hytop/tchart"
"testing"
"time"
"github.com/charmbracelet/lipgloss"
"github.com/emirpasic/gods/v2/trees/binaryheap"
)
const S = `├───────────────────────────────┼──────────────────────┼──────────────────────┼
......@@ -52,7 +50,7 @@ func TestModel(t *testing.T) {
}
func TestMyTimeChart(t *testing.T) {
chart := New(100, 5, 0.0, 100.0, map[string]lipgloss.Color{"default": lipgloss.Color("#ff2222ff"), "other": lipgloss.Color("#0037ffff")})
chart := NewTimeChart(100, 5, 0.0, 100.0, map[string]lipgloss.Color{"default": lipgloss.Color("#ff2222ff"), "other": lipgloss.Color("#0037ffff")})
chart.Init()
s := chart.View()
t.Logf("\n%s", s)
......@@ -73,35 +71,6 @@ func TestMyTimeChart(t *testing.T) {
}
func TestBinaryHeap(t *testing.T) {
heap := binaryheap.NewWith(func(a, b time.Time) int {
if a.After(b) {
return 1
}
if a.Before(b) {
return -1
}
return 0
})
now := time.Now()
for i := range 5 {
heap.Push(now.Add(time.Duration(i) * time.Second))
}
for {
tt, ok := heap.Pop()
if ok {
t.Logf("%s", tt)
} else {
break
}
}
}
func TestNewModelProcessInfo(t *testing.T) {
m := NewModelProcessInfo(130)
t.Logf("\n%s", m.Header)
for l := range strings.SplitSeq(m.Header, "\n") {
t.Log(lipgloss.Width(l))
}
t.Logf("\n%s", m.View())
t.Log(ReDCUName.FindString("BW200, U"))
t.Log(ReDCUName.FindString("K100_Ai, U"))
}
......@@ -17,6 +17,10 @@ import (
"github.com/moby/moby/client"
)
func init() {
_ = initContainerInfo()
}
/**
有两种方法获取进程属于哪个容器
1. 通过查询pid命名空间,仅在没有指定--pid参数时有效
......@@ -47,7 +51,7 @@ type ContainersInfo struct {
type ContainerPsInfo struct {
Pid int32
Ppid uint64
Ppid int32
Uid string
Cmd string
}
......@@ -83,18 +87,18 @@ func ParsePsInfo(topInfo map[string]container.TopResponse) (map[string][]Contain
for _, fields := range topResp.Processes {
item := ContainerPsInfo{}
if v, ok := indexMap["pid"]; ok {
pid, err := strconv.ParseUint(fields[v], 10, 64)
pid, err := strconv.ParseInt(fields[v], 10, 64)
if err != nil {
return nil, err
}
item.Pid = int32(pid)
}
if v, ok := indexMap["ppid"]; ok {
ppid, err := strconv.ParseUint(fields[v], 10, 64)
ppid, err := strconv.ParseInt(fields[v], 10, 64)
if err != nil {
return nil, err
}
item.Ppid = ppid
item.Ppid = int32(ppid)
}
if v, ok := indexMap["uid"]; ok {
item.Uid = fields[v]
......@@ -128,10 +132,6 @@ func (info *ContainersInfo) Get() (map[string]container.InspectResponse, sync.Lo
return info.inspectInfo, rl
}
func init() {
_ = initContainerInfo()
}
func initContainerInfo() error {
inspect, lists, tops, err := getContainerInfo()
if err != nil {
......
......@@ -90,16 +90,11 @@ func TestDocker(t *testing.T) {
}
func TestGetProcessIdInDocker(t *testing.T) {
// now := time.Now()
// pids, err := ContainerInfo.GetProcessIdInDocker(true)
// d := time.Since(now)
// if err != nil {
// t.Error(err)
// }
// t.Logf("%v", d.Seconds())
// t.Logf("%+v", pids)
now := time.Now()
err := initContainerInfo()
if err != nil {
t.Error(err)
}
pids, err := ContainerInfo.GetProcessIdInDocker(false)
d := time.Since(now)
if err != nil {
......
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