proc_cli/commands/
stuck.rs1use crate::core::Process;
9use crate::error::Result;
10use crate::ui::{OutputFormat, Printer};
11use clap::Args;
12use dialoguer::Confirm;
13use std::time::Duration;
14
15#[derive(Args, Debug)]
17pub struct StuckCommand {
18 #[arg(long, short = 't', default_value = "300")]
20 pub timeout: u64,
21
22 #[arg(long, short = 'k')]
24 pub kill: bool,
25
26 #[arg(long, short = 'y')]
28 pub yes: bool,
29
30 #[arg(long, short = 'j')]
32 pub json: bool,
33
34 #[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 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 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}