benchmarker/
io.rs

1//! Module to deal with imports and exports
2
3use std::{
4    fmt::Display,
5    fs::{read_to_string, File},
6    io::{self, Write},
7    path::Path,
8};
9
10use crate::bencher::DEFAULT_RUNS;
11use plotly::{Histogram, Plot};
12
13///Imports a set of traces from a CSV file
14///
15/// # Errors
16///
17/// Can fail if we fail to read the file using [`read_to_string`]
18pub fn import_csv(file: impl AsRef<Path>) -> io::Result<Vec<(String, Vec<u128>)>> {
19    let lines = read_to_string(file)?; //read in the csv file
20    if lines.trim().is_empty() {
21        //if it is empty (need to trim in case of extra newlines etc), just return an empty list
22        return Ok(vec![]);
23    }
24    let no_lines = lines.lines().count(); //have to get lines twice, as count consumes
25    let lines = lines.lines();
26
27    let mut trace_contents: Vec<(String, Vec<u128>)> = Vec::with_capacity(no_lines);
28    for line in lines {
29        let mut title = String::new();
30        let mut contents = Vec::with_capacity(
31            trace_contents
32                .first()
33                .map_or(DEFAULT_RUNS, |(_, v)| v.len()),
34        ); //make a new vec with the capacity of the first one, or if we don't have one yet, use DEFAULT_RUNS
35        for (j, time) in line.split(',').enumerate() {
36            if j == 0 {
37                title = time.to_string(); //here, it isn't a time, its a title - the first item is the title
38            } else {
39                contents.push(time.parse().expect("unable to parse time")); //here, it is a time, so we need to parse it.
40            }
41        }
42        trace_contents.push((title, contents));
43    }
44
45    Ok(trace_contents)
46}
47
48///Getting multiple traces from multiple files in CSV format
49///
50/// # Errors
51/// If we can't do something with the file
52pub fn get_traces(
53    trace_file_names: impl IntoIterator<Item = impl AsRef<Path>>,
54    trace: Option<(String, Vec<u128>)>,
55) -> io::Result<Vec<(String, Vec<u128>)>> {
56    let mut traces: Vec<(String, Vec<u128>)> = trace_file_names
57        .into_iter() //for each trace
58        .map(import_csv) //import it
59        .collect::<io::Result<Vec<Vec<(String, Vec<u128>)>>>>()? //collect any results and bubble
60        .into_iter() //make that back into an iterator
61        .flatten() //flatten it - Vec<Vec<T>> to a flat Vec<T>
62        .collect(); //then get that into a Vec<T>
63    if let Some((name, times)) = trace {
64        //if we got a trace to start with
65        traces.push((name, times)); //add it
66    }
67    Ok(traces)
68}
69
70///Exports a set of traces to a CSV file
71///
72/// # Errors
73///
74/// Can have errors if we fail to create a file or write to it, or if we fail to read the traces
75pub fn export_csv(
76    trace: Option<(String, Vec<u128>)>,
77    file_name_input: impl AsRef<Path> + Display,
78    extra_trace_file_names: impl IntoIterator<Item = impl AsRef<Path>>,
79) -> io::Result<usize> {
80    let traces = get_traces(extra_trace_file_names, trace)?; //get the traces from the file and provided
81    export_csv_no_file_input(file_name_input, traces) //export
82}
83
84///Exports a set of traces to a CSV file
85///
86/// # Errors
87///
88/// Can have errors if we fail to create a file or write to it
89pub fn export_csv_no_file_input(
90    file_name_input: impl AsRef<Path> + Display,
91    traces: Vec<(String, Vec<u128>)>,
92) -> io::Result<usize> {
93    let mut to_be_written = String::new(); //string with space to be written to
94
95    for (name, times) in traces {
96        to_be_written += &name;
97        for time in times {
98            to_be_written += ",";
99            to_be_written += &time.to_string();
100        }
101        to_be_written += "\n";
102    } //manually write a csv - title,time1,time2,time3 etc
103
104    let mut file = File::create(format!("{file_name_input}.csv"))?; //make a file
105    let to_be_written = to_be_written.as_bytes(); //get the bytes to be written
106    file.write_all(to_be_written)?; //write them all
107
108    Ok(to_be_written.len())
109}
110
111///Exports a set of traces to a plotly plot
112///
113/// # Errors
114///
115/// Can have errors if we fail to create a file or write to it, or if we fail to read the traces
116pub fn export_html(
117    trace: Option<(String, Vec<u128>)>,
118    file_name_input: impl AsRef<Path> + Display,
119    extra_trace_file_names: impl IntoIterator<Item = impl AsRef<Path>>,
120) -> io::Result<usize> {
121    let traces = get_traces(extra_trace_file_names, trace)?; //get the traces from the file and provided
122    export_html_no_file_input(file_name_input, traces) //and export them
123}
124
125///Exports a set of traces to a HTML file
126///
127/// # Errors
128///
129/// Can have errors if we fail to create a file or write to it
130pub fn export_html_no_file_input(
131    file_name_input: impl AsRef<Path> + Display,
132    traces: Vec<(String, Vec<u128>)>,
133) -> io::Result<usize> {
134    let mut plot = Plot::new(); //make a new plotly plot
135    for (name, trace) in traces {
136        plot.add_trace(Histogram::new(trace).name(name)); //for each trace, add it to a plotly plot
137    }
138
139    let mut file = File::create(format!("{file_name_input}.html"))?; //make a file
140    let html = plot.to_html(); //make the html
141    let html = html.as_bytes(); //get the bytes - 2 steps to avoid dropping temporary value
142    file.write_all(html)?; //write all of the bytes
143
144    Ok(html.len())
145}