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    /// Executes the stuck command, finding processes in uninterruptible states.
41    pub fn execute(&self) -> Result<()> {
42        let format = if self.json {
43            OutputFormat::Json
44        } else {
45            OutputFormat::Human
46        };
47        let printer = Printer::new(format, self.verbose);
48
49        let timeout = Duration::from_secs(self.timeout);
50        let processes = Process::find_stuck(timeout)?;
51
52        if processes.is_empty() {
53            printer.success(&format!(
54                "No stuck processes found (threshold: {}s)",
55                self.timeout
56            ));
57            return Ok(());
58        }
59
60        printer.warning(&format!(
61            "Found {} potentially stuck process{}",
62            processes.len(),
63            if processes.len() == 1 { "" } else { "es" }
64        ));
65        printer.print_processes(&processes);
66
67        // Kill if requested
68        if self.kill {
69            if !self.yes && !self.json {
70                let confirmed = Confirm::new()
71                    .with_prompt(format!(
72                        "Kill {} stuck process{}?",
73                        processes.len(),
74                        if processes.len() == 1 { "" } else { "es" }
75                    ))
76                    .default(false)
77                    .interact()
78                    .unwrap_or(false);
79
80                if !confirmed {
81                    printer.warning("Cancelled");
82                    return Ok(());
83                }
84            }
85
86            let mut killed = Vec::new();
87            let mut failed = Vec::new();
88
89            for proc in processes {
90                // Use kill_and_wait to ensure stuck processes are actually terminated
91                match proc.kill_and_wait() {
92                    Ok(_) => killed.push(proc),
93                    Err(e) => failed.push((proc, e.to_string())),
94                }
95            }
96
97            printer.print_kill_result(&killed, &failed);
98        }
99
100        Ok(())
101    }
102}