Commit 63d7bbfc authored by liming6's avatar liming6
Browse files

feature 添加展示进程信息功能

parent 9608d37d
package backend
import (
"get-container/docker"
"get-container/gpu"
"get-container/utils"
"maps"
"os"
"strconv"
"sync"
"time"
"github.com/shirou/gopsutil/v4/process"
)
/*
......@@ -11,8 +19,29 @@ import (
var (
MapIdDCU = sync.Map{} // 记录dcu信息
DockerPidInfo *DockerProcessMap = nil
User = ""
HostName = ""
)
func init() {
i, err := docker.ContainerInfo.GetProcessIdInDocker(false)
if err != nil || i == nil {
DockerPidInfo = &DockerProcessMap{lock: sync.RWMutex{}, pids: make(map[int32]bool)}
return
}
DockerPidInfo = &DockerProcessMap{lock: sync.RWMutex{}, pids: maps.Clone(i)}
HostName, _ = os.Hostname()
uid := os.Getuid()
u, err := utils.GetSysUserById(uid)
if err == nil && u != nil {
User = u.Name
} else {
User = strconv.Itoa(uid)
}
}
type DCUInfo struct {
Id int
Name string // full
......@@ -167,3 +196,108 @@ func GetDCUInfo() map[int]DCUInfo {
})
return result
}
type DockerProcessMap struct {
lock sync.RWMutex
pids map[int32]bool
}
func (dpm *DockerProcessMap) GetPidInfo() map[int32]bool {
rl := dpm.lock.RLocker()
rl.Lock()
defer rl.Unlock()
return maps.Clone(dpm.pids)
}
// Update 重新获取数据,这是一个耗时的操作
func (dpm *DockerProcessMap) Update() map[int32]bool {
i, err := docker.ContainerInfo.GetProcessIdInDocker(true)
if err != nil || i == nil {
dpm.pids = make(map[int32]bool)
return make(map[int32]bool)
}
dpm.pids = maps.Clone(i)
return maps.Clone(i)
}
type DCUProcessInfo struct {
DCU int // 使用的dcu号
DCUMem string // 使用的dcu内存容量
SDMA int
Info ProcessInfo // 通用进程信息
}
type ProcessInfo struct {
Pid int32 // 进程号
User string // 用户名或uid
CPU float64 // CPU使用率
Mem float32 // 内存使用率
Time string // 占用的CPU时间
Cmd string // 命令
InDocker bool // 是否在docker容器里
}
func getProcessInfo(pids []int32) map[int32]ProcessInfo {
result := make(map[int32]ProcessInfo)
if len(pids) == 0 {
return result
}
dockerPids := DockerPidInfo.GetPidInfo()
for _, pid := range pids {
p, err := process.NewProcess(int32(pid))
if err != nil {
continue
}
item := ProcessInfo{Pid: p.Pid}
item.User, _ = p.Username()
item.CPU, _ = p.CPUPercent()
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.Cmd, _ = p.Cmdline()
a, b := dockerPids[item.Pid]
item.InDocker = a && b
result[p.Pid] = item
}
return result
}
// GetDCUProcessInfo 返回值的key为dcu index
func GetDCUProcessInfo() map[int][]DCUProcessInfo {
result := make(map[int][]DCUProcessInfo)
info, err := gpu.GetDCUPidInfo()
if err != nil {
return result
}
pids := make([]int32, 0)
for _, v := range info {
pids = append(pids, v.Pid)
}
pinfo := getProcessInfo(pids)
for _, v := range info {
index := make([]int, 0)
for _, i := range v.HCUIndex {
ii, err := strconv.Atoi(i)
if err != nil {
continue
}
index = append(index, ii)
}
for _, i := range index {
l, have := result[i]
if !have {
result[i] = make([]DCUProcessInfo, 0)
l = result[i]
}
item := DCUProcessInfo{DCU: i}
item.Info = pinfo[v.Pid]
item.DCUMem = v.VRamUsed.HumanReadStr(1)
item.SDMA = v.SDMAUsed
l = append(l, item)
result[i] = l
}
}
return result
}
......@@ -21,7 +21,7 @@ type ModelMsg struct {
Version *gpu.HYVersionInfo // gpu版本相关信息
MyVersion string
DCUInfo map[int]backend.DCUInfo // DCU全量信息
// DCUPidInfo []gpu.DCUPidInfo // 使用dcu的进程信息
DCUPidInfo map[int][]backend.DCUProcessInfo // 使用dcu的进程信息
systemInfo *utils.SysInfo // 系统信息
}
......@@ -33,7 +33,7 @@ type ModelMain struct {
Header *ModelHeader
DCUInfo *ModelDCUInfo
SysLoad *ModelSysLoad
// ProcessInfo *ModelProcess
ProcessInfo *ModelProcessInfo
index uint64 // 记录update次数的值
modelMsg *ModelMsg // 记录模型信息
}
......@@ -45,6 +45,7 @@ func NewModelMain(width, height int) ModelMain {
result.Header = &ModelHeader{}
result.DCUInfo = &ModelDCUInfo{width: width, height: height, info: make(map[int]backend.DCUInfo)}
result.SysLoad = NewModelSysLoad(width)
result.ProcessInfo = NewModelProcessInfo(width)
return result
}
......@@ -78,7 +79,9 @@ func (m *ModelMain) Init() tea.Cmd {
if c := m.SysLoad.Init(); c != nil {
cmds = append(cmds, c)
}
if c := m.ProcessInfo.Init(); c != nil {
cmds = append(cmds, c)
}
m.modelMsg = &modelMsg
// 将调用初始化方法
cmds = append(cmds, tickCmd(), sendMsgCmd(&modelMsg))
......@@ -98,24 +101,28 @@ func (m *ModelMain) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
header, _ := m.Header.Update(m.modelMsg)
dcuInfo, _ := m.DCUInfo.Update(m.modelMsg)
sysLoad, _ := m.SysLoad.Update(m.modelMsg)
pidinfo, _ := m.ProcessInfo.Update(m.modelMsg)
m.Header = header.(*ModelHeader)
m.DCUInfo = dcuInfo.(*ModelDCUInfo)
m.SysLoad = sysLoad.(*ModelSysLoad)
m.ProcessInfo = pidinfo.(*ModelProcessInfo)
return m, tickCmd()
case ModelMsg: // 初始化
header, _ := m.Header.Update(m.modelMsg)
dcuInfo, _ := m.DCUInfo.Update(m.modelMsg)
sysLoad, _ := m.SysLoad.Update(m.modelMsg)
pidinfo, _ := m.ProcessInfo.Update(m.modelMsg)
m.Header = header.(*ModelHeader)
m.DCUInfo = dcuInfo.(*ModelDCUInfo)
m.SysLoad = sysLoad.(*ModelSysLoad)
m.ProcessInfo = pidinfo.(*ModelProcessInfo)
return m, nil
}
return m, nil
}
func (m *ModelMain) View() string {
return m.Header.View() + m.DCUInfo.View() + m.SysLoad.View() + "\n"
return m.Header.View() + m.DCUInfo.View() + m.SysLoad.View() + m.ProcessInfo.Header + m.ProcessInfo.View() + "\n"
}
var myBorder = lipgloss.Border{
......@@ -150,6 +157,7 @@ func initModelInfo(model *ModelMsg) error {
}
model.DCUInfo = backend.GetDCUInfo()
model.systemInfo, err = utils.GetSysInfo()
model.DCUPidInfo = backend.GetDCUProcessInfo()
return err
}
......@@ -164,4 +172,5 @@ func updateModelInfo(modelMsg *ModelMsg, index uint64, t time.Time) {
}
modelMsg.DCUInfo = backend.GetDCUInfo()
modelMsg.systemInfo, _ = utils.GetSysInfo()
modelMsg.DCUPidInfo = backend.GetDCUProcessInfo()
}
package tui
import (
"fmt"
"get-container/cmd/dcutop/backend"
"maps"
"strings"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
type ModelProcessInfo struct {
Header string
Title string
DoubleMiddleLine string
MiddleLine string
BottomLine string
NoProceseLine string // 没有进程时显示的行
Cache map[int][]backend.DCUProcessInfo
width int
style lipgloss.Style
}
const (
Processes = " Processes:"
ModelProcessInfoTitle = " DCU PID USER DCU-MEM SDMA %CPU %MEM TIME Command"
ModelProcessInfoNoPro = " No running processes found"
)
func NewModelProcessInfo(width int) *ModelProcessInfo {
result := ModelProcessInfo{width: width, Cache: make(map[int][]backend.DCUProcessInfo), style: lipgloss.NewStyle()}
return &result
}
func (m *ModelProcessInfo) Init() tea.Cmd {
sb := strings.Builder{}
sb.WriteString(myBorder.Left)
sb.WriteString(Processes)
uah := fmt.Sprintf("%s@%s ", m.style.Foreground(lipgloss.Color("#edff2cff")).SetString(backend.User).String(),
m.style.Foreground(lipgloss.Color("#a3ff2bff")).SetString(backend.HostName).String())
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')
m.Header = sb.String()
sb.Reset()
sb.WriteString(myBorder.Left)
sb.WriteString(ModelProcessInfoTitle)
sb.WriteString(strings.Repeat(" ", m.width-2-lipgloss.Width(ModelProcessInfoTitle)))
sb.WriteString(myBorder.Right)
sb.WriteByte('\n')
m.Title = sb.String()
m.DoubleMiddleLine = "╞" + strings.Repeat("═", m.width-2) + "╡\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.NoProceseLine = myBorder.Left + ModelProcessInfoNoPro + strings.Repeat(" ", m.width-2-lipgloss.Width(ModelProcessInfoNoPro)) + myBorder.Right + "\n"
return nil
}
func (m *ModelProcessInfo) View() string {
haveProcess := false
lines := make([]string, 0)
sb := strings.Builder{}
for index := range 64 {
processes, have := m.Cache[index]
if !have {
continue
}
for _, process := range processes {
haveProcess = true
sb.WriteString(myBorder.Left)
sb.WriteString(FormatStr(fmt.Sprintf("%d", process.DCU), 4, lipgloss.Right))
sb.WriteByte(' ')
sb.WriteString(FormatStr(fmt.Sprintf("%d", process.Info.Pid), 6, lipgloss.Right))
sb.WriteByte(' ')
sb.WriteString(FormatStr(process.Info.User, 9, lipgloss.Right))
sb.WriteByte(' ')
sb.WriteString(FormatStr(process.DCUMem, 8, lipgloss.Right))
sb.WriteByte(' ')
sb.WriteString(FormatStr(fmt.Sprintf("%d", process.SDMA), 4, lipgloss.Right))
sb.WriteByte(' ')
sb.WriteString(FormatStr(fmt.Sprintf("%.1f", process.Info.CPU), 5, lipgloss.Right))
sb.WriteByte(' ')
sb.WriteString(FormatStr(fmt.Sprintf("%.1f", process.Info.Mem), 5, lipgloss.Right))
sb.WriteByte(' ')
sb.WriteString(FormatStr(process.Info.Time, 9, lipgloss.Right))
sb.WriteString(" ")
w := m.width - lipgloss.Width(sb.String()) - 2
tw := lipgloss.Width(process.Info.Cmd)
if w >= tw {
sb.WriteString(process.Info.Cmd)
sb.WriteString(strings.Repeat(" ", w-tw))
} else {
sb.WriteString(FormatStr(process.Info.Cmd, w-2, lipgloss.Left))
sb.WriteString("..")
}
sb.WriteByte(' ')
sb.WriteString(myBorder.Right)
sb.WriteByte('\n')
lines = append(lines, sb.String())
sb.Reset()
}
lines = append(lines, m.MiddleLine)
}
if !haveProcess {
return m.Title + m.DoubleMiddleLine + m.NoProceseLine + m.BottomLine
}
sb.WriteString(m.Title)
sb.WriteString(m.DoubleMiddleLine)
for _, v := range lines[:len(lines)-1] {
sb.WriteString(v)
}
sb.WriteString(m.BottomLine)
return sb.String()
}
func (m *ModelProcessInfo) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := inputMsg.(type) {
case *ModelMsg:
clear(m.Cache)
maps.Copy(m.Cache, msg.DCUPidInfo)
}
return m, nil
}
......@@ -128,7 +128,7 @@ 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)
mem := fmt.Sprintf(" MEM: %s (%.1f%%)", memUsed.HumanReadStr(), 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)
dcu := fmt.Sprintf(" AVG DCU UTL: %.1f%%", m.current.DCUUsageAvg)
......
......@@ -3,11 +3,11 @@ package tui
import (
"fmt"
"get-container/cmd/dcutop/tchart"
"strings"
"testing"
"time"
"github.com/charmbracelet/lipgloss"
"github.com/emirpasic/gods/v2/maps/linkedhashmap"
"github.com/emirpasic/gods/v2/trees/binaryheap"
)
......@@ -97,19 +97,11 @@ func TestBinaryHeap(t *testing.T) {
}
}
func TestLinkedHashMap(t *testing.T) {
m := linkedhashmap.New[time.Time, int]()
now := time.Now()
for i := range 5 {
m.Put(now.Add(time.Duration(i)*time.Second), 5-i)
}
mi := m.Iterator()
for {
if mi.Next() {
t.Logf("%v: %d", mi.Key(), mi.Value())
} 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.Log(m.Size())
t.Logf("\n%s", m.View())
}
......@@ -7,13 +7,14 @@ import (
"context"
"errors"
"fmt"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"os"
"regexp"
"strings"
"sync"
"time"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
)
/**
......@@ -45,66 +46,64 @@ type ContainersInfo struct {
}
type ContainerPsInfo struct {
Pid uint64
Pid int32
Ppid uint64
Uid string
Cmd []string
Cmd string
}
func ParsePsInfo(topInfo map[string]container.TopResponse) (map[string]ContainerPsInfo, error) {
func ParsePsInfo(topInfo map[string]container.TopResponse) (map[string][]ContainerPsInfo, error) {
if topInfo == nil {
return nil, errors.New("topInfo is nil")
}
result := make(map[string]ContainerPsInfo)
indexMap, t := make(map[string]int), 0
result := make(map[string][]ContainerPsInfo)
for cid, topResp := range topInfo {
indexMap, t := make(map[string]int), 0
result[cid] = make([]ContainerPsInfo, 0)
for index, key := range topResp.Titles {
switch strings.TrimSpace(strings.ToLower(key)) {
case "pid":
indexMap[key] = index
indexMap["pid"] = index
t++
break
case "ppid":
indexMap[key] = index
indexMap["ppid"] = index
t++
break
case "uid":
indexMap[key] = index
indexMap["uid"] = index
t++
break
case "cmd":
indexMap[key] = index
indexMap["cmd"] = index
t++
break
default:
break
}
if t >= 4 {
break
}
}
for _, fields := range topResp.Processes {
item := ContainerPsInfo{}
if v, ok := indexMap["pid"]; ok {
pid, err := strconv.ParseUint(topResp.Processes[v][0], 10, 64)
pid, err := strconv.ParseUint(fields[v], 10, 64)
if err != nil {
return nil, err
}
item.Pid = pid
item.Pid = int32(pid)
}
if v, ok := indexMap["ppid"]; ok {
ppid, err := strconv.ParseUint(topResp.Processes[v][0], 10, 64)
ppid, err := strconv.ParseUint(fields[v], 10, 64)
if err != nil {
return nil, err
}
item.Ppid = ppid
}
if v, ok := indexMap["uid"]; ok {
item.Uid = topResp.Processes[v][0]
item.Uid = fields[v]
}
if v, ok := indexMap["cmd"]; ok {
item.Cmd = topResp.Processes[v]
item.Cmd = fields[v]
}
result[cid] = append(result[cid], item)
}
result[cid] = item
}
return result, nil
}
......@@ -148,6 +147,31 @@ func initContainerInfo() error {
return nil
}
// GetProcessIdInDocker 获取所用容器的进程信息
func (info *ContainersInfo) GetProcessIdInDocker(update bool) (map[int32]bool, error) {
result := make(map[int32]bool)
if update {
err := info.Update()
if err != nil {
return result, err
}
}
rl := info.lock.RLocker()
rl.Lock()
i, err := ParsePsInfo(info.topInfo)
rl.Unlock()
rl = nil
if err != nil {
return result, err
}
for _, v := range i {
for _, k := range v {
result[k.Pid] = true
}
}
return result, nil
}
// FindContainerIdByPid 根据pid获取该进程属于哪个docker容器,返回容器id,如果为nil,表示找不到容器id
func FindContainerIdByPid(pid uint64, method FindCIDMethod) (*string, error) {
switch method {
......@@ -161,7 +185,7 @@ func FindContainerIdByPid(pid uint64, method FindCIDMethod) (*string, error) {
}
func FindContainerIdByPidBatch(pids []uint64, method FindCIDMethod) (map[uint64]string, error) {
if pids == nil || len(pids) == 0 {
if len(pids) == 0 {
return nil, nil
}
switch method {
......
......@@ -2,9 +2,11 @@ package docker
import (
"context"
"github.com/moby/moby/client"
"strings"
"testing"
"time"
"github.com/moby/moby/client"
)
func TestRegexp(t *testing.T) {
......@@ -86,3 +88,23 @@ 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()
pids, err := ContainerInfo.GetProcessIdInDocker(false)
d := time.Since(now)
if err != nil {
t.Error(err)
}
t.Logf("%d", d.Nanoseconds())
t.Logf("%+v", pids)
}
......@@ -450,7 +450,7 @@ func parseRunningInfo(info string) (map[int]DCURunningInfo, error) {
}
type DCUPidInfo struct {
Pid uint64
Pid int32
PASId uint64
HCUNode []string
HCUIndex []string
......@@ -527,7 +527,7 @@ func parseDCUPidInfo(s string) ([]DCUPidInfo, error) {
if innerErr != nil {
return nil, innerErr
}
i.Pid = pid
i.Pid = int32(pid)
i.PASId, innerErr = strconv.ParseUint(info[PASIDHeader], 10, 64)
if innerErr != nil {
return nil, innerErr
......
================================= System Management Interface ==================================
================================================================================================
PIDs for KFD processes:
PID: 16407
PASID: 32768
HCU Node(Include CPU sort):
HCU Index:
GPUID:
PCI BUS:
VRAM USED(MiB): 0
VRAM USED(%): inf
SDMA USED: 0
PID: 17343
PASID: 32784
HCU Node(Include CPU sort): ['14']
HCU Index: ['6']
GPUID: ['11320']
PCI BUS: ['0000:25:00.0']
VRAM USED(MiB): 62524
VRAM USED(%): 95
SDMA USED: 0
PID: 17341
PASID: 32780
HCU Node(Include CPU sort): ['12']
HCU Index: ['4']
GPUID: ['7796']
PCI BUS: ['0000:1d:00.0']
VRAM USED(MiB): 62460
VRAM USED(%): 95
SDMA USED: 0
PID: 16579
PASID: 32769
HCU Node(Include CPU sort):
HCU Index:
GPUID:
PCI BUS:
VRAM USED(MiB): 0
VRAM USED(%): inf
SDMA USED: 0
PID: 17344
PASID: 32797
HCU Node(Include CPU sort): ['15']
HCU Index: ['7']
GPUID: ['50100']
PCI BUS: ['0000:2c:00.0']
VRAM USED(MiB): 62524
VRAM USED(%): 95
SDMA USED: 0
PID: 17099
PASID: 32778
HCU Node(Include CPU sort):
HCU Index:
GPUID:
PCI BUS:
VRAM USED(MiB): 0
VRAM USED(%): inf
SDMA USED: 0
PID: 17342
PASID: 32779
HCU Node(Include CPU sort): ['13']
HCU Index: ['5']
GPUID: ['13523']
PCI BUS: ['0000:20:00.0']
VRAM USED(MiB): 62524
VRAM USED(%): 95
SDMA USED: 0
================================================================================================
======================================== End of SMI Log ========================================
......@@ -36,6 +36,7 @@ func GetProcessInfo(pid int32) {
if err != nil {
return
}
p.Times()
cmdStr, err := p.Cmdline()
if err != nil {
return
......
......@@ -5,6 +5,7 @@ import (
"time"
"github.com/shirou/gopsutil/v4/cpu"
"github.com/shirou/gopsutil/v4/process"
)
func TestGetSysUsers(t *testing.T) {
......@@ -36,3 +37,11 @@ func BenchmarkGetSysInfo(b *testing.B) {
}
b.Logf("%+v", result)
}
func TestCPUTime(t *testing.T) {
p, _ := process.NewProcess(1)
tt, _ := p.Times()
t.Logf("%s", tt.String())
tt1 := time.Duration(int((tt.User + tt.System))) * time.Second
t.Logf("%s", tt1.String())
}
......@@ -63,7 +63,7 @@ type MemorySize struct {
Unit StorageCapacityUnit
}
func (s MemorySize) HumanReadStr() string {
func (s MemorySize) HumanReadStr(i int) string {
total := s.Num * uint64(s.Unit)
units := []StorageCapacityUnit{Byte, KiB, MiB, GiB, TiB, PiB}
var target StorageCapacityUnit
......@@ -76,7 +76,19 @@ func (s MemorySize) HumanReadStr() string {
}
}
num := float64(total) / float64(target)
switch i {
case 0:
return fmt.Sprintf("%d %s", int(num), target)
case 1:
return fmt.Sprintf("%.1f %s", num, target)
case 2:
return fmt.Sprintf("%.2f %s", num, target)
case 3:
return fmt.Sprintf("%.3f %s", num, target)
default:
return fmt.Sprintf("%.3f %s", num, target)
}
}
func ParseUnit(s string) (StorageCapacityUnit, error) {
......
package utils
import (
"strings"
"testing"
)
......@@ -56,8 +57,17 @@ func TestParseMemorySize(t *testing.T) {
func TestHumanReadStr(t *testing.T) {
ms := MemorySize{Num: 1025, Unit: Byte}
t.Log(ms.HumanReadStr())
t.Log(ms.HumanReadStr(2))
ms.Num = 1025
ms.Unit = PiB
t.Log(ms.HumanReadStr())
t.Log(ms.HumanReadStr(1))
}
func TestFo(t *testing.T) {
sb := strings.Builder{}
sb.WriteString("hello world\n")
sb.WriteString("time is come\n")
t.Log(sb.String())
sb.WriteString("okoko")
t.Log(sb.String())
}
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