1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
45//! Benchmarking support
6//!
7//! Benchmarks are split up into two parts: the functions to be benchmarked live here, which the benchmarking code itself lives in `benches/bench.rs`.
8//! It's easier to write benchmarking code inside the main crate, where we have access to private items.
9//! However, it's easier to integrate with Cargo and criterion if benchmarks live in a separate crate.
10//!
11//! All benchmarks are defined as structs that implement either the [Benchmark] or [BenchmarkWithInput]
1213use std::{
14 path::PathBuf,
15 sync::{
16 atomic::{AtomicU32, Ordering},
17 Mutex,
18 },
19};
20use tempfile::TempDir;
2122use crate::{SuggestIngestionConstraints, SuggestStore};
23use remote_settings::{RemoteSettingsConfig2, RemoteSettingsContext, RemoteSettingsService};
2425use std::sync::Arc;
2627pub mod client;
28pub mod geoname;
29pub mod ingest;
30pub mod query;
3132/// Trait for simple benchmarks
33///
34/// This supports simple benchmarks that don't require any input. Note: global setup can be done
35/// in the `new()` method for the struct.
36pub trait Benchmark {
37/// Perform the operations that we're benchmarking.
38fn benchmarked_code(&self);
39}
4041/// Trait for benchmarks that require input
42///
43/// This will run using Criterion's `iter_batched` function. Criterion will create a batch of
44/// inputs, then pass each one to the benchmark's iterations.
45///
46/// This supports simple benchmarks that don't require any input.
47pub trait BenchmarkWithInput {
48/// Input that will be created once and then passed by reference to each
49 /// of the benchmark's iterations.
50type GlobalInput;
5152/// Input that will be created for each of the benchmark's iterations.
53type IterationInput;
5455/// Generate the global input (not included in the benchmark time)
56fn global_input(&self) -> Self::GlobalInput;
5758/// Generate the per-iteration input (not included in the benchmark time)
59fn iteration_input(&self) -> Self::IterationInput;
6061/// Perform the operations that we're benchmarking.
62fn benchmarked_code(&self, g_input: &Self::GlobalInput, i_input: Self::IterationInput);
63}
6465fn unique_db_filename() -> String {
66static COUNTER: AtomicU32 = AtomicU32::new(0);
67format!("db{}.sqlite", COUNTER.fetch_add(1, Ordering::Relaxed))
68}
6970fn unique_remote_settings_dir() -> String {
71static COUNTER: AtomicU32 = AtomicU32::new(0);
72format!(
73"remote-settings-{}",
74 COUNTER.fetch_add(1, Ordering::Relaxed)
75 )
76}
7778// Create a "starter" store that will do an initial ingest, and then
79// initialize every returned store with a copy of its DB so that each one
80// doesn't need to reingest.
81static STARTER: Mutex<Option<(TempDir, PathBuf)>> = Mutex::new(None);
8283/// Creates a new store that will contain all provider data currently in remote
84/// settings.
85fn new_store() -> SuggestStore {
86let mut starter = STARTER.lock().unwrap();
87let (starter_dir, starter_db_path) = starter.get_or_insert_with(|| {
88let temp_dir = tempfile::tempdir().unwrap();
89let db_path = temp_dir.path().join(unique_db_filename());
90let remote_settings_dir = temp_dir.path().join(unique_remote_settings_dir());
91let rs_config = RemoteSettingsConfig2 {
92 bucket_name: None,
93 server: None,
94 app_context: Some(RemoteSettingsContext::default()),
95 };
96let remote_settings_service = Arc::new(RemoteSettingsService::new(
97 remote_settings_dir.to_string_lossy().to_string(),
98 rs_config,
99 ));
100let store = SuggestStore::new(&db_path.to_string_lossy(), remote_settings_service);
101 store
102 .ingest(SuggestIngestionConstraints::all_providers())
103 .expect("Error during ingestion");
104 store.checkpoint();
105 (temp_dir, db_path)
106 });
107108let db_path = starter_dir.path().join(unique_db_filename());
109let rs_config = RemoteSettingsConfig2 {
110 bucket_name: None,
111 server: None,
112 app_context: Some(RemoteSettingsContext::default()),
113 };
114let remote_settings_service = Arc::new(RemoteSettingsService::new("".to_string(), rs_config));
115 std::fs::copy(starter_db_path, &db_path).expect("Error copying starter DB file");
116 SuggestStore::new(&db_path.to_string_lossy(), remote_settings_service)
117}
118119/// Cleanup the temp directory created for SuggestStore instances used in the benchmarks.
120pub fn cleanup() {
121*STARTER.lock().unwrap() = None;
122}