rsb/
output.rs

1//! output module defines the output of the task
2//!
3//! # Example
4//!
5//! ```ignore
6//! use std::io;
7//! use std::sync::Arc;
8//! use clap::Parser;
9//! use rsb::{Arg, Task};
10//! use rsb::arg::OutputFormat;
11//!
12//! let arg = Arg::parse();
13//! let output_format = arg.output_format;
14//! let task = Arc::new(Task::new(arg, Some(pb))?).run()?;
15//! let result = match output_format {
16//!     OutputFormat::Text => task.text_output()?,
17//!     OutputFormat::Json => {
18//!         let output = task.json_output()?;
19//!         serde_json::to_string_pretty(&output)?
20//!     }
21//! };
22//! writeln!(&mut io::stdout(), "{result}")?;
23//! ```
24
25use 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/// the [Output] after executing the task, copied from the statistical results
37#[derive(Debug, Deserialize, Serialize)]
38pub struct Output {
39    /// average per second, data is sampled every second and averaged at the
40    /// end
41    pub avg_req_per_second: f64,
42
43    /// the data source is the same as `avg_req_per_second`,just
44    /// calculates its standard deviation
45    pub stdev_per_second: f64,
46
47    /// the data source is the same as `avg_req_per_second`, find its maximum
48    /// value
49    pub max_req_per_second: f64,
50
51    /// during the running of the program, the time taken for each request from
52    /// initiating to receiving the response will be recorded, and the average
53    /// value will be calculated at the end
54    pub avg_req_used_time: Micros,
55
56    /// the data source is the same as `avg_req_used_time`, just calculates its
57    /// standard deviation
58    pub stdev_req_used_time: Micros,
59
60    /// the data source is the same as `avg_req_used_time`, find its maximum
61    /// value
62    pub max_req_used_time: Micros,
63
64    /// sorts the response time of each request, then calculates it based on
65    /// the incoming percentage sequence parameter
66    pub latencies: Vec<Latency>,
67
68    /// status code [100, 200)
69    pub rsp1xx: u64,
70
71    /// status code [200, 300)
72    pub rsp2xx: u64,
73
74    /// status code [300, 400)
75    pub rsp3xx: u64,
76
77    /// status code [400, 500)
78    pub rsp4xx: u64,
79
80    /// status code [500, 511]
81    pub rsp5xx: u64,
82
83    /// other response code
84    pub rsp_others: u64,
85
86    /// errors encountered during the request and their count
87    pub errors: HashMap<String, u64>,
88
89    /// Calculate the throughput of the Server, the calculation formula is:
90    /// `connections / avg_req_used_time`
91    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/// Latency indicates how many seconds the first percentage of requests took
127#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
128pub struct Latency {
129    /// values from 0 to 1
130    pub percent: f32,
131    /// indicates the time taken to execute the request, unit: microseconds
132    pub micros: Micros,
133}
134
135impl Latency {
136    /// construct [Latency]
137    ///
138    /// Arguments:
139    ///
140    /// * `percent` - values from 0 to 1
141    /// * `micros` - indicates the time taken to execute the request, unit:
142    ///   microseconds
143    pub fn new(percent: f32, micros: Micros) -> Self {
144        Self { percent, micros }
145    }
146}
147
148/// Micros represents microseconds
149#[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}