usereport/
analysis.rs

1use crate::{runner, Command, CommandResult, Runner};
2
3use chrono::{DateTime, Local};
4use serde::Serialize;
5use snafu::{ResultExt, Snafu};
6use std::{collections::HashMap, fmt::Debug};
7use uname;
8
9/// Error type
10#[derive(Debug, Snafu)]
11#[allow(missing_docs)]
12pub enum Error {
13    /// Context initialization failed
14    #[snafu(display("context initialization failed because {}", source))]
15    InitContextFailed { source: std::io::Error },
16    /// Analysis run failed
17    #[snafu(display("analysis failed because {}", source))]
18    RunAnalysisFailed { source: runner::Error },
19}
20
21/// Result type
22pub type Result<T, E = Error> = std::result::Result<T, E>;
23
24// Copy: This allows to reuse the into_iter object; safe for &Vec or &[]
25#[derive(Debug)]
26pub struct Analysis<'a, I: IntoIterator<Item = &'a Command> + Copy> {
27    runner:                Box<dyn Runner<'a, I>>,
28    hostinfos:             I,
29    commands:              I,
30    repetitions:           usize,
31    max_parallel_commands: usize,
32}
33
34impl<'a, I: IntoIterator<Item = &'a Command> + Copy> Analysis<'a, I> {
35    pub fn new(runner: Box<dyn Runner<'a, I>>, hostinfos: I, commands: I) -> Self {
36        Analysis {
37            hostinfos,
38            commands,
39            runner,
40            repetitions: 1,
41            max_parallel_commands: 64,
42        }
43    }
44
45    pub fn with_repetitions(self, repetitions: usize) -> Self { Analysis { repetitions, ..self } }
46
47    pub fn with_max_parallel_commands(self, max_parallel_commands: usize) -> Self {
48        Analysis {
49            max_parallel_commands,
50            ..self
51        }
52    }
53
54    pub fn run(&self, context: Context) -> Result<AnalysisReport> {
55        let hostinfo_results = self.run_commands(self.hostinfos)?;
56        let command_results = self.run_commands_rep(self.commands, self.repetitions)?;
57
58        Ok(AnalysisReport {
59            context,
60            hostinfo_results,
61            command_results,
62            repetitions: self.repetitions,
63            max_parallel_commands: self.max_parallel_commands,
64        })
65    }
66
67    fn run_commands_rep(&self, commands: I, repetitions: usize) -> Result<Vec<Vec<CommandResult>>> {
68        let mut results = Vec::new();
69        for _ in 0..repetitions {
70            let run_results = self.run_commands(commands)?;
71            results.push(run_results);
72        }
73
74        Ok(results)
75    }
76
77    fn run_commands(&self, commands: I) -> Result<Vec<CommandResult>> {
78        let results = self
79            .runner
80            .run(commands, self.max_parallel_commands)
81            .context(RunAnalysisFailed {})?;
82
83        Ok(results)
84    }
85}
86
87#[derive(Debug, Serialize)]
88pub struct AnalysisReport {
89    pub(crate) context:               Context,
90    pub(crate) hostinfo_results:      Vec<CommandResult>,
91    pub(crate) command_results:       Vec<Vec<CommandResult>>,
92    pub(crate) repetitions:           usize,
93    pub(crate) max_parallel_commands: usize,
94}
95
96impl AnalysisReport {
97    pub fn new(
98        context: Context,
99        hostinfo_results: Vec<CommandResult>,
100        command_results: Vec<Vec<CommandResult>>,
101        repetitions: usize,
102        max_parallel_commands: usize,
103    ) -> AnalysisReport {
104        AnalysisReport {
105            context,
106            hostinfo_results,
107            command_results,
108            repetitions,
109            max_parallel_commands,
110        }
111    }
112
113    pub fn context(&self) -> &Context { &self.context }
114
115    pub fn hostinfo_results(&self) -> &[CommandResult] { &self.hostinfo_results }
116
117    pub fn command_results(&self) -> &[Vec<CommandResult>] { &self.command_results }
118
119    pub fn repetitions(&self) -> usize { self.repetitions }
120
121    pub fn max_parallel_commands(&self) -> usize { self.max_parallel_commands }
122}
123
124#[derive(Debug, Serialize)]
125pub struct Context {
126    pub(crate) hostname:  String,
127    pub(crate) uname:     String,
128    pub(crate) date_time: DateTime<Local>,
129    pub(crate) more:      HashMap<String, String>,
130}
131
132impl Context {
133    pub fn new() -> Result<Context> {
134        let uname = uname::uname().context(InitContextFailed {})?;
135        let hostname = uname.nodename.to_string();
136        let uname = format!(
137            "{} {} {} {} {}",
138            uname.sysname, uname.nodename, uname.release, uname.version, uname.machine
139        );
140        let date_time = Local::now();
141
142        Ok(Context {
143            uname,
144            hostname,
145            date_time,
146            more: Default::default(),
147        })
148    }
149
150    pub fn add<T: Into<String>>(&mut self, key: T, value: T) -> &mut Context {
151        let _ = self.more.insert(key.into(), value.into());
152        self
153    }
154
155    pub fn hostname(&self) -> &str { &self.hostname }
156
157    pub fn uname(&self) -> &str { &self.uname }
158
159    pub fn date_time(&self) -> &DateTime<Local> { &self.date_time }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165
166    use spectral::prelude::*;
167
168    #[test]
169    fn ok() {
170        let hostinfos: Vec<Command> = Vec::new();
171        let commands: Vec<Command> = Vec::new();
172        let tr = runner::ThreadRunner::new();
173        let runner = Box::new(tr);
174        let analysis = Analysis::new(runner, &hostinfos, &commands)
175            .with_repetitions(1)
176            .with_max_parallel_commands(64);
177        let context = Context::new().expect("failed to create context");
178
179        let res = analysis.run(context);
180        asserting("Analysis run").that(&res).is_ok();
181    }
182
183    #[test]
184    fn second_runner() {
185        #[derive(Debug)]
186        struct MyRunner {};
187
188        impl<'a, I: IntoIterator<Item = &'a Command>> Runner<'a, I> for MyRunner {
189            fn run(&self, _commands: I, _max_parallel_commands: usize) -> runner::Result<Vec<CommandResult>> {
190                Ok(Vec::new())
191            }
192        }
193
194        let hostinfos: Vec<Command> = Vec::new();
195        let commands: Vec<Command> = Vec::new();
196        let runner = Box::new(MyRunner {});
197        let analysis = Analysis::new(runner, hostinfos.as_slice(), commands.as_slice())
198            .with_repetitions(1)
199            .with_max_parallel_commands(64);
200        let context = Context::new().expect("failed to create context");
201
202        let res = analysis.run(context);
203        asserting("Analysis run").that(&res).is_ok();
204    }
205}