pcli/command/debug.rs
1use anyhow::Result;
2use serde::Serialize;
3use std::path::PathBuf;
4use std::process::Command;
5
6#[derive(Debug, clap::Subcommand)]
7pub enum DebugCmd {
8 /// Emit debugging info, useful for requesting support
9 Info,
10}
11
12impl DebugCmd {
13 pub fn offline(&self) -> bool {
14 true
15 }
16
17 pub fn exec(&self, data_dir: PathBuf) -> Result<()> {
18 match self {
19 DebugCmd::Info => {
20 let debug_info = DebugInfo::new(data_dir);
21 // Using derived serialization as a cheap Display impl for formatting
22 // the output. It's human-readable enough when pretty, plus we can parse it.
23 let d = serde_json::to_string_pretty(&debug_info)?;
24 println!("{d}");
25 Ok(())
26 }
27 }
28 }
29}
30
31/// Represents a fact sheet about system status, bottling up
32/// common support-related questions like "What chain are you on?"
33/// or "What version of Tendermint are you running?". Intended to display
34/// output via `pcli debug info`, for ease of pasting into chat or issues.
35// The DebugInfo struct is only used to print info to stdout,
36// so its field names won't be accessed elsewhere; thus allow dead_code.
37#[allow(dead_code)]
38#[derive(Debug, Serialize)]
39pub struct DebugInfo {
40 /// CometBFT version, if cometbft is found on PATH.
41 cometbft_version: Option<String>,
42 /// Tendermint version, if tendermint is found on PATH.
43 // Preserved for a while during Tendermint -> CometBFT,
44 // to aid in debugging.
45 tendermint_version: Option<String>,
46 /// pd version, if pd is found on PATH.
47 pd_version: Option<String>,
48 /// pcli version; baked in at compile time, so will always be present.
49 pcli_version: String,
50 /// Platform and architecture info for current host.
51 uname: Option<String>,
52 /// Status of directory for storing view info locally.
53 pcli_data_directory: Option<std::path::PathBuf>,
54 /// Status of pcli config TOML, containing key material for pcli.
55 pcli_config_file: Option<std::path::PathBuf>,
56}
57
58impl DebugInfo {
59 pub fn new(data_dir: std::path::PathBuf) -> Self {
60 let dd = Self::get_pcli_data_directory(data_dir);
61 Self {
62 cometbft_version: Self::get_cometbft_version(),
63 tendermint_version: Self::get_tendermint_version(),
64 pd_version: Self::get_pd_version(),
65 pcli_version: Self::get_pcli_version(),
66 uname: Self::get_uname(),
67 pcli_data_directory: dd.clone(),
68 pcli_config_file: Self::get_pcli_config_file(dd),
69 }
70 }
71 /// Attempt to retrieve version info for Tendermint by running
72 /// `tendermint version`. Depending on deployment, tendermint may not be on the PATH;
73 /// it may be in container context that `pcli` doesn't have access to. That's OK:
74 /// we'll just report `None` in that case.
75 fn get_tendermint_version() -> Option<String> {
76 let cmd = Command::new("tendermint").args(["version"]).output();
77 match cmd {
78 Ok(c) => match std::str::from_utf8(&c.stdout) {
79 Ok(o) => Some(o.trim_end().to_string()),
80 Err(_) => None,
81 },
82 Err(_) => None,
83 }
84 }
85 /// Attempt to retrieve version info for CometBFT by running
86 /// `cometbft version`. Depending on deployment, cometbft may not be on the PATH;
87 /// it may be in container context that `pcli` doesn't have access to. That's OK:
88 /// we'll just report `None` in that case.
89 fn get_cometbft_version() -> Option<String> {
90 let cmd = Command::new("cometbft").args(["version"]).output();
91 match cmd {
92 Ok(c) => match std::str::from_utf8(&c.stdout) {
93 Ok(o) => Some(o.trim_end().to_string()),
94 Err(_) => None,
95 },
96 Err(_) => None,
97 }
98 }
99 /// Return host info, including kernel and architecture. Should work
100 /// equally well on Linux or macOS; Windows will return None.
101 fn get_uname() -> Option<String> {
102 let cmd = Command::new("uname").args(["-a"]).output();
103 match cmd {
104 Ok(c) => match std::str::from_utf8(&c.stdout) {
105 Ok(o) => Some(o.trim_end().to_string()),
106 Err(_) => None,
107 },
108 Err(_) => None,
109 }
110 }
111 /// Return the version for `pcli` baked in at compile time.
112 fn get_pcli_version() -> String {
113 env!("CARGO_PKG_VERSION").to_string()
114 }
115 /// Attempt to find `pd` on PATH, and return its version number. Depending on deployment,
116 /// `pd` may not be on the path; it may in a container context elsewhere.
117 fn get_pd_version() -> Option<String> {
118 match Command::new("pd").args(["--version"]).output() {
119 Ok(c) => match std::str::from_utf8(&c.stdout) {
120 Ok(o) => Some(o.trim_end().to_string()),
121 Err(_) => None,
122 },
123 Err(_) => None,
124 }
125 }
126
127 /// Check whether data dir, as provided by arg-parsing, exists.
128 fn get_pcli_data_directory(data_dir: PathBuf) -> Option<PathBuf> {
129 match data_dir.exists() {
130 true => Some(data_dir),
131 false => None,
132 }
133 }
134 /// Check pcli config TOML file exists.
135 fn get_pcli_config_file(data_dir: Option<PathBuf>) -> Option<PathBuf> {
136 match data_dir {
137 Some(dd) => {
138 let mut k = dd;
139 k.push("config.toml");
140 if k.exists() {
141 Some(k)
142 } else {
143 None
144 }
145 }
146 None => None,
147 }
148 }
149}