acorn_lib/doctor/
mod.rs

1//! # Doctor module
2//!
3//! The purpose of this module is to enable the user to more easily use ACORN in more contexts.
4//!
5//! Toward this end, `acorn doctor` will
6//! * gather information about the system,
7//! * display information about the system,
8//! * and semi-automatically repair issues with the system.
9use crate::schema::validate::is_ip6;
10use crate::util::{print_values_as_table, to_string, Label, SemanticVersion};
11use human_units::FormatSize;
12use sysinfo::{Networks, System};
13use which::which;
14
15/// Trait for printing tables
16pub trait TableFormatPrint {
17    /// Initializes the struct
18    fn init() -> Self;
19    /// Prints the table
20    fn print(self);
21}
22/// Information about installed software
23#[derive(Debug, Clone)]
24pub struct InstalledSoftwareData {
25    /// Software version
26    pub version: Option<String>,
27    /// Path to software executable
28    pub path: Option<String>,
29}
30/// Information about system memory
31pub struct MemoryInformation {
32    /// Total memory
33    pub total: String,
34    /// Available memory
35    pub available: String,
36    /// Used memory
37    pub used: String,
38    /// Swap memory
39    pub swap: String,
40}
41/// Network details
42#[derive(Debug, Clone)]
43pub struct Network {
44    /// IP Address
45    pub ip_address: Vec<String>,
46    /// MAC Address
47    pub mac_address: String,
48    /// Maximum transmission unit (MTU)
49    pub mtu: String,
50}
51/// Information about system network
52#[derive(Debug, Clone)]
53pub struct NetworkInformation {
54    /// List of networks
55    pub networks: Vec<Network>,
56}
57// TODO: Add detailed CPU information printer
58/// Information about microprocessor
59#[derive(Debug, Clone)]
60pub struct Processor {
61    /// CPU frequency (GHz)
62    pub frequency: u64,
63    /// CPU brand (e.g. AMD, Intel, etc...)
64    pub brand: String,
65    /// CPU vendor (e.g. Dell, Lenovo, etc...)
66    pub vendor: String,
67}
68/// Information about system
69pub struct SystemInformation {
70    /// System name
71    pub name: String,
72    /// Kernel version
73    pub kernel_version: String,
74    /// Operating system (OS) version
75    pub os_version: String,
76    /// Host name
77    pub host_name: String,
78    /// CPU architecture
79    pub cpu_arch: String,
80    /// CPU count (number of cores)
81    pub cpu_count: String,
82}
83/// Details of specific installed software applicable to ACORN
84pub struct SystemSoftwareInformation {
85    /// ### ACORN
86    ///
87    /// The subject of this documentation
88    ///
89    /// <div class="warning">See these <a href="https://code.ornl.gov/research-enablement/acorn#installation">installation instructions</a> for information on how to install the ACORN CLI application</div>
90    pub acorn: InstalledSoftwareData,
91    /// ### Git
92    ///
93    /// Version control system
94    ///
95    /// See <https://git-scm.com/>
96    pub git: InstalledSoftwareData,
97    /// ### Node.js
98    ///
99    /// Scripting language runtime
100    ///
101    /// See <https://nodejs.org/en>
102    pub node: InstalledSoftwareData,
103    /// ### npm
104    ///
105    /// [`Node.js`] package manager
106    ///
107    /// See <https://www.npmjs.com/>
108    ///
109    /// [`Node.js`]: ./struct.SystemSoftwareInformation.html#structfield.node
110    pub npm: InstalledSoftwareData,
111    /// ### npx
112    ///
113    /// Run a command from a local or remote npm package
114    ///
115    /// Bundled with [`npm`]
116    ///
117    /// [`npm`]: ./struct.SystemSoftwareInformation.html#structfield.npm
118    pub npx: InstalledSoftwareData,
119    /// ### Pandoc
120    ///
121    /// File conversion tool
122    ///
123    /// See <https://pandoc.org/>
124    pub pandoc: InstalledSoftwareData,
125    /// ### Vale
126    ///
127    /// Prose linter
128    ///
129    /// See <https://vale.sh/>
130    pub vale: InstalledSoftwareData,
131}
132impl InstalledSoftwareData {
133    fn from_command(name: &str) -> InstalledSoftwareData {
134        InstalledSoftwareData {
135            version: match SemanticVersion::from_command(name) {
136                | Some(version) => Some(version.to_string()),
137                | None => None,
138            },
139            path: match which(name) {
140                | Ok(path) => Some(path.display().to_string()),
141                | Err(_) => None,
142            },
143        }
144    }
145    fn as_row(&self, title: &str) -> Vec<String> {
146        to_string(vec![
147            title,
148            &self.clone().is_installed(),
149            &self.clone().version.unwrap_or_else(|| "---".to_string()),
150            &self.clone().path.unwrap_or_else(|| "---".to_string()),
151        ])
152    }
153    fn is_installed(&self) -> String {
154        if self.version.is_some() {
155            Label::CHECKMARK.to_string()
156        } else {
157            Label::CAUTION.to_string()
158        }
159    }
160}
161impl TableFormatPrint for MemoryInformation {
162    fn init() -> MemoryInformation {
163        let mut sys = System::new_all();
164        sys.refresh_all();
165        MemoryInformation {
166            total: sys.total_memory().format_size().to_string(),
167            available: sys.available_memory().format_size().to_string(),
168            used: sys.used_memory().format_size().to_string(),
169            swap: format!("{} of {}", sys.used_swap().format_size(), sys.total_swap().format_size()),
170        }
171    }
172    fn print(self) {
173        let MemoryInformation {
174            total,
175            available,
176            used,
177            swap,
178        } = self;
179        let headers = vec!["Attribute", "Value"];
180        let rows = vec![
181            to_string(vec!["Total", &total]),
182            to_string(vec!["Available", &available]),
183            to_string(vec!["Used", &used]),
184            to_string(vec!["Swap", &swap]),
185        ];
186        print_values_as_table("Memory", headers, rows);
187    }
188}
189impl TableFormatPrint for NetworkInformation {
190    fn init() -> NetworkInformation {
191        let networks = Networks::new_with_refreshed_list()
192            .into_iter()
193            .map(|(_, network)| Network {
194                ip_address: parse_network_addresses(network),
195                mac_address: network.mac_address().to_string(),
196                mtu: network.mtu().to_string(),
197            })
198            .collect();
199        NetworkInformation { networks }
200    }
201    fn print(self) {
202        let headers = vec!["Network", "MAC Address", "MTU"];
203        let rows = self
204            .networks
205            .iter()
206            .filter_map(|network| {
207                if network.ip_address.is_empty() {
208                    None
209                } else {
210                    let ip = network.ip_address.join("\n");
211                    let mac = network.clone().mac_address;
212                    let mtu = network.clone().mtu;
213                    Some(vec![ip, mac, mtu])
214                }
215            })
216            .collect::<Vec<_>>();
217        print_values_as_table("Network", headers, rows);
218    }
219}
220impl TableFormatPrint for SystemInformation {
221    fn init() -> SystemInformation {
222        let mut sys = System::new_all();
223        sys.refresh_all();
224        SystemInformation {
225            name: System::name().unwrap(),
226            kernel_version: System::kernel_version().unwrap(),
227            os_version: System::os_version().unwrap(),
228            host_name: System::host_name().unwrap(),
229            cpu_arch: System::cpu_arch(),
230            cpu_count: sys.cpus().len().to_string(),
231        }
232    }
233    fn print(self) {
234        let SystemInformation {
235            name,
236            kernel_version,
237            os_version,
238            host_name,
239            cpu_arch,
240            cpu_count,
241        } = self;
242        let headers = vec!["Attribute", "Value"];
243        let rows = vec![
244            to_string(vec!["Name", &name]),
245            to_string(vec!["Kernel Version", &kernel_version]),
246            to_string(vec!["OS Version", &os_version]),
247            to_string(vec!["Host Name", &host_name]),
248            to_string(vec!["CPU Architecture", &cpu_arch]),
249            to_string(vec!["CPU Count", &cpu_count]),
250        ];
251        print_values_as_table("System", headers, rows);
252    }
253}
254impl TableFormatPrint for SystemSoftwareInformation {
255    fn init() -> SystemSoftwareInformation {
256        SystemSoftwareInformation {
257            acorn: InstalledSoftwareData::from_command("acorn"),
258            git: InstalledSoftwareData::from_command("git"),
259            node: InstalledSoftwareData::from_command("node"),
260            npm: InstalledSoftwareData::from_command("npm"),
261            npx: InstalledSoftwareData::from_command("npx"),
262            pandoc: InstalledSoftwareData::from_command("pandoc"),
263            vale: InstalledSoftwareData::from_command("vale"),
264        }
265    }
266    fn print(self) {
267        let SystemSoftwareInformation {
268            acorn,
269            git,
270            node,
271            npm,
272            npx,
273            pandoc,
274            vale,
275        } = self;
276        let headers = vec!["Name", "Installed", "Version", "Location"];
277        let rows = vec![
278            acorn.as_row("Acorn"),
279            git.as_row("Git"),
280            node.as_row("Node.js"),
281            npm.as_row("npm"),
282            npx.as_row("npx"),
283            pandoc.as_row("Pandoc"),
284            vale.as_row("Vale"),
285        ];
286        print_values_as_table("Software", headers, rows);
287    }
288}
289fn parse_network_addresses(data: &sysinfo::NetworkData) -> Vec<String> {
290    data.ip_networks()
291        .iter()
292        .map(|ip| ip.addr.to_string())
293        .filter(|x| is_ip6(x).is_err())
294        .collect::<Vec<String>>()
295}