1
0
mirror of https://github.com/tw93/Mole.git synced 2026-03-22 15:00:07 +00:00
Files
Mole/cmd/status/metrics_process.go
Felix 82e25632e0 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>
2026-03-21 08:22:01 +08:00

99 lines
2.0 KiB
Go

package main
import (
"context"
"fmt"
"runtime"
"sort"
"strconv"
"strings"
"time"
)
func collectProcesses() ([]ProcessInfo, error) {
if runtime.GOOS != "darwin" {
return nil, nil
}
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
out, err := runCmd(ctx, "ps", "-Aceo", "pid=,ppid=,pcpu=,pmem=,comm=", "-r")
if err != nil {
return nil, err
}
return parseProcessOutput(out), nil
}
func parseProcessOutput(raw string) []ProcessInfo {
var procs []ProcessInfo
for line := range strings.Lines(strings.TrimSpace(raw)) {
fields := strings.Fields(line)
if len(fields) < 5 {
continue
}
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{
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)
}