libimagdiagnosticscmd/
lib.rs1#![forbid(unsafe_code)]
21
22#![deny(
23 non_camel_case_types,
24 non_snake_case,
25 path_statements,
26 trivial_numeric_casts,
27 unstable_features,
28 unused_allocation,
29 unused_import_braces,
30 unused_imports,
31 unused_must_use,
32 unused_mut,
33 unused_qualifications,
34 while_true,
35)]
36
37extern crate clap;
38extern crate toml;
39extern crate toml_query;
40extern crate indicatif;
41extern crate failure;
42extern crate resiter;
43#[macro_use] extern crate log;
44
45extern crate libimagrt;
46extern crate libimagerror;
47extern crate libimagentrylink;
48extern crate libimagstore;
49
50use std::io::Write;
51
52use libimagrt::runtime::Runtime;
53use libimagrt::application::ImagApplication;
54use libimagstore::store::FileLockEntry;
55use libimagstore::storeid::StoreId;
56use libimagentrylink::linkable::Linkable;
57
58use toml::Value;
59use toml_query::read::TomlValueReadExt;
60use indicatif::{ProgressBar, ProgressStyle};
61use failure::Fallible as Result;
62use failure::err_msg;
63use clap::App;
64use resiter::AndThen;
65use resiter::IterInnerOkOrElse;
66
67use std::collections::BTreeMap;
68
69mod ui;
70
71#[derive(Debug)]
72struct Diagnostic {
73 pub id: StoreId,
74 pub entry_store_version: String,
75 pub header_sections: usize,
76 pub bytecount_content: usize,
77 pub overall_byte_size: usize,
78 pub verified: bool,
79 pub num_links: usize,
80}
81
82impl Diagnostic {
83
84 fn for_entry<'a>(entry: &FileLockEntry<'a>) -> Result<Diagnostic> {
85 Ok(Diagnostic {
86 id: entry.get_location().clone(),
87 entry_store_version: entry
88 .get_header()
89 .read("imag.version")
90 .map(|opt| match opt {
91 Some(&Value::String(ref s)) => s.clone(),
92 Some(_) => "Non-String type in 'imag.version'".to_owned(),
93 None => "No version".to_owned(),
94 })
95 .unwrap_or_else(|_| "Error reading version".to_owned()),
96 header_sections: match entry.get_header() {
97 Value::Table(ref map) => map.keys().count(),
98 _ => 0
99 },
100 bytecount_content: entry.get_content().as_str().len(),
101 overall_byte_size: entry.to_str()?.as_str().len(),
102 verified: entry.verify().is_ok(),
103 num_links: entry.links().map(Iterator::count).unwrap_or(0),
104 })
105 }
106}
107
108pub enum ImagDiagnostics {}
113impl ImagApplication for ImagDiagnostics {
114 fn run(rt: Runtime) -> Result<()> {
115 let template = get_config(&rt, "rt.progressbar_style")?;
116 let tick_chars = get_config(&rt, "rt.progressticker_chars")?;
117 let verbose = rt.cli().is_present("more-output");
118
119 let style = if let Some(tick_chars) = tick_chars {
120 ProgressStyle::default_spinner().tick_chars(&tick_chars)
121 } else {
122 ProgressStyle::default_spinner()
123 };
124
125 let spinner = ProgressBar::new_spinner();
126 spinner.enable_steady_tick(100);
127 spinner.set_style(style);
128 spinner.set_message("Accumulating data");
129
130 let diags = rt.store()
131 .entries()?
132 .into_get_iter()
133 .map_inner_ok_or_else(|| err_msg("Unable to get entry"))
134 .and_then_ok(|e| {
135 let diag = Diagnostic::for_entry(&e);
136 debug!("Diagnostic for '{:?}' = {:?}", e.get_location(), diag);
137 drop(e);
138 diag
139 })
140 .collect::<Result<Vec<_>>>()?;
141
142 spinner.finish();
143 let n = diags.len();
144 let progress = ProgressBar::new(n as u64);
145 let style = if let Some(template) = template {
146 ProgressStyle::default_bar().template(&template)
147 } else {
148 ProgressStyle::default_bar()
149 };
150 progress.set_style(style);
151 progress.set_message("Calculating stats");
152
153 let mut version_counts : BTreeMap<String, usize> = BTreeMap::new();
154 let mut sum_header_sections = 0;
155 let mut sum_bytecount_content = 0;
156 let mut sum_overall_byte_size = 0;
157 let mut max_overall_byte_size : Option<(usize, StoreId)> = None;
158 let mut verified_count = 0;
159 let mut unverified_count = 0;
160 let mut unverified_entries = vec![];
161 let mut num_links = 0;
162 let mut max_links : Option<(usize, StoreId)> = None;
163
164 for diag in diags.iter() {
165 sum_header_sections += diag.header_sections;
166 sum_bytecount_content += diag.bytecount_content;
167 sum_overall_byte_size += diag.overall_byte_size;
168 match max_overall_byte_size {
169 None => max_overall_byte_size = Some((diag.num_links, diag.id.clone())),
170 Some((num, _)) => if num < diag.overall_byte_size {
171 max_overall_byte_size = Some((diag.overall_byte_size, diag.id.clone()));
172 }
173 }
174
175 let n = version_counts.get(&diag.entry_store_version).map(Clone::clone).unwrap_or(0);
176 version_counts.insert(diag.entry_store_version.clone(), n+1);
177
178 if diag.verified {
179 verified_count += 1;
180 } else {
181 unverified_count += 1;
182 if verbose {
183 unverified_entries.push(diag.id.clone());
184 }
185 }
186
187 num_links += diag.num_links;
188 match max_links {
189 None => max_links = Some((diag.num_links, diag.id.clone())),
190 Some((num, _)) => if num < diag.num_links {
191 max_links = Some((diag.num_links, diag.id.clone()));
192 }
193 }
194
195 progress.inc(1);
196 }
197
198 progress.finish();
199
200 let mut out = rt.stdout();
201
202 writeln!(out, "imag version {}", { env!("CARGO_PKG_VERSION") })?;
203 writeln!(out)?;
204 writeln!(out, "{} entries", n)?;
205
206 for (k, v) in version_counts {
207 writeln!(out, "{} entries with store version '{}'", v, k)?;
208 }
209 if n != 0 {
210 writeln!(out, "{} header sections in the average entry", sum_header_sections / n)?;
211 writeln!(out, "{} average content bytecount", sum_bytecount_content / n)?;
212 writeln!(out, "{} average overall bytecount", sum_overall_byte_size / n)?;
213
214 if let Some((num, path)) = max_overall_byte_size {
215 writeln!(out, "Largest Entry ({} bytes): {}", num, path.local_display_string())?;
216 }
217
218 writeln!(out, "{} average internal link count per entry", num_links / n)?;
219
220 if let Some((num, path)) = max_links {
221 writeln!(out, "Entry with most internal links ({}): {}",
222 num,
223 path.local_display_string())?;
224 }
225 writeln!(out, "{} verified entries", verified_count)?;
226 writeln!(out, "{} unverified entries", unverified_count)?;
227 if verbose {
228 for unve in unverified_entries.iter() {
229 writeln!(out, "Unverified: {}", unve)?;
230 }
231 }
232 }
233 Ok(())
234 }
235
236 fn build_cli<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
237 ui::build_ui(app)
238 }
239
240 fn name() -> &'static str {
241 env!("CARGO_PKG_NAME")
242 }
243
244 fn description() -> &'static str {
245 "Print diagnostics about imag and the imag store"
246 }
247
248 fn version() -> &'static str {
249 env!("CARGO_PKG_VERSION")
250 }
251}
252
253fn get_config(rt: &Runtime, s: &'static str) -> Result<Option<String>> {
254 let cfg = rt.config().ok_or_else(|| err_msg("No configuration"))?;
255
256 match cfg.read(s)? {
257 Some(&Value::String(ref s)) => Ok(Some(s.to_owned())),
258 Some(_) => Err(err_msg("Config type wrong: 'rt.progressbar_style' should be a string")),
259 None => Ok(None),
260 }
261}