wrk_api_bench/
result.rs

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}