proc_cli/commands/
stuck.rs

1//! `proc stuck` - Find stuck/hung processes
2//!
3//! Examples:
4//!   proc stuck              # Find processes stuck > 5 minutes
5//!   proc stuck --timeout 60 # Find processes stuck > 1 minute
6//!   proc stuck --kill       # Find and kill stuck processes
7
8use crate::core::Process;
9use crate::error::Result;
10use crate::ui::{OutputFormat, Printer};
11use clap::Args;
12use dialoguer::Confirm;
13use std::time::Duration;
14
15/// Find stuck/hung processes
16#[derive(Args, Debug)]
17pub struct StuckCommand {
18    /// Timeout in seconds to consider a process stuck (default: 300 = 5 minutes)
19    #[arg(long, short = 't', default_value = "300")]
20    pub timeout: u64,
21
22    /// Kill found stuck processes
23    #[arg(long, short = 'k')]
24    pub kill: bool,
25
26    /// Skip confirmation when killing
27    #[arg(long, short = 'y')]
28    pub yes: bool,
29
30    /// Output as JSON
31    #[arg(long, short = 'j')]
32    pub json: bool,
33
34    /// Show verbose output
35    #[arg(long, short = 'v')]
36    pub verbose: bool,
37}
38
39impl StuckCommand {
40    pub fn execute(&self) -> Result<()> {
41        let format = if self.json {
42            OutputFormat::Json
43        } else {
44            OutputFormat::Human
45        };
46        let printer = Printer::new(format, self.verbose);
47
48        let timeout = Duration::from_secs(self.timeout);
49        let processes = Process::find_stuck(timeout)?;
50
51        if processes.is_empty() {
52            printer.success(&format!(
53                "No stuck processes found (threshold: {}s)",
54                self.timeout
55            ));
56            return Ok(());
57        }
58
59        printer.warning(&format!(
60            "Found {} potentially stuck process{}",
61            processes.len(),
62            if processes.len() == 1 { "" } else { "es" }
63        ));
64        printer.print_processes(&processes);
65
66        // Kill if requested
67        if self.kill {
68            if !self.yes && !self.json {
69                let confirmed = Confirm::new()
70                    .with_prompt(format!(
71                        "Kill {} stuck process{}?",
72                        processes.len(),
73                        if processes.len() == 1 { "" } else { "es" }
74                    ))
75                    .default(false)
76                    .interact()
77                    .unwrap_or(false);
78
79                if !confirmed {
80                    printer.warning("Cancelled");
81                    return Ok(());
82                }
83            }
84
85            let mut killed = Vec::new();
86            let mut failed = Vec::new();
87
88            for proc in processes {
89                // Use kill_and_wait to ensure stuck processes are actually terminated
90                match proc.kill_and_wait() {
91                    Ok(_) => killed.push(proc),
92                    Err(e) => failed.push((proc, e.to_string())),
93                }
94            }
95
96            printer.print_kill_result(&killed, &failed);
97        }
98
99        Ok(())
100    }
101}