Rust · 3401 bytes Raw Blame History
1 //! gartopctl - Control CLI for gartop daemon
2
3 use anyhow::{Context, Result};
4 use clap::{Parser, Subcommand};
5 use gartop_ipc::{Command, Response, SortField};
6 use std::io::{BufRead, BufReader, Write};
7 use std::os::unix::net::UnixStream;
8
9 #[derive(Parser)]
10 #[command(name = "gartopctl")]
11 #[command(about = "Control the gartop daemon", long_about = None)]
12 struct Cli {
13 #[command(subcommand)]
14 command: Commands,
15 }
16
17 #[derive(Subcommand)]
18 enum Commands {
19 /// Get daemon status
20 Status,
21 /// Get current CPU stats
22 Cpu,
23 /// Get current memory stats
24 Memory,
25 /// Get CPU usage history
26 CpuHistory {
27 /// Number of samples
28 #[arg(short, long)]
29 count: Option<usize>,
30 },
31 /// Get memory usage history
32 MemoryHistory {
33 /// Number of samples
34 #[arg(short, long)]
35 count: Option<usize>,
36 },
37 /// List running processes
38 Processes {
39 /// Sort by field (cpu, memory, pid, name)
40 #[arg(short, long, default_value = "cpu")]
41 sort: String,
42 /// Limit results
43 #[arg(short, long)]
44 limit: Option<usize>,
45 },
46 /// Kill a process
47 Kill {
48 /// Process ID
49 pid: i32,
50 /// Signal number (default: 15 SIGTERM)
51 #[arg(short, long)]
52 signal: Option<i32>,
53 },
54 /// Reload configuration
55 Reload,
56 /// Stop the daemon
57 Quit,
58 }
59
60 fn main() -> Result<()> {
61 let cli = Cli::parse();
62
63 let cmd = match cli.command {
64 Commands::Status => Command::Status,
65 Commands::Cpu => Command::GetCpu,
66 Commands::Memory => Command::GetMemory,
67 Commands::CpuHistory { count } => Command::GetCpuHistory { count },
68 Commands::MemoryHistory { count } => Command::GetMemoryHistory { count },
69 Commands::Processes { sort, limit } => {
70 let sort_by = match sort.as_str() {
71 "memory" | "mem" => Some(SortField::Memory),
72 "pid" => Some(SortField::Pid),
73 "name" => Some(SortField::Name),
74 _ => Some(SortField::Cpu),
75 };
76 Command::GetProcesses { sort_by, limit }
77 }
78 Commands::Kill { pid, signal } => Command::KillProcess { pid, signal },
79 Commands::Reload => Command::Reload,
80 Commands::Quit => Command::Quit,
81 };
82
83 let response = send_command(&cmd)?;
84
85 if response.success {
86 if let Some(data) = response.data {
87 println!("{}", serde_json::to_string_pretty(&data)?);
88 } else {
89 println!("OK");
90 }
91 } else {
92 eprintln!("Error: {}", response.error.unwrap_or_default());
93 std::process::exit(1);
94 }
95
96 Ok(())
97 }
98
99 fn send_command(cmd: &Command) -> Result<Response> {
100 let socket_path = gartop_ipc::socket_path();
101
102 if !socket_path.exists() {
103 anyhow::bail!("gartop daemon not running (socket not found at {})", socket_path.display());
104 }
105
106 let mut stream = UnixStream::connect(&socket_path)
107 .with_context(|| "Failed to connect to gartop daemon")?;
108
109 // Send command as JSON line
110 let json = serde_json::to_string(cmd)?;
111 writeln!(stream, "{}", json)?;
112 stream.flush()?;
113
114 // Read response
115 let mut reader = BufReader::new(stream);
116 let mut line = String::new();
117 reader.read_line(&mut line)?;
118
119 let response: Response = serde_json::from_str(&line)?;
120 Ok(response)
121 }
122