libimagdiagnosticscmd/
lib.rs

1//
2// imag - the personal information management suite for the commandline
3// Copyright (C) 2015-2020 Matthias Beyer <mail@beyermatthias.de> and contributors
4//
5// This library is free software; you can redistribute it and/or
6// modify it under the terms of the GNU Lesser General Public
7// License as published by the Free Software Foundation; version
8// 2.1 of the License.
9//
10// This library is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13// Lesser General Public License for more details.
14//
15// You should have received a copy of the GNU Lesser General Public
16// License along with this library; if not, write to the Free Software
17// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18//
19
20#![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
108/// Marker enum for implementing ImagApplication on
109///
110/// This is used by binaries crates to execute business logic
111/// or to build a CLI completion.
112pub 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}