1
0
mirror of https://github.com/tw93/Mole.git synced 2026-03-22 17: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:
Felix
2026-03-21 08:22:01 +08:00
committed by GitHub
parent a99f9f97f2
commit 82e25632e0
9 changed files with 577 additions and 54 deletions

View File

@@ -21,7 +21,10 @@ var (
BuildTime = ""
// Command-line flags
jsonOutput = flag.Bool("json", false, "output metrics as JSON instead of TUI")
jsonOutput = flag.Bool("json", false, "output metrics as JSON instead of TUI")
procCPUThreshold = flag.Float64("proc-cpu-threshold", 100, "alert when a process stays above this CPU percent")
procCPUWindow = flag.Duration("proc-cpu-window", 5*time.Minute, "continuous duration a process must exceed the CPU threshold")
procCPUAlerts = flag.Bool("proc-cpu-alerts", true, "enable persistent high-CPU process alerts")
)
func shouldUseJSONOutput(forceJSON bool, stdout *os.File) bool {
@@ -116,11 +119,29 @@ func saveCatHidden(hidden bool) {
func newModel() model {
return model{
collector: NewCollector(),
collector: NewCollector(processWatchOptionsFromFlags()),
catHidden: loadCatHidden(),
}
}
func processWatchOptionsFromFlags() ProcessWatchOptions {
return ProcessWatchOptions{
Enabled: *procCPUAlerts,
CPUThreshold: *procCPUThreshold,
Window: *procCPUWindow,
}
}
func validateFlags() error {
if *procCPUThreshold < 0 {
return fmt.Errorf("--proc-cpu-threshold must be >= 0")
}
if *procCPUWindow <= 0 {
return fmt.Errorf("--proc-cpu-window must be > 0")
}
return nil
}
func (m model) Init() tea.Cmd {
return tea.Batch(tickAfter(0), animTick())
}
@@ -179,6 +200,7 @@ func (m model) View() string {
}
header, mole := renderHeader(m.metrics, m.errMessage, m.animFrame, termWidth, m.catHidden)
alertBar := renderProcessAlertBar(m.metrics.ProcessAlerts, termWidth)
var cardContent string
if termWidth <= 80 {
@@ -204,6 +226,9 @@ func (m model) View() string {
// Combine header, mole, and cards with consistent spacing
parts := []string{header}
if alertBar != "" {
parts = append(parts, alertBar)
}
if mole != "" {
parts = append(parts, mole)
}
@@ -235,7 +260,7 @@ func animTickWithSpeed(cpuUsage float64) tea.Cmd {
// runJSONMode collects metrics once and outputs as JSON.
func runJSONMode() {
collector := NewCollector()
collector := NewCollector(processWatchOptionsFromFlags())
// First collection initializes network state (returns nil for network)
_, _ = collector.Collect()
@@ -269,6 +294,10 @@ func runTUIMode() {
func main() {
flag.Parse()
if err := validateFlags(); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(2)
}
if shouldUseJSONOutput(*jsonOutput, os.Stdout) {
runJSONMode()
@@ -276,3 +305,13 @@ func main() {
runTUIMode()
}
}
func activeAlerts(alerts []ProcessAlert) []ProcessAlert {
var active []ProcessAlert
for _, alert := range alerts {
if alert.Status == "active" {
active = append(active, alert)
}
}
return active
}