xmtop/widgets/proc.go

336 lines
7.3 KiB
Go
Raw Normal View History

2018-02-19 15:25:02 +08:00
package widgets
import (
"fmt"
"log"
"os/exec"
2018-02-19 15:25:02 +08:00
"sort"
"strconv"
"strings"
2018-02-19 15:25:02 +08:00
"time"
2018-03-04 09:05:52 +08:00
psCPU "github.com/shirou/gopsutil/cpu"
2019-02-04 12:11:35 +08:00
tui "github.com/gizak/termui/v3"
2020-02-29 04:48:35 +08:00
ui "github.com/xxxserxxx/gotop/v3/termui"
"github.com/xxxserxxx/gotop/v3/utils"
2018-02-19 15:25:02 +08:00
)
const (
2019-03-01 08:29:52 +08:00
UP_ARROW = "▲"
DOWN_ARROW = "▼"
2018-02-19 15:25:02 +08:00
)
2019-03-01 08:29:52 +08:00
type ProcSortMethod string
const (
ProcSortCpu ProcSortMethod = "c"
ProcSortMem = "m"
ProcSortPid = "p"
)
2018-02-19 15:25:02 +08:00
type Proc struct {
2019-03-01 08:29:52 +08:00
Pid int
CommandName string
FullCommand string
Cpu float64
Mem float64
}
type ProcWidget struct {
2018-02-19 15:25:02 +08:00
*ui.Table
2019-06-05 06:08:02 +08:00
entry *ui.Entry
2019-03-12 13:52:37 +08:00
cpuCount int
2019-03-01 08:29:52 +08:00
updateInterval time.Duration
sortMethod ProcSortMethod
filter string
2019-03-01 08:29:52 +08:00
groupedProcs []Proc
ungroupedProcs []Proc
showGroupedProcs bool
2018-02-19 15:25:02 +08:00
}
func NewProcWidget() *ProcWidget {
cpuCount, err := psCPU.Counts(false)
if err != nil {
log.Printf("failed to get CPU count from gopsutil: %v", err)
}
2019-03-01 08:29:52 +08:00
self := &ProcWidget{
Table: ui.NewTable(),
updateInterval: time.Second,
2019-03-12 13:52:37 +08:00
cpuCount: cpuCount,
2019-03-01 08:29:52 +08:00
sortMethod: ProcSortCpu,
showGroupedProcs: true,
filter: "",
2019-06-05 06:08:02 +08:00
}
self.entry = &ui.Entry{
Style: self.TitleStyle,
Label: " Filter: ",
2019-06-05 06:11:58 +08:00
Value: "",
2019-06-05 06:08:02 +08:00
UpdateCallback: func(val string) {
self.filter = val
self.update()
},
2018-02-19 15:25:02 +08:00
}
2019-01-01 08:55:50 +08:00
self.Title = " Processes "
2019-03-01 08:29:52 +08:00
self.ShowCursor = true
self.ShowLocation = true
2019-03-01 08:29:52 +08:00
self.ColGap = 3
2018-05-22 07:02:31 +08:00
self.PadLeft = 2
2019-03-01 08:29:52 +08:00
self.ColResizer = func() {
self.ColWidths = []int{
5, utils.MaxInt(self.Inner.Dx()-26, 10), 4, 4,
}
}
2018-02-19 15:25:02 +08:00
2018-03-28 05:27:23 +08:00
self.UniqueCol = 0
2019-03-01 08:29:52 +08:00
if self.showGroupedProcs {
2018-03-28 05:27:23 +08:00
self.UniqueCol = 1
2018-02-19 15:25:02 +08:00
}
2018-04-13 10:43:17 +08:00
self.update()
2018-02-19 15:25:02 +08:00
go func() {
2019-03-01 08:29:52 +08:00
for range time.NewTicker(self.updateInterval).C {
self.Lock()
2018-03-28 05:27:23 +08:00
self.update()
self.Unlock()
2018-02-19 15:25:02 +08:00
}
}()
2018-03-28 05:27:23 +08:00
return self
2018-02-19 15:25:02 +08:00
}
func (p *ProcWidget) EnableMetric() {
// There's (currently) no metric for this
}
func (self *ProcWidget) SetEditingFilter(editing bool) {
2019-06-05 06:08:02 +08:00
self.entry.SetEditing(editing)
}
func (self *ProcWidget) HandleEvent(e tui.Event) bool {
2019-06-05 06:08:02 +08:00
return self.entry.HandleEvent(e)
}
2019-06-05 06:08:02 +08:00
func (self *ProcWidget) SetRect(x1, y1, x2, y2 int) {
self.Table.SetRect(x1, y1, x2, y2)
self.entry.SetRect(x1+2, y2-1, x2-2, y2)
}
2019-06-05 06:08:02 +08:00
func (self *ProcWidget) Draw(buf *tui.Buffer) {
self.Table.Draw(buf)
self.entry.Draw(buf)
}
func (self *ProcWidget) filterProcs(procs []Proc) []Proc {
if self.filter == "" {
return procs
}
var filtered []Proc
for _, proc := range procs {
if strings.Contains(proc.FullCommand, self.filter) || strings.Contains(fmt.Sprintf("%d", proc.Pid), self.filter) {
filtered = append(filtered, proc)
}
}
return filtered
}
2019-03-01 08:29:52 +08:00
func (self *ProcWidget) update() {
procs, err := getProcs()
if err != nil {
log.Printf("failed to retrieve processes: %v", err)
return
}
2019-03-12 13:52:37 +08:00
// have to iterate over the entry number in order to modify the array in place
2019-03-01 08:29:52 +08:00
for i := range procs {
2019-03-12 13:52:37 +08:00
procs[i].Cpu /= float64(self.cpuCount)
2019-03-01 08:29:52 +08:00
}
procs = self.filterProcs(procs)
2019-03-01 08:29:52 +08:00
self.ungroupedProcs = procs
self.groupedProcs = groupProcs(procs)
self.sortProcs()
self.convertProcsToTableRows()
}
// sortProcs sorts either the grouped or ungrouped []Process based on the sortMethod.
2018-02-24 13:15:38 +08:00
// Called with every update, when the sort method is changed, and when processes are grouped and ungrouped.
2019-03-01 08:29:52 +08:00
func (self *ProcWidget) sortProcs() {
2018-03-28 05:27:23 +08:00
self.Header = []string{"Count", "Command", "CPU%", "Mem%"}
2018-02-19 15:25:02 +08:00
2019-03-01 08:29:52 +08:00
if !self.showGroupedProcs {
2018-03-28 05:27:23 +08:00
self.Header[0] = "PID"
2018-02-19 15:25:02 +08:00
}
2019-03-01 08:29:52 +08:00
var procs *[]Proc
if self.showGroupedProcs {
procs = &self.groupedProcs
} else {
procs = &self.ungroupedProcs
2018-02-19 15:25:02 +08:00
}
2018-03-28 05:27:23 +08:00
switch self.sortMethod {
2019-03-01 08:29:52 +08:00
case ProcSortCpu:
sort.Sort(sort.Reverse(SortProcsByCpu(*procs)))
self.Header[2] += DOWN_ARROW
case ProcSortPid:
if self.showGroupedProcs {
sort.Sort(sort.Reverse(SortProcsByPid(*procs)))
2018-02-19 15:25:02 +08:00
} else {
2019-03-01 08:29:52 +08:00
sort.Sort(SortProcsByPid(*procs))
2018-02-19 15:25:02 +08:00
}
2019-03-01 08:29:52 +08:00
self.Header[0] += DOWN_ARROW
case ProcSortMem:
sort.Sort(sort.Reverse(SortProcsByMem(*procs)))
self.Header[3] += DOWN_ARROW
2018-02-19 15:25:02 +08:00
}
}
2019-03-01 08:29:52 +08:00
// convertProcsToTableRows converts a []Proc to a [][]string and sets it to the table Rows
func (self *ProcWidget) convertProcsToTableRows() {
var procs *[]Proc
if self.showGroupedProcs {
procs = &self.groupedProcs
} else {
procs = &self.ungroupedProcs
}
2019-03-01 08:29:52 +08:00
strings := make([][]string, len(*procs))
for i := range *procs {
strings[i] = make([]string, 4)
strings[i][0] = strconv.Itoa(int((*procs)[i].Pid))
if self.showGroupedProcs {
strings[i][1] = (*procs)[i].CommandName
} else {
strings[i][1] = (*procs)[i].FullCommand
}
strings[i][2] = fmt.Sprintf("%4s", strconv.FormatFloat((*procs)[i].Cpu, 'f', 1, 64))
strings[i][3] = fmt.Sprintf("%4s", strconv.FormatFloat(float64((*procs)[i].Mem), 'f', 1, 64))
}
self.Rows = strings
}
2019-03-01 08:29:52 +08:00
func (self *ProcWidget) ChangeProcSortMethod(method ProcSortMethod) {
if self.sortMethod != method {
self.sortMethod = method
self.ScrollTop()
self.sortProcs()
self.convertProcsToTableRows()
}
}
2019-03-01 08:29:52 +08:00
func (self *ProcWidget) ToggleShowingGroupedProcs() {
self.showGroupedProcs = !self.showGroupedProcs
if self.showGroupedProcs {
self.UniqueCol = 1
} else {
self.UniqueCol = 0
}
2019-03-01 08:29:52 +08:00
self.ScrollTop()
self.sortProcs()
self.convertProcsToTableRows()
2018-02-19 15:25:02 +08:00
}
// KillProc kills a process or group of processes depending on if we're
// displaying the processes grouped or not.
func (self *ProcWidget) KillProc(sigName string) {
2019-03-01 08:29:52 +08:00
self.SelectedItem = ""
command := "kill"
if self.UniqueCol == 1 {
command = "pkill"
}
cmd := exec.Command(command, "--signal", sigName, self.Rows[self.SelectedRow][self.UniqueCol])
2019-03-01 08:29:52 +08:00
cmd.Start()
cmd.Wait()
}
// groupProcs groupes a []Proc based on command name.
2018-02-19 15:25:02 +08:00
// The first field changes from PID to count.
2019-03-01 08:29:52 +08:00
// Cpu and Mem are added together for each Proc.
func groupProcs(procs []Proc) []Proc {
groupedProcsMap := make(map[string]Proc)
for _, proc := range procs {
val, ok := groupedProcsMap[proc.CommandName]
2018-02-19 15:25:02 +08:00
if ok {
2019-03-01 08:29:52 +08:00
groupedProcsMap[proc.CommandName] = Proc{
val.Pid + 1,
val.CommandName,
"",
2019-03-01 08:29:52 +08:00
val.Cpu + proc.Cpu,
val.Mem + proc.Mem,
2018-02-19 15:25:02 +08:00
}
} else {
2019-03-01 08:29:52 +08:00
groupedProcsMap[proc.CommandName] = Proc{
2018-02-19 15:25:02 +08:00
1,
2019-03-01 08:29:52 +08:00
proc.CommandName,
"",
2019-03-01 08:29:52 +08:00
proc.Cpu,
proc.Mem,
2018-02-19 15:25:02 +08:00
}
}
}
2018-03-04 09:05:52 +08:00
2019-03-01 08:29:52 +08:00
groupedProcsList := make([]Proc, len(groupedProcsMap))
i := 0
for _, val := range groupedProcsMap {
groupedProcsList[i] = val
2018-02-19 15:25:02 +08:00
i++
}
2018-03-04 09:05:52 +08:00
2019-03-01 08:29:52 +08:00
return groupedProcsList
}
2019-03-01 08:29:52 +08:00
// []Proc Sorting //////////////////////////////////////////////////////////////
2018-02-19 15:25:02 +08:00
2019-03-01 08:29:52 +08:00
type SortProcsByCpu []Proc
2018-02-19 15:25:02 +08:00
// Len implements Sort interface
2019-03-01 08:29:52 +08:00
func (self SortProcsByCpu) Len() int {
return len(self)
2018-02-19 15:25:02 +08:00
}
// Swap implements Sort interface
2019-03-01 08:29:52 +08:00
func (self SortProcsByCpu) Swap(i, j int) {
self[i], self[j] = self[j], self[i]
2018-02-19 15:25:02 +08:00
}
// Less implements Sort interface
2019-03-01 08:29:52 +08:00
func (self SortProcsByCpu) Less(i, j int) bool {
return self[i].Cpu < self[j].Cpu
2018-02-19 15:25:02 +08:00
}
2019-03-01 08:29:52 +08:00
type SortProcsByPid []Proc
2018-02-19 15:25:02 +08:00
// Len implements Sort interface
2019-03-01 08:29:52 +08:00
func (self SortProcsByPid) Len() int {
return len(self)
2018-02-19 15:25:02 +08:00
}
// Swap implements Sort interface
2019-03-01 08:29:52 +08:00
func (self SortProcsByPid) Swap(i, j int) {
self[i], self[j] = self[j], self[i]
2018-02-19 15:25:02 +08:00
}
// Less implements Sort interface
2019-03-01 08:29:52 +08:00
func (self SortProcsByPid) Less(i, j int) bool {
return self[i].Pid < self[j].Pid
2018-02-19 15:25:02 +08:00
}
2019-03-01 08:29:52 +08:00
type SortProcsByMem []Proc
2018-02-19 15:25:02 +08:00
// Len implements Sort interface
2019-03-01 08:29:52 +08:00
func (self SortProcsByMem) Len() int {
return len(self)
2018-02-19 15:25:02 +08:00
}
// Swap implements Sort interface
2019-03-01 08:29:52 +08:00
func (self SortProcsByMem) Swap(i, j int) {
self[i], self[j] = self[j], self[i]
2018-02-19 15:25:02 +08:00
}
// Less implements Sort interface
2019-03-01 08:29:52 +08:00
func (self SortProcsByMem) Less(i, j int) bool {
return self[i].Mem < self[j].Mem
2018-02-19 15:25:02 +08:00
}