Skip to content

Commit cc56276

Browse files
committed
add execution history to the simulator, the history records
three indexes(connection, interaction pointer, and secondary pointer) that can uniquely identify the executed interaction at any point. we will use the history for shrinking purposes.
1 parent daa77fe commit cc56276

File tree

2 files changed

+104
-46
lines changed

2 files changed

+104
-46
lines changed

simulator/generation/plan.rs

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
use std::{fmt::Display, rc::Rc};
22

33
use limbo_core::{Connection, Result, StepResult};
4-
use rand::SeedableRng;
5-
use rand_chacha::ChaCha8Rng;
64

75
use crate::{
86
model::{
@@ -201,19 +199,12 @@ impl InteractionPlan {
201199
}
202200
}
203201

204-
impl ArbitraryFrom<SimulatorEnv> for InteractionPlan {
205-
fn arbitrary_from<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Self {
202+
impl InteractionPlan {
203+
// todo: This is a hack to get around the fact that `ArbitraryFrom<T>` can't take a mutable
204+
// reference of T, so instead write a bespoke function without using the trait system.
205+
pub(crate) fn arbitrary_from<R: rand::Rng>(rng: &mut R, env: &mut SimulatorEnv) -> Self {
206206
let mut plan = InteractionPlan::new();
207207

208-
let mut env = SimulatorEnv {
209-
opts: env.opts.clone(),
210-
tables: vec![],
211-
connections: vec![],
212-
io: env.io.clone(),
213-
db: env.db.clone(),
214-
rng: ChaCha8Rng::seed_from_u64(rng.next_u64()),
215-
};
216-
217208
let num_interactions = env.opts.max_interactions;
218209

219210
// First create at least one table
@@ -231,8 +222,8 @@ impl ArbitraryFrom<SimulatorEnv> for InteractionPlan {
231222
plan.plan.len(),
232223
num_interactions
233224
);
234-
let property = Property::arbitrary_from(rng, &(&env, plan.stats()));
235-
property.shadow(&mut env);
225+
let property = Property::arbitrary_from(rng, &(env, plan.stats()));
226+
property.shadow(env);
236227

237228
plan.plan.push(property);
238229
}

simulator/main.rs

Lines changed: 98 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
#![allow(clippy::arc_with_non_send_sync, dead_code)]
22
use clap::Parser;
3+
use core::panic;
4+
use generation::pick_index;
35
use generation::plan::{Interaction, InteractionPlan, ResultSet};
4-
use generation::{pick_index, ArbitraryFrom};
5-
use limbo_core::{Database, Result};
6+
use limbo_core::{Database, LimboError, Result};
67
use model::table::Value;
78
use rand::prelude::*;
89
use rand_chacha::ChaCha8Rng;
@@ -36,10 +37,12 @@ fn main() {
3637

3738
let db_path = output_dir.join("simulator.db");
3839
let plan_path = output_dir.join("simulator.plan");
40+
let history_path = output_dir.join("simulator.history");
3941

4042
// Print the seed, the locations of the database and the plan file
4143
log::info!("database path: {:?}", db_path);
4244
log::info!("simulator plan path: {:?}", plan_path);
45+
log::info!("simulator history path: {:?}", history_path);
4346
log::info!("seed: {}", seed);
4447

4548
std::panic::set_hook(Box::new(move |info| {
@@ -73,28 +76,34 @@ fn main() {
7376
std::panic::catch_unwind(|| run_simulation(seed, &cli_opts, &db_path, &plan_path));
7477

7578
match (result, result2) {
76-
(Ok(Ok(_)), Err(_)) => {
79+
(Ok(ExecutionResult { error: None, .. }), Err(_)) => {
7780
log::error!("doublecheck failed! first run succeeded, but second run panicked.");
7881
}
79-
(Ok(Err(_)), Err(_)) => {
82+
(Ok(ExecutionResult { error: Some(_), .. }), Err(_)) => {
8083
log::error!(
8184
"doublecheck failed! first run failed assertion, but second run panicked."
8285
);
8386
}
84-
(Err(_), Ok(Ok(_))) => {
87+
(Err(_), Ok(ExecutionResult { error: None, .. })) => {
8588
log::error!("doublecheck failed! first run panicked, but second run succeeded.");
8689
}
87-
(Err(_), Ok(Err(_))) => {
90+
(Err(_), Ok(ExecutionResult { error: Some(_), .. })) => {
8891
log::error!(
8992
"doublecheck failed! first run panicked, but second run failed assertion."
9093
);
9194
}
92-
(Ok(Ok(_)), Ok(Err(_))) => {
95+
(
96+
Ok(ExecutionResult { error: None, .. }),
97+
Ok(ExecutionResult { error: Some(_), .. }),
98+
) => {
9399
log::error!(
94100
"doublecheck failed! first run succeeded, but second run failed assertion."
95101
);
96102
}
97-
(Ok(Err(_)), Ok(Ok(_))) => {
103+
(
104+
Ok(ExecutionResult { error: Some(_), .. }),
105+
Ok(ExecutionResult { error: None, .. }),
106+
) => {
98107
log::error!(
99108
"doublecheck failed! first run failed assertion, but second run succeeded."
100109
);
@@ -122,18 +131,32 @@ fn main() {
122131
std::fs::rename(&old_db_path, &db_path).unwrap();
123132
std::fs::rename(&old_plan_path, &plan_path).unwrap();
124133
} else if let Ok(result) = result {
125-
match result {
126-
Ok(_) => {
134+
// No panic occurred, so write the history to a file
135+
let f = std::fs::File::create(&history_path).unwrap();
136+
let mut f = std::io::BufWriter::new(f);
137+
for execution in result.history.history.iter() {
138+
writeln!(
139+
f,
140+
"{} {} {}",
141+
execution.connection_index, execution.interaction_index, execution.secondary_index
142+
)
143+
.unwrap();
144+
}
145+
146+
match result.error {
147+
None => {
127148
log::info!("simulation completed successfully");
128149
}
129-
Err(e) => {
150+
Some(e) => {
130151
log::error!("simulation failed: {:?}", e);
131152
}
132153
}
133154
}
155+
134156
// Print the seed, the locations of the database and the plan file at the end again for easily accessing them.
135157
println!("database path: {:?}", db_path);
136158
println!("simulator plan path: {:?}", plan_path);
159+
println!("simulator history path: {:?}", history_path);
137160
println!("seed: {}", seed);
138161
}
139162

@@ -142,7 +165,7 @@ fn run_simulation(
142165
cli_opts: &SimulatorCLI,
143166
db_path: &Path,
144167
plan_path: &Path,
145-
) -> Result<()> {
168+
) -> ExecutionResult {
146169
let mut rng = ChaCha8Rng::seed_from_u64(seed);
147170

148171
let (create_percent, read_percent, write_percent, delete_percent) = {
@@ -160,21 +183,15 @@ fn run_simulation(
160183
};
161184

162185
if cli_opts.minimum_size < 1 {
163-
return Err(limbo_core::LimboError::InternalError(
164-
"minimum size must be at least 1".to_string(),
165-
));
186+
panic!("minimum size must be at least 1");
166187
}
167188

168189
if cli_opts.maximum_size < 1 {
169-
return Err(limbo_core::LimboError::InternalError(
170-
"maximum size must be at least 1".to_string(),
171-
));
190+
panic!("maximum size must be at least 1");
172191
}
173192

174193
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-
));
194+
panic!("maximum size must be greater than or equal to minimum size");
178195
}
179196

180197
let opts = SimulatorOpts {
@@ -212,7 +229,7 @@ fn run_simulation(
212229

213230
log::info!("Generating database interaction plan...");
214231
let mut plans = (1..=env.opts.max_connections)
215-
.map(|_| InteractionPlan::arbitrary_from(&mut env.rng.clone(), &env))
232+
.map(|_| InteractionPlan::arbitrary_from(&mut env.rng.clone(), &mut env))
216233
.collect::<Vec<_>>();
217234

218235
let mut f = std::fs::File::create(plan_path).unwrap();
@@ -224,9 +241,6 @@ fn run_simulation(
224241
log::info!("Executing database interaction plan...");
225242

226243
let result = execute_plans(&mut env, &mut plans);
227-
if result.is_err() {
228-
log::error!("error executing plans: {:?}", result.as_ref().err());
229-
}
230244

231245
env.io.print_stats();
232246

@@ -235,23 +249,76 @@ fn run_simulation(
235249
result
236250
}
237251

238-
fn execute_plans(env: &mut SimulatorEnv, plans: &mut [InteractionPlan]) -> Result<()> {
252+
struct Execution {
253+
connection_index: usize,
254+
interaction_index: usize,
255+
secondary_index: usize,
256+
}
257+
258+
impl Execution {
259+
fn new(connection_index: usize, interaction_index: usize, secondary_index: usize) -> Self {
260+
Self {
261+
connection_index,
262+
interaction_index,
263+
secondary_index,
264+
}
265+
}
266+
}
267+
268+
struct ExecutionHistory {
269+
history: Vec<Execution>,
270+
}
271+
272+
impl ExecutionHistory {
273+
fn new() -> Self {
274+
Self {
275+
history: Vec::new(),
276+
}
277+
}
278+
}
279+
280+
struct ExecutionResult {
281+
history: ExecutionHistory,
282+
error: Option<limbo_core::LimboError>,
283+
}
284+
285+
impl ExecutionResult {
286+
fn new(history: ExecutionHistory, error: Option<LimboError>) -> Self {
287+
Self { history, error }
288+
}
289+
}
290+
291+
fn execute_plans(env: &mut SimulatorEnv, plans: &mut [InteractionPlan]) -> ExecutionResult {
292+
let mut history = ExecutionHistory::new();
239293
let now = std::time::Instant::now();
240294
// todo: add history here by recording which interaction was executed at which tick
241295
for _tick in 0..env.opts.ticks {
242296
// Pick the connection to interact with
243297
let connection_index = pick_index(env.connections.len(), &mut env.rng);
298+
history.history.push(Execution::new(
299+
connection_index,
300+
plans[connection_index].interaction_pointer,
301+
plans[connection_index].secondary_pointer,
302+
));
244303
// Execute the interaction for the selected connection
245-
execute_plan(env, connection_index, plans)?;
304+
match execute_plan(env, connection_index, plans) {
305+
Ok(_) => {}
306+
Err(err) => {
307+
return ExecutionResult::new(history, Some(err));
308+
}
309+
}
246310
// Check if the maximum time for the simulation has been reached
247311
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-
));
312+
return ExecutionResult::new(
313+
history,
314+
Some(limbo_core::LimboError::InternalError(
315+
"maximum time for simulation reached".into(),
316+
)),
317+
);
251318
}
252319
}
253320

254-
Ok(())
321+
ExecutionResult::new(history, None)
255322
}
256323

257324
fn execute_plan(

0 commit comments

Comments
 (0)