1
use std::{collections::BTreeMap, ops::RangeInclusive, path::Path, time::Duration};
2

            
3
use plotters::{
4
    coord::ranged1d::{NoDefaultFormatting, ValueFormatter},
5
    prelude::*,
6
};
7

            
8
use crate::utils::format_nanoseconds;
9
1
pub fn overview_graph(
10
1
    summaries: BTreeMap<usize, Vec<BTreeMap<&'static str, Duration>>>,
11
1
    plot_dir: impl AsRef<Path>,
12
1
) {
13
1
    let chart_path = plot_dir.as_ref().join("Overview.png");
14
1
    let chart_root = BitMapBackend::new(&chart_path, (800, 480)).into_drawing_area();
15
1
    chart_root.fill(&BACKGROUND_COLOR).unwrap();
16
1

            
17
1
    let mut results_by_backend = BTreeMap::new();
18
1
    let highest_concurrency = *summaries.keys().max().unwrap();
19
4
    for (concurrency, reports) in summaries {
20
6
        for report in reports {
21
18
            for (backend, wall_time) in report {
22
15
                let results = results_by_backend
23
15
                    .entry(backend)
24
15
                    .or_insert_with(BTreeMap::new);
25
15
                results
26
15
                    .entry(concurrency)
27
15
                    .and_modify(|total: &mut Nanos| total.0 += wall_time.as_nanos() as u64)
28
15
                    .or_insert_with(|| Nanos(wall_time.as_nanos() as u64));
29
15
            }
30
        }
31
    }
32
1
    let longest_measurement = Iterator::max(
33
1
        results_by_backend
34
1
            .values()
35
5
            .flat_map(|results| results.values().copied()),
36
1
    )
37
1
    .unwrap();
38
1
    let mut chart = ChartBuilder::on(&chart_root)
39
1
        .caption(
40
1
            "Commerce Benchmark Suite Overview",
41
1
            ("sans-serif", 30., &TEXT_COLOR),
42
1
        )
43
1
        .margin_left(10)
44
1
        .margin_right(50)
45
1
        .margin_bottom(10)
46
1
        .x_label_area_size(50)
47
1
        .y_label_area_size(80)
48
1
        .build_cartesian_2d(
49
1
            1..highest_concurrency,
50
1
            NanosRange(Nanos(0)..=longest_measurement),
51
1
        )
52
1
        .unwrap();
53
1

            
54
1
    chart
55
1
        .configure_mesh()
56
1
        .disable_x_mesh()
57
1
        .x_desc("Number of Agents")
58
1
        .y_desc("Total Wall Time")
59
1
        .axis_desc_style(("sans-serif", 15, &TEXT_COLOR))
60
1
        .x_label_style(&TEXT_COLOR)
61
1
        .y_label_style(&TEXT_COLOR)
62
1
        .light_line_style(&TEXT_COLOR.mix(0.1))
63
1
        .bold_line_style(&TEXT_COLOR.mix(0.3))
64
1
        .draw()
65
1
        .unwrap();
66

            
67
6
    for (backend, points) in results_by_backend {
68
5
        chart
69
5
            .draw_series(LineSeries::new(
70
5
                points.into_iter(),
71
5
                &label_to_color(backend),
72
5
            ))
73
5
            .unwrap()
74
5
            .label(backend.to_string())
75
5
            .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &label_to_color(backend)));
76
5
    }
77
1
    chart
78
1
        .configure_series_labels()
79
1
        .border_style(&TEXT_COLOR)
80
1
        .background_style(&BACKGROUND_COLOR)
81
1
        .label_font(&TEXT_COLOR)
82
1
        .position(SeriesLabelPosition::UpperLeft)
83
1
        .draw()
84
1
        .unwrap();
85
1
    chart_root.present().unwrap();
86
1
}
87

            
88
#[derive(Clone, Debug)]
89
pub struct NanosRange(RangeInclusive<Nanos>);
90
14
#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)]
91
pub struct Nanos(u64);
92

            
93
impl ValueFormatter<Nanos> for NanosRange {
94
10
    fn format(value: &Nanos) -> String {
95
10
        format_nanoseconds(value.0 as f64)
96
10
    }
97
}
98

            
99
impl Ranged for NanosRange {
100
    type ValueType = Nanos;
101
    type FormatOption = NoDefaultFormatting;
102

            
103
125
    fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 {
104
125
        let limited_size = limit.1 - limit.0;
105
125
        let full_size = self.0.end().0 + 1 - self.0.start().0;
106
125
        let normalized_offset = value.0.saturating_sub(self.0.start().0) as f64 / full_size as f64;
107
125
        limit.0 + (normalized_offset * limited_size as f64) as i32
108
125
    }
109

            
110
2
    fn key_points<Hint: plotters::coord::ranged1d::KeyPointHint>(
111
2
        &self,
112
2
        hint: Hint,
113
2
    ) -> Vec<Self::ValueType> {
114
2
        let total_range = self.0.end().0 - self.0.start().0;
115
2
        let num_points = hint.max_num_points();
116
2
        let mut important_points = Vec::with_capacity(num_points);
117
2
        important_points.push(*self.0.start());
118
2
        if num_points > 2 {
119
2
            let steps = num_points - 2;
120
2
            let step_size = total_range as f64 / steps as f64;
121
2
            important_points.extend(
122
2
                (1..num_points - 1)
123
106
                    .map(|step| Nanos(self.0.start().0 + (step as f64 * step_size) as u64)),
124
2
            );
125
2
        }
126
2
        important_points.push(*self.0.end());
127
2

            
128
2
        important_points
129
2
    }
130

            
131
    fn range(&self) -> std::ops::Range<Self::ValueType> {
132
        Nanos(self.0.start().0)..Nanos(self.0.end().0 + 1)
133
    }
134
}
135

            
136
impl DiscreteRanged for NanosRange {
137
    fn size(&self) -> usize {
138
        (self.0.end().0 - self.0.start().0) as usize
139
    }
140

            
141
    fn index_of(&self, value: &Self::ValueType) -> Option<usize> {
142
        if value.0 <= self.0.end().0 {
143
            if let Some(index) = value.0.checked_sub(self.0.start().0) {
144
                return Some(index as usize);
145
            }
146
        }
147
        None
148
    }
149

            
150
    fn from_index(&self, index: usize) -> Option<Self::ValueType> {
151
        Some(Nanos(self.0.start().0 + index as u64))
152
    }
153
}
154

            
155
1808
pub fn label_to_color(label: &str) -> RGBColor {
156
1808
    match label {
157
1808
        "bonsaidb-local" => COLORS[0],
158
1469
        "bonsaidb-local+lz4" => COLORS[5],
159
1093
        "bonsaidb-quic" => COLORS[1],
160
676
        "bonsaidb-ws" => COLORS[2],
161
366
        "postgresql" => COLORS[3],
162
        "sqlite" => COLORS[4],
163
        _ => panic!("Unknown label: {}", label),
164
    }
165
1808
}
166

            
167
// https://coolors.co/dc0ab4-50e991-00bfa0-3355ff-9b19f5-ffa300-e60049-0bb4ff-e6d800
168
pub const COLORS: [RGBColor; 9] = [
169
    RGBColor(220, 10, 180),
170
    RGBColor(80, 233, 145),
171
    RGBColor(0, 191, 160),
172
    RGBColor(51, 85, 255),
173
    RGBColor(155, 25, 245),
174
    RGBColor(255, 163, 0),
175
    RGBColor(230, 0, 73),
176
    RGBColor(11, 180, 255),
177
    RGBColor(230, 216, 0),
178
];
179

            
180
pub const BACKGROUND_COLOR: RGBColor = RGBColor(0, 0, 0);
181
pub const TEXT_COLOR: RGBColor = RGBColor(200, 200, 200);