1use std::{
2 io::Write,
3 path::{Path, PathBuf},
4 process::{Command, Stdio},
5};
6
7use tempfile::NamedTempFile;
8
9use crate::{wrk::Benchmarks, Result, WrkError};
10
11#[derive(Debug, Clone)]
12pub struct Gnuplot {
13 title: String,
14 output: PathBuf,
15}
16
17impl Gnuplot {
18 pub fn new(title: &str, output: &Path) -> Self {
19 Self {
20 title: title.to_string(),
21 output: output.to_path_buf(),
22 }
23 }
24
25 pub fn plot(&self, benchmarks: &Benchmarks) -> Result<()> {
26 if benchmarks.len() < 2 {
27 return Err(WrkError::Plot(format!(
28 "There are {} availble datapoints. Unable to plot history with less than 2 datapoints",
29 benchmarks.len()
30 )));
31 }
32 let dates: Vec<_> = benchmarks
33 .iter()
34 .map(|b| b.date().format("%Y-%m-%d-%H:%M:%S").to_string())
35 .collect();
36 let serie: Vec<_> = benchmarks.iter().map(|b| *b.requests_sec() as u64).collect();
37 let min_x = dates.iter().min().unwrap();
38 let max_x = dates.iter().max().unwrap();
39 let min_y = *serie.iter().min().unwrap_or(&0) as f64;
40 let min_y = (min_y - (min_y * 0.15)) as u64;
41 let max_y = *serie.iter().max().unwrap_or(&1000) as f64;
42 let max_y = (max_y + (max_y * 0.15)) as u64;
43 let mut data_file = NamedTempFile::new()?;
44 for (i, b) in benchmarks.iter().enumerate() {
45 data_file.write_all(format!("{} {}\n", dates[i], b.requests_sec()).as_bytes())?;
46 }
47 let gnuplot = format!(
48 r#"set xdata time
49set timefmt "%Y-%m-%d-%H:%M:%S"
50set format x "%m/%y/%d %H:%M:%S"
51set xrange ["{}":"{}"]
52set yrange [{}:{}]
53set key off
54set xtics rotate by -45
55set title "{}"
56set terminal png
57set output "{}"
58plot "{}" using 1:2 with linespoints linetype 6 linewidth 2"#,
59 min_x,
60 max_x,
61 min_y,
62 max_y,
63 self.title,
64 self.output.display(),
65 data_file.path().display()
66 );
67 let mut child = Command::new("gnuplot").stdin(Stdio::piped()).spawn()?;
68 if let Some(mut stdin) = child.stdin.take() {
69 stdin.write_all(gnuplot.as_ref())?;
70 }
71 let status = child.wait()?;
72 if status.success() {
73 Ok(())
74 } else {
75 let err = WrkError::Plot(format!(
76 "Error plotting file {} which is kept for debug",
77 data_file.path().display()
78 ));
79 data_file.keep()?;
80 Err(err)
81 }
82 }
83}