1
//! Commerce Benchmark
2
//!
3
//! For more information about this benchmark, see ./README.md.
4

            
5
use std::{collections::BTreeMap, io::ErrorKind, sync::Arc, time::Duration};
6

            
7
use clap::Parser;
8
use serde::{Deserialize, Serialize};
9
use tera::{Context, Tera};
10

            
11
use crate::{
12
    model::InitialDataSetConfig,
13
    utils::{current_timestamp_string, format_nanoseconds, local_git_rev},
14
};
15

            
16
mod bonsai;
17
mod plan;
18
#[cfg(feature = "postgresql")]
19
mod postgres;
20
mod utils;
21
use plan::ShopperPlanConfig;
22
mod execute;
23
mod model;
24
mod plot;
25

            
26
2
#[derive(Debug, Parser)]
27
struct Options {
28
    #[clap(long = "bench")]
29
    run_from_cargo_bench: bool,
30
    #[clap(long = "nocapture")]
31
    _cargo_bench_compat_nocapture: bool,
32

            
33
    benchmark_name: Option<String>,
34

            
35
    #[clap(long)]
36
    suite: bool,
37

            
38
3
    #[clap(long, short, default_value = "Commerce Benchmark")]
39
2
    label: String,
40

            
41
    #[clap(long, short)]
42
    seed: Option<u64>,
43

            
44
3
    #[clap(long, default_value = "1")]
45
2
    min_customers: u32,
46
3
    #[clap(long, default_value = "500")]
47
2
    max_customers: u32,
48

            
49
3
    #[clap(long, default_value = "1")]
50
2
    min_products: u32,
51
3
    #[clap(long, default_value = "200")]
52
2
    max_products: u32,
53

            
54
3
    #[clap(long, default_value = "1")]
55
2
    min_categories: u32,
56
3
    #[clap(long, default_value = "30")]
57
2
    max_categories: u32,
58

            
59
3
    #[clap(long, default_value = "10")]
60
2
    min_orders: u32,
61
3
    #[clap(long, default_value = "1000")]
62
2
    max_orders: u32,
63

            
64
3
    #[clap(long, default_value = "10")]
65
2
    min_reviews: u32,
66
3
    #[clap(long, default_value = "500")]
67
2
    max_reviews: u32,
68

            
69
3
    #[clap(long, default_value = "0.25")]
70
2
    add_to_cart_chance: f64,
71
3
    #[clap(long, default_value = "0.25")]
72
2
    purchase_chance: f64,
73
3
    #[clap(long, default_value = "0.25")]
74
2
    rating_chance: f64,
75

            
76
3
    #[clap(long, default_value = "1")]
77
2
    min_searches: u32,
78
3
    #[clap(long, default_value = "20")]
79
2
    max_searches: u32,
80

            
81
    #[clap(long, short = 'j')]
82
    agents: Option<usize>,
83

            
84
    #[clap(long, short = 'n')]
85
    shoppers: Option<usize>,
86
}
87

            
88
1
fn main() {
89
1
    match dotenv::dotenv() {
90
        Ok(_) => {}
91
1
        Err(dotenv::Error::Io(err)) if err.kind() == ErrorKind::NotFound => {}
92
        Err(other) => panic!("Error with .env file: {}", other),
93
    }
94
1
    let tera = Arc::new(tera::Tera::new("benches/commerce/templates/**/*").unwrap());
95
1
    let options = Options::parse();
96
1
    let benchmark_name = options.benchmark_name.unwrap_or_default();
97
1

            
98
1
    let is_testing = !options.run_from_cargo_bench;
99
1

            
100
1
    if options.suite || is_testing {
101
1
        run_standard_benchmarks(
102
1
            tera.clone(),
103
1
            &benchmark_name,
104
1
            options.shoppers,
105
1
            options.run_from_cargo_bench,
106
1
        );
107
1
    }
108

            
109
1
    if !options.suite || is_testing {
110
1
        execute::Benchmark {
111
1
            label: options.label,
112
1
            seed: options.seed,
113
1
            agents: options.agents,
114
1
            shoppers: options.shoppers,
115
1
            data_config: &InitialDataSetConfig {
116
1
                number_of_customers: options.min_customers..=options.max_customers,
117
1
                number_of_products: options.min_products..=options.max_products,
118
1
                number_of_categories: options.min_categories..=options.max_categories,
119
1
                number_of_orders: options.min_orders..=options.max_orders,
120
1
                number_of_reviews: options.min_reviews..=options.max_reviews,
121
1
            },
122
1
            shopper_config: &ShopperPlanConfig {
123
1
                chance_of_adding_product_to_cart: options.add_to_cart_chance,
124
1
                chance_of_purchasing: options.purchase_chance,
125
1
                chance_of_rating: options.rating_chance,
126
1
                product_search_attempts: options.min_searches..=options.max_searches,
127
1
            },
128
1
        }
129
1
        .execute(&benchmark_name, "commerce-bench", tera);
130
1
    }
131
1
}
132

            
133
1
fn run_standard_benchmarks(
134
1
    tera: Arc<Tera>,
135
1
    name_filter: &str,
136
1
    shoppers: Option<usize>,
137
1
    run_full: bool,
138
1
) {
139
1
    let shoppers = shoppers.unwrap_or(100);
140
1

            
141
1
    let mut initial_datasets = vec![(
142
1
        "small",
143
1
        InitialDataSetConfig {
144
1
            number_of_customers: 100..=100,
145
1
            number_of_products: 100..=100,
146
1
            number_of_categories: 10..=10,
147
1
            number_of_orders: 125..=125,
148
1
            number_of_reviews: 50..=50,
149
1
        },
150
1
    )];
151
1
    if run_full {
152
        initial_datasets.push((
153
            "medium",
154
            InitialDataSetConfig {
155
                number_of_customers: 1_000..=1_000,
156
                number_of_products: 1_000..=1_000,
157
                number_of_categories: 50..=50,
158
                number_of_orders: 1500..=1500,
159
                number_of_reviews: 500..=500,
160
            },
161
        ));
162
        initial_datasets.push((
163
            "large",
164
            InitialDataSetConfig {
165
                number_of_customers: 5_000..=5_000,
166
                number_of_products: 5_000..=5_000,
167
                number_of_categories: 100..=100,
168
                number_of_orders: 5_000..=5_000,
169
                number_of_reviews: 1_000..=1_000,
170
            },
171
        ));
172
1
    }
173

            
174
1
    let mut shopper_plans = vec![(
175
1
        "balanced",
176
1
        ShopperPlanConfig {
177
1
            chance_of_adding_product_to_cart: 0.25,
178
1
            chance_of_purchasing: 0.25,
179
1
            chance_of_rating: 0.25,
180
1
            product_search_attempts: 1..=10,
181
1
        },
182
1
    )];
183
1
    if run_full {
184
        shopper_plans.push((
185
            "readheavy",
186
            ShopperPlanConfig {
187
                chance_of_adding_product_to_cart: 0.1,
188
                chance_of_purchasing: 0.1,
189
                chance_of_rating: 0.1,
190
                product_search_attempts: 1..=10,
191
            },
192
        ));
193
        shopper_plans.push((
194
            "writeheavy",
195
            ShopperPlanConfig {
196
                chance_of_adding_product_to_cart: 0.9,
197
                chance_of_purchasing: 0.9,
198
                chance_of_rating: 0.9,
199
                product_search_attempts: 1..=10,
200
            },
201
        ));
202
1
    }
203

            
204
1
    let mut number_of_agents = vec![1];
205
1
    let num_cpus = num_cpus::get();
206
1
    if num_cpus > 1 {
207
1
        number_of_agents.push(num_cpus);
208
1
    }
209
1
    number_of_agents.push(num_cpus * 2);
210
1

            
211
1
    let mut datasets = Vec::new();
212
1
    let mut summaries = BTreeMap::<usize, Vec<BTreeMap<&'static str, Duration>>>::new();
213
2
    for (dataset_label, data_config) in &initial_datasets {
214
2
        for (plan_label, shopper_config) in &shopper_plans {
215
1
            println!(
216
1
                "Running standard benchmark {}-{}",
217
1
                dataset_label, plan_label
218
1
            );
219
4
            for &concurrency in &number_of_agents {
220
3
                let summaries = summaries.entry(concurrency).or_default();
221
3
                let measurements = execute::Benchmark {
222
3
                    label: format!(
223
3
                        "{}, {}, {} agent(s)",
224
3
                        dataset_label, plan_label, concurrency
225
3
                    ),
226
3
                    seed: Some(0),
227
3
                    agents: Some(concurrency),
228
3
                    shoppers: Some(shoppers),
229
3
                    data_config,
230
3
                    shopper_config,
231
3
                }
232
3
                .execute(
233
3
                    name_filter,
234
3
                    format!(
235
3
                        "./commerce-bench/{}-{}/{}/",
236
3
                        dataset_label, plan_label, concurrency
237
3
                    ),
238
3
                    tera.clone(),
239
3
                );
240
3
                datasets.push(DataSet {
241
3
                    size: dataset_label.to_string(),
242
3
                    pattern: plan_label.to_string(),
243
3
                    concurrency: concurrency.to_string(),
244
3
                    path: format!(
245
3
                        "{}-{}/{}/index.html",
246
3
                        dataset_label, plan_label, concurrency
247
3
                    ),
248
3
                    results: measurements
249
3
                        .iter()
250
15
                        .map(|(k, v)| (k.to_string(), format_nanoseconds(v.as_nanos() as f64)))
251
3
                        .collect(),
252
3
                });
253
3
                summaries.push(measurements);
254
3
            }
255
        }
256
    }
257

            
258
1
    plot::overview_graph(summaries, "./commerce-bench");
259
1

            
260
1
    std::fs::write(
261
1
        "./commerce-bench/index.html",
262
1
        tera.render(
263
1
            "overview.html",
264
1
            &Context::from_serialize(&Overview {
265
1
                datasets,
266
1
                timestamp: current_timestamp_string(),
267
1
                revision: local_git_rev(),
268
1
            })
269
1
            .unwrap(),
270
1
        )
271
1
        .unwrap()
272
1
        .as_bytes(),
273
1
    )
274
1
    .unwrap();
275
1
}
276

            
277
1
#[derive(Debug, Serialize, Deserialize)]
278
struct Overview {
279
    datasets: Vec<DataSet>,
280
    timestamp: String,
281
    revision: String,
282
}
283

            
284
3
#[derive(Debug, Serialize, Deserialize)]
285
struct DataSet {
286
    size: String,
287
    pattern: String,
288
    concurrency: String,
289
    path: String,
290
    results: BTreeMap<String, String>,
291
}