264 lines
8.9 KiB
Rust
264 lines
8.9 KiB
Rust
use rpki::bundle::record_io::load_validation_time;
|
|
use rpki::storage::RocksStore;
|
|
use rpki::validation::run_tree_from_tal::{
|
|
run_tree_from_tal_and_ta_der_payload_delta_replay_step_serial_audit,
|
|
run_tree_from_tal_and_ta_der_payload_replay_serial_audit,
|
|
};
|
|
use rpki::validation::tree::TreeRunConfig;
|
|
use serde::Serialize;
|
|
use std::fs;
|
|
use std::path::{Path, PathBuf};
|
|
use std::time::Instant;
|
|
|
|
fn usage() -> &'static str {
|
|
"Usage: measure_sequence_replay --bundle-root <dir> [--rir <rir[,rir...]>] --out <path> [--keep-db]"
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct Args {
|
|
bundle_root: Option<PathBuf>,
|
|
rirs: Option<Vec<String>>,
|
|
out: Option<PathBuf>,
|
|
keep_db: bool,
|
|
}
|
|
|
|
fn parse_args() -> Result<Args, String> {
|
|
let mut out = Args::default();
|
|
let argv: Vec<String> = std::env::args().skip(1).collect();
|
|
let mut i = 0usize;
|
|
while i < argv.len() {
|
|
match argv[i].as_str() {
|
|
"--bundle-root" => {
|
|
i += 1;
|
|
out.bundle_root = Some(PathBuf::from(
|
|
argv.get(i).ok_or("--bundle-root requires a value")?,
|
|
));
|
|
}
|
|
"--rir" => {
|
|
i += 1;
|
|
let value = argv.get(i).ok_or("--rir requires a value")?;
|
|
out.rirs = Some(
|
|
value
|
|
.split(',')
|
|
.map(|s| s.trim().to_lowercase())
|
|
.filter(|s| !s.is_empty())
|
|
.collect(),
|
|
);
|
|
}
|
|
"--out" => {
|
|
i += 1;
|
|
out.out = Some(PathBuf::from(argv.get(i).ok_or("--out requires a value")?));
|
|
}
|
|
"--keep-db" => out.keep_db = true,
|
|
"--help" | "-h" => return Err(usage().to_string()),
|
|
other => return Err(format!("unknown argument: {other}\n{}", usage())),
|
|
}
|
|
i += 1;
|
|
}
|
|
if out.bundle_root.is_none() || out.out.is_none() {
|
|
return Err(format!("--bundle-root and --out are required\n{}", usage()));
|
|
}
|
|
Ok(out)
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct PhaseTiming {
|
|
duration_seconds: f64,
|
|
vrp_count: usize,
|
|
vap_count: usize,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct RirTiming {
|
|
rir: String,
|
|
base: PhaseTiming,
|
|
steps: Vec<(String, PhaseTiming)>,
|
|
}
|
|
|
|
fn discover_rirs(bundle_root: &Path) -> Result<Vec<String>, String> {
|
|
let mut out = Vec::new();
|
|
for entry in fs::read_dir(bundle_root)
|
|
.map_err(|e| format!("read_dir failed: {}: {e}", bundle_root.display()))?
|
|
{
|
|
let entry = entry.map_err(|e| format!("read_dir entry failed: {e}"))?;
|
|
let path = entry.path();
|
|
if path.is_dir() && path.join("bundle.json").exists() && path.join("tal.tal").exists() {
|
|
out.push(
|
|
path.file_name()
|
|
.and_then(|s| s.to_str())
|
|
.ok_or_else(|| format!("invalid rir dir name: {}", path.display()))?
|
|
.to_string(),
|
|
);
|
|
}
|
|
}
|
|
out.sort();
|
|
Ok(out)
|
|
}
|
|
|
|
fn path_join(root: &Path, relative: &str) -> PathBuf {
|
|
root.join(relative)
|
|
}
|
|
|
|
fn main() {
|
|
if let Err(err) = real_main() {
|
|
eprintln!("{err}");
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
|
|
fn real_main() -> Result<(), String> {
|
|
let args = parse_args()?;
|
|
let bundle_root = args.bundle_root.unwrap();
|
|
let out_path = args.out.unwrap();
|
|
let rirs = match args.rirs {
|
|
Some(v) => v,
|
|
None => discover_rirs(&bundle_root)?,
|
|
};
|
|
let mut results = Vec::new();
|
|
let tmp_root = out_path
|
|
.parent()
|
|
.unwrap_or_else(|| Path::new("."))
|
|
.join(".tmp-sequence-replay");
|
|
fs::create_dir_all(&tmp_root)
|
|
.map_err(|e| format!("create tmp root failed: {}: {e}", tmp_root.display()))?;
|
|
|
|
for rir in rirs {
|
|
let rir_dir = bundle_root.join(&rir);
|
|
let bundle: serde_json::Value = serde_json::from_slice(
|
|
&fs::read(rir_dir.join("bundle.json"))
|
|
.map_err(|e| format!("read bundle failed: {}: {e}", rir_dir.display()))?,
|
|
)
|
|
.map_err(|e| format!("parse bundle failed for {}: {e}", rir_dir.display()))?;
|
|
let tal_bytes = fs::read(rir_dir.join("tal.tal"))
|
|
.map_err(|e| format!("read tal.tal failed for {}: {e}", rir_dir.display()))?;
|
|
let ta_bytes = fs::read(rir_dir.join("ta.cer"))
|
|
.map_err(|e| format!("read ta.cer failed for {}: {e}", rir_dir.display()))?;
|
|
|
|
let db_dir = tmp_root.join(format!("{rir}-db"));
|
|
if db_dir.exists() {
|
|
fs::remove_dir_all(&db_dir)
|
|
.map_err(|e| format!("remove old db failed: {}: {e}", db_dir.display()))?;
|
|
}
|
|
let store =
|
|
RocksStore::open(&db_dir).map_err(|e| format!("open rocksdb failed for {rir}: {e}"))?;
|
|
|
|
let base_archive = path_join(
|
|
&rir_dir,
|
|
bundle["base"]["relativeArchivePath"]
|
|
.as_str()
|
|
.ok_or("bundle missing base.relativeArchivePath")?,
|
|
);
|
|
let base_locks = path_join(
|
|
&rir_dir,
|
|
bundle["base"]["relativeLocksPath"]
|
|
.as_str()
|
|
.ok_or("bundle missing base.relativeLocksPath")?,
|
|
);
|
|
let base_validation_time = load_validation_time(&base_locks)
|
|
.map_err(|e| format!("load base validation time failed for {rir}: {e}"))?;
|
|
|
|
let start = Instant::now();
|
|
let base_out = run_tree_from_tal_and_ta_der_payload_replay_serial_audit(
|
|
&store,
|
|
&rpki::policy::Policy::default(),
|
|
&tal_bytes,
|
|
&ta_bytes,
|
|
None,
|
|
&base_archive,
|
|
&base_locks,
|
|
base_validation_time,
|
|
&TreeRunConfig {
|
|
max_depth: None,
|
|
max_instances: None,
|
|
compact_audit: false,
|
|
persist_vcir: true,
|
|
build_ccr_accumulator: true,
|
|
},
|
|
)
|
|
.map_err(|e| format!("base replay failed for {rir}: {e}"))?;
|
|
let base_timing = PhaseTiming {
|
|
duration_seconds: start.elapsed().as_secs_f64(),
|
|
vrp_count: base_out.tree.vrps.len(),
|
|
vap_count: base_out.tree.aspas.len(),
|
|
};
|
|
|
|
let mut previous_locks = base_locks.clone();
|
|
let mut step_timings = Vec::new();
|
|
for step in bundle["deltaSequence"]["steps"]
|
|
.as_array()
|
|
.ok_or("bundle missing deltaSequence.steps")?
|
|
{
|
|
let step_id = step["id"].as_str().ok_or("step missing id")?.to_string();
|
|
let step_dir = path_join(
|
|
&rir_dir,
|
|
step["relativePath"]
|
|
.as_str()
|
|
.ok_or("step missing relativePath")?,
|
|
);
|
|
let delta_archive = path_join(
|
|
&rir_dir,
|
|
step["relativeArchivePath"]
|
|
.as_str()
|
|
.ok_or("step missing relativeArchivePath")?,
|
|
);
|
|
let delta_locks = path_join(
|
|
&rir_dir,
|
|
step["relativeTransitionLocksPath"]
|
|
.as_str()
|
|
.ok_or("step missing relativeTransitionLocksPath")?,
|
|
);
|
|
let validation_time = load_validation_time(&delta_locks).map_err(|e| {
|
|
format!("load step validation time failed for {rir}/{step_id}: {e}")
|
|
})?;
|
|
let start = Instant::now();
|
|
let step_out = run_tree_from_tal_and_ta_der_payload_delta_replay_step_serial_audit(
|
|
&store,
|
|
&rpki::policy::Policy::default(),
|
|
&tal_bytes,
|
|
&ta_bytes,
|
|
None,
|
|
&delta_archive,
|
|
&previous_locks,
|
|
&delta_locks,
|
|
validation_time,
|
|
&TreeRunConfig {
|
|
max_depth: None,
|
|
max_instances: None,
|
|
compact_audit: false,
|
|
persist_vcir: true,
|
|
build_ccr_accumulator: true,
|
|
},
|
|
)
|
|
.map_err(|e| format!("delta step replay failed for {rir}/{step_id}: {e}"))?;
|
|
step_timings.push((
|
|
step_id.clone(),
|
|
PhaseTiming {
|
|
duration_seconds: start.elapsed().as_secs_f64(),
|
|
vrp_count: step_out.tree.vrps.len(),
|
|
vap_count: step_out.tree.aspas.len(),
|
|
},
|
|
));
|
|
previous_locks = step_dir.join("target-locks.json");
|
|
}
|
|
|
|
results.push(RirTiming {
|
|
rir,
|
|
base: base_timing,
|
|
steps: step_timings,
|
|
});
|
|
|
|
if !args.keep_db && db_dir.exists() {
|
|
fs::remove_dir_all(&db_dir)
|
|
.map_err(|e| format!("remove db failed: {}: {e}", db_dir.display()))?;
|
|
}
|
|
}
|
|
|
|
fs::write(
|
|
&out_path,
|
|
serde_json::to_vec_pretty(&results).map_err(|e| format!("encode json failed: {e}"))?,
|
|
)
|
|
.map_err(|e| format!("write out failed: {}: {e}", out_path.display()))?;
|
|
println!("{}", out_path.display());
|
|
Ok(())
|
|
}
|