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

            
137
1
fn run_standard_benchmarks(
138
1
    tera: Arc<Tera>,
139
1
    name_filter: &str,
140
1
    shoppers: Option<usize>,
141
1
    run_full: bool,
142
1
) {
143
1
    let shoppers = shoppers.unwrap_or(100);
144
1

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

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

            
208
1
    let mut number_of_agents = vec![1];
209
1
    let num_cpus = num_cpus::get();
210
1
    if num_cpus > 1 {
211
1
        number_of_agents.push(num_cpus);
212
1
    }
213
1
    number_of_agents.push(num_cpus * 2);
214
1

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

            
262
1
    plot::overview_graph(summaries, "./commerce-bench");
263
1

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

            
281
1
#[derive(Debug, Serialize, Deserialize)]
282
struct Overview {
283
    datasets: Vec<DataSet>,
284
    timestamp: String,
285
    revision: String,
286
}
287

            
288
3
#[derive(Debug, Serialize, Deserialize)]
289
struct DataSet {
290
    size: String,
291
    pattern: String,
292
    concurrency: String,
293
    path: String,
294
    results: BTreeMap<String, String>,
295
}