xmtop/widgets/proc.go

354 lines
7.1 KiB
Go
Raw Normal View History

2018-02-19 15:25:02 +08:00
package widgets
import (
"fmt"
"os/exec"
2018-02-19 15:25:02 +08:00
"sort"
"strconv"
"time"
2018-03-30 06:48:43 +08:00
ui "github.com/cjbassi/termui"
2018-04-15 00:01:15 +08:00
"github.com/mattn/go-runewidth"
2018-03-04 09:05:52 +08:00
psCPU "github.com/shirou/gopsutil/cpu"
psProc "github.com/shirou/gopsutil/process"
2018-02-19 15:25:02 +08:00
)
2018-04-15 00:01:15 +08:00
var arrowWidth int
2018-02-19 15:25:02 +08:00
const (
UP = "▲"
2018-03-04 09:05:52 +08:00
DOWN = "▼"
2018-02-19 15:25:02 +08:00
)
2018-04-15 00:01:15 +08:00
func init() {
arrowWidth = runewidth.StringWidth(UP)
}
2018-02-24 13:15:38 +08:00
// Process represents each process.
2018-02-19 15:25:02 +08:00
type Process struct {
PID int32
Command string
CPU float64
Mem float32
}
type Proc struct {
*ui.Table
cpuCount int
interval time.Duration
sortMethod string
groupedProcs []Process
ungroupedProcs []Process
group bool
KeyPressed chan bool
}
2018-04-13 10:43:17 +08:00
func NewProc(keyPressed chan bool) *Proc {
2018-03-04 09:05:52 +08:00
cpuCount, _ := psCPU.Counts(false)
2018-03-28 05:27:23 +08:00
self := &Proc{
2018-02-19 15:25:02 +08:00
Table: ui.NewTable(),
interval: time.Second,
cpuCount: cpuCount,
sortMethod: "c",
group: true,
KeyPressed: keyPressed,
}
2018-03-28 05:27:23 +08:00
self.ColResizer = self.ColResize
self.Label = "Process List"
self.ColWidths = []int{5, 10, 4, 4}
2018-02-19 15:25:02 +08:00
2018-03-28 05:27:23 +08:00
self.UniqueCol = 0
if self.group {
self.UniqueCol = 1
2018-02-19 15:25:02 +08:00
}
2018-03-28 05:27:23 +08:00
self.keyBinds()
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
2018-03-28 05:27:23 +08:00
ticker := time.NewTicker(self.interval)
2018-02-19 15:25:02 +08:00
go func() {
for range ticker.C {
2018-03-28 05:27:23 +08:00
self.update()
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
}
2018-03-28 05:27:23 +08:00
func (self *Proc) update() {
2018-03-04 09:05:52 +08:00
psProcesses, _ := psProc.Processes()
processes := make([]Process, len(psProcesses))
for i, psProcess := range psProcesses {
pid := psProcess.Pid
command, _ := psProcess.Name()
cpu, _ := psProcess.CPUPercent()
mem, _ := psProcess.MemoryPercent()
2018-02-19 15:25:02 +08:00
processes[i] = Process{
pid,
command,
2018-03-28 05:27:23 +08:00
cpu / float64(self.cpuCount),
2018-02-19 15:25:02 +08:00
mem,
}
}
2018-03-28 05:27:23 +08:00
self.ungroupedProcs = processes
self.groupedProcs = Group(processes)
2018-02-19 15:25:02 +08:00
2018-03-28 05:27:23 +08:00
self.Sort()
2018-02-19 15:25:02 +08:00
}
2018-02-24 13:15:38 +08:00
// Sort 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.
2018-03-28 05:27:23 +08:00
func (self *Proc) Sort() {
self.Header = []string{"Count", "Command", "CPU%", "Mem%"}
2018-02-19 15:25:02 +08:00
2018-03-28 05:27:23 +08:00
if !self.group {
self.Header[0] = "PID"
2018-02-19 15:25:02 +08:00
}
2018-03-28 05:27:23 +08:00
processes := &self.ungroupedProcs
if self.group {
processes = &self.groupedProcs
2018-02-19 15:25:02 +08:00
}
2018-03-28 05:27:23 +08:00
switch self.sortMethod {
2018-02-19 15:25:02 +08:00
case "c":
sort.Sort(sort.Reverse(ProcessByCPU(*processes)))
2018-03-28 05:27:23 +08:00
self.Header[2] += DOWN
2018-02-19 15:25:02 +08:00
case "p":
2018-03-28 05:27:23 +08:00
if self.group {
2018-02-19 15:25:02 +08:00
sort.Sort(sort.Reverse(ProcessByPID(*processes)))
} else {
sort.Sort(ProcessByPID(*processes))
}
2018-03-28 05:27:23 +08:00
self.Header[0] += DOWN
2018-02-19 15:25:02 +08:00
case "m":
sort.Sort(sort.Reverse(ProcessByMem(*processes)))
2018-03-28 05:27:23 +08:00
self.Header[3] += DOWN
2018-02-19 15:25:02 +08:00
}
2018-03-28 05:27:23 +08:00
self.Rows = FieldsToStrings(*processes)
2018-02-19 15:25:02 +08:00
}
// ColResize overrides the default ColResize in the termui table.
2018-03-28 05:27:23 +08:00
func (self *Proc) ColResize() {
// calculate gap size based on total width
2018-03-28 05:27:23 +08:00
self.Gap = 3
if self.X < 50 {
self.Gap = 1
} else if self.X < 75 {
self.Gap = 2
}
2018-03-28 05:27:23 +08:00
self.CellXPos = []int{
self.Gap,
self.Gap + self.ColWidths[0] + self.Gap,
2018-04-20 11:10:27 +08:00
(self.X + 2) - self.Gap - self.ColWidths[3] - self.Gap - self.ColWidths[2],
(self.X + 2) - self.Gap - self.ColWidths[3],
}
2018-04-14 23:18:18 +08:00
rowWidth := self.Gap +
self.ColWidths[0] + self.Gap +
self.ColWidths[1] + self.Gap +
self.ColWidths[2] + self.Gap +
self.ColWidths[3] + self.Gap
// only renders a column if it fits
2018-03-28 05:27:23 +08:00
if self.X < (rowWidth - self.Gap - self.ColWidths[3]) {
self.ColWidths[2] = 0
self.ColWidths[3] = 0
} else if self.X < rowWidth {
self.CellXPos[2] = self.CellXPos[3]
self.ColWidths[3] = 0
}
}
2018-03-28 05:27:23 +08:00
func (self *Proc) keyBinds() {
ui.On("<MouseLeft>", func(e ui.Event) {
2018-03-28 05:27:23 +08:00
self.Click(e.MouseX, e.MouseY)
self.KeyPressed <- true
2018-02-19 15:25:02 +08:00
})
ui.On("<MouseWheelUp>", "<MouseWheelDown>", func(e ui.Event) {
2018-02-19 15:25:02 +08:00
switch e.Key {
case "<MouseWheelDown>":
2018-03-28 05:27:23 +08:00
self.Down()
case "<MouseWheelUp>":
2018-03-28 05:27:23 +08:00
self.Up()
2018-02-19 15:25:02 +08:00
}
2018-03-28 05:27:23 +08:00
self.KeyPressed <- true
2018-02-19 15:25:02 +08:00
})
ui.On("<up>", "<down>", func(e ui.Event) {
switch e.Key {
case "<up>":
2018-03-28 05:27:23 +08:00
self.Up()
2018-02-19 15:25:02 +08:00
case "<down>":
2018-03-28 05:27:23 +08:00
self.Down()
2018-02-19 15:25:02 +08:00
}
2018-03-28 05:27:23 +08:00
self.KeyPressed <- true
2018-02-19 15:25:02 +08:00
})
viKeys := []string{"j", "k", "gg", "G", "<C-d>", "<C-u>", "<C-f>", "<C-b>"}
2018-02-19 15:25:02 +08:00
ui.On(viKeys, func(e ui.Event) {
switch e.Key {
case "j":
2018-03-28 05:27:23 +08:00
self.Down()
2018-02-19 15:25:02 +08:00
case "k":
2018-03-28 05:27:23 +08:00
self.Up()
2018-02-19 15:25:02 +08:00
case "gg":
2018-03-28 05:27:23 +08:00
self.Top()
2018-02-19 15:25:02 +08:00
case "G":
2018-03-28 05:27:23 +08:00
self.Bottom()
case "<C-d>":
2018-03-28 05:27:23 +08:00
self.HalfPageDown()
case "<C-u>":
2018-03-28 05:27:23 +08:00
self.HalfPageUp()
case "<C-f>":
2018-03-28 05:27:23 +08:00
self.PageDown()
case "<C-b>":
2018-03-28 05:27:23 +08:00
self.PageUp()
2018-02-19 15:25:02 +08:00
}
2018-03-28 05:27:23 +08:00
self.KeyPressed <- true
2018-02-19 15:25:02 +08:00
})
ui.On("dd", func(e ui.Event) {
2018-03-28 05:27:23 +08:00
self.Kill()
2018-02-19 15:25:02 +08:00
})
ui.On("<tab>", func(e ui.Event) {
2018-03-28 05:27:23 +08:00
self.group = !self.group
if self.group {
self.UniqueCol = 1
2018-02-19 15:25:02 +08:00
} else {
2018-03-28 05:27:23 +08:00
self.UniqueCol = 0
2018-02-19 15:25:02 +08:00
}
2018-03-28 05:27:23 +08:00
self.Sort()
self.Top()
self.KeyPressed <- true
2018-02-19 15:25:02 +08:00
})
ui.On("m", "c", "p", func(e ui.Event) {
2018-03-28 05:27:23 +08:00
if self.sortMethod != e.Key {
self.sortMethod = e.Key
self.Top()
self.Sort()
self.KeyPressed <- true
2018-02-19 15:25:02 +08:00
}
})
}
// Group groupes a []Process based on command name.
// The first field changes from PID to count.
// CPU and Mem are added together for each Process.
func Group(P []Process) []Process {
2018-03-04 09:05:52 +08:00
groupedP := make(map[string]Process)
for _, process := range P {
val, ok := groupedP[process.Command]
2018-02-19 15:25:02 +08:00
if ok {
2018-03-04 09:05:52 +08:00
groupedP[process.Command] = Process{
2018-02-19 15:25:02 +08:00
val.PID + 1,
val.Command,
2018-03-04 09:05:52 +08:00
val.CPU + process.CPU,
val.Mem + process.Mem,
2018-02-19 15:25:02 +08:00
}
} else {
2018-03-04 09:05:52 +08:00
groupedP[process.Command] = Process{
2018-02-19 15:25:02 +08:00
1,
2018-03-04 09:05:52 +08:00
process.Command,
process.CPU,
process.Mem,
2018-02-19 15:25:02 +08:00
}
}
}
2018-03-04 09:05:52 +08:00
groupList := make([]Process, len(groupedP))
var i int
for _, val := range groupedP {
2018-02-19 15:25:02 +08:00
groupList[i] = val
i++
}
2018-03-04 09:05:52 +08:00
2018-02-19 15:25:02 +08:00
return groupList
}
// FieldsToStrings converts a []Process to a [][]string
func FieldsToStrings(P []Process) [][]string {
strings := make([][]string, len(P))
for i, p := range P {
strings[i] = make([]string, 4)
strings[i][0] = strconv.Itoa(int(p.PID))
strings[i][1] = p.Command
strings[i][2] = fmt.Sprintf("%4s", strconv.FormatFloat(p.CPU, 'f', 1, 64))
strings[i][3] = fmt.Sprintf("%4s", strconv.FormatFloat(float64(p.Mem), 'f', 1, 32))
2018-02-19 15:25:02 +08:00
}
return strings
}
// Kill kills process or group of processes.
2018-03-28 05:27:23 +08:00
func (self *Proc) Kill() {
self.SelectedItem = ""
command := "kill"
2018-03-28 05:27:23 +08:00
if self.UniqueCol == 1 {
command = "pkill"
}
2018-03-28 05:27:23 +08:00
cmd := exec.Command(command, self.Rows[self.SelectedRow][self.UniqueCol])
cmd.Start()
}
2018-02-26 14:51:51 +08:00
/////////////////////////////////////////////////////////////////////////////////
// []Process Sorting //
/////////////////////////////////////////////////////////////////////////////////
2018-02-19 15:25:02 +08:00
type ProcessByCPU []Process
// Len implements Sort interface
func (P ProcessByCPU) Len() int {
return len(P)
}
// Swap implements Sort interface
func (P ProcessByCPU) Swap(i, j int) {
P[i], P[j] = P[j], P[i]
}
// Less implements Sort interface
func (P ProcessByCPU) Less(i, j int) bool {
return P[i].CPU < P[j].CPU
}
type ProcessByPID []Process
// Len implements Sort interface
func (P ProcessByPID) Len() int {
return len(P)
}
// Swap implements Sort interface
func (P ProcessByPID) Swap(i, j int) {
P[i], P[j] = P[j], P[i]
}
// Less implements Sort interface
func (P ProcessByPID) Less(i, j int) bool {
return P[i].PID < P[j].PID
}
type ProcessByMem []Process
// Len implements Sort interface
func (P ProcessByMem) Len() int {
return len(P)
}
// Swap implements Sort interface
func (P ProcessByMem) Swap(i, j int) {
P[i], P[j] = P[j], P[i]
}
// Less implements Sort interface
func (P ProcessByMem) Less(i, j int) bool {
return P[i].Mem < P[j].Mem
}