Skip to content

Commit

Permalink
feat: load config and overwrite with CLI args
Browse files Browse the repository at this point in the history
  • Loading branch information
enigbe committed Feb 1, 2024
1 parent 38ca305 commit d3ea832
Show file tree
Hide file tree
Showing 2 changed files with 240 additions and 38 deletions.
209 changes: 209 additions & 0 deletions sim-cli/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
use std::{path::PathBuf, str::FromStr};

use anyhow::Context;
use log::LevelFilter;
use serde::Deserialize;

use crate::Cli;

#[derive(Debug)]
pub struct SimulationConfig {
// Path to a directory where simulation results are save
pub data_dir: PathBuf,
// Path to simulation file
pub sim_file: PathBuf,
// Duration for which the simulation should run
pub total_time: Option<u32>,
// Number of activity results to batch together before printing to csv file [min: 1]
pub print_batch_size: u32,
/// Level of verbosity of the messages displayed by the simulator.
/// Possible values: [off, error, warn, info, debug, trace]
pub log_level: LevelFilter,
/// Expected payment amount for the random activity generator
pub expected_pmt_amt: u64,
/// Multiplier of the overall network capacity used by the random activity generator
pub capacity_multiplier: f64,
/// Do not create an output file containing the simulations results
pub no_results: bool,
/// Duration after which results are logged
pub log_interval: u64,
}

impl<'de> Deserialize<'de> for SimulationConfig {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct SimulationConfigHelper {
data_dir: PathBuf,
sim_file: PathBuf,
total_time: Option<u32>,
print_batch_size: u32,
log_level: String,
expected_pmt_amt: u64,
capacity_multiplier: f64,
no_results: bool,
log_interval: u64,
}

let helper: SimulationConfigHelper = Deserialize::deserialize(deserializer)?;

let log_level =
LevelFilter::from_str(&helper.log_level).map_err(serde::de::Error::custom)?;

Ok(SimulationConfig {
data_dir: helper.data_dir,
sim_file: helper.sim_file,
total_time: helper.total_time,
print_batch_size: helper.print_batch_size,
log_level,
expected_pmt_amt: helper.expected_pmt_amt,
capacity_multiplier: helper.capacity_multiplier,
no_results: helper.no_results,
log_interval: helper.log_interval,
})
}
}

impl SimulationConfig {
/// The default simulation configuration file with default simulation values
pub const DEFAULT_SIM_CONFIG_FILE: &'static str = "conf.json";

/// Loads the simulation configuration from a path and overwrites loaded values with
/// CLI arguments
pub fn load(
path: &PathBuf,
Cli {
data_dir,
sim_file,
total_time,
print_batch_size,
log_level,
expected_pmt_amt,
capacity_multiplier,
no_results,
}: &Cli,
) -> anyhow::Result<Self> {
// 1. Load simulation config from specified path
let mut sim_conf = serde_json::from_str::<SimulationConfig>(
&std::fs::read_to_string(&path)
.context(format!("Failed to read content of {:?} to string", path))?,
)
.context("Failed to deserialize configuration string into SimulationConfig")?;

// 2. Override config values with CLI arguments (if passed)
if let Some(data_directory) = data_dir {
log::info!(
"SimulatinConfig::data_dir {} overwritten by CLI argument {}",
sim_conf.data_dir.display(),
data_directory.display()
);
sim_conf.data_dir = data_directory.to_owned();
}

if let Some(simulation_file) = sim_file {
log::info!(
"SimulatinConfig::sim_file {} overwritten by CLI argument {}",
sim_conf.sim_file.display(),
simulation_file.display()
);
sim_conf.sim_file = simulation_file.to_owned();
}

if let Some(total_simulation_time) = total_time {
log::info!(
"SimulatinConfig::total_time {:?} overwritten by CLI argument {}",
sim_conf.total_time,
total_simulation_time
);
sim_conf.total_time = Some(*total_simulation_time);
}

if let Some(batch_size_to_print) = print_batch_size {
log::info!(
"SimulatinConfig::print_batch_size {:?} overwritten by CLI argument {}",
sim_conf.print_batch_size,
batch_size_to_print
);
sim_conf.print_batch_size = *batch_size_to_print;
}

if let Some(log_level_filter) = log_level {
log::info!(
"SimulatinConfig::log_level {:?} overwritten by CLI argument {}",
sim_conf.log_level,
log_level_filter
);
sim_conf.log_level = *log_level_filter;
}

if let Some(expected_payment_amount) = expected_pmt_amt {
log::info!(
"SimulatinConfig::expected_pmt_amt {:?} overwritten by CLI argument {}",
sim_conf.expected_pmt_amt,
expected_payment_amount
);
sim_conf.expected_pmt_amt = *expected_payment_amount;
}

if let Some(capacity_multiplier) = capacity_multiplier {
log::info!(
"SimulatinConfig::capacity_multiplier {:?} overwritten by CLI argument {}",
sim_conf.capacity_multiplier,
capacity_multiplier
);
sim_conf.capacity_multiplier = *capacity_multiplier;
}

if let Some(results) = no_results {
log::info!(
"SimulatinConfig::no_results {:?} overwritten by CLI argument {}",
sim_conf.no_results,
results
);
sim_conf.no_results = *results;
}

Ok(sim_conf)
}
}

#[cfg(test)]
mod test {
use std::{path::PathBuf, str::FromStr};

use crate::Cli;

use super::SimulationConfig;

#[test]
fn replace_config_with_cli_args() {
let cli = Cli{
data_dir: Some(PathBuf::from_str("data").expect("Failed to create test data directory")),
sim_file: Some(PathBuf::from_str("sim.json").expect("Failed to create test simulation file")),
total_time: Some(60),
print_batch_size: Some(10),
log_level: Some(log::LevelFilter::Debug),
expected_pmt_amt: Some(500),
capacity_multiplier: Some(3.142),
no_results: Some(true)
};

let conf = SimulationConfig::load(
&PathBuf::from("../conf.json"),
&cli
)
.expect("Failed to create simulation config");

assert_eq!(Some(conf.data_dir), cli.data_dir);
assert_eq!(Some(conf.sim_file), cli.sim_file);
assert_eq!(conf.total_time, cli.total_time);
assert_eq!(Some(conf.print_batch_size), cli.print_batch_size);
assert_eq!(Some(conf.log_level), cli.log_level);
assert_eq!(Some(conf.expected_pmt_amt), cli.expected_pmt_amt);
assert_eq!(Some(conf.capacity_multiplier), cli.capacity_multiplier);
assert_eq!(Some(conf.no_results), cli.no_results);

}
}
69 changes: 31 additions & 38 deletions sim-cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
mod config;

use bitcoin::secp256k1::PublicKey;
use config::SimulationConfig;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
Expand All @@ -14,21 +17,6 @@ use sim_lib::{
};
use simple_logger::SimpleLogger;

/// The default directory where the simulation files are stored and where the results will be written to.
pub const DEFAULT_DATA_DIR: &str = ".";

/// The default simulation file to be used by the simulator.
pub const DEFAULT_SIM_FILE: &str = "sim.json";

/// The default expected payment amount for the simulation, around ~$10 at the time of writing.
pub const EXPECTED_PAYMENT_AMOUNT: u64 = 3_800_000;

/// The number of times over each node in the network sends its total deployed capacity in a calendar month.
pub const ACTIVITY_MULTIPLIER: f64 = 2.0;

/// Default batch size to flush result data to disk
const DEFAULT_PRINT_BATCH_SIZE: u32 = 500;

/// Deserializes a f64 as long as it is positive and greater than 0.
fn deserialize_f64_greater_than_zero(x: String) -> Result<f64, String> {
match x.parse::<f64>() {
Expand All @@ -49,45 +37,49 @@ fn deserialize_f64_greater_than_zero(x: String) -> Result<f64, String> {
#[command(version, about)]
struct Cli {
/// Path to a directory containing simulation files, and where simulation results will be stored
#[clap(long, short, default_value = DEFAULT_DATA_DIR)]
data_dir: PathBuf,
#[clap(long, short)]
data_dir: Option<PathBuf>,
/// Path to the simulation file to be used by the simulator
/// This can either be an absolute path, or relative path with respect to data_dir
#[clap(long, short, default_value = DEFAULT_SIM_FILE)]
sim_file: PathBuf,
#[clap(long, short)]
sim_file: Option<PathBuf>,
/// Total time the simulator will be running
#[clap(long, short)]
total_time: Option<u32>,
/// Number of activity results to batch together before printing to csv file [min: 1]
#[clap(long, short, default_value_t = DEFAULT_PRINT_BATCH_SIZE, value_parser = clap::builder::RangedU64ValueParser::<u32>::new().range(1..u32::MAX as u64))]
print_batch_size: u32,
#[clap(long, short, value_parser = clap::builder::RangedU64ValueParser::<u32>::new().range(1..u32::MAX as u64))]
print_batch_size: Option<u32>,
/// Level of verbosity of the messages displayed by the simulator.
/// Possible values: [off, error, warn, info, debug, trace]
#[clap(long, short, verbatim_doc_comment, default_value = "info")]
log_level: LevelFilter,
#[clap(long, short, verbatim_doc_comment)]
log_level: Option<LevelFilter>,
/// Expected payment amount for the random activity generator
#[clap(long, short, default_value_t = EXPECTED_PAYMENT_AMOUNT, value_parser = clap::builder::RangedU64ValueParser::<u64>::new().range(1..u64::MAX))]
expected_pmt_amt: u64,
#[clap(long, short, value_parser = clap::builder::RangedU64ValueParser::<u64>::new().range(1..u64::MAX))]
expected_pmt_amt: Option<u64>,
/// Multiplier of the overall network capacity used by the random activity generator
#[clap(long, short, default_value_t = ACTIVITY_MULTIPLIER, value_parser = clap::builder::StringValueParser::new().try_map(deserialize_f64_greater_than_zero))]
capacity_multiplier: f64,
#[clap(long, short, value_parser = clap::builder::StringValueParser::new().try_map(deserialize_f64_greater_than_zero))]
capacity_multiplier: Option<f64>,
/// Do not create an output file containing the simulations results
#[clap(long, default_value_t = false)]
no_results: bool,
#[clap(long)]
no_results: Option<bool>,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
let conf = SimulationConfig::load(
&PathBuf::from(SimulationConfig::DEFAULT_SIM_CONFIG_FILE),
&cli,
)?;

SimpleLogger::new()
.with_level(LevelFilter::Warn)
.with_module_level("sim_lib", cli.log_level)
.with_module_level("sim_cli", cli.log_level)
.with_module_level("sim_lib", conf.log_level)
.with_module_level("sim_cli", conf.log_level)
.init()
.unwrap();

let sim_path = read_sim_path(cli.data_dir.clone(), cli.sim_file).await?;
let sim_path = read_sim_path(conf.data_dir.clone(), conf.sim_file).await?;
let SimParams { nodes, activity } =
serde_json::from_str(&std::fs::read_to_string(sim_path)?)
.map_err(|e| anyhow!("Could not deserialize node connection data or activity description from simulation file (line {}, col {}).", e.line(), e.column()))?;
Expand Down Expand Up @@ -190,10 +182,10 @@ async fn main() -> anyhow::Result<()> {
});
}

let write_results = if !cli.no_results {
let write_results = if !conf.no_results {
Some(WriteResults {
results_dir: mkdir(cli.data_dir.join("results")).await?,
batch_size: cli.print_batch_size,
results_dir: mkdir(conf.data_dir.join("results")).await?,
batch_size: conf.print_batch_size,
})
} else {
None
Expand All @@ -202,10 +194,11 @@ async fn main() -> anyhow::Result<()> {
let sim = Simulation::new(
clients,
validated_activities,
cli.total_time,
cli.expected_pmt_amt,
cli.capacity_multiplier,
conf.total_time,
conf.expected_pmt_amt,
conf.capacity_multiplier,
write_results,
conf.log_interval,
);
let sim2 = sim.clone();

Expand Down

0 comments on commit d3ea832

Please sign in to comment.