2018-02-19 15:25:02 +08:00
|
|
|
package widgets
|
|
|
|
|
|
|
|
import (
|
2018-04-20 10:58:04 +08:00
|
|
|
"fmt"
|
2018-12-05 13:44:25 +08:00
|
|
|
"log"
|
2018-02-25 13:35:57 +08:00
|
|
|
"os/exec"
|
2018-02-19 15:25:02 +08:00
|
|
|
"sort"
|
|
|
|
"strconv"
|
2019-06-01 10:58:29 +08:00
|
|
|
"strings"
|
2018-02-19 15:25:02 +08:00
|
|
|
"time"
|
|
|
|
|
2019-06-01 10:58:29 +08:00
|
|
|
tui "github.com/gizak/termui/v3"
|
2020-06-27 00:50:15 +08:00
|
|
|
"github.com/xxxserxxx/gotop/v4/devices"
|
2020-04-24 03:07:08 +08:00
|
|
|
ui "github.com/xxxserxxx/gotop/v4/termui"
|
|
|
|
"github.com/xxxserxxx/gotop/v4/utils"
|
2018-02-19 15:25:02 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2020-04-28 09:33:41 +08:00
|
|
|
_downArrow = "▼"
|
2018-02-19 15:25:02 +08:00
|
|
|
)
|
|
|
|
|
2019-03-01 08:29:52 +08:00
|
|
|
type ProcSortMethod string
|
|
|
|
|
|
|
|
const (
|
2020-04-28 09:33:41 +08:00
|
|
|
ProcSortCPU ProcSortMethod = "c"
|
2019-03-01 08:29:52 +08:00
|
|
|
ProcSortMem = "m"
|
|
|
|
ProcSortPid = "p"
|
2022-02-20 11:02:36 +08:00
|
|
|
ProcSortCmd = "n"
|
2019-03-01 08:29:52 +08:00
|
|
|
)
|
2018-05-16 04:13:22 +08:00
|
|
|
|
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
|
2020-04-28 09:33:41 +08:00
|
|
|
CPU float64
|
2019-03-01 08:29:52 +08:00
|
|
|
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
|
2019-06-01 10:58:29 +08:00
|
|
|
filter string
|
2019-03-01 08:29:52 +08:00
|
|
|
groupedProcs []Proc
|
|
|
|
ungroupedProcs []Proc
|
|
|
|
showGroupedProcs bool
|
2018-02-19 15:25:02 +08:00
|
|
|
}
|
|
|
|
|
2019-03-08 15:43:10 +08:00
|
|
|
func NewProcWidget() *ProcWidget {
|
2020-06-27 00:50:15 +08:00
|
|
|
cpuCount, err := devices.CpuCount()
|
2018-12-05 13:44:25 +08:00
|
|
|
if err != nil {
|
2020-06-19 08:51:01 +08:00
|
|
|
log.Println(tr.Value("error.proc.err.count", err.Error()))
|
2018-12-05 13:44:25 +08:00
|
|
|
}
|
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,
|
2020-04-28 09:33:41 +08:00
|
|
|
sortMethod: ProcSortCPU,
|
2019-03-01 08:29:52 +08:00
|
|
|
showGroupedProcs: true,
|
2019-06-01 10:58:29 +08:00
|
|
|
filter: "",
|
2019-06-05 06:08:02 +08:00
|
|
|
}
|
|
|
|
self.entry = &ui.Entry{
|
|
|
|
Style: self.TitleStyle,
|
2020-06-19 08:51:01 +08:00
|
|
|
Label: tr.Value("widget.proc.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
|
|
|
}
|
2020-06-19 08:51:01 +08:00
|
|
|
self.Title = tr.Value("widget.proc.label")
|
2019-03-01 08:29:52 +08:00
|
|
|
self.ShowCursor = true
|
2019-03-23 06:03:00 +08:00
|
|
|
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 {
|
2019-03-08 15:43:10 +08:00
|
|
|
self.Lock()
|
2018-03-28 05:27:23 +08:00
|
|
|
self.update()
|
2019-03-08 15:43:10 +08:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2020-04-28 09:33:41 +08:00
|
|
|
func (proc *ProcWidget) EnableMetric() {
|
2020-02-18 23:44:29 +08:00
|
|
|
// There's (currently) no metric for this
|
|
|
|
}
|
|
|
|
|
2020-04-28 09:33:41 +08:00
|
|
|
func (proc *ProcWidget) SetEditingFilter(editing bool) {
|
|
|
|
proc.entry.SetEditing(editing)
|
2019-06-01 10:58:29 +08:00
|
|
|
}
|
|
|
|
|
2020-04-28 09:33:41 +08:00
|
|
|
func (proc *ProcWidget) HandleEvent(e tui.Event) bool {
|
|
|
|
return proc.entry.HandleEvent(e)
|
2019-06-04 07:02:22 +08:00
|
|
|
}
|
|
|
|
|
2020-04-28 09:33:41 +08:00
|
|
|
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)
|
2019-06-01 10:58:29 +08:00
|
|
|
}
|
|
|
|
|
2020-04-28 09:33:41 +08:00
|
|
|
func (proc *ProcWidget) Draw(buf *tui.Buffer) {
|
|
|
|
proc.Table.Draw(buf)
|
|
|
|
proc.entry.Draw(buf)
|
2019-06-01 10:58:29 +08:00
|
|
|
}
|
|
|
|
|
2020-04-28 09:33:41 +08:00
|
|
|
func (proc *ProcWidget) filterProcs(procs []Proc) []Proc {
|
|
|
|
if proc.filter == "" {
|
2019-06-01 10:58:29 +08:00
|
|
|
return procs
|
|
|
|
}
|
|
|
|
var filtered []Proc
|
2020-04-28 09:33:41 +08:00
|
|
|
for _, p := range procs {
|
|
|
|
if strings.Contains(p.FullCommand, proc.filter) || strings.Contains(fmt.Sprintf("%d", p.Pid), proc.filter) {
|
|
|
|
filtered = append(filtered, p)
|
2019-06-01 10:58:29 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return filtered
|
|
|
|
}
|
|
|
|
|
2020-04-28 09:33:41 +08:00
|
|
|
func (proc *ProcWidget) update() {
|
2019-03-01 08:29:52 +08:00
|
|
|
procs, err := getProcs()
|
|
|
|
if err != nil {
|
2020-06-19 08:51:01 +08:00
|
|
|
log.Printf(tr.Value("widget.proc.error.retrieve", err.Error()))
|
2019-03-01 08:29:52 +08:00
|
|
|
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 {
|
2020-04-28 09:33:41 +08:00
|
|
|
procs[i].CPU /= float64(proc.cpuCount)
|
2019-03-01 08:29:52 +08:00
|
|
|
}
|
|
|
|
|
2020-04-28 09:33:41 +08:00
|
|
|
procs = proc.filterProcs(procs)
|
|
|
|
proc.ungroupedProcs = procs
|
|
|
|
proc.groupedProcs = groupProcs(procs)
|
2019-03-01 08:29:52 +08:00
|
|
|
|
2020-04-28 09:33:41 +08:00
|
|
|
proc.sortProcs()
|
|
|
|
proc.convertProcsToTableRows()
|
2019-03-01 08:29:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2020-04-28 09:33:41 +08:00
|
|
|
func (proc *ProcWidget) sortProcs() {
|
2020-06-19 08:51:01 +08:00
|
|
|
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"),
|
|
|
|
}
|
2018-02-19 15:25:02 +08:00
|
|
|
|
2020-04-28 09:33:41 +08:00
|
|
|
if !proc.showGroupedProcs {
|
2020-06-19 08:51:01 +08:00
|
|
|
proc.Header[0] = tr.Value("widget.proc.header.pid")
|
2018-02-19 15:25:02 +08:00
|
|
|
}
|
|
|
|
|
2019-03-01 08:29:52 +08:00
|
|
|
var procs *[]Proc
|
2020-04-28 09:33:41 +08:00
|
|
|
if proc.showGroupedProcs {
|
|
|
|
procs = &proc.groupedProcs
|
2019-03-01 08:29:52 +08:00
|
|
|
} else {
|
2020-04-28 09:33:41 +08:00
|
|
|
procs = &proc.ungroupedProcs
|
2018-02-19 15:25:02 +08:00
|
|
|
}
|
|
|
|
|
2020-04-28 09:33:41 +08:00
|
|
|
switch proc.sortMethod {
|
|
|
|
case ProcSortCPU:
|
|
|
|
sort.Sort(sort.Reverse(SortProcsByCPU(*procs)))
|
|
|
|
proc.Header[2] += _downArrow
|
2019-03-01 08:29:52 +08:00
|
|
|
case ProcSortPid:
|
2020-04-28 09:33:41 +08:00
|
|
|
if proc.showGroupedProcs {
|
2019-03-01 08:29:52 +08:00
|
|
|
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
|
|
|
}
|
2020-04-28 09:33:41 +08:00
|
|
|
proc.Header[0] += _downArrow
|
2019-03-01 08:29:52 +08:00
|
|
|
case ProcSortMem:
|
|
|
|
sort.Sort(sort.Reverse(SortProcsByMem(*procs)))
|
2020-04-28 09:33:41 +08:00
|
|
|
proc.Header[3] += _downArrow
|
2022-02-20 11:02:36 +08:00
|
|
|
case ProcSortCmd:
|
|
|
|
sort.Sort(sort.Reverse(SortProcsByCmd(*procs)))
|
|
|
|
proc.Header[1] += _downArrow
|
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
|
2020-04-28 09:33:41 +08:00
|
|
|
func (proc *ProcWidget) convertProcsToTableRows() {
|
2019-03-01 08:29:52 +08:00
|
|
|
var procs *[]Proc
|
2020-04-28 09:33:41 +08:00
|
|
|
if proc.showGroupedProcs {
|
|
|
|
procs = &proc.groupedProcs
|
2019-03-01 08:29:52 +08:00
|
|
|
} else {
|
2020-04-28 09:33:41 +08:00
|
|
|
procs = &proc.ungroupedProcs
|
2018-02-25 13:35:57 +08:00
|
|
|
}
|
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))
|
2020-04-28 09:33:41 +08:00
|
|
|
if proc.showGroupedProcs {
|
2019-03-01 08:29:52 +08:00
|
|
|
strings[i][1] = (*procs)[i].CommandName
|
|
|
|
} else {
|
|
|
|
strings[i][1] = (*procs)[i].FullCommand
|
|
|
|
}
|
2020-04-28 09:33:41 +08:00
|
|
|
strings[i][2] = fmt.Sprintf("%4s", strconv.FormatFloat((*procs)[i].CPU, 'f', 1, 64))
|
2019-03-01 08:29:52 +08:00
|
|
|
strings[i][3] = fmt.Sprintf("%4s", strconv.FormatFloat(float64((*procs)[i].Mem), 'f', 1, 64))
|
|
|
|
}
|
2020-04-28 09:33:41 +08:00
|
|
|
proc.Rows = strings
|
2018-02-25 13:35:57 +08:00
|
|
|
}
|
|
|
|
|
2020-04-28 09:33:41 +08:00
|
|
|
func (proc *ProcWidget) ChangeProcSortMethod(method ProcSortMethod) {
|
|
|
|
if proc.sortMethod != method {
|
|
|
|
proc.sortMethod = method
|
|
|
|
proc.ScrollTop()
|
|
|
|
proc.sortProcs()
|
|
|
|
proc.convertProcsToTableRows()
|
2018-11-30 10:17:13 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-28 09:33:41 +08:00
|
|
|
func (proc *ProcWidget) ToggleShowingGroupedProcs() {
|
|
|
|
proc.showGroupedProcs = !proc.showGroupedProcs
|
|
|
|
if proc.showGroupedProcs {
|
|
|
|
proc.UniqueCol = 1
|
2018-11-30 10:17:13 +08:00
|
|
|
} else {
|
2020-04-28 09:33:41 +08:00
|
|
|
proc.UniqueCol = 0
|
2018-11-30 10:17:13 +08:00
|
|
|
}
|
2020-04-28 09:33:41 +08:00
|
|
|
proc.ScrollTop()
|
|
|
|
proc.sortProcs()
|
|
|
|
proc.convertProcsToTableRows()
|
2018-02-19 15:25:02 +08:00
|
|
|
}
|
|
|
|
|
2019-10-04 08:59:22 +08:00
|
|
|
// KillProc kills a process or group of processes depending on if we're
|
|
|
|
// displaying the processes grouped or not.
|
2020-04-28 09:33:41 +08:00
|
|
|
func (proc *ProcWidget) KillProc(sigName string) {
|
|
|
|
proc.SelectedItem = ""
|
2019-03-01 08:29:52 +08:00
|
|
|
command := "kill"
|
2020-04-28 09:33:41 +08:00
|
|
|
if proc.UniqueCol == 1 {
|
2019-03-01 08:29:52 +08:00
|
|
|
command = "pkill"
|
|
|
|
}
|
2020-04-28 09:33:41 +08:00
|
|
|
cmd := exec.Command(command, "--signal", sigName, proc.Rows[proc.SelectedRow][proc.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,
|
2018-05-17 05:38:28 +08:00
|
|
|
"",
|
2020-04-28 09:33:41 +08:00
|
|
|
val.CPU + proc.CPU,
|
2019-03-01 08:29:52 +08:00
|
|
|
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,
|
2018-05-17 05:38:28 +08:00
|
|
|
"",
|
2020-04-28 09:33:41 +08:00
|
|
|
proc.CPU,
|
2019-03-01 08:29:52 +08:00
|
|
|
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
|
2018-02-25 13:35:57 +08:00
|
|
|
}
|
|
|
|
|
2019-03-01 08:29:52 +08:00
|
|
|
// []Proc Sorting //////////////////////////////////////////////////////////////
|
2018-02-19 15:25:02 +08:00
|
|
|
|
2020-04-28 09:33:41 +08:00
|
|
|
type SortProcsByCPU []Proc
|
2018-02-19 15:25:02 +08:00
|
|
|
|
|
|
|
// Len implements Sort interface
|
2020-04-28 09:33:41 +08:00
|
|
|
func (procs SortProcsByCPU) Len() int {
|
|
|
|
return len(procs)
|
2018-02-19 15:25:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Swap implements Sort interface
|
2020-04-28 09:33:41 +08:00
|
|
|
func (procs SortProcsByCPU) Swap(i, j int) {
|
|
|
|
procs[i], procs[j] = procs[j], procs[i]
|
2018-02-19 15:25:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Less implements Sort interface
|
2020-04-28 09:33:41 +08:00
|
|
|
func (procs SortProcsByCPU) Less(i, j int) bool {
|
|
|
|
return procs[i].CPU < procs[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
|
2020-04-28 09:33:41 +08:00
|
|
|
func (procs SortProcsByPid) Len() int {
|
|
|
|
return len(procs)
|
2018-02-19 15:25:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Swap implements Sort interface
|
2020-04-28 09:33:41 +08:00
|
|
|
func (procs SortProcsByPid) Swap(i, j int) {
|
|
|
|
procs[i], procs[j] = procs[j], procs[i]
|
2018-02-19 15:25:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Less implements Sort interface
|
2020-04-28 09:33:41 +08:00
|
|
|
func (procs SortProcsByPid) Less(i, j int) bool {
|
|
|
|
return procs[i].Pid < procs[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
|
2020-04-28 09:33:41 +08:00
|
|
|
func (procs SortProcsByMem) Len() int {
|
|
|
|
return len(procs)
|
2018-02-19 15:25:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Swap implements Sort interface
|
2020-04-28 09:33:41 +08:00
|
|
|
func (procs SortProcsByMem) Swap(i, j int) {
|
|
|
|
procs[i], procs[j] = procs[j], procs[i]
|
2018-02-19 15:25:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Less implements Sort interface
|
2020-04-28 09:33:41 +08:00
|
|
|
func (procs SortProcsByMem) Less(i, j int) bool {
|
|
|
|
return procs[i].Mem < procs[j].Mem
|
2018-02-19 15:25:02 +08:00
|
|
|
}
|
2022-02-20 11:02:36 +08:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|