Skip to content

Commit

Permalink
sim-cli: source simulation file from data directory
Browse files Browse the repository at this point in the history
Adds a simln data-dir cli param, but maintains the default value to cwd
Sources the simulation file fromt this data dir
Prompts the user to select a simulatin file if the default/configured
file is not found
  • Loading branch information
okjodom committed Nov 8, 2023
1 parent 3416e02 commit 7911df1
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 29 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ sim.json
package-lock.json
activity-generator/releases/*
.DS_Store
*simulation_*.csv
/results
79 changes: 77 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sim-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Instantly simulate real-world Lightning network activity
[dependencies]
anyhow = { version = "1.0.69", features = ["backtrace"] }
clap = { version = "4.1.6", features = ["derive", "env", "std", "help", "usage", "error-context", "suggestions"], default-features = false }
dialoguer = "0.11.0"
log = "0.4.20"
serde = "1.0.183"
serde_json = "1.0.104"
Expand Down
74 changes: 68 additions & 6 deletions sim-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,16 @@ use clap::Parser;
use log::LevelFilter;
use sim_lib::{
cln::ClnNode, lnd::LndNode, ActivityDefinition, LightningError, LightningNode, NodeConnection,
NodeId, SimParams, Simulation,
NodeId, PrintResults, SimParams, Simulation,
};
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;

Expand Down Expand Up @@ -42,9 +48,12 @@ fn deserialize_f64_greater_than_zero(x: String) -> Result<f64, String> {
#[derive(Parser)]
#[command(version, about)]
struct Cli {
/// Path to a directory containing simulation files, and where simulation results will be stored
#[clap(long, short, env = "SIM_LN_DATA_DIR", default_value = DEFAULT_DATA_DIR)]
data_dir: PathBuf,
/// Path to the simulation file to be used by the simulator
#[clap(index = 1)]
sim_file: PathBuf,
#[clap(long, short, default_value = DEFAULT_SIM_FILE)]
sim_file: String,
/// Total time the simulator will be running
#[clap(long, short)]
total_time: Option<u32>,
Expand Down Expand Up @@ -77,8 +86,9 @@ async fn main() -> anyhow::Result<()> {
.init()
.unwrap();

let sim_path = read_sim_path(cli.data_dir.clone(), cli.sim_file).await?;
let SimParams { nodes, activity } =
serde_json::from_str(&std::fs::read_to_string(cli.sim_file)?)
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()))?;

let mut clients: HashMap<PublicKey, Arc<Mutex<dyn LightningNode + Send>>> = HashMap::new();
Expand Down Expand Up @@ -179,14 +189,22 @@ async fn main() -> anyhow::Result<()> {
});
}

let print_results = if !cli.no_results {
Some(PrintResults {
results_dir: mkdir(cli.data_dir.clone().join("results")).await?,
batch_size: cli.print_batch_size,
})
} else {
None
};

let sim = Simulation::new(
clients,
validated_activities,
cli.total_time,
cli.print_batch_size,
cli.expected_pmt_amt,
cli.capacity_multiplier,
cli.no_results,
print_results,
);
let sim2 = sim.clone();

Expand All @@ -199,3 +217,47 @@ async fn main() -> anyhow::Result<()> {

Ok(())
}

async fn read_sim_path(data_dir: PathBuf, sim_file: String) -> anyhow::Result<PathBuf> {
let sim_path = data_dir.join(sim_file);

let sim_path = if sim_path.extension().is_none() {
sim_path.with_extension("json")
} else {
sim_path
};

if sim_path.exists() {
Ok(sim_path)
} else {
let sim_files: Vec<String> = std::fs::read_dir(data_dir.clone())?
.filter_map(|f| {
f.ok().and_then(|f| {
if f.path().extension()?.to_str()? == "json" {
return f.file_name().into_string().ok();
}
None
})
})
.collect::<Vec<_>>();

if sim_files.is_empty() {
anyhow::bail!("no simulation files found in {:?}.", data_dir);
}

let selection = dialoguer::Select::new()
.with_prompt("Select a simulation file")
.items(&sim_files)
.default(0)
.interact()?;

Ok(data_dir.join(sim_files[selection].clone()))
}
}

async fn mkdir(dir: PathBuf) -> anyhow::Result<PathBuf> {
if !dir.exists() {
tokio::fs::create_dir(&dir).await?;
}
Ok(dir)
}
42 changes: 22 additions & 20 deletions sim-lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::fmt::{Display, Formatter};
use std::marker::Send;
use std::path::PathBuf;
use std::time::UNIX_EPOCH;
use std::{collections::HashMap, sync::Arc, time::SystemTime};
use thiserror::Error;
Expand Down Expand Up @@ -326,26 +327,31 @@ pub struct Simulation {
shutdown_listener: Listener,
// Total simulation time. The simulation will run forever if undefined.
total_time: Option<time::Duration>,
/// The number of activity results to batch before printing in CSV.
print_batch_size: u32,
/// The expected payment size for the network.
expected_payment_msat: u64,
/// The number of times that the network sends its total capacity in a month of operation when generating random
/// activity.
activity_multiplier: f64,
/// Whether we want the simulation not to produce and result file. Useful for developing, defaults to false.
no_results: bool,
/// Configurations for printing results to CSV. Results are not printed if this option is None.
print_results: Option<PrintResults>,
}

#[derive(Clone)]
pub struct PrintResults {
/// Data directory where CSV result files are printed.
pub results_dir: PathBuf,
/// The number of activity results to batch before printing in CSV.
pub batch_size: u32,
}

impl Simulation {
pub fn new(
nodes: HashMap<PublicKey, Arc<Mutex<dyn LightningNode + Send>>>,
activity: Vec<ActivityDefinition>,
total_time: Option<u32>,
print_batch_size: u32,
expected_payment_msat: u64,
activity_multiplier: f64,
no_results: bool,
print_results: Option<PrintResults>,
) -> Self {
let (shutdown_trigger, shutdown_listener) = triggered::trigger();
Self {
Expand All @@ -354,10 +360,9 @@ impl Simulation {
shutdown_trigger,
shutdown_listener,
total_time: total_time.map(|x| Duration::from_secs(x as u64)),
print_batch_size,
expected_payment_msat,
activity_multiplier,
no_results,
print_results,
}
}

Expand Down Expand Up @@ -561,8 +566,7 @@ impl Simulation {
result_logger,
results_receiver,
listener,
self.print_batch_size,
self.no_results,
self.print_results.clone(),
));
log::debug!("Simulator data collection set up.");
}
Expand Down Expand Up @@ -886,14 +890,11 @@ async fn consume_simulation_results(
logger: Arc<Mutex<PaymentResultLogger>>,
receiver: Receiver<(Payment, PaymentResult)>,
listener: Listener,
print_batch_size: u32,
no_results: bool,
print_results: Option<PrintResults>,
) {
log::debug!("Simulation results consumer started.");

if let Err(e) =
write_payment_results(logger, receiver, listener, print_batch_size, no_results).await
{
if let Err(e) = write_payment_results(logger, receiver, listener, print_results).await {
log::error!("Error while reporting payment results: {:?}.", e);
}

Expand All @@ -904,17 +905,17 @@ async fn write_payment_results(
logger: Arc<Mutex<PaymentResultLogger>>,
mut receiver: Receiver<(Payment, PaymentResult)>,
listener: Listener,
print_batch_size: u32,
no_results: bool,
print_results: Option<PrintResults>,
) -> Result<(), SimulationError> {
let mut writer = if !no_results {
Some(WriterBuilder::new().from_path(format!(
let mut writer = if print_results.is_some() {
let file = print_results.as_ref().unwrap().results_dir.join(format!(
"simulation_{:?}.csv",
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs()
))?)
));
Some(WriterBuilder::new().from_path(file)?)
} else {
None
};
Expand All @@ -939,6 +940,7 @@ async fn write_payment_results(
SimulationError::CsvError(e)
})?;

let print_batch_size = print_results.as_ref().unwrap().batch_size;
counter = counter % print_batch_size + 1;
if print_batch_size == counter {
w.flush().map_err(|_| SimulationError::FileError)?;
Expand Down

0 comments on commit 7911df1

Please sign in to comment.