1
use std::collections::BTreeMap;
2
use std::ops::RangeInclusive;
3
use std::path::Path;
4
use std::time::Duration;
5

            
6
use plotters::coord::ranged1d::{NoDefaultFormatting, ValueFormatter};
7
use plotters::prelude::*;
8

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

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

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

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

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

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

            
97
impl Ranged for NanosRange {
98
    type FormatOption = NoDefaultFormatting;
99
    type ValueType = Nanos;
100

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

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

            
126
2
        important_points
127
2
    }
128

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

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

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

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

            
153
1932
pub fn label_to_color(label: &str) -> RGBColor {
154
1932
    match label {
155
1932
        "bonsaidb-local" => COLORS[0],
156
1616
        "bonsaidb-local+lz4" => COLORS[6],
157
1293
        "bonsaidb-quic" => COLORS[1],
158
782
        "bonsaidb-ws" => COLORS[2],
159
384
        "postgresql" => COLORS[3],
160
        "sqlite" => COLORS[4],
161
        "mongodb" => COLORS[5],
162
        _ => panic!("Unknown label: {label}"),
163
    }
164
1932
}
165

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

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