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#[derive(Debug, Snafu)]
11#[allow(missing_docs)]
12pub enum Error {
13 #[snafu(display("context initialization failed because {}", source))]
15 InitContextFailed { source: std::io::Error },
16 #[snafu(display("analysis failed because {}", source))]
18 RunAnalysisFailed { source: runner::Error },
19}
20
21pub type Result<T, E = Error> = std::result::Result<T, E>;
23
24#[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}