1use std::fmt;
2
3use chrono::{DateTime, Utc};
4use getset::{Getters, MutGetters, Setters};
5use prettytable::{format, Attr, Cell, Row, Table};
6use serde::{Deserialize, Serialize};
7
8use crate::{Benchmark, BenchmarkBuilder};
9
10#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Getters, Setters, MutGetters, Builder)]
11pub struct WrkResult {
12 #[builder(default)]
13 #[serde(default)]
14 #[getset(get = "pub", set = "pub", get_mut = "pub")]
15 success: bool,
16 #[builder(default = "String::new()")]
17 #[serde(default)]
18 #[getset(get = "pub", set = "pub", get_mut = "pub")]
19 error: String,
20 #[builder(default)]
21 #[serde(default)]
22 #[getset(get = "pub", set = "pub", get_mut = "pub")]
23 benchmark: Benchmark,
24 #[builder(default = "Utc::now()")]
25 #[serde(default = "Utc::now")]
26 #[getset(get = "pub", set = "pub", get_mut = "pub")]
27 date: DateTime<Utc>,
28 #[builder(default = "0.0")]
29 #[getset(get = "pub", set = "pub", get_mut = "pub")]
30 requests: f64,
31 #[builder(default = "0.0")]
32 #[getset(get = "pub", set = "pub", get_mut = "pub")]
33 errors: f64,
34 #[builder(default = "0.0")]
35 #[getset(get = "pub", set = "pub", get_mut = "pub")]
36 successes: f64,
37 #[builder(default = "0.0")]
38 #[getset(get = "pub", set = "pub", get_mut = "pub")]
39 requests_sec: f64,
40 #[builder(default = "0.0")]
41 #[getset(get = "pub", set = "pub", get_mut = "pub")]
42 avg_latency_ms: f64,
43 #[builder(default = "0.0")]
44 #[getset(get = "pub", set = "pub", get_mut = "pub")]
45 min_latency_ms: f64,
46 #[builder(default = "0.0")]
47 #[getset(get = "pub", set = "pub", get_mut = "pub")]
48 max_latency_ms: f64,
49 #[builder(default = "0.0")]
50 #[getset(get = "pub", set = "pub", get_mut = "pub")]
51 stdev_latency_ms: f64,
52 #[builder(default = "0.0")]
53 #[getset(get = "pub", set = "pub", get_mut = "pub")]
54 transfer_mb: f64,
55 #[builder(default = "0.0")]
56 #[getset(get = "pub", set = "pub", get_mut = "pub")]
57 errors_connect: f64,
58 #[builder(default = "0.0")]
59 #[getset(get = "pub", set = "pub", get_mut = "pub")]
60 errors_read: f64,
61 #[builder(default = "0.0")]
62 #[getset(get = "pub", set = "pub", get_mut = "pub")]
63 errors_write: f64,
64 #[builder(default = "0.0")]
65 #[getset(get = "pub", set = "pub", get_mut = "pub")]
66 errors_status: f64,
67 #[builder(default = "0.0")]
68 #[getset(get = "pub", set = "pub", get_mut = "pub")]
69 errors_timeout: f64,
70}
71
72impl Default for WrkResult {
73 fn default() -> Self {
74 Self {
75 success: false,
76 error: String::new(),
77 benchmark: Benchmark::default(),
78 date: Utc::now(),
79 requests: 0.0,
80 errors: 0.0,
81 successes: 0.0,
82 requests_sec: 0.0,
83 avg_latency_ms: 0.0,
84 min_latency_ms: 0.0,
85 max_latency_ms: 0.0,
86 stdev_latency_ms: 0.0,
87 transfer_mb: 0.0,
88 errors_connect: 0.0,
89 errors_read: 0.0,
90 errors_write: 0.0,
91 errors_status: 0.0,
92 errors_timeout: 0.0,
93 }
94 }
95}
96
97impl WrkResult {
98 pub fn fail(error: String) -> Self {
99 Self {
100 error,
101 ..Default::default()
102 }
103 }
104}
105
106#[derive(Debug, Default, Clone)]
107pub struct Deviation {
108 pub deviation: WrkResult,
109 pub new: WrkResult,
110 pub old: WrkResult,
111}
112
113impl Deviation {
114 pub fn new(new: WrkResult, old: WrkResult) -> Self {
115 let requests_sec = Self::calculate(new.requests_sec(), old.requests_sec());
116 let requests = Self::calculate(new.requests(), old.requests());
117 let successes = Self::calculate(new.successes(), old.successes());
118 let errors = Self::calculate(new.errors(), old.errors());
119 let avg_latency_ms = Self::calculate(new.avg_latency_ms(), old.avg_latency_ms());
120 let min_latency_ms = Self::calculate(new.min_latency_ms(), old.min_latency_ms());
121 let max_latency_ms = Self::calculate(new.max_latency_ms(), old.max_latency_ms());
122 let stdev_latency_ms = Self::calculate(new.stdev_latency_ms(), old.stdev_latency_ms());
123 let transfer_mb = Self::calculate(new.transfer_mb(), old.transfer_mb());
124 let errors_connect = Self::calculate(new.errors_connect(), old.errors_connect());
125 let errors_read = Self::calculate(new.errors_read(), old.errors_read());
126 let errors_write = Self::calculate(new.errors_write(), old.errors_write());
127 let errors_status = Self::calculate(new.errors_status(), old.errors_status());
128 let errors_timeout = Self::calculate(new.errors_timeout(), old.errors_timeout());
129 let deviation = WrkResultBuilder::default()
130 .date(*new.date())
131 .requests(requests)
132 .errors(errors)
133 .successes(successes)
134 .requests_sec(requests_sec)
135 .avg_latency_ms(avg_latency_ms)
136 .min_latency_ms(min_latency_ms)
137 .max_latency_ms(max_latency_ms)
138 .stdev_latency_ms(stdev_latency_ms)
139 .transfer_mb(transfer_mb)
140 .errors_connect(errors_connect)
141 .errors_read(errors_read)
142 .errors_write(errors_write)
143 .errors_status(errors_status)
144 .errors_timeout(errors_timeout)
145 .build()
146 .unwrap();
147 Self { deviation, new, old }
148 }
149
150 fn calculate(new: &f64, old: &f64) -> f64 {
151 (new - old) / old * 100.0
152 }
153
154 pub fn to_github_markdown(&self) -> String {
155 let mut result = String::from("### Rust Wrk benchmark report:\\n");
156 result += &format!(
157 "#### Duration: {} sec, Connections: {}, Threads: {}\\n\\n",
158 self.new.benchmark().duration().as_secs(),
159 self.new.benchmark().connections(),
160 self.new.benchmark().threads()
161 );
162 result += "|Measurement|Deviation|Current|Old|\\n|-|-|-|-|\\n";
163 result += &format!(
164 "|Requests/sec|{:.2}%|{}|{}|\\n",
165 self.deviation.requests_sec(),
166 self.new.requests_sec(),
167 self.old.requests_sec()
168 );
169 result += &format!(
170 "|Total requests|{:.2}%|{}|{}|\\n",
171 self.deviation.requests(),
172 self.new.requests(),
173 self.old.requests()
174 );
175 result += &format!(
176 "|Total errors|{:.2}%|{}|{}|\\n",
177 self.deviation.errors(),
178 self.new.errors(),
179 self.old.errors()
180 );
181 result += &format!(
182 "|Total successes|{:.2}%|{}|{}|\\n",
183 self.deviation.successes(),
184 self.new.successes(),
185 self.old.successes()
186 );
187 result += &format!(
188 "|Average latency ms|{:.2}%|{}|{}|\\n",
189 self.deviation.avg_latency_ms(),
190 self.new.avg_latency_ms(),
191 self.old.avg_latency_ms()
192 );
193 result += &format!(
194 "|Minimum latency ms|{:.2}%|{}|{}|\\n",
195 self.deviation.min_latency_ms(),
196 self.new.min_latency_ms(),
197 self.old.min_latency_ms()
198 );
199 result += &format!(
200 "|Maximum latency ms|{:.2}%|{}|{}|\\n",
201 self.deviation.max_latency_ms(),
202 self.new.max_latency_ms(),
203 self.old.max_latency_ms()
204 );
205 result += &format!(
206 "|Stdev latency ms|{:.2}%|{}|{}|\\n",
207 self.deviation.stdev_latency_ms(),
208 self.new.stdev_latency_ms(),
209 self.old.stdev_latency_ms()
210 );
211 result += &format!(
212 "|Transfer Mb|{:.2}%|{}|{}|\\n",
213 self.deviation.transfer_mb(),
214 self.new.transfer_mb(),
215 self.old.transfer_mb()
216 );
217 result += &format!(
218 "|Connect errors|{:.2}%|{}|{}|\\n",
219 self.deviation.errors_connect(),
220 self.new.errors_connect(),
221 self.old.errors_connect()
222 );
223 result += &format!(
224 "|Read errors|{:.2}%|{}|{}|\\n",
225 self.deviation.errors_read(),
226 self.new.errors_read(),
227 self.old.errors_read()
228 );
229 result += &format!(
230 "|Write errors|{:.2}%|{}|{}|\\n",
231 self.deviation.errors_write(),
232 self.new.errors_write(),
233 self.old.errors_write()
234 );
235 result += &format!(
236 "|Status errors (not 2xx/3xx)|{:.2}%|{}|{}|\\n",
237 self.deviation.errors_status(),
238 self.new.errors_status(),
239 self.old.errors_status()
240 );
241 result += &format!(
242 "|Timeout errors|{:.2}%|{}|{}|\\n",
243 self.deviation.errors_timeout(),
244 self.new.errors_timeout(),
245 self.old.errors_timeout()
246 );
247 result
248 }
249}
250
251impl fmt::Display for Deviation {
252 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
253 let mut table = Table::new();
254 table.set_format(*format::consts::FORMAT_CLEAN);
255 table.add_row(Row::new(vec![
256 Cell::new("Measurement").with_style(Attr::Bold),
257 Cell::new("Deviation").with_style(Attr::Bold),
258 Cell::new("Current").with_style(Attr::Bold),
259 Cell::new("Old").with_style(Attr::Bold),
260 ]));
261 table.add_row(Row::new(vec![
262 Cell::new("Requests per second").with_style(Attr::Bold),
263 Cell::new(&format!("{:.2}%", self.deviation.requests_sec())),
264 Cell::new(&self.new.requests_sec().to_string()),
265 Cell::new(&self.old.requests_sec().to_string()),
266 ]));
267 table.add_row(Row::new(vec![
268 Cell::new("Total requests").with_style(Attr::Bold),
269 Cell::new(&format!("{:.2}%", self.deviation.requests())),
270 Cell::new(&self.new.requests().to_string()),
271 Cell::new(&self.old.requests().to_string()),
272 ]));
273 table.add_row(Row::new(vec![
274 Cell::new("Total errors").with_style(Attr::Bold),
275 Cell::new(&format!("{:.2}%", self.deviation.errors())),
276 Cell::new(&self.new.errors().to_string()),
277 Cell::new(&self.old.errors().to_string()),
278 ]));
279 table.add_row(Row::new(vec![
280 Cell::new("Total successes").with_style(Attr::Bold),
281 Cell::new(&format!("{:.2}%", self.deviation.successes())),
282 Cell::new(&self.new.successes().to_string()),
283 Cell::new(&self.old.successes().to_string()),
284 ]));
285 table.add_row(Row::new(vec![
286 Cell::new("Average latency ms").with_style(Attr::Bold),
287 Cell::new(&format!("{:.2}%", self.deviation.avg_latency_ms())),
288 Cell::new(&self.new.avg_latency_ms().to_string()),
289 Cell::new(&self.old.avg_latency_ms().to_string()),
290 ]));
291 table.add_row(Row::new(vec![
292 Cell::new("Minimum latency ms").with_style(Attr::Bold),
293 Cell::new(&format!("{:.2}%", self.deviation.min_latency_ms())),
294 Cell::new(&self.new.min_latency_ms().to_string()),
295 Cell::new(&self.old.min_latency_ms().to_string()),
296 ]));
297 table.add_row(Row::new(vec![
298 Cell::new("Maximum latency ms").with_style(Attr::Bold),
299 Cell::new(&format!("{:.2}%", self.deviation.max_latency_ms())),
300 Cell::new(&self.new.max_latency_ms().to_string()),
301 Cell::new(&self.old.max_latency_ms().to_string()),
302 ]));
303 table.add_row(Row::new(vec![
304 Cell::new("Stdev latency ms").with_style(Attr::Bold),
305 Cell::new(&format!("{:.2}%", self.deviation.stdev_latency_ms())),
306 Cell::new(&self.new.stdev_latency_ms().to_string()),
307 Cell::new(&self.old.stdev_latency_ms().to_string()),
308 ]));
309 table.add_row(Row::new(vec![
310 Cell::new("Transfer Mb").with_style(Attr::Bold),
311 Cell::new(&format!("{:.2}%", self.deviation.transfer_mb())),
312 Cell::new(&self.new.transfer_mb().to_string()),
313 Cell::new(&self.old.transfer_mb().to_string()),
314 ]));
315 table.add_row(Row::new(vec![
316 Cell::new("Connect errors").with_style(Attr::Bold),
317 Cell::new(&format!("{:.2}%", self.deviation.errors_connect())),
318 Cell::new(&self.new.errors_connect().to_string()),
319 Cell::new(&self.old.errors_connect().to_string()),
320 ]));
321 table.add_row(Row::new(vec![
322 Cell::new("Read errors").with_style(Attr::Bold),
323 Cell::new(&format!("{:.2}%", self.deviation.errors_read())),
324 Cell::new(&self.new.errors_read().to_string()),
325 Cell::new(&self.old.errors_read().to_string()),
326 ]));
327 table.add_row(Row::new(vec![
328 Cell::new("Write errors").with_style(Attr::Bold),
329 Cell::new(&format!("{:.2}%", self.deviation.errors_write())),
330 Cell::new(&self.new.errors_write().to_string()),
331 Cell::new(&self.old.errors_write().to_string()),
332 ]));
333 table.add_row(Row::new(vec![
334 Cell::new("Status errors (not 2xx/3xx)").with_style(Attr::Bold),
335 Cell::new(&format!("{:.2}%", self.deviation.errors_status())),
336 Cell::new(&self.new.errors_status().to_string()),
337 Cell::new(&self.old.errors_status().to_string()),
338 ]));
339 table.add_row(Row::new(vec![
340 Cell::new("Timeout errors").with_style(Attr::Bold),
341 Cell::new(&format!("{:.2}%", self.deviation.errors_timeout())),
342 Cell::new(&self.new.errors_timeout().to_string()),
343 Cell::new(&self.old.errors_timeout().to_string()),
344 ]));
345 write!(f, "## Rust Wrk benchmark report:\n{}", table)
346 }
347}