Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sim-cli: source simulation file from data directory #132

Merged
merged 2 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

19 changes: 12 additions & 7 deletions README.md
okjodom marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,14 @@ Install the CLI:
cargo install --locked --path sim-cli/
```

Run with Simulation File (see [setup instructions](#simulation-file-setup) for details):
```
sim-cli sim.json
To run the simulation, create a simulation file `sim.json` in the working directory (see [setup instructions](#simulation-file-setup) for details) and run:

```
sim-cli
```

Run `sim-cli -h` for details on `--data-dir` and `--sim-file` options that allow specifying custom simulation file locations.

### Simulation File Setup
The simulator requires access details for a set of `nodes` that you
have permission to execute commands on. Note that the current version
Expand Down Expand Up @@ -190,10 +193,12 @@ described above). Activity sources and destinations may reference the
*must* provide a valid public key.

### Simulation Output
A summary of the results will be logged by the simulator, and a full
list of payments made with their outcomes is available in
`simulation_{timestamp}.csv` in the directory that the simulation was
executed in. For more detailed logs, use the `--log-level` cli flag.

A summary of the results will be logged by the simulator. A full list of payments made with their outcomes
is available in `simulation_{timestamp}.csv` within the configured `{data_dir}/results`.

Run `sim-cli -h` for details on data directory (`--data-dir`) and other options including `--print-batch-size`
which affect how simulation outputs are persisted

## Lightning Environments
If you're looking to get started with local lightning development, we
Expand Down
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"
okjodom marked this conversation as resolved.
Show resolved Hide resolved
log = "0.4.20"
serde = "1.0.183"
serde_json = "1.0.104"
Expand Down
81 changes: 76 additions & 5 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, SimParams, Simulation, WriteResults,
};
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,8 +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, default_value = DEFAULT_DATA_DIR)]
data_dir: PathBuf,
/// Path to the simulation file to be used by the simulator
okjodom marked this conversation as resolved.
Show resolved Hide resolved
#[clap(index = 1)]
/// 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,
/// Total time the simulator will be running
#[clap(long, short)]
Expand Down Expand Up @@ -77,8 +87,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 +190,22 @@ async fn main() -> anyhow::Result<()> {
});
}

let write_results = if !cli.no_results {
Some(WriteResults {
results_dir: mkdir(cli.data_dir.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,
write_results,
);
let sim2 = sim.clone();

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

Ok(())
}

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

if sim_path.exists() {
Ok(sim_path)
} else {
log::info!("Simulation file '{}' does not exist.", sim_path.display());
select_sim_file(data_dir).await
}
}

async fn select_sim_file(data_dir: PathBuf) -> anyhow::Result<PathBuf> {
let sim_files = std::fs::read_dir(data_dir.clone())?
.filter_map(|f| {
f.ok().and_then(|f| {
if f.path().extension()?.to_str()? == "json" {
f.file_name().into_string().ok()
} else {
None
}
})
})
.collect::<Vec<_>>();

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

let selection = dialoguer::Select::new()
.with_prompt(format!(
"Select a simulation file. Found these in {}",
data_dir.canonicalize()?.display()
))
.items(&sim_files)
.default(0)
.interact()?;

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

async fn mkdir(dir: PathBuf) -> anyhow::Result<PathBuf> {
tokio::fs::create_dir_all(&dir).await?;
Ok(dir)
}
Loading