mirror of
https://github.com/tw93/Mole.git
synced 2026-03-22 21:55:08 +00:00
feat(status): alert on persistent high-cpu processes (#602)
* feat(status): alert on persistent high-cpu processes * refactor(status): keep high-cpu alerts read-only * fix(status): address lint and sudo test regressions --------- Co-authored-by: Tw93 <hitw93@gmail.com>
This commit is contained in:
@@ -2,52 +2,97 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func collectTopProcesses() []ProcessInfo {
|
||||
func collectProcesses() ([]ProcessInfo, error) {
|
||||
if runtime.GOOS != "darwin" {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Use ps to get top processes by CPU.
|
||||
out, err := runCmd(ctx, "ps", "-Aceo", "pcpu,pmem,comm", "-r")
|
||||
out, err := runCmd(ctx, "ps", "-Aceo", "pid=,ppid=,pcpu=,pmem=,comm=", "-r")
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
return parseProcessOutput(out), nil
|
||||
}
|
||||
|
||||
func parseProcessOutput(raw string) []ProcessInfo {
|
||||
var procs []ProcessInfo
|
||||
i := 0
|
||||
for line := range strings.Lines(strings.TrimSpace(out)) {
|
||||
if i == 0 {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if i > 5 {
|
||||
break
|
||||
}
|
||||
i++
|
||||
for line := range strings.Lines(strings.TrimSpace(raw)) {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 3 {
|
||||
if len(fields) < 5 {
|
||||
continue
|
||||
}
|
||||
cpuVal, _ := strconv.ParseFloat(fields[0], 64)
|
||||
memVal, _ := strconv.ParseFloat(fields[1], 64)
|
||||
name := fields[len(fields)-1]
|
||||
|
||||
pid, err := strconv.Atoi(fields[0])
|
||||
if err != nil || pid <= 0 {
|
||||
continue
|
||||
}
|
||||
ppid, _ := strconv.Atoi(fields[1])
|
||||
cpuVal, err := strconv.ParseFloat(fields[2], 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
memVal, err := strconv.ParseFloat(fields[3], 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
command := strings.Join(fields[4:], " ")
|
||||
if command == "" {
|
||||
continue
|
||||
}
|
||||
name := command
|
||||
// Strip path from command name.
|
||||
if idx := strings.LastIndex(name, "/"); idx >= 0 {
|
||||
name = name[idx+1:]
|
||||
}
|
||||
procs = append(procs, ProcessInfo{
|
||||
Name: name,
|
||||
CPU: cpuVal,
|
||||
Memory: memVal,
|
||||
PID: pid,
|
||||
PPID: ppid,
|
||||
Name: name,
|
||||
Command: command,
|
||||
CPU: cpuVal,
|
||||
Memory: memVal,
|
||||
})
|
||||
}
|
||||
return procs
|
||||
}
|
||||
|
||||
func topProcesses(processes []ProcessInfo, limit int) []ProcessInfo {
|
||||
if limit <= 0 || len(processes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
procs := make([]ProcessInfo, len(processes))
|
||||
copy(procs, processes)
|
||||
sort.Slice(procs, func(i, j int) bool {
|
||||
if procs[i].CPU != procs[j].CPU {
|
||||
return procs[i].CPU > procs[j].CPU
|
||||
}
|
||||
if procs[i].Memory != procs[j].Memory {
|
||||
return procs[i].Memory > procs[j].Memory
|
||||
}
|
||||
return procs[i].PID < procs[j].PID
|
||||
})
|
||||
|
||||
if len(procs) > limit {
|
||||
procs = procs[:limit]
|
||||
}
|
||||
return procs
|
||||
}
|
||||
|
||||
func formatProcessLabel(proc ProcessInfo) string {
|
||||
if proc.Name != "" {
|
||||
return fmt.Sprintf("%s (%d)", proc.Name, proc.PID)
|
||||
}
|
||||
return fmt.Sprintf("pid %d", proc.PID)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user