Skip to main content

mpc_bench/
statistics.rs

1use std::{
2    collections::HashMap,
3    fs::File,
4    time::{Duration, Instant},
5};
6
7use stats::{mean, stddev};
8use tabled::{builder::Builder, Style};
9
10#[derive(Debug)]
11/// Contains the aggregated statistics for multiple repetitions of the same experiment.
12pub struct AggregatedStats {
13    _name: String,
14    party_names: Vec<String>,
15    timings: Vec<Vec<Timings>>,
16}
17
18/// The names, means and standard deviations of all parties' measured run times.
19pub struct TimingSummary {
20    timing_names: Vec<String>,
21    party_names: Vec<String>,
22    party_means: Vec<Vec<Option<f64>>>,
23    party_stdevs: Vec<Vec<Option<f64>>>,
24}
25
26impl TimingSummary {
27    /// Prints a pretty table of the summarized timings.
28    pub fn print(&self) {
29        let mut builder = Builder::default();
30
31        // Add header
32        builder.add_record(
33            ["Parties".to_string()]
34                .into_iter()
35                .chain(self.timing_names.iter().cloned()),
36        );
37
38        // Add each party's data
39        for ((means, stdevs), party_name) in self
40            .party_means
41            .iter()
42            .zip(&self.party_stdevs)
43            .zip(&self.party_names)
44        {
45            builder.add_record([party_name.clone()].into_iter().chain(
46                means.iter().zip(stdevs).map(|data| match data {
47                    (&Some(mean), &Some(stdev)) => format!("{:.3} ± {:.3} s", mean, stdev),
48                    _ => "".to_string(),
49                }),
50            ));
51        }
52
53        let table = builder.build().with(Style::modern());
54
55        println!("{}", table);
56    }
57}
58
59impl AggregatedStats {
60    /// Constructs `AggregatedStats` with the given name for tracking statistics.
61    pub fn new(name: String, party_names: Vec<String>) -> Self {
62        AggregatedStats {
63            _name: name,
64            party_names,
65            timings: vec![],
66        }
67    }
68
69    /// Incorporates each party's resulting statistics into this aggregate.
70    pub fn incorporate_party_stats(&mut self, party_stats: Vec<Timings>) {
71        self.timings.push(party_stats);
72    }
73
74    // TODO: These methods have many underlying assumptions and are not ergonomic.
75    /// Outputs one party's timings to a csv named `csv_filename`.
76    pub fn output_party_csv(&self, party_id: usize, csv_filename: &str) {
77        // Open CSV file
78        let writer = File::create(csv_filename).unwrap();
79        let mut csv_writer = csv::Writer::from_writer(writer);
80
81        // Write header
82        let headers: Vec<String> = self.timings[0][party_id]
83            .measured_durations
84            .iter()
85            .map(|(name, _)| name.clone())
86            .collect();
87        csv_writer.write_record(&headers).unwrap();
88
89        for party_timings in &self.timings {
90            let durations: Vec<String> = party_timings[party_id]
91                .measured_durations
92                .iter()
93                .map(|(_, dur)| dur.as_micros().to_string())
94                .collect();
95            csv_writer.write_record(&durations).unwrap();
96        }
97
98        csv_writer.flush().unwrap();
99    }
100
101    /// Summarizes the timings of all parties.
102    pub fn summarize_timings(&self) -> TimingSummary {
103        let mut timing_names = vec![];
104        let mut party_timings_per_name: Vec<HashMap<String, Vec<f64>>> =
105            (0..self.party_names.len())
106                .map(|_| HashMap::new())
107                .collect();
108
109        for (party_timings, map) in self.timings.iter().zip(&mut party_timings_per_name) {
110            for timing in party_timings {
111                for (t, d) in &timing.measured_durations {
112                    if !timing_names.contains(t) {
113                        timing_names.push(t.clone());
114                    }
115
116                    map.entry(t.clone()).or_insert(vec![]).push(d.as_secs_f64());
117                }
118            }
119        }
120
121        println!("{:?}", party_timings_per_name);
122
123        let party_means = (0..self.party_names.len())
124            .map(|i| {
125                timing_names
126                    .iter()
127                    .map(|t| {
128                        party_timings_per_name[i]
129                            .get(t)
130                            .map(|durations| mean(durations.iter().cloned()))
131                    })
132                    .collect::<Vec<_>>()
133            })
134            .collect();
135        let party_stdevs = (0..self.party_names.len())
136            .map(|i| {
137                timing_names
138                    .iter()
139                    .map(|t| {
140                        party_timings_per_name[i]
141                            .get(t)
142                            .map(|durations| stddev(durations.iter().cloned()))
143                    })
144                    .collect::<Vec<_>>()
145            })
146            .collect();
147
148        TimingSummary {
149            timing_names,
150            party_names: self.party_names.clone(),
151            party_means,
152            party_stdevs,
153        }
154    }
155}
156
157/// Statistics pertaining to one party, such as the number of bytes sent and the durations measured.
158#[derive(Debug)]
159pub struct Timings {
160    measured_durations: Vec<(String, Duration)>,
161}
162
163impl Timings {
164    pub(crate) fn new() -> Self {
165        Timings {
166            measured_durations: vec![],
167        }
168    }
169
170    pub(crate) fn write_duration(&mut self, name: String, duration: Duration) {
171        self.measured_durations.push((name, duration));
172    }
173}
174
175/// A `Timer` that starts measuring a duration upon creation, until it is stopped.
176pub struct Timer {
177    name: String,
178    start_time: Instant,
179}
180
181impl Timer {
182    fn new(name: String) -> Self {
183        Timer {
184            name,
185            start_time: Instant::now(),
186        }
187    }
188
189    fn stop(&self) -> (String, Duration) {
190        (self.name.clone(), self.start_time.elapsed())
191    }
192}
193
194impl Timings {
195    /// Creates a timer with the given `name` that starts running immediately.
196    pub fn create_timer(&self, name: &str) -> Timer {
197        Timer::new(String::from(name))
198    }
199
200    /// Stops the `timer` and writes it measured duration to this party's statistics.
201    pub fn stop_timer(&mut self, timer: Timer) {
202        let (name, duration) = timer.stop();
203        self.write_duration(name, duration);
204    }
205}