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}