acorn_lib/doctor/
mod.rs

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