1use std::collections::HashMap;
26use std::fmt::{Display, Formatter, Write};
27use std::sync::atomic::Ordering;
28use std::time::Duration;
29
30use serde::{Deserialize, Serialize};
31use tokio::runtime;
32
33use crate::statistics::Statistics;
34use crate::Arg;
35
36#[derive(Debug, Deserialize, Serialize)]
38pub struct Output {
39 pub avg_req_per_second: f64,
42
43 pub stdev_per_second: f64,
46
47 pub max_req_per_second: f64,
50
51 pub avg_req_used_time: Micros,
55
56 pub stdev_req_used_time: Micros,
59
60 pub max_req_used_time: Micros,
63
64 pub latencies: Vec<Latency>,
67
68 pub rsp1xx: u64,
70
71 pub rsp2xx: u64,
73
74 pub rsp3xx: u64,
76
77 pub rsp4xx: u64,
79
80 pub rsp5xx: u64,
82
83 pub rsp_others: u64,
85
86 pub errors: HashMap<String, u64>,
88
89 pub throughput: f64,
92}
93
94impl Output {
95 pub(crate) async fn from_statistics(s: &Statistics) -> Self {
96 Self {
97 avg_req_per_second: *(s.avg_req_per_second.lock().await),
98 stdev_per_second: *(s.stdev_per_second.lock().await),
99 max_req_per_second: *(s.max_req_per_second.lock().await),
100 avg_req_used_time: (*(s.avg_req_used_time.lock().await)).into(),
101 stdev_req_used_time: (*(s.stdev_req_used_time.lock().await)).into(),
102 max_req_used_time: (*(s.max_req_used_time.lock().await)).into(),
103 latencies: (*(s.latencies.lock().await).clone())
104 .to_owned()
105 .iter()
106 .map(|x| Latency::new(x.0, x.1.into()))
107 .collect(),
108 rsp1xx: s.rsp1xx.load(Ordering::Acquire),
109 rsp2xx: s.rsp2xx.load(Ordering::Acquire),
110 rsp3xx: s.rsp3xx.load(Ordering::Acquire),
111 rsp4xx: s.rsp4xx.load(Ordering::Acquire),
112 rsp5xx: s.rsp5xx.load(Ordering::Acquire),
113 rsp_others: s.rsp_others.load(Ordering::Acquire),
114 errors: ((s.errors.lock().await).clone().to_owned()).to_owned(),
115 throughput: *(s.throughput.lock().await),
116 }
117 }
118
119 pub(crate) fn sync_from_statistics(s: &Statistics) -> anyhow::Result<Self> {
120 runtime::Builder::new_current_thread()
121 .build()?
122 .block_on(async { Ok(Self::from_statistics(s).await) })
123 }
124}
125
126#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
128pub struct Latency {
129 pub percent: f32,
131 pub micros: Micros,
133}
134
135impl Latency {
136 pub fn new(percent: f32, micros: Micros) -> Self {
144 Self { percent, micros }
145 }
146}
147
148#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
150pub struct Micros(u64);
151
152impl From<Duration> for Micros {
153 fn from(duration: Duration) -> Self {
154 Self(duration.as_micros() as u64)
155 }
156}
157
158impl From<&Duration> for Micros {
159 fn from(duration: &Duration) -> Self {
160 Self(duration.as_micros() as u64)
161 }
162}
163
164impl Display for Micros {
165 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
166 let duration = Duration::from_micros(self.0);
167 write!(f, "{duration:.2?}")
168 }
169}
170
171pub(crate) fn sync_text_output(
172 s: &Statistics,
173 arg: &Arg,
174) -> anyhow::Result<String> {
175 runtime::Builder::new_current_thread()
176 .build()?
177 .block_on(text_output(s, arg))
178}
179
180pub(crate) async fn text_output(
181 s: &Statistics,
182 arg: &Arg,
183) -> anyhow::Result<String> {
184 let mut output = String::new();
185 writeln!(
186 &mut output,
187 "{:<14}{:^14}{:^14}{:^14}
188 {:<12}{:^14.2}{:^14.2}{:^14.2}
189 {:<12}{:^14}{:^14}{:^14}",
190 "Statistics",
191 "Avg",
192 "Stdev",
193 "Max",
194 "Reqs/sec",
195 *(s.avg_req_per_second.lock().await),
196 *(s.stdev_per_second.lock().await),
197 *(s.max_req_per_second.lock().await),
198 "Latency",
199 format!("{:.2?}", *(s.avg_req_used_time.lock().await)),
200 format!("{:.2?}", *(s.stdev_req_used_time.lock().await)),
201 format!("{:.2?}", *(s.max_req_used_time.lock().await)),
202 )?;
203
204 if arg.latencies {
205 let latencies = &*(s.latencies.lock().await);
206 if !latencies.is_empty() {
207 writeln!(&mut output, " {:<20}", "Latency Distribution")?;
208 for (percent, duration) in latencies {
209 writeln!(
210 &mut output,
211 " {:^10}{:^10}",
212 format!("{:.0}%", *percent * 100f32),
213 format!("{:.2?}", *duration),
214 )?;
215 }
216 }
217 }
218
219 writeln!(&mut output, " {:<20}", "HTTP codes:")?;
220 writeln!(
221 &mut output,
222 " 1XX - {}, 2XX - {}, 3XX - {}, 4XX - {}, 5XX - {}",
223 s.rsp1xx.load(Ordering::Acquire),
224 s.rsp2xx.load(Ordering::Acquire),
225 s.rsp3xx.load(Ordering::Acquire),
226 s.rsp4xx.load(Ordering::Acquire),
227 s.rsp5xx.load(Ordering::Acquire),
228 )?;
229 writeln!(
230 &mut output,
231 " others - {}",
232 s.rsp_others.load(Ordering::Acquire)
233 )?;
234
235 let errors = s.errors.lock().await;
236 if !errors.is_empty() {
237 writeln!(&mut output, " {:<10}", "Errors:")?;
238 for (k, v) in &*errors {
239 writeln!(&mut output, " \"{k:>}\":{v:>8}")?;
240 }
241 }
242 write!(
243 &mut output,
244 " {:<12}{:>10.2}/s",
245 "Throughput:",
246 *(s.throughput.lock().await)
247 )?;
248
249 Ok(output)
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 #[test]
257 fn test_micros_convert() {
258 let duration = Duration::from_micros(1);
259 let micros: Micros = duration.into();
260 assert_eq!("1.00µs", format!("{micros}"));
261
262 let duration = Duration::from_millis(1);
263 let micros: Micros = (&duration).into();
264 assert_eq!("1.00ms", format!("{micros}"));
265
266 let duration = Duration::from_millis(1);
267 let micros = Micros::from(duration);
268 assert_eq!("1.00ms", format!("{micros}"));
269 }
270}