-
Notifications
You must be signed in to change notification settings - Fork 42
/
Copy pathsled_reservation_benchmark.rs
346 lines (306 loc) · 9.97 KB
/
sled_reservation_benchmark.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//! Benchmarks creating sled reservations
use criterion::black_box;
use criterion::{criterion_group, criterion_main, Criterion};
use nexus_db_model::ByteCount;
use nexus_db_model::Generation;
use nexus_db_model::Project;
use nexus_db_model::Resources;
use nexus_db_model::Sled;
use nexus_db_model::SledBaseboard;
use nexus_db_model::SledReservationConstraintBuilder;
use nexus_db_model::SledSystemHardware;
use nexus_db_model::SledUpdate;
use nexus_db_queries::authz;
use nexus_db_queries::context::OpContext;
use nexus_db_queries::db::pub_test_utils::TestDatabase;
use nexus_db_queries::db::DataStore;
use nexus_types::external_api::params;
use omicron_common::api::external;
use omicron_test_utils::dev;
use omicron_uuid_kinds::InstanceUuid;
use omicron_uuid_kinds::PropolisUuid;
use slog::Logger;
use std::net::Ipv6Addr;
use std::net::SocketAddrV6;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::Duration;
use std::time::Instant;
use uuid::Uuid;
/////////////////////////////////////////////////////////////////
//
// TEST HELPERS
//
// These are largely ripped out of "nexus/db-queries/src/db/datastore".
//
// Benchmarks are compiled as external binaries from library crates, so we
// can only access `pub` code.
//
// It may be worth refactoring some of these functions to a test utility
// crate to avoid the de-duplication.
async fn create_project(
opctx: &OpContext,
datastore: &DataStore,
) -> (authz::Project, Project) {
let authz_silo = opctx.authn.silo_required().unwrap();
// Create a project
let project = Project::new(
authz_silo.id(),
params::ProjectCreate {
identity: external::IdentityMetadataCreateParams {
name: "project".parse().unwrap(),
description: "desc".to_string(),
},
},
);
datastore.project_create(&opctx, project).await.unwrap()
}
fn rack_id() -> Uuid {
Uuid::parse_str(nexus_test_utils::RACK_UUID).unwrap()
}
// Creates a "fake" Sled Baseboard.
fn sled_baseboard_for_test() -> SledBaseboard {
SledBaseboard {
serial_number: Uuid::new_v4().to_string(),
part_number: String::from("test-part"),
revision: 1,
}
}
// Creates "fake" sled hardware accounting
fn sled_system_hardware_for_test() -> SledSystemHardware {
SledSystemHardware {
is_scrimlet: false,
usable_hardware_threads: 32,
usable_physical_ram: ByteCount::try_from(1 << 40).unwrap(),
reservoir_size: ByteCount::try_from(1 << 39).unwrap(),
}
}
fn test_new_sled_update() -> SledUpdate {
let sled_id = Uuid::new_v4();
let addr = SocketAddrV6::new(Ipv6Addr::LOCALHOST, 0, 0, 0);
let repo_depot_port = 0;
SledUpdate::new(
sled_id,
addr,
repo_depot_port,
sled_baseboard_for_test(),
sled_system_hardware_for_test(),
rack_id(),
Generation::new(),
)
}
async fn create_sleds(datastore: &DataStore, count: usize) -> Vec<Sled> {
let mut sleds = vec![];
for _ in 0..count {
let (sled, _) =
datastore.sled_upsert(test_new_sled_update()).await.unwrap();
sleds.push(sled);
}
sleds
}
fn small_resource_request() -> Resources {
Resources::new(
1,
// Just require the bare non-zero amount of RAM.
ByteCount::try_from(1024).unwrap(),
ByteCount::try_from(1024).unwrap(),
)
}
async fn create_reservation(opctx: &OpContext, db: &DataStore) -> PropolisUuid {
let instance_id = InstanceUuid::new_v4();
let vmm_id = PropolisUuid::new_v4();
db.sled_reservation_create(
&opctx,
instance_id,
vmm_id,
small_resource_request(),
SledReservationConstraintBuilder::new().build(),
)
.await
.expect("Failed to create reservation");
vmm_id
}
async fn delete_reservation(
opctx: &OpContext,
db: &DataStore,
vmm_id: PropolisUuid,
) {
db.sled_reservation_delete(&opctx, vmm_id)
.await
.expect("Failed to delete reservation");
}
/////////////////////////////////////////////////////////////////
//
// TEST HARNESS
//
// This structure shares logic between benchmarks, making it easy
// to perform shared tasks such as creating contention for reservations.
struct TestHarness {
log: Logger,
db: TestDatabase,
sleds: Vec<Sled>,
}
impl TestHarness {
async fn new(log: &Logger, sled_count: usize) -> Self {
let db = TestDatabase::new_with_datastore(log).await;
let (opctx, datastore) = (db.opctx(), db.datastore());
let (authz_project, _project) =
create_project(&opctx, &datastore).await;
let sleds = create_sleds(&datastore, sled_count).await;
Self { log: log.clone(), db, sleds }
}
async fn create_reservation(&self) -> PropolisUuid {
let (opctx, datastore) = (self.db.opctx(), self.db.datastore());
create_reservation(opctx, datastore).await
}
async fn delete_reservation(&self, vmm_id: PropolisUuid) {
let (opctx, datastore) = (self.db.opctx(), self.db.datastore());
delete_reservation(opctx, datastore, vmm_id).await
}
// Spin up a number of background tasks which perform the work of "create
// reservation, destroy reservation" in a loop.
fn create_contention(&self, count: usize) -> ContendingTasks {
let mut tasks = tokio::task::JoinSet::new();
let should_exit = Arc::new(AtomicBool::new(false));
for _ in 0..count {
tasks.spawn({
let should_exit = should_exit.clone();
let opctx =
self.db.opctx().child(std::collections::BTreeMap::new());
let datastore = self.db.datastore().clone();
async move {
loop {
if should_exit.load(Ordering::SeqCst) {
return;
}
let vmm_id =
create_reservation(&opctx, &datastore).await;
delete_reservation(&opctx, &datastore, vmm_id).await;
}
}
});
}
ContendingTasks { tasks, should_exit }
}
async fn terminate(self) {
self.db.terminate().await;
}
}
// A handle to tasks created by [TestHarness::create_contention].
//
// Should be terminated after the benchmark has completed executing.
#[must_use]
struct ContendingTasks {
tasks: tokio::task::JoinSet<()>,
should_exit: Arc<AtomicBool>,
}
impl ContendingTasks {
async fn terminate(self) {
self.should_exit.store(true, Ordering::SeqCst);
self.tasks.join_all().await;
}
}
/////////////////////////////////////////////////////////////////
//
// PARAMETERS
//
// Describes varations between otherwise shared test logic
#[derive(Copy, Clone)]
struct TestParams {
vmms: usize,
contending_tasks: usize,
}
/////////////////////////////////////////////////////////////////
//
// BENCHMARKS
//
// You can run these with the following command:
//
// ```bash
// cargo bench -p nexus-db-queries
// ```
async fn bench_reservation(
log: &Logger,
params: TestParams,
iterations: u64,
) -> Duration {
const SLED_COUNT: usize = 4;
let harness = TestHarness::new(&log, SLED_COUNT).await;
let tasks = harness.create_contention(params.contending_tasks);
let start = Instant::now();
for _ in 0..iterations {
let mut vmm_ids = Vec::with_capacity(params.vmms);
black_box({
for _ in 0..params.vmms {
vmm_ids.push(harness.create_reservation().await);
}
for vmm_id in vmm_ids.drain(..) {
harness.delete_reservation(vmm_id).await;
}
});
}
let duration = start.elapsed();
tasks.terminate().await;
harness.terminate().await;
duration
}
// Typically we run our database tests using "cargo nextest run",
// which triggers the "crdb-seed" binary to create an initialized
// database when we boot up.
//
// If we're using "cargo bench", we don't get that guarantee.
// Go through the database ensuring process manually.
async fn setup_db(log: &Logger) {
print!("setting up seed cockroachdb database... ");
let (seed_tar, status) = dev::seed::ensure_seed_tarball_exists(
log,
dev::seed::should_invalidate_seed(),
)
.await
.expect("Failed to create seed tarball for CRDB");
status.log(log, &seed_tar);
unsafe {
std::env::set_var(dev::CRDB_SEED_TAR_ENV, seed_tar);
}
println!("OK");
}
fn sled_reservation_benchmark(c: &mut Criterion) {
let logctx = dev::test_setup_log("sled-reservation");
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(setup_db(&logctx.log));
let mut group = c.benchmark_group("vmm-reservation");
for vmms in [1, 10, 100] {
for contending_tasks in [0, 1, 2] {
let params = TestParams { vmms, contending_tasks };
let name = format!(
"{vmms}-vmms-{contending_tasks}-other-tasks"
);
group.bench_function(&name, |b| {
b.to_async(&rt).iter_custom(|iters| {
let log = logctx.log.clone();
async move {
bench_reservation(&log, params, iters).await
}
})
});
}
}
group.finish();
logctx.cleanup_successful();
}
criterion_group!(
name = benches;
// To accomodate the fact that these benchmarks are a bit bulky,
// we set the following:
// - Smaller sample size, to keep running time down
// - Higher noise threshold, to avoid avoid false positive change detection
config = Criterion::default()
.sample_size(10)
.noise_threshold(0.10);
targets = sled_reservation_benchmark
);
criterion_main!(benches);