xmtop/widgets/proc.go
2022-02-19 22:02:36 -05:00

360 lines
8.0 KiB
Go

package widgets
import (
"fmt"
"log"
"os/exec"
"sort"
"strconv"
"strings"
"time"
tui "github.com/gizak/termui/v3"
"github.com/xxxserxxx/gotop/v4/devices"
ui "github.com/xxxserxxx/gotop/v4/termui"
"github.com/xxxserxxx/gotop/v4/utils"
)
const (
_downArrow = "▼"
)
type ProcSortMethod string
const (
ProcSortCPU ProcSortMethod = "c"
ProcSortMem = "m"
ProcSortPid = "p"
ProcSortCmd = "n"
)
type Proc struct {
Pid int
CommandName string
FullCommand string
CPU float64
Mem float64
}
type ProcWidget struct {
*ui.Table
entry *ui.Entry
cpuCount int
updateInterval time.Duration
sortMethod ProcSortMethod
filter string
groupedProcs []Proc
ungroupedProcs []Proc
showGroupedProcs bool
}
func NewProcWidget() *ProcWidget {
cpuCount, err := devices.CpuCount()
if err != nil {
log.Println(tr.Value("error.proc.err.count", err.Error()))
}
self := &ProcWidget{
Table: ui.NewTable(),
updateInterval: time.Second,
cpuCount: cpuCount,
sortMethod: ProcSortCPU,
showGroupedProcs: true,
filter: "",
}
self.entry = &ui.Entry{
Style: self.TitleStyle,
Label: tr.Value("widget.proc.filter"),
Value: "",
UpdateCallback: func(val string) {
self.filter = val
self.update()
},
}
self.Title = tr.Value("widget.proc.label")
self.ShowCursor = true
self.ShowLocation = true
self.ColGap = 3
self.PadLeft = 2
self.ColResizer = func() {
self.ColWidths = []int{
5, utils.MaxInt(self.Inner.Dx()-26, 10), 4, 4,
}
}
self.UniqueCol = 0
if self.showGroupedProcs {
self.UniqueCol = 1
}
self.update()
go func() {
for range time.NewTicker(self.updateInterval).C {
self.Lock()
self.update()
self.Unlock()
}
}()
return self
}
func (proc *ProcWidget) EnableMetric() {
// There's (currently) no metric for this
}
func (proc *ProcWidget) SetEditingFilter(editing bool) {
proc.entry.SetEditing(editing)
}
func (proc *ProcWidget) HandleEvent(e tui.Event) bool {
return proc.entry.HandleEvent(e)
}
func (proc *ProcWidget) SetRect(x1, y1, x2, y2 int) {
proc.Table.SetRect(x1, y1, x2, y2)
proc.entry.SetRect(x1+2, y2-1, x2-2, y2)
}
func (proc *ProcWidget) Draw(buf *tui.Buffer) {
proc.Table.Draw(buf)
proc.entry.Draw(buf)
}
func (proc *ProcWidget) filterProcs(procs []Proc) []Proc {
if proc.filter == "" {
return procs
}
var filtered []Proc
for _, p := range procs {
if strings.Contains(p.FullCommand, proc.filter) || strings.Contains(fmt.Sprintf("%d", p.Pid), proc.filter) {
filtered = append(filtered, p)
}
}
return filtered
}
func (proc *ProcWidget) update() {
procs, err := getProcs()
if err != nil {
log.Printf(tr.Value("widget.proc.error.retrieve", err.Error()))
return
}
// have to iterate over the entry number in order to modify the array in place
for i := range procs {
procs[i].CPU /= float64(proc.cpuCount)
}
procs = proc.filterProcs(procs)
proc.ungroupedProcs = procs
proc.groupedProcs = groupProcs(procs)
proc.sortProcs()
proc.convertProcsToTableRows()
}
// sortProcs sorts either the grouped or ungrouped []Process based on the sortMethod.
// Called with every update, when the sort method is changed, and when processes are grouped and ungrouped.
func (proc *ProcWidget) sortProcs() {
proc.Header = []string{
tr.Value("widget.proc.header.count"),
tr.Value("widget.proc.header.command"),
tr.Value("widget.proc.header.cpu"),
tr.Value("widget.proc.header.mem"),
}
if !proc.showGroupedProcs {
proc.Header[0] = tr.Value("widget.proc.header.pid")
}
var procs *[]Proc
if proc.showGroupedProcs {
procs = &proc.groupedProcs
} else {
procs = &proc.ungroupedProcs
}
switch proc.sortMethod {
case ProcSortCPU:
sort.Sort(sort.Reverse(SortProcsByCPU(*procs)))
proc.Header[2] += _downArrow
case ProcSortPid:
if proc.showGroupedProcs {
sort.Sort(sort.Reverse(SortProcsByPid(*procs)))
} else {
sort.Sort(SortProcsByPid(*procs))
}
proc.Header[0] += _downArrow
case ProcSortMem:
sort.Sort(sort.Reverse(SortProcsByMem(*procs)))
proc.Header[3] += _downArrow
case ProcSortCmd:
sort.Sort(sort.Reverse(SortProcsByCmd(*procs)))
proc.Header[1] += _downArrow
}
}
// convertProcsToTableRows converts a []Proc to a [][]string and sets it to the table Rows
func (proc *ProcWidget) convertProcsToTableRows() {
var procs *[]Proc
if proc.showGroupedProcs {
procs = &proc.groupedProcs
} else {
procs = &proc.ungroupedProcs
}
strings := make([][]string, len(*procs))
for i := range *procs {
strings[i] = make([]string, 4)
strings[i][0] = strconv.Itoa(int((*procs)[i].Pid))
if proc.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))
}
proc.Rows = strings
}
func (proc *ProcWidget) ChangeProcSortMethod(method ProcSortMethod) {
if proc.sortMethod != method {
proc.sortMethod = method
proc.ScrollTop()
proc.sortProcs()
proc.convertProcsToTableRows()
}
}
func (proc *ProcWidget) ToggleShowingGroupedProcs() {
proc.showGroupedProcs = !proc.showGroupedProcs
if proc.showGroupedProcs {
proc.UniqueCol = 1
} else {
proc.UniqueCol = 0
}
proc.ScrollTop()
proc.sortProcs()
proc.convertProcsToTableRows()
}
// KillProc kills a process or group of processes depending on if we're
// displaying the processes grouped or not.
func (proc *ProcWidget) KillProc(sigName string) {
proc.SelectedItem = ""
command := "kill"
if proc.UniqueCol == 1 {
command = "pkill"
}
cmd := exec.Command(command, "--signal", sigName, proc.Rows[proc.SelectedRow][proc.UniqueCol])
cmd.Start()
cmd.Wait()
}
// groupProcs groupes a []Proc based on command name.
// The first field changes from PID to count.
// 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]
if ok {
groupedProcsMap[proc.CommandName] = Proc{
val.Pid + 1,
val.CommandName,
"",
val.CPU + proc.CPU,
val.Mem + proc.Mem,
}
} else {
groupedProcsMap[proc.CommandName] = Proc{
1,
proc.CommandName,
"",
proc.CPU,
proc.Mem,
}
}
}
groupedProcsList := make([]Proc, len(groupedProcsMap))
i := 0
for _, val := range groupedProcsMap {
groupedProcsList[i] = val
i++
}
return groupedProcsList
}
// []Proc Sorting //////////////////////////////////////////////////////////////
type SortProcsByCPU []Proc
// Len implements Sort interface
func (procs SortProcsByCPU) Len() int {
return len(procs)
}
// Swap implements Sort interface
func (procs SortProcsByCPU) Swap(i, j int) {
procs[i], procs[j] = procs[j], procs[i]
}
// Less implements Sort interface
func (procs SortProcsByCPU) Less(i, j int) bool {
return procs[i].CPU < procs[j].CPU
}
type SortProcsByPid []Proc
// Len implements Sort interface
func (procs SortProcsByPid) Len() int {
return len(procs)
}
// Swap implements Sort interface
func (procs SortProcsByPid) Swap(i, j int) {
procs[i], procs[j] = procs[j], procs[i]
}
// Less implements Sort interface
func (procs SortProcsByPid) Less(i, j int) bool {
return procs[i].Pid < procs[j].Pid
}
type SortProcsByMem []Proc
// Len implements Sort interface
func (procs SortProcsByMem) Len() int {
return len(procs)
}
// Swap implements Sort interface
func (procs SortProcsByMem) Swap(i, j int) {
procs[i], procs[j] = procs[j], procs[i]
}
// Less implements Sort interface
func (procs SortProcsByMem) Less(i, j int) bool {
return procs[i].Mem < procs[j].Mem
}
type SortProcsByCmd []Proc
// Len implements Sort interface
func (procs SortProcsByCmd) Len() int {
return len(procs)
}
// Swap implements Sort interface
func (procs SortProcsByCmd) Swap(i, j int) {
procs[i], procs[j] = procs[j], procs[i]
}
// Less implements Sort interface
func (procs SortProcsByCmd) Less(i, j int) bool {
return strings.ToLower(procs[j].CommandName) < strings.ToLower(procs[i].CommandName)
}