Files
turso/simulator/runner/env.rs
2025-04-11 15:33:32 -04:00

186 lines
5.5 KiB
Rust

use std::fmt::Display;
use std::mem;
use std::path::Path;
use std::rc::Rc;
use std::sync::Arc;
use limbo_core::Database;
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
use crate::model::table::Table;
use crate::runner::io::SimulatorIO;
use super::cli::SimulatorCLI;
pub(crate) struct SimulatorEnv {
pub(crate) opts: SimulatorOpts,
pub(crate) tables: Vec<Table>,
pub(crate) connections: Vec<SimConnection>,
pub(crate) io: Arc<SimulatorIO>,
pub(crate) db: Arc<Database>,
pub(crate) rng: ChaCha8Rng,
}
impl SimulatorEnv {
pub(crate) fn clone_without_connections(&self) -> Self {
SimulatorEnv {
opts: self.opts.clone(),
tables: self.tables.clone(),
connections: (0..self.connections.len())
.map(|_| SimConnection::Disconnected)
.collect(),
io: self.io.clone(),
db: self.db.clone(),
rng: self.rng.clone(),
}
}
}
impl SimulatorEnv {
pub(crate) fn new(seed: u64, cli_opts: &SimulatorCLI, db_path: &Path) -> Self {
let mut rng = ChaCha8Rng::seed_from_u64(seed);
let (create_percent, read_percent, write_percent, delete_percent, drop_percent) = {
let total = 100.0;
let read_percent = rng.gen_range(0.0..=total);
let write_percent = total - read_percent;
// Create percent should be 5-15% of the write percent
let create_percent = rng.gen_range(0.05..=0.15) * write_percent;
// Drop percent should be 2-5% of the write percent
let drop_percent = rng.gen_range(0.02..=0.05) * write_percent;
// Delete percent should be 10-20% of the write percent
let delete_percent = rng.gen_range(0.1..=0.2) * write_percent;
let write_percent = write_percent - create_percent - delete_percent - drop_percent;
(
create_percent,
read_percent,
write_percent,
delete_percent,
drop_percent,
)
};
let opts = SimulatorOpts {
ticks: rng.gen_range(cli_opts.minimum_tests..=cli_opts.maximum_tests),
max_connections: 1, // TODO: for now let's use one connection as we didn't implement
// correct transactions processing
max_tables: rng.gen_range(0..128),
create_percent,
read_percent,
write_percent,
delete_percent,
drop_percent,
page_size: 4096, // TODO: randomize this too
max_interactions: rng.gen_range(cli_opts.minimum_tests..=cli_opts.maximum_tests),
max_time_simulation: cli_opts.maximum_time,
};
let io = Arc::new(SimulatorIO::new(seed, opts.page_size).unwrap());
// Remove existing database file if it exists
if db_path.exists() {
std::fs::remove_file(db_path).unwrap();
}
let wal_path = db_path.with_extension("db-wal");
if wal_path.exists() {
std::fs::remove_file(wal_path).unwrap();
}
let db = match Database::open_file(io.clone(), db_path.to_str().unwrap(), false) {
Ok(db) => db,
Err(e) => {
panic!("error opening simulator test file {:?}: {:?}", db_path, e);
}
};
let connections = (0..opts.max_connections)
.map(|_| SimConnection::Disconnected)
.collect::<Vec<_>>();
SimulatorEnv {
opts,
tables: Vec::new(),
connections,
rng,
io,
db,
}
}
}
pub trait ConnectionTrait
where
Self: std::marker::Sized + Clone,
{
fn is_connected(&self) -> bool;
fn disconnect(&mut self);
}
pub(crate) enum SimConnection {
LimboConnection(Rc<limbo_core::Connection>),
SQLiteConnection(rusqlite::Connection),
Disconnected,
}
impl SimConnection {
pub(crate) fn is_connected(&self) -> bool {
match self {
SimConnection::LimboConnection(_) | SimConnection::SQLiteConnection(_) => true,
SimConnection::Disconnected => false,
}
}
pub(crate) fn disconnect(&mut self) {
let conn = mem::replace(self, SimConnection::Disconnected);
match conn {
SimConnection::LimboConnection(conn) => {
conn.close().unwrap();
}
SimConnection::SQLiteConnection(conn) => {
conn.close().unwrap();
}
SimConnection::Disconnected => {}
}
}
}
impl Display for SimConnection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SimConnection::LimboConnection(_) => {
write!(f, "LimboConnection")
}
SimConnection::SQLiteConnection(_) => {
write!(f, "SQLiteConnection")
}
SimConnection::Disconnected => {
write!(f, "Disconnected")
}
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct SimulatorOpts {
pub(crate) ticks: usize,
pub(crate) max_connections: usize,
pub(crate) max_tables: usize,
// this next options are the distribution of workload where read_percent + write_percent +
// delete_percent == 100%
pub(crate) create_percent: f64,
pub(crate) read_percent: f64,
pub(crate) write_percent: f64,
pub(crate) delete_percent: f64,
pub(crate) drop_percent: f64,
pub(crate) max_interactions: usize,
pub(crate) page_size: usize,
pub(crate) max_time_simulation: usize,
}