Skip to content

Commit 21a9f29

Browse files
committed
Merge 'simulator: allow failure assertions' from Alperen Keleş
- add creating the same table two times to the list of checked properties as a failure property example Reviewed-by: Pekka Enberg <[email protected]> Reviewed-by: Jussi Saurio <[email protected]> Reviewed-by: Pere Diaz Bou <[email protected]> Closes #554
2 parents 80fe5b0 + d8ce88c commit 21a9f29

File tree

7 files changed

+137
-42
lines changed

7 files changed

+137
-42
lines changed

simulator/generation/mod.rs

+13-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
use std::{iter::Sum, ops::SubAssign};
2+
13
use anarchist_readable_name_generator_lib::readable_name_custom;
2-
use rand::Rng;
4+
use rand::{distributions::uniform::SampleUniform, Rng};
35

46
pub mod plan;
57
pub mod query;
@@ -13,12 +15,17 @@ pub trait ArbitraryFrom<T> {
1315
fn arbitrary_from<R: Rng>(rng: &mut R, t: &T) -> Self;
1416
}
1517

16-
pub(crate) fn frequency<'a, T, R: rand::Rng>(
17-
choices: Vec<(usize, Box<dyn FnOnce(&mut R) -> T + 'a>)>,
18+
pub(crate) fn frequency<
19+
'a,
20+
T,
21+
R: rand::Rng,
22+
N: Sum + PartialOrd + Copy + Default + SampleUniform + SubAssign,
23+
>(
24+
choices: Vec<(N, Box<dyn FnOnce(&mut R) -> T + 'a>)>,
1825
rng: &mut R,
1926
) -> T {
20-
let total = choices.iter().map(|(weight, _)| weight).sum::<usize>();
21-
let mut choice = rng.gen_range(0..total);
27+
let total = choices.iter().map(|(weight, _)| *weight).sum::<N>();
28+
let mut choice = rng.gen_range(N::default()..total);
2229

2330
for (weight, f) in choices {
2431
if choice < weight {
@@ -38,7 +45,7 @@ pub(crate) fn one_of<'a, T, R: rand::Rng>(
3845
choices[index](rng)
3946
}
4047

41-
pub(crate) fn pick<'a, T, R: rand::Rng>(choices: &'a Vec<T>, rng: &mut R) -> &'a T {
48+
pub(crate) fn pick<'a, T, R: rand::Rng>(choices: &'a [T], rng: &mut R) -> &'a T {
4249
let index = rng.gen_range(0..choices.len());
4350
&choices[index]
4451
}

simulator/generation/plan.rs

+57-17
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::generation::{frequency, Arbitrary, ArbitraryFrom};
1616

1717
use super::{pick, pick_index};
1818

19-
pub(crate) type ResultSet = Vec<Vec<Value>>;
19+
pub(crate) type ResultSet = Result<Vec<Vec<Value>>>;
2020

2121
pub(crate) struct InteractionPlan {
2222
pub(crate) plan: Vec<Interaction>,
@@ -45,14 +45,15 @@ pub(crate) struct InteractionStats {
4545
pub(crate) read_count: usize,
4646
pub(crate) write_count: usize,
4747
pub(crate) delete_count: usize,
48+
pub(crate) create_count: usize,
4849
}
4950

5051
impl Display for InteractionStats {
5152
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5253
write!(
5354
f,
54-
"Read: {}, Write: {}, Delete: {}",
55-
self.read_count, self.write_count, self.delete_count
55+
"Read: {}, Write: {}, Delete: {}, Create: {}",
56+
self.read_count, self.write_count, self.delete_count, self.create_count
5657
)
5758
}
5859
}
@@ -100,7 +101,9 @@ impl Interactions {
100101
match interaction {
101102
Interaction::Query(query) => match query {
102103
Query::Create(create) => {
103-
env.tables.push(create.table.clone());
104+
if !env.tables.iter().any(|t| t.name == create.table.name) {
105+
env.tables.push(create.table.clone());
106+
}
104107
}
105108
Query::Insert(insert) => {
106109
let table = env
@@ -137,14 +140,15 @@ impl InteractionPlan {
137140
let mut read = 0;
138141
let mut write = 0;
139142
let mut delete = 0;
143+
let mut create = 0;
140144

141145
for interaction in &self.plan {
142146
match interaction {
143147
Interaction::Query(query) => match query {
144148
Query::Select(_) => read += 1,
145149
Query::Insert(_) => write += 1,
146150
Query::Delete(_) => delete += 1,
147-
Query::Create(_) => {}
151+
Query::Create(_) => create += 1,
148152
},
149153
Interaction::Assertion(_) => {}
150154
Interaction::Fault(_) => {}
@@ -155,6 +159,7 @@ impl InteractionPlan {
155159
read_count: read,
156160
write_count: write,
157161
delete_count: delete,
162+
create_count: create,
158163
}
159164
}
160165
}
@@ -172,7 +177,7 @@ impl ArbitraryFrom<SimulatorEnv> for InteractionPlan {
172177
rng: ChaCha8Rng::seed_from_u64(rng.next_u64()),
173178
};
174179

175-
let num_interactions = rng.gen_range(0..env.opts.max_interactions);
180+
let num_interactions = env.opts.max_interactions;
176181

177182
// First create at least one table
178183
let create_query = Create::arbitrary(rng);
@@ -197,7 +202,7 @@ impl ArbitraryFrom<SimulatorEnv> for InteractionPlan {
197202
}
198203

199204
impl Interaction {
200-
pub(crate) fn execute_query(&self, conn: &mut Rc<Connection>) -> Result<ResultSet> {
205+
pub(crate) fn execute_query(&self, conn: &mut Rc<Connection>) -> ResultSet {
201206
match self {
202207
Self::Query(query) => {
203208
let query_str = query.to_string();
@@ -342,13 +347,40 @@ fn property_insert_select<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Inte
342347
),
343348
func: Box::new(move |stack: &Vec<ResultSet>| {
344349
let rows = stack.last().unwrap();
345-
rows.iter().any(|r| r == &row)
350+
match rows {
351+
Ok(rows) => rows.iter().any(|r| r == &row),
352+
Err(_) => false,
353+
}
346354
}),
347355
});
348356

349357
Interactions(vec![insert_query, select_query, assertion])
350358
}
351359

360+
fn property_double_create_failure<R: rand::Rng>(rng: &mut R, _env: &SimulatorEnv) -> Interactions {
361+
let create_query = Create::arbitrary(rng);
362+
let table_name = create_query.table.name.clone();
363+
let cq1 = Interaction::Query(Query::Create(create_query.clone()));
364+
let cq2 = Interaction::Query(Query::Create(create_query.clone()));
365+
366+
let assertion = Interaction::Assertion(Assertion {
367+
message:
368+
"creating two tables with the name should result in a failure for the second query"
369+
.to_string(),
370+
func: Box::new(move |stack: &Vec<ResultSet>| {
371+
let last = stack.last().unwrap();
372+
match last {
373+
Ok(_) => false,
374+
Err(e) => e
375+
.to_string()
376+
.contains(&format!("Table {table_name} already exists")),
377+
}
378+
}),
379+
});
380+
381+
Interactions(vec![cq1, cq2, assertion])
382+
}
383+
352384
fn create_table<R: rand::Rng>(rng: &mut R, _env: &SimulatorEnv) -> Interactions {
353385
let create_query = Interaction::Query(Query::Create(Create::arbitrary(rng)));
354386
Interactions(vec![create_query])
@@ -375,17 +407,21 @@ impl ArbitraryFrom<(&SimulatorEnv, InteractionStats)> for Interactions {
375407
rng: &mut R,
376408
(env, stats): &(&SimulatorEnv, InteractionStats),
377409
) -> Self {
378-
let remaining_read =
379-
((((env.opts.max_interactions * env.opts.read_percent) as f64) / 100.0) as usize)
380-
.saturating_sub(stats.read_count);
381-
let remaining_write = ((((env.opts.max_interactions * env.opts.write_percent) as f64)
382-
/ 100.0) as usize)
383-
.saturating_sub(stats.write_count);
410+
let remaining_read = ((env.opts.max_interactions as f64 * env.opts.read_percent / 100.0)
411+
- (stats.read_count as f64))
412+
.max(0.0);
413+
let remaining_write = ((env.opts.max_interactions as f64 * env.opts.write_percent / 100.0)
414+
- (stats.write_count as f64))
415+
.max(0.0);
416+
let remaining_create = ((env.opts.max_interactions as f64 * env.opts.create_percent
417+
/ 100.0)
418+
- (stats.create_count as f64))
419+
.max(0.0);
384420

385421
frequency(
386422
vec![
387423
(
388-
usize::min(remaining_read, remaining_write),
424+
f64::min(remaining_read, remaining_write),
389425
Box::new(|rng: &mut R| property_insert_select(rng, env)),
390426
),
391427
(
@@ -397,10 +433,14 @@ impl ArbitraryFrom<(&SimulatorEnv, InteractionStats)> for Interactions {
397433
Box::new(|rng: &mut R| random_write(rng, env)),
398434
),
399435
(
400-
remaining_write / 10,
436+
remaining_create,
401437
Box::new(|rng: &mut R| create_table(rng, env)),
402438
),
403-
(1, Box::new(|rng: &mut R| random_fault(rng, env))),
439+
(1.0, Box::new(|rng: &mut R| random_fault(rng, env))),
440+
(
441+
remaining_create / 2.0,
442+
Box::new(|rng: &mut R| property_double_create_failure(rng, env)),
443+
),
404444
],
405445
rng,
406446
)

simulator/generation/table.rs

+1-5
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,7 @@ impl Arbitrary for Column {
4141

4242
impl Arbitrary for ColumnType {
4343
fn arbitrary<R: Rng>(rng: &mut R) -> Self {
44-
pick(
45-
&vec![Self::Integer, Self::Float, Self::Text, Self::Blob],
46-
rng,
47-
)
48-
.to_owned()
44+
pick(&[Self::Integer, Self::Float, Self::Text, Self::Blob], rng).to_owned()
4945
}
5046
}
5147

simulator/main.rs

+45-9
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,15 @@ fn main() {
121121
// Move the old database and plan file back
122122
std::fs::rename(&old_db_path, &db_path).unwrap();
123123
std::fs::rename(&old_plan_path, &plan_path).unwrap();
124+
} else if let Ok(result) = result {
125+
match result {
126+
Ok(_) => {
127+
log::info!("simulation completed successfully");
128+
}
129+
Err(e) => {
130+
log::error!("simulation failed: {:?}", e);
131+
}
132+
}
124133
}
125134
// Print the seed, the locations of the database and the plan file at the end again for easily accessing them.
126135
println!("database path: {:?}", db_path);
@@ -136,30 +145,50 @@ fn run_simulation(
136145
) -> Result<()> {
137146
let mut rng = ChaCha8Rng::seed_from_u64(seed);
138147

139-
let (read_percent, write_percent, delete_percent) = {
140-
let mut remaining = 100;
141-
let read_percent = rng.gen_range(0..=remaining);
148+
let (create_percent, read_percent, write_percent, delete_percent) = {
149+
let mut remaining = 100.0;
150+
let read_percent = rng.gen_range(0.0..=remaining);
142151
remaining -= read_percent;
143-
let write_percent = rng.gen_range(0..=remaining);
152+
let write_percent = rng.gen_range(0.0..=remaining);
144153
remaining -= write_percent;
145154
let delete_percent = remaining;
146-
(read_percent, write_percent, delete_percent)
155+
156+
let create_percent = write_percent / 10.0;
157+
let write_percent = write_percent - create_percent;
158+
159+
(create_percent, read_percent, write_percent, delete_percent)
147160
};
148161

162+
if cli_opts.minimum_size < 1 {
163+
return Err(limbo_core::LimboError::InternalError(
164+
"minimum size must be at least 1".to_string(),
165+
));
166+
}
167+
149168
if cli_opts.maximum_size < 1 {
150-
panic!("maximum size must be at least 1");
169+
return Err(limbo_core::LimboError::InternalError(
170+
"maximum size must be at least 1".to_string(),
171+
));
172+
}
173+
174+
if cli_opts.maximum_size < cli_opts.minimum_size {
175+
return Err(limbo_core::LimboError::InternalError(
176+
"maximum size must be greater than or equal to minimum size".to_string(),
177+
));
151178
}
152179

153180
let opts = SimulatorOpts {
154-
ticks: rng.gen_range(1..=cli_opts.maximum_size),
181+
ticks: rng.gen_range(cli_opts.minimum_size..=cli_opts.maximum_size),
155182
max_connections: 1, // TODO: for now let's use one connection as we didn't implement
156183
// correct transactions procesing
157184
max_tables: rng.gen_range(0..128),
185+
create_percent,
158186
read_percent,
159187
write_percent,
160188
delete_percent,
161189
page_size: 4096, // TODO: randomize this too
162-
max_interactions: rng.gen_range(1..=cli_opts.maximum_size),
190+
max_interactions: rng.gen_range(cli_opts.minimum_size..=cli_opts.maximum_size),
191+
max_time_simulation: cli_opts.maximum_time,
163192
};
164193
let io = Arc::new(SimulatorIO::new(seed, opts.page_size).unwrap());
165194

@@ -207,12 +236,19 @@ fn run_simulation(
207236
}
208237

209238
fn execute_plans(env: &mut SimulatorEnv, plans: &mut [InteractionPlan]) -> Result<()> {
239+
let now = std::time::Instant::now();
210240
// todo: add history here by recording which interaction was executed at which tick
211241
for _tick in 0..env.opts.ticks {
212242
// Pick the connection to interact with
213243
let connection_index = pick_index(env.connections.len(), &mut env.rng);
214244
// Execute the interaction for the selected connection
215245
execute_plan(env, connection_index, plans)?;
246+
// Check if the maximum time for the simulation has been reached
247+
if now.elapsed().as_secs() >= env.opts.max_time_simulation as u64 {
248+
return Err(limbo_core::LimboError::InternalError(
249+
"maximum time for simulation reached".into(),
250+
));
251+
}
216252
}
217253

218254
Ok(())
@@ -266,7 +302,7 @@ fn execute_interaction(
266302
};
267303

268304
log::debug!("{}", interaction);
269-
let results = interaction.execute_query(conn)?;
305+
let results = interaction.execute_query(conn);
270306
log::debug!("{:?}", results);
271307
stack.push(results);
272308
}

simulator/model/query.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ pub(crate) enum Query {
6161
Delete(Delete),
6262
}
6363

64-
#[derive(Debug)]
64+
#[derive(Debug, Clone)]
6565
pub(crate) struct Create {
6666
pub(crate) table: Table,
6767
}

simulator/runner/cli.rs

+15-1
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,24 @@ pub struct SimulatorCLI {
1515
)]
1616
pub doublecheck: bool,
1717
#[clap(
18-
short,
18+
short = 'n',
1919
long,
2020
help = "change the maximum size of the randomly generated sequence of interactions",
2121
default_value_t = 1024
2222
)]
2323
pub maximum_size: usize,
24+
#[clap(
25+
short = 'k',
26+
long,
27+
help = "change the minimum size of the randomly generated sequence of interactions",
28+
default_value_t = 1
29+
)]
30+
pub minimum_size: usize,
31+
#[clap(
32+
short = 't',
33+
long,
34+
help = "change the maximum time of the simulation(in seconds)",
35+
default_value_t = 60 * 60 // default to 1 hour
36+
)]
37+
pub maximum_time: usize,
2438
}

simulator/runner/env.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@ pub(crate) struct SimulatorOpts {
3030
pub(crate) max_tables: usize,
3131
// this next options are the distribution of workload where read_percent + write_percent +
3232
// delete_percent == 100%
33-
pub(crate) read_percent: usize,
34-
pub(crate) write_percent: usize,
35-
pub(crate) delete_percent: usize,
33+
pub(crate) create_percent: f64,
34+
pub(crate) read_percent: f64,
35+
pub(crate) write_percent: f64,
36+
pub(crate) delete_percent: f64,
3637
pub(crate) max_interactions: usize,
3738
pub(crate) page_size: usize,
39+
pub(crate) max_time_simulation: usize,
3840
}

0 commit comments

Comments
 (0)