package widgets import ( "fmt" "os/exec" "sort" "strconv" "time" ui "github.com/cjbassi/termui" "github.com/mattn/go-runewidth" psCPU "github.com/shirou/gopsutil/cpu" psProc "github.com/shirou/gopsutil/process" ) var arrowWidth int const ( UP = "▲" DOWN = "▼" ) func init() { arrowWidth = runewidth.StringWidth(UP) } // Process represents each process. 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 } func NewProc(keyPressed chan bool) *Proc { cpuCount, _ := psCPU.Counts(false) self := &Proc{ Table: ui.NewTable(), interval: time.Second, cpuCount: cpuCount, sortMethod: "c", group: true, KeyPressed: keyPressed, } self.ColResizer = self.ColResize self.Label = "Process List" self.ColWidths = []int{5, 10, 4, 4} self.UniqueCol = 0 if self.group { self.UniqueCol = 1 } self.keyBinds() self.update() ticker := time.NewTicker(self.interval) go func() { for range ticker.C { self.update() } }() return self } func (self *Proc) update() { 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() processes[i] = Process{ pid, command, cpu / float64(self.cpuCount), mem, } } self.ungroupedProcs = processes self.groupedProcs = Group(processes) self.Sort() } // 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. func (self *Proc) Sort() { self.Header = []string{"Count", "Command", "CPU%", "Mem%"} if !self.group { self.Header[0] = "PID" } processes := &self.ungroupedProcs if self.group { processes = &self.groupedProcs } switch self.sortMethod { case "c": sort.Sort(sort.Reverse(ProcessByCPU(*processes))) self.Header[2] += DOWN case "p": if self.group { sort.Sort(sort.Reverse(ProcessByPID(*processes))) } else { sort.Sort(ProcessByPID(*processes)) } self.Header[0] += DOWN case "m": sort.Sort(sort.Reverse(ProcessByMem(*processes))) self.Header[3] += DOWN } self.Rows = FieldsToStrings(*processes) } // ColResize overrides the default ColResize in the termui table. func (self *Proc) ColResize() { // calculate gap size based on total width self.Gap = 3 if self.X < 50 { self.Gap = 1 } else if self.X < 75 { self.Gap = 2 } self.CellXPos = []int{ self.Gap, self.Gap + self.ColWidths[0] + self.Gap, (self.X + 2) - self.Gap - self.ColWidths[3] - self.Gap - self.ColWidths[2], (self.X + 2) - self.Gap - self.ColWidths[3], } 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 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 } } func (self *Proc) keyBinds() { ui.On("", func(e ui.Event) { self.Click(e.MouseX, e.MouseY) self.KeyPressed <- true }) ui.On("", "", func(e ui.Event) { switch e.Key { case "": self.Down() case "": self.Up() } self.KeyPressed <- true }) ui.On("", "", func(e ui.Event) { switch e.Key { case "": self.Up() case "": self.Down() } self.KeyPressed <- true }) viKeys := []string{"j", "k", "gg", "G", "", "", "", ""} ui.On(viKeys, func(e ui.Event) { switch e.Key { case "j": self.Down() case "k": self.Up() case "gg": self.Top() case "G": self.Bottom() case "": self.HalfPageDown() case "": self.HalfPageUp() case "": self.PageDown() case "": self.PageUp() } self.KeyPressed <- true }) ui.On("dd", func(e ui.Event) { self.Kill() }) ui.On("", func(e ui.Event) { self.group = !self.group if self.group { self.UniqueCol = 1 } else { self.UniqueCol = 0 } self.Sort() self.Top() self.KeyPressed <- true }) ui.On("m", "c", "p", func(e ui.Event) { if self.sortMethod != e.Key { self.sortMethod = e.Key self.Top() self.Sort() self.KeyPressed <- true } }) } // 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 { groupedP := make(map[string]Process) for _, process := range P { val, ok := groupedP[process.Command] if ok { groupedP[process.Command] = Process{ val.PID + 1, val.Command, val.CPU + process.CPU, val.Mem + process.Mem, } } else { groupedP[process.Command] = Process{ 1, process.Command, process.CPU, process.Mem, } } } groupList := make([]Process, len(groupedP)) var i int for _, val := range groupedP { groupList[i] = val i++ } 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)) } return strings } // Kill kills process or group of processes. func (self *Proc) Kill() { self.SelectedItem = "" command := "kill" if self.UniqueCol == 1 { command = "pkill" } cmd := exec.Command(command, self.Rows[self.SelectedRow][self.UniqueCol]) cmd.Start() } ///////////////////////////////////////////////////////////////////////////////// // []Process Sorting // ///////////////////////////////////////////////////////////////////////////////// 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 }